深入理解隐马尔可夫模型(HMM):概念、算法与应用

725 阅读11分钟

本人是编程小白,刚学习完马尔可夫模型,以下是我对此模型的理解,如有错误还请各位大佬指点,小弟在此谢过!!!

隐马尔可夫模型(HMM,Hidden Markov Model)是一种基于概率的统计模型,广泛应用于序列数据的建模。HMM的核心思想是通过可观察的结果推测系统内部的隐性状态,广泛应用于语音识别、手写识别、中文分词等领域。

1. 什么是隐马尔可夫模型

隐马尔可夫模型是一种描述系统状态转移和观测数据间接联系的模型。其特点包括:

  • 状态隐藏:我们无法直接观察到系统的状态,只能通过观测数据推测。
  • 马尔可夫性:当前状态仅依赖于前一个状态,与更早的状态无关。

举个例子:

假设你无法直接观察到自己的行为(状态),但可以通过明天的天气预报(观测值)推测明天的行为。HMM就是为了解决这种推测问题而提出的。

2. HMM的基本组成部分

  • 状态集合(States) :表示系统可能处于的状态集合,如天气(晴天、雨天)。
  • 观测集合(Observations) :每个状态可能生成的观测值集合,如行为(滑板、睡觉)。
  • 状态 转移概率 矩阵( Transition Matrix :描述不同状态之间的转换概率,记作 A = {aij},其中aij表示从状态si转移到状态sj的概率。如:今天天晴,明天下雨的概率为0.3
  • 观测概率矩阵(Emission Matrix) :描述在某一状态下生成某个观测值的概率,如在天晴状态下,滑板的概率是0.8,宅家的概率是0.2。
  • 初始状态概率(Initial State Probabilities) :系统初始状态的概率分布,如 初始状态下,天晴的概率是0.6,下雨概率是0.4。

3. HMM的三大问题

HMM 通常用于解决以下三类问题:

  • 评估问题(Evaluation Problem)
    • 给定模型参数和观测序列,计算该观测序列出现的概率。
    • 例如:已知模型参数和连续两天的天气,计算这种观测序列的概率。
  • 解码问题(Decoding Problem)
    • 给定模型参数和观测序列,找到最可能的状态序列。
    • 例如:已知模型参数和连续两天的天气,推测这两天的可能行为。
  • 学习问题(Learning Problem)
    • 给定观测序列,估计模型参数(状态转移矩阵、观测概率矩阵、初始状态概率)。
    • 例如:已知连续多天的天气,推测模型参数。

4. HMM的算法

为了解决问题,HMM 使用以下经典算法:

  • 前向算法(Forward Algorithm) :用于解决评估问题,计算观测序列的概率。
  • 维特比算法(Viterbi Algorithm) :用于解决解码问题,找到最可能的状态序列。
  • Baum-Welch 算法(EM 算法的一种) :用于解决学习问题,通过迭代优化模型参数。

5. HMM的简单例子

示例一: 天气模型

假设有一个天气模型,其中:

  • 状态集合:S = {晴天,雨天}
  • 观测集合:O = {滑板,睡觉}

状态转移矩阵:

  • 今天晴天,明天晴天的概率是0.8,雨天的概率是0.2
  • 今天雨天,明天晴天的概率是0.3,雨天的概率是0.7

初始状态概率:

  • 初始是晴天的概率是0.6,雨天的概率是0.4

观测序列是:{滑板,睡觉,滑板},我们可以用HMM来评估这个序列的概率,或者解码出最可能的天气序列。

代码实现:

使用Python的hmmlearn库

from hmmlearn import hmm
import numpy as np

# 定义模型参数
states = ['晴天' , '雨天']
observations = ['滑板', '睡觉']
n_states = len(states)
n_observations = len(observations)

# 初始状态概率
start_prob = np.array([0.6, 0.4])

# 状态转移矩阵
trans_mat = np.array([[0.7, 0.3], [0.4, 0.6]])

# 观测概率矩阵
emit_mat = np.array([[0.8, 0.2], [0.3, 0.7]])

# 创建 HMM 模型
model = hmm.CategoricalHMM(n_components=n_states)
model.startprob_ = start_prob
model.transmat_ = trans_mat
model.emissionprob_ = emit_mat

# 观测序列
obs_seq = np.array([[0, 1, 0]]).T

# 解码问题:找到最可能的状态序列
logprob , state_seq = model.decode(obs_seq, algorithm='viterbi')
print("最可能的状态序列:", [states[i] for i in state_seq])

示例二:骰子模型

  1. 背景设定:

假设你手中有三个骰子:一个六面骰(D6),一个四面骰(D4),和一个八面骰(D8)。每次掷骰子时,首先从这三种骰子中随机选择一个(每种骰子的选择概率为1/3),然后掷骰子,得到一个结果。你已经掷了十次骰子,得到的结果是:(1, 6, 3, 5, 2, 7, 3, 5, 2, 4),这是我们所说的可见状态链。但你并不知道每次掷骰子时使用的是哪一个骰子。现在你希望找出最有可能的骰子序列。

  1. 隐含状态链:

在隐马尔可夫模型中,不仅仅有这么一串可见状态链,还有一串隐含状态链(骰子序列),这是我们无法直接观察到的,是通过可见状态链(掷出的数字序列)来推测的。在这个例子里,这串隐含状态链就是你用的骰子的序列。比如,隐含状态链有可能是:

D6 D8 D8 D6 D4 D8 D6 D6 D4 D8

一般来说,HMM中说到的马尔可夫链其实是指隐含状态链,因为隐含状态(骰子)之间存在转换概率。

  1. 转换概率:

隐含状态(即骰子)之间的转换概率是我们需要定义的,例如:

  • D6后面可能是D4、D6、或者D8,且每种的概率是1/3。
  • D4后面可以是D4、D6或者D8,转换概率也可以定义为1/3。

你可以自定义这些转换概率,甚至做一些特别的设置,比如:

  • D6后面不能接D4,D6后面是D6的概率是0.9,D8的概率是0.1,这样就是一个新的HMM。
  1. 输出概率

每个骰子掷出的数字(1到6、1到4、1到8)会有一个输出概率。举个例子,D6的掷出结果1的概率是1/6,掷出其他数字的概率也是1/6。如果是被做过手脚的D6,结果1的概率可能是1/2,其他数字的概率可能是1/10。

  1. Viterbi算法(解码问题的解法):

假设我们知道有三种骰子,且有一个掷骰子的结果序列(比如:1 6 3 5 2 7 3 5 2 4),我们想找出最有可能的骰子序列(隐含状态链)。

其实最简单而暴力的方法就是穷举所有可能的骰子序列,然后计算每个序列产生观测结果的概率。最终,选择对应最大概率的序列。然而,随着序列长度的增加,穷举法所需要的计算量会急剧增大,因此这种方法只适用于较短的序列。

另一个更高效的方法是使用Viterbi算法,这是一种经典的动态规划算法,专门用于求解隐马尔可夫模型中的最优状态序列问题。为了更好地理解这个算法,我们可以从简单的例子入手。

首先,如果我们只掷一次骰子:

看到结果为1,对应的最大概率骰子序列就是D4,因为D4产生1的概率是1/4,高于1/6和1/8.

P1(D4)=P(D4)P(D4>1)P1(D4)=P(D4)*P(D4->1)

=1314=\frac{1}{3}*\frac{1}{4}

把这个情况拓展,我们掷两次骰子:

结果为1,6,这时问题变得复杂起来,我们要计算三个值,分别是第二个骰子是D6,D4,D8的最大概率。显然,要取到最大概率,第一个骰子必须为D4。这时,第二个骰子取到D6的最大概率是

P2(D6)=P1(D4)P(D4>D6)P(D6>6)P2(D6)=P1(D4)*P(D4->D6)*P(D6->6)

=1121316=1216=\frac{1}{12}*\frac{1}{3}*\frac{1}{6}=\frac{1}{216}

同样的,我们可以计算第二个骰子是D4或D8时的最大概率。我们发现,第二个骰子取到D6的概率最大。而使这个概率最大时,第一个骰子为D4。所以最大概率骰子序列就是D4 D6。

继续拓展,我们掷三次骰子:

同样,我们计算第三个骰子分别是D6,D4,D8的最大概率。我们再次发现,要取到最大概率,第二个骰子必须为D6。这时,第三个骰子取到D4的最大概率是

P3(D4)=P2(D6)P(D6>D4)P(D4>3)P3(D4)=P2(D6)*P(D6->D4)*P(D4->3)

=12161314=\frac{1}{216}*\frac{1}{3}*\frac{1}{4}

同上,我们可以计算第三个骰子是D6或D8时的最大概率。我们发现,第三个骰子取到D4的概率最大。而使这个概率最大时,第二个骰子为D6,第一个骰子为D4。所以最大概率骰子序列就是D4 D6 D4。

通过以上步骤,你可以看出,Viterbi算法的核心是动态规划,每次通过已知的前一步最大概率来计算当前步的最大概率,并逐步构建出整个序列。具体步骤如下:

  1. 初始化:从观测序列的第一项开始,计算每个可能的骰子的概率。
  2. 递推:对于每一个新的观测结果,计算每种骰子状态的最大概率,并依赖前一个骰子的选择。
  3. 回溯:当处理完所有的观测结果后,从最后一个骰子的状态回溯,找出最有可能的骰子序列。

最终,Viterbi算法不仅能得到最有可能的骰子序列,还能避免穷举所有可能序列的巨大计算量,提高了效率。

代码实现:

from  hmmlearn import  hmm
import numpy as np

# 定义参数模型
states = ['D6', 'D4', 'D8']
observations = list(range(1,9))
n_states = len(states)
n_observations = len(observations)

# 初始状态概率
states_prob = np.array([1/3, 1/3, 1/3])

# 状态转移矩阵
trans_mat = np.array([
    [1/3,1/3,1/3],
[1/3,1/3,1/3],
[1/3,1/3,1/3]
])

# 观测概率矩阵
emit_mat = np.array([
    [1/4 if i < 4 else 0 for i in range(8)],
    [1/6 if i < 6 else 0 for i in range(8)],
    [1/8 for _ in range(8)]
])

# 观测序列
obs_seq = np.array([[1,6,3,5,2,7,3,5,2,4]]).T - 1

# 创建 hmm 模型
model = hmm.CategoricalHMM(n_components=n_states)
model.startprob_ = states_prob
model.transmat_ = trans_mat
model.emissionprob_ = emit_mat

logprob, state_seq = model.decode(obs_seq, algorithm='viterbi')

print("观测序列: ", obs_seq.flatten() + 1)
print("最可能的状态序列: ", [states[i] for i in state_seq])

6. HMM的应用

使用HMM进行建模的问题,必须满足以下条件:

  • 隐性状态的转移必须满足马尔可夫性(状态转移的马尔可夫性:一个状态只与前一个状态有关)
  • 隐性状态必须能够大概被估计

语音识别

语音识别问题就是将一段语音信号转换为文字序列的过程

在这个问题里,隐性状态就是:语音信号对应的文字序列,显性状态就是:语音信号。

语音识别的HMM模型学习有三个步骤:

  1. 统计文字的发音概率,建立隐性表现概率矩阵
  2. 统计字词之间的转换概率(不需要考虑语音,直接统计字词之间的转移概率即可)
  3. 语音模型的估计(Evaluation): 计算"四是四”,"十是十"等等的概率,比较得出最有可能出现的文字序列。

由此可见,其原理和上面的破解骰子序列是一样的。

手写识别

手写识别问题就是将在手写设备上书写时产生的有序轨迹信息转换为文字的过程。

原理和语音识别差不多,只不过手写识别是将字的图像当成了显性序列。

中文分词

总所周知,在汉语中,词与词之间不存在分隔符,词本身也缺乏明显的形态标记(英文中,词与词之间用空格分隔,这是天然的分词标记)。因此,中文信息处理的特有问题就是如何将汉语的字串分割为合理的词语序。

例如,英文句子:you should go to kindergarten now. 天然的空格已然将词分好,只需去除其中的介词“to”即可;而“你现在应该去幼儿园了”这个句子表达同样的意思却没有明显的分隔符,中文分词的目的是得到“你/现在/应该/去/幼儿园/了”。那么如何进行分词呢?

主流的方法有三种:

  • 第1类是基于语言学知识的规则方法,如:各种形态的最大匹配、最少切分方法。
  • 第2类是基于大规模语料库的机器学习方法,这是目前应用比较广泛、效果较好的解决方案。用到的统计模型有N元语言模型、信道—噪声模型、最大期望、HMM等。
  • 第3类也是实际的分词系统中用到的,即规则与统计等多类方法的综合。

7. 总结

  • HMM 是一种强大的工具,用于建模序列数据。
  • 它的核心是状态、观测、状态转移概率和观测概率。
  • 通过前向算法、维特比算法和 Baum-Welch 算法,可以解决评估、解码和学习问题。

参考博客:developer.aliyun.com/article/441…