监督学习 · 10

隐马尔可夫模型

从天气猜活动——隐马尔可夫模型的三个基本问题、前向算法、Baum-Welch 学习和维特比解码。

16 min read

从序列到隐状态

之前讨论的分类和回归问题,输入输出都是"一个点"。但很多真实问题是序列——语音是一串音频帧,句子是一串词,股价是一串数值。更关键的是:我们观测到的序列背后,往往有一个看不见的状态序列在驱动。

举个经典例子:你住在一个没有窗户的地下室,每天只能看到朋友的活动——散步、购物、打扫。你想推断外面的天气是晴、多云还是下雨。你看不到天气(隐状态),只能看到活动(观测),但你知道天气会影响活动选择,而且今天的天气和昨天有关。

这就是**隐马尔可夫模型(Hidden Markov Model, HMM)**的基本设定。

形式化定义

HMM 由三组参数 λ=(A,B,π)\lambda = (A, B, \pi) 完全确定:

状态集合 S={s1,s2,,sN}S = \{s_1, s_2, \dots, s_N\}:所有可能的隐状态。在天气例子里 N=3N = 3(晴、云、雨)。

观测集合 O={o1,o2,,oM}O = \{o_1, o_2, \dots, o_M\}:所有可能的观测值。M=3M = 3(散步、购物、打扫)。

初始概率向量 π\piπi=P(q1=si)\pi_i = P(q_1 = s_i),第一个时刻处于状态 sis_i 的概率。

π=(0.6,0.3,0.1)(晴天开局的概率最大)\pi = (0.6, 0.3, 0.1) \quad \text{(晴天开局的概率最大)}

状态转移矩阵 AAaij=P(qt+1=sjqt=si)a_{ij} = P(q_{t+1} = s_j \mid q_t = s_i),从状态 ii 转到状态 jj 的概率。

A=(0.70.20.10.30.40.30.20.30.5)A = \begin{pmatrix} 0.7 & 0.2 & 0.1 \\ 0.3 & 0.4 & 0.3 \\ 0.2 & 0.3 & 0.5 \end{pmatrix}

第一行表示"今天晴 → 明天晴 0.7、明天云 0.2、明天雨 0.1"。每行的和为 1。

观测概率矩阵 BBbi(k)=P(ot=vkqt=si)b_i(k) = P(o_t = v_k \mid q_t = s_i),在状态 ii 下观测到 vkv_k 的概率。

B=(0.60.30.10.30.40.30.10.30.6)B = \begin{pmatrix} 0.6 & 0.3 & 0.1 \\ 0.3 & 0.4 & 0.3 \\ 0.1 & 0.3 & 0.6 \end{pmatrix}

晴天大概率散步,雨天大概率打扫——符合直觉。

两个关键假设

HMM 的整个数学推导依赖两条简化假设:

  1. 齐次马尔可夫假设:当前状态只依赖前一个状态,与更早的历史无关。
P(qtqt1,qt2,,q1)=P(qtqt1)P(q_t \mid q_{t-1}, q_{t-2}, \dots, q_1) = P(q_t \mid q_{t-1})
  1. 观测独立假设:当前观测只依赖当前状态,与其他时刻的状态和观测无关。
P(otqt,qt1,,ot1,)=P(otqt)P(o_t \mid q_t, q_{t-1}, \dots, o_{t-1}, \dots) = P(o_t \mid q_t)

这两个假设很强——现实中未必成立(比如天气可能受前几天的影响),但它们让计算变得可行。放松这些假设的模型就是条件随机场

三个基本问题

HMM 的全部理论围绕三个问题展开:

问题 1:概率计算(Evaluation)

给定模型 λ=(A,B,π)\lambda = (A, B, \pi) 和观测序列 O=(o1,o2,,oT)O = (o_1, o_2, \dots, o_T),计算这个观测序列出现的概率 P(Oλ)P(O \mid \lambda)

用途:模型选择。训练好几个 HMM(比如每个说话人一个),给定一段语音,看哪个模型的 P(Oλ)P(O \mid \lambda) 最大——就是谁在说话。

问题 2:学习(Learning)

给定观测序列 OO,估计模型参数 λ=(A,B,π)\lambda = (A, B, \pi),使得 P(Oλ)P(O \mid \lambda) 最大。

用途:从数据中训练模型。

问题 3:预测/解码(Decoding)

