前部分既然实战,那就直接代码,理论不清楚的看这里:
(1)04 隐马尔可夫模型 (HMM)
:https://cloud.tencent.com/developer/article/1604283
(2)一个隐马尔科夫模型的应用实例:中文分词: https://blog.csdn.net/u014365862/article/details/54891582
代码语言:javascript复制'''
下面抽取5次,得到已知的观察序列,
来对HMM的参数进行估计,即使用MultinomialHMM进行参数的训练
'''
import numpy as np
import hmmlearn.hmm as hmm
states = ['盒子1', '盒子2', '盒子3']
obs = ['白球', '黑球']
n_states = len(states)
m_obs = len(obs)
model2 = hmm.MultinomialHMM(n_components=n_states, n_iter=20, tol=0.001)
X2 = np.array([
[0, 1, 0, 0, 1],
[0, 0, 0, 1, 1],
[1, 1, 0, 1, 0],
[0, 1, 0, 1, 1],
[0, 0, 0, 1, 0]
])
model2.fit(X2)
print("输出根据数据训练出来的π")
print(model2.startprob_)
print("输出根据数据训练出来的A")
print(model2.transmat_)
print("输出根据数据训练出来的B")
print(model2.emissionprob_)
代码语言:javascript复制输出根据数据训练出来的π
[ 9.99999998e-01 1.81378454e-09 8.05262820e-30]
输出根据数据训练出来的A
[[ 0.30459785 0.18910843 0.50629372]
[ 0.44920744 0.35079347 0.19999909]
[ 0.48427612 0.45080508 0.0649188 ]]
输出根据数据训练出来的B
[[ 0.9600675 0.0399325 ]
[ 0.48750091 0.51249909]
[ 0.02464824 0.97535176]]
解码问题:已知观察序列,求什么样的隐藏状态序列最可能生成一个给定的观察序列
代码语言:javascript复制import numpy as np
import hmmlearn.hmm as hmm
# 首先定义变量
status = ['盒子1', '盒子2', '盒子3'] # 隐藏的状态集合
obs = ['白球', '黑球'] # 观察值集合
n_status = len(status) # 隐藏状态的长度
m_obs = len(obs) # 观察值的长度
start_probability = np.array([0.2, 0.5, 0.3])
'''
初始概率分布: π 表示初次抽时,抽到1盒子的概率是0.2,抽到2盒子的概率是0.5,抽到3盒子的概率是0.3
'''
transition_probability = np.array([
[0.5, 0.4, 0.1],
[0.2, 0.2, 0.6],
[0.2, 0.5, 0.3]
])
'''
状态转移概率矩阵 A[0][0]=0.5 表示当前我抽到1盒子,下次还抽到1盒子的概率是0.5
'''
emission_probalitity = np.array([
[0.4, 0.6],
[0.8, 0.2],
[0.5, 0.5]
])
'''
观测概率矩阵 B:如最初的图,B[0][0]=0.4,表示第一个盒子抽到白球概率0.4,B[0][1]=0.6,表示第一个盒子抽到黑球概率0.6
以上各个参数解释请参考下面链接:
https://www.jianshu.com/p/da633461684f
https://www.jianshu.com/p/0f175b9781de
'''
# 下面开始定义模型
'''
hmmlearn中主要有两种模型,分布为:GaussianHMM和MultinomialHMM;
如果观测值是连续的,那么建议使用GaussianHMM,否则使用MultinomialHMM
参数:
初始的隐藏状态概率π参数为: startprob;
状态转移矩阵A参数为: transmat;
状态和观测值之间的转移矩阵B参数为: emissionprob_(MultinomialHMM模型中)或者在GaussianHMM模型中直接给定均值(means)和方差/协方差矩阵(covars)
'''
model = hmm.MultinomialHMM(n_components=n_status) # 观测值,球的黑或白,是离散的,n_status隐藏状态的长度
model.startprob_ = start_probability
model.transmat_ = transition_probability
model.emissionprob_ = emission_probalitity
'''
下面运行viterbi预测问题。已知观察序列(白球 黑球 白球 白球 黑球),
求什么样的隐藏状态序列(盒子2 盒子3 盒子2 盒子2 盒子3)最可能生成一个给定的观察序列。
status = ['盒子1', '盒子2', '盒子3']
obs = ['白球', '黑球']
'''
se = np.array([[0, 1, 0, 0, 1]]).T # (白球 黑球 白球 白球 黑球)
logprob, box_index = model.decode(se, algorithm='viterbi')
print("颜色:", end="")
print(" ".join(map(lambda t: obs[t], [0, 1, 0, 0, 1])))
print("盒子:", end="")
print(" ".join(map(lambda t: status[t], box_index)))
print("概率值:", end="")
print(np.exp(logprob)) # 这个是因为在hmmlearn底层将概率进行了对数化,防止出现乘积为0的情况
输出结果如下:
代码语言:javascript复制颜色:白球 黑球 白球 白球 黑球
盒子:盒子2 盒子3 盒子2 盒子2 盒子3
概率值:0.002304
理解了hmm理论和简单的实践,再看一下如何用在语音识别上的:
语音信号预处理:
分帧:
首先说说我们做信号处理的目的----获得个频率成分的分布,而实现的该功能的数学方法就是FFT ,FFT要求输入信号时平稳的,语音作为非平稳信号要实现就要通过分帧
这里分帧就涉及到帧长,对于帧长要满足的条件有两点:
1、正常语速下,音素的持续时间大约是 50~200 毫秒,所以帧长一般取为小于 50 毫秒。
2、语音的基频,男声在 100 赫兹左右,女声在 200 赫兹左右,换算成周期就是 10 毫秒和 5 毫秒。既然一帧要包含多个周期,所以一般取至少 20 毫秒。
综上帧长一般取为 20 ~ 50 毫秒,20、25、30、40、50 都是比较常用的数值,以上摘自知乎逻辑上很合理的解释,我通常听见的是(10-30ms)
一帧的数据长度 N=帧时间长度/T=帧时间长度(单位秒)*fs(单位Hz)
加窗:
加窗的目的是让一帧信号的幅度在两端渐变到 0。渐变对傅里叶变换有好处,可以提高变换结果(即频谱)的分辨率,具体的数学就不讲了
加窗的代价是一帧信号两端的部分被削弱了,没有像中央的部分那样得到重视。弥补的办法是,帧不要背靠背地截取,而是相互重叠一部分。相邻两帧的起始位置的时间差叫做帧移,常见的取法帧移与帧长的比值一般取为0~1/2。
通常加窗之后我们可以通过FFT变化实现特征数的降维以及提取出比原始语音更具表征力的特征
以上可以理解为若干帧对应一个音素,若干音素对应一个单词,如果我们想要识别对应的单词状态,我们只要知道对应的帧状态就行,用计算机能识别的方式最简单的就是概率匹配,这些概率我们就可以通过声学模型获得,所以要做的就是通过训练获得合适的模型参数以拟合好的匹配效果。
GMM HMM算法
语音识别就分为三步:第一步,把帧识别成状态(难点)。第二步,把状态组合成音素。第三步,把音素组合成单词。第一步可以当做gmm做的,后面都是hmm做的。
声学模型:描述一种语言的基本单位被称为音素Phoneme,例如BRYAN这个词就可以看做是由B, R, AY, AX, N五个音素构成的。英语中貌似有50多个音素,可以用50几个HMM state来表示这些音素,这种表示方法就是context independent模型中的单音素monophone模式。然而语音没有图像识别那么简单,因为我们再说话的时候很多发音都是连在一起的,很难区分,所以一般用左中右三个HMM state来描述一个音素,也就是说BRYAN这个词中的R音素就变成了用B-R, R, R-AY三个HMM state来表示。这样BRYAN这个词根据上下文就需要15个state了,根据所有单词的上下文总共大概需要几千个HMM state,这种方式属于context dependent模型中的三音素triphone模式。这个HMM state的个数在各家语音识别系统中都不一样,是一个需要调的参数。所以声学模型就是如何设置HMM state,对于信号中的每一frame抽怎样的特征,然后用训练什么分类器。
可以理解为整个GMM HMM网络其实主要是为了HMM网络服务的,为什么这么说,先说说HMM对于语音识别需要解决的问题,比如把一系列MFCC特征正确的识别成对应HMM state 系列。这个过程涉及两个概率需要学习,一是把当前frame的特征识别为这个state的概率,也就是通常HMM中说的Likelihood---这里指计算层面(也是GMM中的mean vector 和covariance matrix ),即GMM网络是为了获得当前state状态概率的,二是上个state转化为这个state的概率也就是状态转移概率Transition probabilities,这个过程是HMM中说的 Decoding---这里指计算层面。一个序列转化为另一个序列理论上有指数级种转化方式,所以每一个frame只取概率最高的那个state,这样的路线选择方法被称为Viterbi 方法。
下面结合实际语音输入讲讲整个过程:
首先我们在训练阶段,我们是知道这段语音所表示的句子吧。我们通过句子,然后分词,然后分成每个音素,在隐马尔科夫(HMM)模型中一般用3-5个上述的单元表示一个音素。简单的理解就是我们每个音素的均值和方差矩阵知道,通过我们的句子我们也知道每个音素间的转移概率矩阵。当然,这些是HMM里的事情。提取特征后的第一步就完成了,简单的说就是为了拟合多维高斯函数。
一开始,我们设置每个音素的均值和方差分别为0和1,转移概率矩阵在htk里也是可以设置两头小中间大,这个对于5个状态的hmm,即每个音素分为5个状态。这步就是初始化hmm。
然后,生成各个音素的hmm。这个可以根据发音字典和原始的hmm来生成。
最后,我们根据训练数据来训练音素级的hmm。这里用到hmm的三大问题。通过训练,我们会得到三个参数:初始状态概率分布π、隐含状态序列的转移矩阵A(就是某个状态转移到另一个状态的概率观察序列中的这个均值或者方差的概率)和某个隐含状态下输出观察值的概率分布B(也就是某个隐含状态下对应于)
参考:
(1)GMM-HMM语音识别模型 原理篇
: https://blog.csdn.net/abcjennifer/article/details/27346787
(2)https://zhuanlan.zhihu.com/p/63753017
(3) 基于GMM-HMM的语音识别系统(1):https://zhuanlan.zhihu.com/p/107693481
-