查看全课大纲(第 58 / 82 节)
  1. 1.函数与数据泛化
  2. 2.向量:从几何直觉到高维空间
  3. 3.矩阵与张量
  4. 4.特征值与特征向量:矩阵变换的不动方向
  5. 5.凸函数:从向量求导的矩阵记号到好优化的函数形状
  6. 6.概率与统计:机器学习的数学基础
  7. 7.独立性与随机变量:贝叶斯公式与二项分布
  8. 8.期望、方差与协方差:随机变量的数字画像
  9. 9.联合概率:当一条轴不够用的时候
  10. 10.概率分布(一):从伯努利到伽马函数
  11. 11.概率分布(二):贝塔、泊松、正态与两个贝叶斯故事
  12. 12.机器学习的模型:找输入与输出的关系
  13. 13.机器学习的应用
  14. 14.过拟合
  15. 15.机器学习的分类
  16. 16.有监督学习:从房价预测说起
  17. 17.损失函数
  18. 18.梯度下降(上):更新规则与学习速率
  19. 19.梯度下降(2)
  20. 20.牛顿方法
  21. 21.KNN简介
  22. 22.距离测算
  23. 23.朴素贝叶斯
  24. 24.朴素贝叶斯的实际应用
  25. 25.信息熵:决策树构建的度量基础
  26. 26.信息增益与决策树的构建
  27. 27.ID3算法
  28. 28.决策树节点的划分
  29. 29.CART树
  30. 30.Bagging
  31. 31.Boosting:自适应提升算法
  32. 32.Gradient Boosting
  33. 33.线性模型:回归与分类的同一副骨架
  34. 34.最小二乘:把最优参数直接解出来
  35. 35.最小二乘法的几何与概率视角
  36. 36.极大似然:给最小二乘一个概率出身
  37. 37.逻辑回归(1):把线性模型接到分类上
  38. 38.逻辑回归(2):损失函数与多分类
  39. 39.---- 手造数据: 5 个样本, 2 个特征; x2 几乎 = 2*x1 (偏差手工指定, 无随机) ----
  40. 40.讲师例: 向量 "23014" -- 三把范数尺子量同一个向量
  41. 41.优化方法(1):坐标下降与前向选择
  42. 42.优化方法(2):前向策略与最小角回归
  43. 43.拉格朗日乘子法(1):从无约束到等式约束
  44. 44.拉格朗日乘子法(2):等式约束下的平行条件与拉格朗日函数
  45. 45.拉格朗日乘子法(3):不等式约束与 KKT 条件
  46. 46.最大间隔:把分类问题变成优化问题
  47. 47.拉格朗日对偶问题:把 w 和 b 全部换成 α
  48. 48.支持向量:边界上的少数派
  49. 49.SVM 计算过程:一笔到底算完,再走进核方法
  50. 50.贝叶斯公式:概率即信念
  51. 51.隐含变量和概率模型:给看不见的原因建模
  52. 52.EM 算法:期望与最大化的迭代
  53. 53.琴生不等式:EM 的数学钥匙
  54. 54.三个硬币问题:EM 的复盘与衔接
  55. 55.高斯混合模型:用隐变量解释双峰数据
  56. 56.隐马尔可夫模型
  57. 57.隐马尔可夫模型(2):前向算法与动态混合模型
  58. 58.隐马尔可夫模型在 NLP 中的运用
  59. 59.EM 算法:把「为什么收敛」推到底
  60. 60.HMM 在 NLP 中的运用:从分词到机器翻译
  61. 61.HMM 参数估计:Baum-Welch 的三块拉格朗日
  62. 62.高维下的高斯模型
  63. 63.主题模型:从词袋到隐含主题
  64. 64.Dirichlet 分布(1)
  65. 65.Dirichlet 分布(2)
  66. 66.统计学习内容回顾
  67. 67.K-means 算法
  68. 68.PCA 降维
  69. 69.SVD 降维
  70. 70.从聚类到流形
  71. 71.局部线性嵌入
  72. 72.随机邻域嵌入:SNE 与 t-SNE
  73. 73.无参模型:让数据自己说话
  74. 74.高斯过程:函数空间里的正态分布
  75. 75.高斯过程(2):噪声、正则化与核的线性组合
  76. 76.神经网络简介:从感知器到自编码器
  77. 77.反向传播:把误差一层层传回去
  78. 78.卷积神经网络(CNN):让特征自己学出来
  79. 79.循环神经网络(RNN):给网络装上记忆
  80. 80.深度学习回顾:静态一类,动态一类
  81. 81.增强学习(1):蒙特卡洛、树搜索与 Q-learning
  82. 82.强化学习(2):从乌鸦吃核桃到井字棋的 value 回传