给定模型 λ\lambda 和观测序列 OO,找到最可能的隐状态序列 Q=(q1,q2,,qT)Q^* = (q_1^*, q_2^*, \dots, q_T^*)

用途:序列标注。观测是句子里的词,隐状态是词性标签——这就是词性标注问题。

三个问题的解法分别是前向算法、Baum-Welch 算法、维特比算法。

前向算法

暴力方法为什么不行

要算 P(Oλ)P(O \mid \lambda),最直接的想法是穷举所有可能的隐状态序列:

P(Oλ)=所有 QP(OQ,λ)P(Qλ)P(O \mid \lambda) = \sum_{\text{所有 } Q} P(O \mid Q, \lambda) \cdot P(Q \mid \lambda)

长度为 TT 的序列,每个时刻有 NN 种状态,一共有 NTN^T 条路径。N=3,T=10N = 3, T = 10 就有近 6 万条路径;T=100T = 100 就是 31005×10473^{100} \approx 5 \times 10^{47}——完全不可算。

前向变量

前向算法的核心思想是动态规划——把大问题拆成子问题,避免重复计算。

定义前向变量

αt(i)=P(o1,o2,,ot,  qt=siλ)\alpha_t(i) = P(o_1, o_2, \dots, o_t, \; q_t = s_i \mid \lambda)

含义:"在时刻 tt,已经观测到 o1,,oto_1, \dots, o_t,并且此刻处于状态 sis_i"的联合概率。

初始化t=1t = 1):

α1(i)=πibi(o1),i=1,2,,N\alpha_1(i) = \pi_i \cdot b_i(o_1), \quad i = 1, 2, \dots, N

递推t=2,,Tt = 2, \dots, T):

αt(j)=[i=1Nαt1(i)aij]bj(ot)\alpha_t(j) = \left[\sum_{i=1}^{N} \alpha_{t-1}(i) \cdot a_{ij}\right] \cdot b_j(o_t)

直觉:要到达 tt 时刻的状态 jj,可以从 t1t-1 时刻的任何状态 ii 转移过来。把所有路径的概率加起来,再乘以在状态 jj 下观测到 oto_t 的概率。

终止

P(Oλ)=i=1NαT(i)P(O \mid \lambda) = \sum_{i=1}^{N} \alpha_T(i)

复杂度从 O(NT)O(N^T) 降到了 O(N2T)O(N^2 T)——从指数级变成多项式级。

在下面的交互演示里点击"下一步",看前向算法如何逐步计算每个节点的 α\alpha 值。切换到"维特比算法"可以看到解码过程。

散步购物打扫散步t=1t=2t=3t=40.36000.09000.0100
α1(i)=[jα0(j)aji]bi(o1)\alpha_1(i) = \left[\sum_j \alpha_0(j) \cdot a_{ji}\right] \cdot b_i(o_1)

初始化:α₁(i) = π(i) · b_i(o₁)。当前显示 t=1 各隐状态的前向概率。

后向算法

类似地,可以从后往前算。定义后向变量

βt(i)=P(ot+1,ot+2,,oTqt=si,λ)\beta_t(i) = P(o_{t+1}, o_{t+2}, \dots, o_T \mid q_t = s_i, \lambda)

递推方向相反:

βt(i)=j=1Naijbj(ot+1)βt+1(j)\beta_t(i) = \sum_{j=1}^{N} a_{ij} \cdot b_j(o_{t+1}) \cdot \beta_{t+1}(j)

前向和后向结合可以算出任意时刻处于某状态的后验概率——这是 Baum-Welch 算法的基础。

学习:Baum-Welch 算法

有标注数据的情况

如果训练数据同时包含观测序列和对应的隐状态序列(比如标注好词性的语料),参数估计很简单——数频率:

a^ij=从状态 i 转移到 j 的次数从状态 i 出发的总次数\hat{a}_{ij} = \frac{\text{从状态 } i \text{ 转移到 } j \text{ 的次数}}{\text{从状态 } i \text{ 出发的总次数}} b^i(k)=在状态 i 下观测到 vk 的次数处于状态 i 的总次数\hat{b}_i(k) = \frac{\text{在状态 } i \text{ 下观测到 } v_k \text{ 的次数}}{\text{处于状态 } i \text{ 的总次数}}

这就是极大似然估计,直接且精确。但现实中隐状态标注往往没有或很昂贵。

无标注数据:EM 思想

