维特比算法
背景
首先介绍HMM,隐马尔可夫模型。 HMM是指状态随时间不断转移,转移过程满足马尔科夫性,然而我们并不知道状态是多少,只知道每个时刻的观测值,那么给定一个观测序列,如何找到隐含的状态转移呢?
建模
首先我们应该先明确所谓找到隐含的状态转移就是找到最有可能的状态转移,所以自然这是一个概率建模。我们一般会先确定相关的概率,首先最容易确定的是,一般假设当前的观测值只和当前状态有关,此外HMM的假设里也可以知道,这样有了这两类概率,我们就对整个模型有了定量描述。至于说具体这些概率值是多少,这取决于你的问题,domain expert可以发挥作用。后面应用中会具体介绍。
算法
一个naive的思路是暴力枚举所有可能的状态转移,对每一条转移序列计算概率,如果设总状态有个,一共有步,那么路径数量可以达到,不是一个好算法。
从最短路径得到启发,其实我们找的就是一个最短路径,甚至比最短路还要简单,因为他不像图那样有复杂拓扑结构。所以很自然这里面存在最优子结构,那就是:从的最有可能的状态转移路径中一定包含了中最有可能的路径。
有了上面的发现,就可以设计动态规划的递推公式了。首先明确状态,表示在第时刻,到达状态且观测为的最有可能的路径概率,因此
右侧的max其实是在枚举找上一轮到达状态最大可能的路径。
对于的初始情况,我们直接用来表示。 这样按照由小到大递推完,同时记录下每个选择max对应的状态,然后在从后往前得到状态转移路径即可。
伪代码如下
# p_trans是状态转移概率P(S|S'),p_observ是观察概率P(O|S)
# observs是观察序列
是观察P(O|s)
dp = np.zeros([T, N])
choice = np.zeros([T, N])
# 初始化
for s in range(N):
dp[0, s] = p_observ(observs[0], s)
# 递推
for t in range(1, T):
for s in range(N):
dp[t, s] = INF
cur_observ_prob = p_observ(observs[0], s)
for s_ in range(N):
if dp[t, s] < dp[t-1,s_] * cur_observ_prob:
dp[t, s] = dp[t-1, s_] * cur_observ_prob
choice[t, s] = s_
# 递推找路径
last = np.argmax(dp[T-1])
path = [last]
cur = choice[T - 1, last]
for t in reversed(range(T-1)):
prev = dp[t, cur]
path.append(prev)
cur = prev
return path.reverse()
应用
其实与其说是viterbi的应用,不如说是HMM的应用。
比如在时空领域,通常需要把GPS定位点和路网做匹配,这里observ就是gps坐标,state就是路网中一条特定的路段。那么一个坐标在某条路段的概率就是,这个可以用距离配合高斯分布估算概率。而状态转移概率则是路网中从一段路转移到另一段路的概率,这里一半假设司机走最短路,所以可以按两个路段最短路的指数分布计算概率。当然这都是很直观的概率建模,你也可以考虑速度、方向、路段的其他信息等优化概率建模,这就是domain expert可以卷和水paper的地方了。。。
在语音识别领域,如何把一段音频转化成文字,可以认为observ是观测到的音频,状态转移是各个单词之间的转移概率。当然NLP的专家有一万种方法可以计算这里的概率。
总之其实模型一旦确定,框架就定死了,优化只不过是在调整里面的具体建模数值。从水文章的角度,HMM这么古老的东西肯定不行啦,但是如果domain里用到了时兴的技术或者对HMM框架有点小微调也许可以。