隐马尔可夫模型在 NLP 中的运用

约 27 分钟

📺 正在播放小象官方高清录播(支持倍速与清晰度调节)

小象实战讲义 · 机器学习算法精讲

上一章我们从三硬币一路走到 EM、HMM——这一节讲师回应了两件事:一是有同学反馈,EM 里边的数学「可能没有完全详细的推导,为什么 EM 算法是收敛的,这个部分还没讲得那么详细」(下一节补上);二是算法和数学挺抽象,更多同学想知道「这方面是用来做什么的」。所以本节是一节应用课:把隐马尔可夫链放进自然语言处理(NLP),看它怎么做词性标注、中文分词、语音识别;最后再看一眼它与现代深度学习(循环神经网络)如何一脉相承。

💡 核心导读

  • 问题定位回顾:机器学习的目标是学一个函数关系 f:xi→yif: x_i \to y_i——yy 取 0/1 是分类、取连续值是预测;HMM 类似这样的模型,但多了一层「时序」。
  • 词性标注(POS tagging):状态 QQ 是所有可能的单词(约两万个),转移矩阵 AA 变成 20K×20K20K \times 20K 的词接词方阵,释放概率 BB 是「给定词,它被标为各词性的分布」。
  • N-gram 语言模型:Unigram(词词独立,也叫词袋)、Bigram(只看前一个词——正是马尔可夫假设)、Trigram(看前两个词)。
  • 分词:给每个字标 0/1——0 是 continue(继续走)、1 是 end(词到此截止)。
  • 语音识别:HMM 最早用得最多的地方——观测从「词性」换成声音信号抽出的特征向量(MFCC)。
  • 一脉相承:循环神经网络可以看成「隐马尔可夫链的概率关系被神经网络所替代」。

先定位:HMM 也是「学一个函数」

开场讲师先把整个机器学习的老框架摆出来——「大家已经都知道了,没什么问题」:数据是 (xi,yi)(x_i, y_i),ii 从 1 到 NN,最终的目的基本都是想学习一个函数关系 ff,把每个 xix_i 映射到它对应的 yiy_i 上。yiy_i 属于 {0,1}\lbrace 0, 1 \rbrace(或者 −1,1-1, 1)的时候,是分类问题;yiy_i 是连续值的时候,相当于一个预测问题。「而隐马尔可夫链的话呢,实际上也是类似于这样的模型」——只是 xx 和 yy 都带上了时间下标,函数关系里多了一层时序。我们在很多不同的实际问题里边都会用到它,第一个例子就来自自然语言。

词性标注:把 NLP 装进 HMM

先定位:NLP(natural language processing,自然语言处理)是人工智能或机器学习里最重要的分支之一——用机器学习的方法对语言建模,对里边的语义、问答、句法、语法进行分析。讲师举的第一个任务是词性标注:POS 的原文是 part of speech,「英文这个词稍微啰嗦一点,中文就是一个词性」。

拿中文例子:「我爱祖国」——「我」是主语、一个名词(N),「爱」是动词(V),「祖国」是另一个名词。讲师把例句加了一档难度:「我爱红花」——「红」是形容词(adj)、「花」是名词,所以整句的词性标注是 N,V,adj,NN, V, \text{adj}, N 这样的序列。给了很多训练数据(每个句子都带词性标注)之后,新句子来了,每个词该标名词、动词还是形容词?

这就是 HMM 的活。先把上节的记号接上(讲师现场还自问了一句:「我们上次用的状态是什么来着?是 X 还是 Q?我记得是 Q。」——对,继续用 QQ 和 YY):状态 q0,q1,…,qTq_0, q_1, \dots, q_T 隐含、转移是 Aqt, qt+1A_{q_t,\, q_{t+1}}、释放是 Bqt, ytB_{q_t,\, y_t}。关键是状态装什么。上次天气例子里 QQ 是晴天/下雨两种;这次:

背后的 QQ,是所有可能的单词——中文就是中文词,英文就是英文词。常用的小学生词典也得有大概两万个词(20K)。