如果只有观测序列,没有隐状态标注——怎么办?这正是 EM 算法的用武之地。

Baum-Welch 算法是 EM 算法在 HMM 上的具体实现:

E 步:用当前参数 λ(n)\lambda^{(n)} 和前向-后向算法,计算隐状态的后验概率。

定义两个关键量:

γt(i)=P(qt=siO,λ)=αt(i)βt(i)j=1Nαt(j)βt(j)\gamma_t(i) = P(q_t = s_i \mid O, \lambda) = \frac{\alpha_t(i) \cdot \beta_t(i)}{\sum_{j=1}^{N} \alpha_t(j) \cdot \beta_t(j)}

γt(i)\gamma_t(i) 是"在时刻 tt 处于状态 ii"的后验概率。

ξt(i,j)=P(qt=si,qt+1=sjO,λ)=αt(i)aijbj(ot+1)βt+1(j)mnαt(m)amnbn(ot+1)βt+1(n)\xi_t(i, j) = P(q_t = s_i, q_{t+1} = s_j \mid O, \lambda) = \frac{\alpha_t(i) \cdot a_{ij} \cdot b_j(o_{t+1}) \cdot \beta_{t+1}(j)}{\sum_{m}\sum_{n} \alpha_t(m) \cdot a_{mn} \cdot b_n(o_{t+1}) \cdot \beta_{t+1}(n)}

ξt(i,j)\xi_t(i, j) 是"在时刻 tt 处于状态 iit+1t+1 时刻转到状态 jj"的后验概率。

M 步:用后验概率更新参数。

π^i=γ1(i)\hat{\pi}_i = \gamma_1(i) a^ij=t=1T1ξt(i,j)t=1T1γt(i)\hat{a}_{ij} = \frac{\sum_{t=1}^{T-1} \xi_t(i, j)}{\sum_{t=1}^{T-1} \gamma_t(i)} b^i(k)=t=1,ot=vkTγt(i)t=1Tγt(i)\hat{b}_i(k) = \frac{\sum_{t=1, \, o_t = v_k}^{T} \gamma_t(i)}{\sum_{t=1}^{T} \gamma_t(i)}

反复迭代 E 步和 M 步,直到似然函数收敛。和所有 EM 算法一样,Baum-Welch 只保证收敛到局部最优,结果依赖初始值。

维特比算法

解码问题要找最可能的隐状态序列:

Q=argmaxQP(QO,λ)Q^* = \arg\max_Q P(Q \mid O, \lambda)

维特比算法和前向算法结构几乎一样,只是把求和换成了取最大值

定义维特比变量

δt(i)=maxq1,,qt1P(q1,,qt1,qt=si,o1,,otλ)\delta_t(i) = \max_{q_1, \dots, q_{t-1}} P(q_1, \dots, q_{t-1}, q_t = s_i, o_1, \dots, o_t \mid \lambda)

含义:在时刻 tt 到达状态 ii 的所有路径中,概率最大的那条路径的概率。

递推

δt(j)=max1iN[δt1(i)aij]bj(ot)\delta_t(j) = \max_{1 \le i \le N} \left[\delta_{t-1}(i) \cdot a_{ij}\right] \cdot b_j(o_t)

同时记录最大值来自哪个状态:

ψt(j)=argmax1iN[δt1(i)aij]\psi_t(j) = \arg\max_{1 \le i \le N} \left[\delta_{t-1}(i) \cdot a_{ij}\right]

终止:找到最后一步概率最大的状态,然后回溯 ψ\psi 得到完整路径。

qT=argmaxiδT(i)q_T^* = \arg\max_{i} \delta_T(i) qt=ψt+1(qt+1),t=T1,T2,,1q_t^* = \psi_{t+1}(q_{t+1}^*), \quad t = T-1, T-2, \dots, 1

在上面的交互演示中切换到"维特比算法",走到最后一步可以看到最优路径高亮——回溯过程把概率最大的隐状态序列串了起来。

前向 vs 维特比

前向算法维特比算法
解决的问题概率计算 P(Oλ)P(O \mid \lambda)解码 QQ^*
核心操作求和(边缘化)取最大值(优化)
输出一个概率值一条最优路径
复杂度O(N2T)O(N^2 T)O(N2T)O(N^2 T)

两者的结构完全对称——理解了一个,另一个自然就懂了。

这个想法在前沿里