之前讨论的分类和回归问题,输入输出都是"一个点"。但很多真实问题是序列——语音是一串音频帧,句子是一串词,股价是一串数值。更关键的是:我们观测到的序列背后,往往有一个看不见的状态序列在驱动。
举个经典例子:你住在一个没有窗户的地下室,每天只能看到朋友的活动——散步、购物、打扫。你想推断外面的天气是晴、多云还是下雨。你看不到天气(隐状态),只能看到活动(观测),但你知道天气会影响活动选择,而且今天的天气和昨天有关。
这就是**隐马尔可夫模型(Hidden Markov Model, HMM)**的基本设定。
HMM 由三组参数 λ=(A,B,π) 完全确定:
状态集合 S={s1,s2,…,sN}:所有可能的隐状态。在天气例子里 N=3(晴、云、雨)。
观测集合 O={o1,o2,…,oM}:所有可能的观测值。M=3(散步、购物、打扫)。
初始概率向量 π:πi=P(q1=si),第一个时刻处于状态 si 的概率。
π=(0.6,0.3,0.1)(晴天开局的概率最大)
状态转移矩阵 A:aij=P(qt+1=sj∣qt=si),从状态 i 转到状态 j 的概率。
A=0.70.30.20.20.40.30.10.30.5
第一行表示"今天晴 → 明天晴 0.7、明天云 0.2、明天雨 0.1"。每行的和为 1。
观测概率矩阵 B:bi(k)=P(ot=vk∣qt=si),在状态 i 下观测到 vk 的概率。
B=0.60.30.10.30.40.30.10.30.6
晴天大概率散步,雨天大概率打扫——符合直觉。
HMM 的整个数学推导依赖两条简化假设:
- 齐次马尔可夫假设:当前状态只依赖前一个状态,与更早的历史无关。
P(qt∣qt−1,qt−2,…,q1)=P(qt∣qt−1)
- 观测独立假设:当前观测只依赖当前状态,与其他时刻的状态和观测无关。
P(ot∣qt,qt−1,…,ot−1,…)=P(ot∣qt)
这两个假设很强——现实中未必成立(比如天气可能受前几天的影响),但它们让计算变得可行。放松这些假设的模型就是条件随机场。
HMM 的全部理论围绕三个问题展开:
问题 1:概率计算(Evaluation)
给定模型 λ=(A,B,π) 和观测序列 O=(o1,o2,…,oT),计算这个观测序列出现的概率 P(O∣λ)。
用途:模型选择。训练好几个 HMM(比如每个说话人一个),给定一段语音,看哪个模型的 P(O∣λ) 最大——就是谁在说话。
问题 2:学习(Learning)
给定观测序列 O,估计模型参数 λ=(A,B,π),使得 P(O∣λ) 最大。
用途:从数据中训练模型。
问题 3:预测/解码(Decoding)
给定模型 λ 和观测序列 O,找到最可能的隐状态序列 Q∗=(q1∗,q2∗,…,qT∗)。
用途:序列标注。观测是句子里的词,隐状态是词性标签——这就是词性标注问题。
三个问题的解法分别是前向算法、Baum-Welch 算法、维特比算法。
要算 P(O∣λ),最直接的想法是穷举所有可能的隐状态序列:
P(O∣λ)=所有 Q∑P(O∣Q,λ)⋅P(Q∣λ)
长度为 T 的序列,每个时刻有 N 种状态,一共有 NT 条路径。N=3,T=10 就有近 6 万条路径;T=100 就是 3100≈5×1047——完全不可算。
前向算法的核心思想是动态规划——把大问题拆成子问题,避免重复计算。
定义前向变量:
αt(i)=P(o1,o2,…,ot,qt=si∣λ)
含义:"在时刻 t,已经观测到 o1,…,ot,并且此刻处于状态 si"的联合概率。
初始化(t=1):
α1(i)=πi⋅bi(o1),i=1,2,…,N
递推(t=2,…,T):
αt(j)=[i=1∑Nαt−1(i)⋅aij]⋅bj(ot)
直觉:要到达 t 时刻的状态 j,可以从 t−1 时刻的任何状态 i 转移过来。把所有路径的概率加起来,再乘以在状态 j 下观测到 ot 的概率。
终止:
P(O∣λ)=i=1∑NαT(i)
复杂度从 O(NT) 降到了 O(N2T)——从指数级变成多项式级。
在下面的交互演示里点击"下一步",看前向算法如何逐步计算每个节点的 α 值。切换到"维特比算法"可以看到解码过程。
α1(i)=[∑jα0(j)⋅aji]⋅bi(o1)初始化:α₁(i) = π(i) · b_i(o₁)。当前显示 t=1 各隐状态的前向概率。
类似地,可以从后往前算。定义后向变量:
βt(i)=P(ot+1,ot+2,…,oT∣qt=si,λ)
递推方向相反:
βt(i)=j=1∑Naij⋅bj(ot+1)⋅βt+1(j)
前向和后向结合可以算出任意时刻处于某状态的后验概率——这是 Baum-Welch 算法的基础。
如果训练数据同时包含观测序列和对应的隐状态序列(比如标注好词性的语料),参数估计很简单——数频率:
a^ij=从状态 i 出发的总次数从状态 i 转移到 j 的次数
b^i(k)=处于状态 i 的总次数在状态 i 下观测到 vk 的次数
这就是极大似然估计,直接且精确。但现实中隐状态标注往往没有或很昂贵。
如果只有观测序列,没有隐状态标注——怎么办?这正是 EM 算法的用武之地。
Baum-Welch 算法是 EM 算法在 HMM 上的具体实现:
E 步:用当前参数 λ(n) 和前向-后向算法,计算隐状态的后验概率。
定义两个关键量:
γt(i)=P(qt=si∣O,λ)=∑j=1Nαt(j)⋅βt(j)αt(i)⋅βt(i)
γt(i) 是"在时刻 t 处于状态 i"的后验概率。
ξt(i,j)=P(qt=si,qt+1=sj∣O,λ)=∑m∑nαt(m)⋅amn⋅bn(ot+1)⋅βt+1(n)αt(i)⋅aij⋅bj(ot+1)⋅βt+1(j)
ξt(i,j) 是"在时刻 t 处于状态 i 且 t+1 时刻转到状态 j"的后验概率。
M 步:用后验概率更新参数。
π^i=γ1(i)
a^ij=∑t=1T−1γt(i)∑t=1T−1ξt(i,j)
b^i(k)=∑t=1Tγt(i)∑t=1,ot=vkTγt(i)
反复迭代 E 步和 M 步,直到似然函数收敛。和所有 EM 算法一样,Baum-Welch 只保证收敛到局部最优,结果依赖初始值。
解码问题要找最可能的隐状态序列:
Q∗=argQmaxP(Q∣O,λ)
维特比算法和前向算法结构几乎一样,只是把求和换成了取最大值。
定义维特比变量:
δt(i)=q1,…,qt−1maxP(q1,…,qt−1,qt=si,o1,…,ot∣λ)
含义:在时刻 t 到达状态 i 的所有路径中,概率最大的那条路径的概率。
递推:
δt(j)=1≤i≤Nmax[δt−1(i)⋅aij]⋅bj(ot)
同时记录最大值来自哪个状态:
ψt(j)=arg1≤i≤Nmax[δt−1(i)⋅aij]
终止:找到最后一步概率最大的状态,然后回溯 ψ 得到完整路径。
qT∗=argimaxδT(i)
qt∗=ψt+1(qt+1∗),t=T−1,T−2,…,1
在上面的交互演示中切换到"维特比算法",走到最后一步可以看到最优路径高亮——回溯过程把概率最大的隐状态序列串了起来。
| 前向算法 | 维特比算法 |
|---|
| 解决的问题 | 概率计算 P(O∣λ) | 解码 Q∗ |
| 核心操作 | 求和(边缘化) | 取最大值(优化) |
| 输出 | 一个概率值 | 一条最优路径 |
| 复杂度 | O(N2T) | O(N2T) |
两者的结构完全对称——理解了一个,另一个自然就懂了。