状态一多,状态转移矩阵 AA 直接膨胀成 20K×20K20K \times 20K 的方阵:看到「我」这个词的时候,它下一个变成其他每个单词的概率各是多大——概率大的块标红、概率小的标绿。而这矩阵怎么学?「根据语料库,看每一个词和每一个词之间相接的关系——哪一个词在哪一个词的后面、哪一个词在哪一个词的前面。」比如「爱」这个词直接跟在「我」后面的概率。用一小段代码把这件事落成数(语料为讲义设定的演示):

from collections import defaultdict

# 一个极小的语料库(讲义设定的演示语料)
corpus = [
    ['我', '爱', '祖国'],
    ['我', '爱', '人民'],
    ['我', '喜欢', '机器', '学习'],
    ['我', '愿意', '学习'],
]

bigram = defaultdict(lambda: defaultdict(int))   # C(前词, 后词):谁接在谁后面
unigram = defaultdict(int)                       # C(词)
for sent in corpus:
    for w in sent:
        unigram[w] += 1
    for a, b in zip(sent, sent[1:]):
        bigram[a][b] += 1

# 转移概率 P(下一个词 | 当前词) = C(当前, 下一) / C(当前)
for w in ['我', '爱']:
    parts = [f"P({v}|{w}) = {bigram[w][v]}/{unigram[w]}" for v in bigram[w]]
    print('   '.join(parts))
P(爱|我) = 2/4   P(喜欢|我) = 1/4   P(愿意|我) = 1/4
P(祖国|爱) = 1/2   P(人民|爱) = 1/2

(「学习」在这个语料里总在句尾,作前词时没有后继——真实的语言模型还要处理句首、句尾标记,这里从简。)这一「给定当前词,下一个词的概率分布」正是语言模型做的事,而它对自身有三档经典假设:

  • Unigram(一元):词与词之间完全独立——「红色」的概率就是它自己的概率,跟前面是「我」还是别的没有任何关系。Unigram 有时也叫 Bag of Words(词袋)。
  • Bigram(二元):给定一个词,它只跟前面一个词有直接的概率关系——这就是马尔可夫链的假设。
  • Trigram(三元):任何一个词的概率跟前面两个词有关系。

B:给定词,它被标成什么词性

BB 这边管的是:给定了状态(词「爱」),想学它被标注为动词、名词、形容词、副词……的概率分布。讲师的描述很具体:在很大的语料库里,「爱」大部分都被标注为动词;有少部分语料把它标成了名词(「爱」确实可以作为名词);被标为副词或形容词的几乎是微乎其微——语料里很少见。BB 的学习就是数数(语料为讲义设定的演示,含「爱」作名词的用法):

from collections import defaultdict

# 一个极小的「已标注语料」:每句话同时给出词与词性
tagged = [
    [('我', 'N'), ('爱', 'V'), ('祖国', 'N')],
    [('我', 'N'), ('爱', 'V'), ('人民', 'N')],
    [('母亲', 'N'), ('爱', 'V'), ('和平', 'N')],
    [('爱', 'N'), ('是', 'V'), ('伟大的', 'Adj')],   # 「爱」作名词的用法
]

# B 的学习:给定词(状态),统计它被标为各词性的频率
pos_of = defaultdict(lambda: defaultdict(int))
for sent in tagged:
    for word, tag in sent:
        pos_of[word][tag] += 1

for word in ['爱', '我']:
    total = sum(pos_of[word].values())
    parts = [f"P({t}|{word}) = {c}/{total}" for t, c in pos_of[word].items()]
    print('   '.join(parts))
P(V|爱) = 3/4   P(N|爱) = 1/4
P(N|我) = 2/2

把这个小语料放大到真实规模,「爱」的分布就会长成讲师说的样子:动词占大头、名词占小头、副词形容词几乎为零。模型把 AA、BB 连同最开始的先验 π\pi 都从语料学好之后,给定一个新句子(「我爱人民」「我喜欢机器学习」……),就能给它加词性标注——用讲师的话说:「它在等于哪些词性的时候,那个整体的似然值是最大的」,就标哪一套。

中文分词:continue 与 end

第二个应用是中文分词。「我爱中国人民」——可能分成「我爱/中国/人民」,也可能「中国人民」整个是一个大词;很少人会把「我爱中」或「爱中」画成一个词。每个字与字之间的空挡,都是潜在的一个分割点。

讲师的编码方案:给每个字打 0 或 1——

  • 0 = continue(继续往前走):这个词还没完,下一个字还属于同一个词;
  • 1 = end(截止):词到这里结束,这里必须要分词。

规则只有一条(讲师边写边自我纠正后的定稿):「零不能结尾,一为结尾。」 一个词内部全是 0,最后一个字是 1;下一个是 1 还要看下一个是 0 还是 1——0 就继续走,直到碰见 1 才收词。这样「我爱中国人民」的一种分词「我/爱/中国人民」就写成 1,1,0,0,11, 1, 0, 0, 1 这样的 marker 序列。给定标注好的训练数据,「给定一句话,它对应的 0110 关系是什么」就成了可以学习的任务。

照讲师的框架,这个模型与词性标注共享同一套骨架:状态 QQ 还是那些词,AA 还是那个巨大的词转移方阵;变的只是 BB 这一头——观测 yy 一下子简单了:只有 0 和 1 两个值(截止还是不截止)。甚至还能再学出一点直觉:训练数据里「我」后面总是分词(都被标了 1),那么看见「我」的时候,它有最大的概率标 1——它自己就得单独画成一个词。

讲师对这类建模给自己留了一句很坦诚的注脚:「也许这个不是很行,但是我们可以去描述」——并且顺势回收了本课程早期的那句金句:模型这件事,没有对错之分,只有好坏之别。(这一节里 HMM 的状态装的是「词」,与教科书里常见的「词性当隐状态」装法方向不同;按讲师口径如实呈现,作为理解应用的描述性框架。)

语音识别:HMM 最早的主场

其实隐马尔可夫链用得最多的,最早是在语音处理里。还是那句话「我爱中国人民」——说出来变成的是一段段声音符号。模型骨架完全不变:

  • 状态 QQ 还是所有词(那个 2 万乘 2 万的方阵),AA 不变:一般说「我」的时候,后边可能出现动词「爱」、出现「喜欢」「愿意」——这就是条件概率;
  • BB 变成了另一个问题:P(yt∣qt)P(y_t \mid q_t) 是「观察到这个词的声音特征的概率」。

「人民」这个词说出口,它的声波长成一个特定的模样。以前做语音识别,要先把这些连续的声音信号做平滑、降噪等处理,总之把连续信号变成高维的离散特征——有一种特征叫 MFCC(「大家可以自己去搜一下」,基于一种变换把连续信号变成离散特征)。一段声音波形进来,经过傅里叶变换、快速傅里叶变换这些数学技巧,最后变成一个向量——比如 15 维。「人民」「中国」「板凳」「鼠标」,任何一个词都有不同的声波、变成不同的向量。训练数据告诉你:类似于这样的声波,更代表「人民」——它对应这个向量的概率就更大、似然值就更大。给定了新的 BB,就能做语音识别。

顺带一句:HMM 能吃的远不止语言。股票价格——某一只股票的价格随时间浮动;美元对英镑的外汇牌价——讲师留了个动手作业:找一堆数据,看能不能用 HMM 学出参数、预测下一个点是涨还是跌。「这些事情都是可以做的。」

时代定位也如实记下:「这些技巧已经不算是比较时髦的东西了,说实在的,可能会显得比较粗糙,跟现在新的机器学习技术来比。但这些代表着我们一步一步怎么去进步、怎么去理解这些事情。」

从 HMM 到循环神经网络

最后一段是给未来的路标(讲师:我们不上深度学习的课程,但讲一下 HMM 跟深度学习后边哪部分非常相关)。神经网络「就算没学过——没吃过猪肉,也见过猪跑」:一个普通的两层网络,输入是 xix_i(比如二维特征),经过隐层节点的非线性变换与线性组合,输出 yiy_i——0/1 分类,或一个预测值。

现在把它放到时间轴上:下一时刻输入 xi+1x_{i+1}、输出 yi+1y_{i+1},再下一时刻 xi+2x_{i+2}、yi+2y_{i+2}……一直到 TT。「就像我们刚才所描述的隐马尔可夫链是一样的」——隐含状态之间,Q2Q_2 的状态一定跟 Q1Q_1 相关,观察到的 Y2Y_2 还是跟 Q2Q_2 相关。关键一步来了:把隐层节点在时间上连起来——前一时刻的隐节点跟当前时刻相关、当前时刻又跟下一时刻相关。于是这一层的输入就有了两个来源:

  1. 本时刻的输入 xix_i;
  2. 上一时刻隐藏层的输出 hi−1h_{i-1}。

这种结构不断延续下去,就叫 Recurrent Neural Networks(循环神经网络,RNN)。「大家可以甚至不用关心每一个神经网络具体怎么去做」——把它们看成一个函数关系就行:原先 yiy_i 只跟 xix_i 相关;加上跨时刻的连接后:

yi=f(xi, hi−1)y_i = f(x_i,\ h_{i-1})

hi−1h_{i-1} 是上一时刻的隐藏输出——当前结果不只由当前输入决定,还由上一时刻的「记忆」决定。「这就像类似于我们隐马尔可夫链的假设是一样的。」

讲师给了一句提纲挈领的总结,也是本节的收束:

循环神经网络可以简单地看成:一种时序关系,或者隐马尔可夫链的这种概率关系,被神经网络所替代——这样就会变成循环神经网络。

「有些人以为深度学习里边的东西都是独立的、完全是神经网络自己的新东西。其实它基本的思想和想法,跟整个机器学习的发展都是一脉相承的。」

📝 动手练一练

  1. Trigram 的代价:如果对词序列用 Trigram 假设(每个词的概率依赖前面两个词),「状态」该装什么?转移矩阵的规模会从 20K×20K20K \times 20K 变成多大?这解释了为什么 Bigram 在早期 NLP 里最常用?
👉 点击查看参考答案

Trigram 下当前词依赖前两个词,状态得装「前一个词 + 当前词」的二元组:可能的组合有 20K×20K20K \times 20K 种,转移矩阵随之变成 (20K)2×(20K)2(20K)^2 \times (20K)^2 量级——参数数量爆炸,而且绝大多数组合在真实语料里从未出现过(极端稀疏)。Bigram 只看前一个词,状态数维持在 20K20K、转移矩阵 20K×20K20K \times 20K,是「表达能力」与「可估性」之间最划算的折中,所以早期 NLP 用得最多。

  1. 分词编码手写:「我爱中国人民」至少有两种合理分词:甲、我 / 爱 / 中国 / 人民;乙、我 / 爱 / 中国人民。请把两种分词各写成一个 0/1 marker 序列(0=continue,1=end),并回答:序列「我爱中」为什么不可能是一个词——它的 marker 序列会违反哪条规则?
👉 点击查看参考答案

按「词内部标 0、最后一个字标 1」:

  • 甲(我/爱/中国/人民):1, 1, 0, 1, 11,\ 1,\ 0,\ 1,\ 1;
  • 乙(我/爱/中国人民):1, 1, 0, 0, 11,\ 1,\ 0,\ 0,\ 1。

两个序列的前两个字相同(「我」「爱」各自成词、都标 1),差别在「国」:甲里「中国」到「国」收词(标 1),乙里「中国人民」还在继续(标 0)。

若「我爱中」是一个词,则「我」「爱」「中」三字同属一个词内部,marker 为 0, 0, 10,\ 0,\ 1(或后面还接字则 0,0,00,0,0)——但语料统计里「我」后面几乎总是分词(「我」标 1 的频率极高),且没有任何正常语料把「我爱中」当一个词。规则层面:只要「中」标 1(end)而其后又接「国」「民」新词,得到的就是「我爱中/国人民」这种不存在的切分——模型从语料学到的转移与释放概率会让这类序列的似然值极低,这正是这套编码能用来分词的原因。

本章小结

本节是 HMM 的应用图景课:先回顾机器学习的函数定位(f:xi→yif: x_i \to y_i,分类或预测),再把 HMM 装进 NLP——词性标注里状态是所有单词(约 20K)、AA 是词接词的语言模型方阵(Unigram 独立/Bigram 马尔可夫/Trigram 看前两个)、BB 是「给定词被标为各词性」的分布,学好后新句子标哪套词性似然最大就标哪套;中文分词把观测简化成 0/1(continue/end,零不能结尾、一为结尾);语音识别是 HMM 最早的主场,观测换成 MFCC 等高维声学特征向量。收尾的路标:循环神经网络的隐层跨时刻相连,yi=f(xi,hi−1)y_i = f(x_i, h_{i-1})——HMM 的概率关系被神经网络替代,「基本的思想和想法,跟整个机器学习的发展都是一脉相承的」。

📋 行动清单

  • 把词性标注、分词、语音识别三个任务各自的 QQ、YY、AA、BB 含义写在一张表上——体会「骨架不变、BB 换观测」的设计模式。
  • 把本节两段代码的演示语料各扩两句(自己编),重新统计转移与词性分布,检查输出是否符合预期。
  • 带着开场的那个问题进入下一节:EM 算法为什么是收敛的——上一章琴生不等式的下界铺垫,下一节见分晓。

—— 小象教研组

配套资料 · 直接下载

课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。

  • 第14章笔记:隐马尔可夫模型(HMM)
    下载