2016-07-29 00:09:36 yinglang19941010 阅读数 1295

HMM处理三种问题,我理解为三种功能
- 给出 O (可观测序列o1 o2 o3… ot),求P(O | hmm)
- 给出 O,求出使P(O, H | hmm)最大的 H (H为与O对应的隐藏层时序序列)
- 给出 O,求出使P(O | hmm) 最大的 hmm(参数调整)

HMM应用于孤立词语音识别流程描述

这里写图片描述

(训练:功能三)
1. 给出a, b, c三个词的音频库(Wa, Wb, Wc),对音频库的所有样本进行特征提取。(这里以mfcc为例),对每一段音频 w 先分成若干帧[w1,w2, w3…wn],对每一帧提取mfcc,获得[mfcc1,mfcc2,mffcc3…mfcc_n]。
2. 对这些mfcc进行聚类处理(比如k-means),获得M个类(类中心),每个类对应一个HMM的可观测层,同时将所有的mfcc进行分类,转化为可观测层的状态,该mfcc归到哪个类就对应哪个可观测状态。(矢量量化)对每一段音频w的mfcc串[mfcc1,mfcc2,mffcc3…mfcc_n],就会获得它的可观测序列[o1, o2, o3…on]
3. 从而获取了词汇 a ,b ,c 的所有O 序列,记为Oa,Ob,Oc;(Ox表示词汇x的所有样本的可观测层序列向量组成的矩阵)
4. 基于上述的HMM的第三个功能,以Oa,Ob,Oc作为条件,分别获得三个HMM,记为HMMa,HMMb,HMMc;

(识别:功能一)

  1. 给出一段孤立词x的音频,对他提取mfcc串,对提取出的mfcc串使用上面聚类的中心进行分类,进而转化为可观测序列记为ox;
  2. 基于HMM的第一个功能,将 ox 输入到HMMa,HMMb,HMMc中获得三个概率,记为Pa,Pb,Pc;
  3. 比较Pa,Pb,Pc,获取最大的Py(y = a 或 b 或 c )
  4. x = y;

HMM + GMM
上述孤立词识别使用的是离散型的HMM,即可观测层的状态个数是有限的(M个中心,所以是M个),因此对于mfcc这种连续的高维特征,必须要做矢量量化,但也因此引入了量化误差,所以提出使用连续性HMM。

首先说明一个概念,其实任何一个隐层状态到可观测状态的概率向量,都可以看成是一个概率分布。所以连续型的HMM把每个隐层状态到可观测层的概率分布都看成是一个GMM分布。

bj(X)=m=1MwjmN(X|μjm,Σjm)

其中N表示高维正态分布(高斯分布)。
X 就直接使用高维特征,比如mfcc。

但是这样带来的问题是,大量的参数引入,使得计算的复杂量很高,同时在数据量不足时,发挥不出优势;所以又提出了半连续型HMM,即:

bj(X)=m=1MwjmN(X|μm,Σm)

也就是所有的bj(X)分布共享一套高斯函数,只是分歧系数w有所不同。

离散HMM简单的代码cpp实现: https://github.com/yinglang/HMMInLab/tree/master/myHMM

2018-05-31 12:30:06 JosephPai 阅读数 3583

1 从语音识别说起

语音识别是什么,通俗来说,就是输入音频,输出识别文字结果。基本方程如下
这里写图片描述
这里写图片描述: 识别结果
W:任一单词(以孤立词举例说明)
O:输入的语音序列(Observation Sequence)

上述方程的变换应用了Bayes Rule.

等式右边是两项乘积,P(W)来自语言模型(Language Model, LM), 常用的模型有 N-gram。 P(O | W)来自于声学模型(Acoustic Model, AM),传统的语音识别系统普遍采用的是基于GMM-HMM的声学模型,其中GMM用于对语音声学特征的分布进行建模,HMM则用于对语音信号的时序性进行建模。具体介绍网上有很多通俗易懂的文章,这里暂不赘述。本文重点讲解声学模型中的解码问题,暂不涉及语言模型。

2 计算P(O | W) —— 解码

P(O | W)可以继续分解如下
P(O | W) = P(A, O | W) 这里写图片描述
其中A表示状态序列,O表示观测序列,a表示转移概率,b表示观测概率
这里写图片描述

如图,上方是状态序列(state sequence)(HMM),下方是观测序列(observation sequence)。我们看到,states之间存在不同的转移方式,每个state针对observation也有不同generate方式。可以想象,通过组合,我们可以得到一个巨大的状态网络。而我们语音识别的任务,通俗来说就是从这个巨大的状态网络中搜索到最佳路径(partial path),也就是最大概率的路径,对应上面公式的argmax。这个搜索匹配的过程在语音识别中叫做解码(decode)。在众多路径中找到最佳路径,一种暴力方法是穷举,但是计算量大到不现实。目前应用的主流方法是Viterbi算法。

3 Viterbi算法

Viterbi算法的基础概括成下面三点:

  1. 如果概率最大的路径P(或者说是最短路径)经过某点a,那么这条路径上从起始点s到a的这一段子路径一定是s到a之间的最短路径。否则用s到a的最短路径来替换上述路径,便构成了一条比P更短的路径,矛盾。
  2. 从S到E的路径必定经过第i时刻的某个状态,假定第i时刻有k个状态,那么如果记录了从S到第i个状态的所有k个节点的最短路径,最终的最短路径必经过其中的一条。这样,在任何时刻,只需要考虑非常有限条最短路径即可。
  3. 结合上述两点,假定当我们从状态i进入状态i+1时,从S到状态i上各个节点的最短路径已经找到,并且记录在这些节点上,那么在计算从起点S到前一个状态i所有的k个结点的最短路径,以及从这k个节点到Xi+1,j的距离即可。

    这里写图片描述

上图是一个简单的四个状态HMM应用Viterbi算法的例子。
Viterbi算法的思想是典型的动态规划思想。动态规划相对穷举已经大大减少了计算量,然而面对巨大的网络,我们意识到还是有很多不必要的计算。有些路径的计算过程中概率已经很小,完全偏离了我们要的最佳路径,继续计算这样的路径显然是没有必要的。这个时候就需要剪枝(pruning),剪枝的Viterbi算法其中一个实现叫做Token Passing,著名的语音识别工具箱HTK的解码部分应用的就是这一概念模型。

4 Token Passing Approach

4.1 概念模型

假设每一个HMM的state可以保存一个或多个Token。Token是一个概念上的对象object,它可以在state之间进行传递,一般都是按照箭头指向的方向,所以也叫前传(propagate)。每一个Token携带着它所经过路径的打分score,这个分值一般是log量级的概率和(因为我们要找的是最大概率路径嘛,也就是最高分的路径。)Token的传递是以观测序列的generate为节拍进行。
你可以想象每一条路径都是一条贪吃蛇,token就是蛇的头部的那一节,身体部分就是他所经过的state路径。算法过程大致如下:

初始化(t=0):
    初始state(入口处)的Token的s=0
    其他state的Token的s=-inf
执行过程(t>0):
    复制若干数目Token,并将其传递至所有与该state连接的其他state中,并且对其值做如下操作:  
    在每个state中,比较所有token,留下分值最高的token,抛弃其他所有token(Viterbi剪枝过程)
终态(t=T):
    比较所有终态(final state)的Token,保留其中分数最高的token

该token对应的就是最佳路径的概率。

这里写图片描述

上图是一个Token Passing示意图,过段时间我计划做一个flash或者录一个小视频来更直观的演示这个过程。

4.2 孤立词识别(Isolated Word Recognition)

将Token Passing的思想应用到语音识别的解码过程中,我们首先从孤立词识别引入。一个英文单词音频一般分为三个音素,而一个音素又可以分为若干个状态,这些层级展开一个网络。他们之间的跳转符合隐马尔假设,所以可以应用Viterbi算法进行搜索解码。
单词级的HMM和音素级的HMM都可以直接套用上文的Token Passing模型,实现如下。
这里写图片描述
这里写图片描述

通过这一过程,我们对每一个单词都建立了一个HMM model,为后面的连续词识别奠定了基础。

4.3 连续识别(Connected Word Recognition 或 Continuous Speech Recognition)

连续识别的主要问题在于:

① 音频中词与词之间的分界线(boundary)不明确
② 音频序列中总的单词数目不能确定

应用Token Passing模型,我们可以以上文的孤立词识别为基础,将不同的孤立词模型组合成一个网络,构成复合的语句级别的HMM模型(sentence model),并进行下图所示的抽象。在interface右边是上文提到的孤立词级别的模式匹配模型,在interface右边是语句级别的连续识别匹配,这时候我们就可以不用关注单词级别的具体实现。

这里写图片描述

具体识别过程,即在上文提到的语句级复合HMM中继续应用Token Passing模型(因为我们的目标和之前一样,还是寻找最佳路径)。

这里写图片描述

此外,在连续词识别中,我们还需要对基础的Token Passing进行一些扩展。为了能够准确的记录我们识别出的句子所包含的单词,我们引入一个新的概念Word Link Record(WLR). 顾名思义,这是一条记录,里面存放着记录word link的指针。Token除了携带score信息,还要携带一个path identifier来记录上一个WLR。
在C/C++中,token的数据结构大概会是:

struct Token{
    double score;
    struct WRL *path_id;      
    }

在上面的Token Passing基础算法中,我们新增如下过程:

在时间 t ,对每一个完成单词级识别的token(从小网络传递到大网络) do:
    创建一个新的WLR
    WLR包含 <token内容(最大概率), 时间t, 记录路径的path id, 刚刚完成识别的单词model id>
    将token中的path id指向刚刚创建的这条新的WLR
end

这里写图片描述

如图所示,WLR构成的链表结构,记录了所有潜在的boundary信息。例如,t-3时刻很可能是单词two的结尾。
当到达T时刻整个句子识别结束后,我们再通过比较,得到得分最高的token,然后通过该token的path id以及WLR的链表结构可以轻松的回溯得出识别结果。

5 总结

以上就是应用Token Passing模型实现Viterbi算法的具体过程,该模型通俗易懂,有效的解决了连续词识别的问题。泛化能力强,实际上One Pass、Level Building等经典算法都可以看做Token Passing的特殊情况。且易于扩展,譬如我们可以通过保留多个token来评比多条路径(N-best)以提升识别效果。
本文为了做到尽量通俗,有诸多不严谨之处,欢迎批评指正。

Reference:
[1] Young S J, Russell N H, Thornton J H S. Token passing: a simple conceptual model for connected speech recognition systems[M]. Cambridge, UK: Cambridge University Engineering Department, 1989.
[2] Young S, Evermann G, Gales M, et al. The HTK book[J]. Cambridge university engineering department, 2002, 3: 175.
[3] University of Cambridge Engineering Part IIB & EIST Part II Module 4F11: Speech Processing Lecture 11: Continuous Speech Recognition
[4] SGN-24006 Analysis of Audio, Speech and Music Signals lec09
[5] speech.zone

2018-09-30 10:17:11 chinatelecom08 阅读数 4861

简介

  • 本文实现了一个基于HMM-GMM的独立词识别模型,数据集有限,训练数据为独立词,为10类。训练样本100个,测试样本10个,测试集上能够达到90%的识别率。
  • 直接下载项目到本地,运行.py文件就可以得到下面的结果,成功执行:
训练集:
识别得到结果:
 ['1', '10', '2', '3', '4', '5', '6', '7', '8', '10', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9', '1', '10', '2', '3', '4', '5', '6', '5', '8', '9', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9', '1', '10', '2', '3', '4', '5', '6', '7', '8', '10', '1', '10', '2', '4', '4', '5', '6', '7', '8', '9', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9', '1', '10', '2', '3', '4', '5', '6', '7', '8', '10', '1', '10', '2', '3', '4', '5', '6', '7', '8', '10', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9']
原始标签类别:
 ['1', '10', '2', '3', '4', '5', '6', '7', '8', '9', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9']
识别率: 0.94
测试集:
识别得到结果:
 ['1', '10', '2', '3', '4', '5', '6', '7', '8', '3']
原始标签类别:
 ['1', '10', '2', '3', '4', '5', '6', '7', '8', '9']
 识别率: 0.9

基础准备

原理部分需要了解hmm-gmm在语音识别中的工作原理是什么,特别要理解一个hmm-gmm模型对应一个孤立词这个概念,弄清楚非常重要。
不过你即使不算很明白其中的含义,也可以成功的执行项目,可以在原理和代码中反复思考传统模型的实现原理。
这部分网上讲的很多,这里不再赘述。

python建模

数据预处理

首先,进行数据预处理,输入为训练集路径或者测试集路径wavpath

# -----------------------------------------------------------------------------------------------------
'''
&usage:		准备所需数据
'''
# -----------------------------------------------------------------------------------------------------
# 生成wavdict,key=wavid,value=wavfile
def gen_wavlist(wavpath):
	wavdict = {}
	labeldict = {}
	for (dirpath, dirnames, filenames) in os.walk(wavpath):
		for filename in filenames:
			if filename.endswith('.wav'):
				filepath = os.sep.join([dirpath, filename])
				fileid = filename.strip('.wav')
				wavdict[fileid] = filepath
				# 获取文件的类别
				label = fileid.split('_')[1]
				labeldict[fileid] = label
	return wavdict, labeldict

查看wavdict和labeldict的数据内容,以test为例,其中音频的类别为文件名后面的数字:
数据格式

wavdict, labeldict = gen_wavlist('test_data')
print(wavdict, labeldict)

输出文件路径和标注类别:

wavdict:
{'1_1': 'test_data\\1_1.wav', '1_10': 'test_data\\1_10.wav', '1_2': 'test_data\\1_2.wav', '1_3': 'test_data\\1_3.wav', '1_4': 'test_data\\1_4.wav', '1_5': 'test_data\\1_5.wav', '1_6': 'test_data\\1_6.wav', '1_7': 'test_data\\1_7.wav', '1_8': 'test_data\\1_8.wav', '1_9': 'test_data\\1_9.wav'}
labeldict:
{'1_1': '1', '1_10': '10', '1_2': '2', '1_3': '3', '1_4': '4', '1_5': '5', '1_6': '6', '1_7': '7', '1_8': '8', '1_9': '9'}

特征提取

我们直接调包python_speech_features实现mfcc,偷懒做法,这样看起来代码比较简洁,如果需要深入了解算法可以自己coding实现。

# pip install python_speech_features ,不行的话百度一下
from python_speech_features import mfcc
# 特征提取,feat = compute_mfcc(wadict[wavid])
def compute_mfcc(file):
	fs, audio = wavfile.read(file)
	# 这里我故意fs/2,有些类似减小step,不建议这样做,投机取巧做法
	mfcc_feat = mfcc(audio, samplerate=(fs/2), numcep=26)
	return mfcc_feat

搭建孤立词模型

我们利用hmmlearn工具包搭建hmm-gmm,可以提前了解一下hmmlearn的使用方法

  • 首先,需要初始化10个独立的hmm-gmm模型,分别对应十个独立词,主要是初始化一个hmm-gmm模型的集合 self.models
class Model():
	def __init__(self, CATEGORY=None, n_comp=3, n_mix = 3, cov_type='diag', n_iter=1000):
		super(Model, self).__init__()
		self.CATEGORY = CATEGORY
		self.category = len(CATEGORY)
		self.n_comp = n_comp
		self.n_mix = n_mix
		self.cov_type = cov_type
		self.n_iter = n_iter
		# 关键步骤,初始化models,返回特定参数的模型的列表
		self.models = []
		for k in range(self.category):
			model = hmm.GMMHMM(n_components=self.n_comp, n_mix = self.n_mix, 
								covariance_type=self.cov_type, n_iter=self.n_iter)
			self.models.append(model)

各个参数的意义:

	CATEGORY:	所有标签的列表
	n_comp:		每个孤立词中的状态数
	n_mix:		每个状态包含的混合高斯数量
	cov_type:	协方差矩阵的类型
	n_iter:		训练迭代次数
  • 然后,用同一种类的数据训练特定的模型。
	# 模型训练
	def train(self, wavdict=None, labeldict=None):
		for k in range(10):
			subdata = []
			model = self.models[k]
			for x in wavdict:
				if labeldict[x] == self.CATEGORY[k]:
					mfcc_feat = compute_mfcc(wavdict[x])
					model.fit(mfcc_feat)
  • 最后,对待测试的数据分别用十个模型打分,选出得分最高的为识别结果。
	# 使用特定的测试集合进行测试
	def test(self, wavdict=None, labeldict=None):
		result = []
		for k in range(self.category):
			subre = []
			label = []
			model = self.models[k]
			for x in wavdict:
				mfcc_feat = compute_mfcc(wavdict[x])
				# 生成每个数据在当前模型下的得分情况
				re = model.score(mfcc_feat)
				subre.append(re)
				label.append(labeldict[x])
			# 汇总得分情况
			result.append(subre)
		# 选取得分最高的种类
		result = np.vstack(result).argmax(axis=0)
		# 返回种类的类别标签
		result = [self.CATEGORY[label] for label in result]
		print('识别得到结果:\n',result)
		print('原始标签类别:\n',label)
		# 检查识别率,为:正确识别的个数/总数
		totalnum = len(label)
		correctnum = 0
		for i in range(totalnum):
		 	if result[i] == label[i]:
		 	 	correctnum += 1 
		print('识别率:', correctnum/totalnum)
  • 你也可以保存和载入模型
	# 利用external joblib保存生成的hmm模型
	def save(self, path="models.pkl"):
		joblib.dump(self.models, path)
		
	# 利用external joblib载入保存的hmm模型
	def load(self, path="models.pkl"):
		self.models = joblib.load(path)

模型的训练和测试

利用上面搭建的模块对模型进行训练和测试,完整项目包括数据在hmm-gmm声学模型,下载下来可以直接运行,如果对你有帮助的话,有账户求star啊。

# 准备训练所需数据
CATEGORY = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']
wavdict, labeldict = gen_wavlist('training_data')
testdict, testlabel = gen_wavlist('test_data')
print(testdict, testlabel)
# 进行训练和测试
models = Model(CATEGORY=CATEGORY)
models.train(wavdict=wavdict, labeldict=labeldict)
models.save()
models.load()
models.test(wavdict=wavdict, labeldict=labeldict)
models.test(wavdict=testdict, labeldict=testlabel)

识别结果:

训练集:
识别得到结果:
 ['1', '10', '2', '3', '4', '5', '6', '7', '8', '10', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9', '1', '10', '2', '3', '4', '5', '6', '5', '8', '9', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9', '1', '10', '2', '3', '4', '5', '6', '7', '8', '10', '1', '10', '2', '4', '4', '5', '6', '7', '8', '9', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9', '1', '10', '2', '3', '4', '5', '6', '7', '8', '10', '1', '10', '2', '3', '4', '5', '6', '7', '8', '10', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9']
原始标签类别:
 ['1', '10', '2', '3', '4', '5', '6', '7', '8', '9', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9', '1', '10', '2', '3', '4', '5', '6', '7', '8', '9']
识别率: 0.94
测试集:
识别得到结果:
 ['1', '10', '2', '3', '4', '5', '6', '7', '8', '3']
原始标签类别:
 ['1', '10', '2', '3', '4', '5', '6', '7', '8', '9']
 识别率: 0.9

hmmlearn安装报错

该工具包安装很可能报错。
可以尝试去https://www.lfd.uci.edu/~gohlke/pythonlibs/#hmmlearn下载对应你电脑版本的文件,我的是64位,python36。
然后cd到你下载的文件的目录下,执行:

pip install hmmlearn‑0.2.1‑cp36‑cp36m‑win_amd64.whl

就可以安装成功了。

一些想法

  • 由于基于python的hmm-gmm语音识别模型较少,网上竟然没找到好使的代码,无奈下只能自己查资料写了一个小样。
  • 目前该项目只是一个demo,数据也较少,后续希望能够增加一些连续语音识别模型,搞成一个传统模型的语音识别系统。
  • 有兴趣的老哥可以查看我的另一个基于深度学习的中文识别系统ch_speech_recognition

整理不易,转载请注明出处https://blog.csdn.net/chinatelecom08
该项目github地址:https://github.com/audier/my_hmm_gmm_speech_recognition

2018-01-04 13:47:58 m0_37788308 阅读数 985

1.语音识别系统的基本结构

这里写图片描述

2.涉及算法

这里写图片描述

3.GMM高斯混合模型

3.1高斯混合模型的基本概念

  高斯混合模型是指具有如下形式的概率分布模型:

p(yθ)=k=1kαkϕ(yθk)

其中,αk是系数,αk0kk=1αk=1ϕ(yθk)是高斯分布密度,θk=(μk,σ2k)
ϕ(yθk)=12πσkexp((yμk)22θ2k)

称为第k个分模型。
  将二变量的混合高斯分布可以推广到多变量的多元混合高斯分布,其联合概率密度函数可写为:
p(x)=k=1Mcm(2π)(D/2)|m|1/2exp[12(xμm)T1m(xμm)]

=Mm=1CmN(x;μm,μm),(Cm>0)

3.2用EM算法解决高斯混合分布问题

  具体EM算法讲解见另一篇博客:《PLSI主题模型》中EM算法的具体讲解部分:
http://blog.csdn.net/m0_37788308/article/details/78115378
  实现EM算法估计高斯混合分布的python代码如下,其中有调用sklearn包进行的估计和直接写的EM算法来估计高斯混合分布。

# -*- coding:utf-8 -*-
#EM算法估计高斯混合分布的参数
import numpy as np
from scipy.stats import multivariate_normal
from sklearn.mixture import GaussianMixture
from mpl_toolkits.mplot3d import Axes3D
import matplotlib as mpl
import matplotlib.pyplot as plt
from sklearn.metrics.pairwise import pairwise_distances_argmin

mpl.rcParams['font.sans-serif'] = [u'SimHei']
mpl.rcParams['axes.unicode_minus'] = False

if __name__ == '__main__':
    style = 'myself'
    # style = 'sklearn'
    np.random.seed(0)
    mu1_fact = (0, 0, 0)
    cov1_fact = np.diag((1, 2, 3))
    data1 = np.random.multivariate_normal(mu1_fact, cov1_fact, 400)
    mu2_fact = (2, 2, 1)
    cov2_fact = np.array(((1, 1, 3), (1, 2, 1), (0, 0, 1)))
    data2 = np.random.multivariate_normal(mu2_fact, cov2_fact, 100)
    data = np.vstack((data1, data2))
    y = np.array([True] * 400 + [False] * 100)
    # print y
    if style == 'sklearn':
        g = GaussianMixture(n_components=2, covariance_type='full', tol=1e-6, max_iter=1000)
        g.fit(data)
        print '类别概率:\t', g.weights_[0]
        print '均值:\n', g.means_, '\n'
        print '方差:\n', g.covariances_, '\n'
        mu1, mu2 = g.means_
        sigma1, sigma2 = g.covariances_
    else:
        num_iter = 100
        n, d = data.shape
        # 随机指定
        mu1 = np.random.standard_normal(d)
        print mu1
        mu2 = np.random.standard_normal(d)
        print mu2
        mu1 = data.min(axis=0)
        mu2 = data.max(axis=0)
        sigma1 = np.identity(d)
        sigma2 = np.identity(d)
        pi = 0.5
        # EM
        for i in range(num_iter):
            # E Step
            norm1 = multivariate_normal(mu1, sigma1)
            norm2 = multivariate_normal(mu2, sigma2)
            tau1 = pi * norm1.pdf(data)
            tau2 = (1 - pi) * norm2.pdf(data)
            gamma = tau1 / (tau1 + tau2)

            # M Step
            mu1 = np.dot(gamma, data) / np.sum(gamma)
            mu2 = np.dot((1 - gamma), data) / np.sum((1 - gamma))
            sigma1 = np.dot(gamma * (data - mu1).T, data - mu1) / np.sum(gamma)
            sigma2 = np.dot((1 - gamma) * (data - mu2).T, data - mu2) / np.sum(1 - gamma)
            pi = np.sum(gamma) / n
            print i, ":\t", mu1, mu2
        print '类别概率:\t', pi
        print '均值:\t', mu1, mu2
        print '方差:\n', sigma1, '\n\n', sigma2, '\n'

    # 预测分类
    norm1 = multivariate_normal(mu1, sigma1)
    norm2 = multivariate_normal(mu2, sigma2)
    tau1 = norm1.pdf(data)
    tau2 = norm2.pdf(data)

    fig = plt.figure(figsize=(13, 7), facecolor='w')
    ax = fig.add_subplot(121, projection='3d')
    ax.scatter(data[:, 0], data[:, 1], data[:, 2], c='b', s=30, marker='o', depthshade=True)
    ax.set_xlabel('X')
    ax.set_ylabel('Y')
    ax.set_zlabel('Z')
    ax.set_title(u'原始数据', fontsize=18)
    ax = fig.add_subplot(122, projection='3d')

    order = pairwise_distances_argmin([mu1_fact, mu2_fact], [mu1, mu2], metric='euclidean')
    print order
    if order[0] == 0:
        c1 = tau1 > tau2
    else:
        c1 = tau1 < tau2
    c2 = ~c1
    acc = np.mean(y == c1)
    print u'准确率:%.2f%%' % (100*acc)

    ax.scatter(data[c1, 0], data[c1, 1], data[c1, 2], c='r', s=30, marker='o', depthshade=True)
    ax.scatter(data[c2, 0], data[c2, 1], data[c2, 2], c='g', s=30, marker='^', depthshade=True)
    ax.set_xlabel('X')
    ax.set_ylabel('Y')
    ax.set_zlabel('Z')
    ax.set_title(u'EM算法分类', fontsize=18)
    plt.suptitle(u'EM算法的实现', fontsize=21)
    plt.subplots_adjust(top=0.90)
    plt.tight_layout()
    plt.show()

4.HMM隐马尔可夫模型

4.1隐马尔可夫的基本概念

  隐马尔可夫模型是关于时序的概率模型,描述有一个隐马尔可夫链随机生成不可观测的状态随机序列,再由各个状态生成一个观测而产生观测随机序列过程。隐马尔可的“隐”字主要体现在隐马尔可夫链随机生成的“状态序列”;每一个状态生成一个观察,称为“观察序列”,观察序列我们是可以看到的,但是“状态序列”就是我们常常难理解的地方。
  隐马尔可夫由“初始概率分布”、“状态转移概率分布”以及“观察概率分布”这三个分布确定。
Q所有可能的状态集合,V是所有可能的观测集合。

Q={q1,q2,...,qN}

V={v1,v2,...,vM}

其中,N是可能的状态数,M是可能的观测序列。I是长度为T的状态序列,O是对应的观测序列。
I={i1,i2,...,ir}

O={o1,o2,...,or}

A是状态转移概率矩阵:
A=[aij]NN

其中,aij=p(it+1=qj|it=qi),i=1,2,...,N;j=1,2,...,N是在时刻t处于状态qi的条件下在时刻t+1转移到状态qj的概率。
B是观测概率矩阵:
A=[bj(k)]NN

其中,
bj(k)=p(ot=vk|it=qj),k=1,2,...,M;j=1,2,...,N

是在时刻t处于状态qj的条件下生成观测v_{k}的概率。
π是初始状态概率向量:
π=(πi)

其中,
πi=p(i1=qi),i=1,2,...,N

是时刻t=1处于状态qi的概率。
隐马尔可夫模型由初始状态概率向量π、状态转移概率矩阵A和观测概率矩阵B决定。πA决定状态序列,B决定观测序列。因此,隐马尔可夫模型λ可以用三元符号表示,即:
λ=(A,B,π)

A,B,π称为隐马尔可夫模型的三要素。

4.2隐马尔可夫的基本假设

从定义可知,隐马尔可夫模型作了两个基本假设:
  (1)齐次马尔科夫性假设,即假设隐藏的马尔科夫链在任意时刻t的状态只依赖与前一时刻的状态,与其他时刻的状态及观测无关,也与时刻t无关。

p(it|it1,ot1,...,i1,o1)=p(it|it1),t=1,2,...,T

  (2)观测独立性假设,即假设任意时刻的观测只依赖于该时刻的马尔科夫链的状态,与其他观测状态无关。
p(ot|iT,oT,iT1,oT1,...,it+1,ot+1,it,it1,ot1,...,i1,o1)=p(ot|it)

4.3HMM的三个基本问题

  (1)概率计算问题。给定模型π=(A,B,π)和观测序列O=(o1,o2,...,oT),计算在模型λ下观测序列O出现的概率p(O|λ)
  (2)学习问题。已知观测序列O=(o1,o2,...,oT),估计模型λ=(A,B,π)参数,使得在该模型下观测序列概率p(O|λ)最大。即用极大似然估计的方法估计参数。
  (3)预测问题。也称为解码问题。已知模型λ=(A,B,π)和观测序列O=(o1,o2,...,oT),求对给定观测序列条件概率p(I|O)最大的状态序列I=(i1,i2,...,iT)。即给定观测序列,求最有可能的对应的状态序列。

5.GMM-HMM如何应用在语音识别上

  解释:这里的语音识别指的是语音的分类,并不是字面意义理解的语音转化为文字。其实这里的语音识别和传统意义上的分类算法一样,通过训练集(train)的训练,对测试集(test)进行分类。只是传统的分类算法输入的是数字数据,而这里的输入的语音。输入的语音信号服从高斯混合分布,也就是隐马尔可夫模型中的第二个问题(学习问题),将语音信号用隐马尔可夫模型中的向前算法(BW算法,也可以理解为EM算法)进行分解,求出隐马尔可夫的三要素(三个参数λ=(A,B,π)),将参数作为识别语音的特征值,从而进行语音的分类识别。

6.问题讲解

(1)在浏览博客中,如果有不理解的地方可以参考视频讲解资料。视频讲解资料链接为:http://v.youku.com/v_show/id_XMzMzMzE1NTY2OA==.html?qq-pf-to=pcqq.c2c
(2)需要《GMM-HMM在语音识别中的应用》的python代码,可以留下邮箱地址。

7.参考文献

[1]李航. 统计学习方法[M]. 清华大学出版社:李航, 2012. 171-189

2019-04-07 15:37:37 asrgreek 阅读数 22665

虽然现在端到端语音识别模型可以直接对后验概率建模,可以不需要HMM结构了。但实际上目前很多state-of-the-art模型还是以HMM结构为主,比如chain model。而且掌握HMM-GMM结构,对于深入理解语音识别过程是由有一定好处的。

但对于外行或者刚接触语音识别的人来说,要弄懂HMM-GMM结构还是要花不少时间的,特别是被一大推公式整懵了。语音识别任务有些特殊,比如语音识别中,标注只是针对整段音频的,而不是针对每一帧;语音识别是针对每个音素都建立一个HMM模型,而不是所有音素用一个HMM模型描述。

当时为了弄懂HMM-GMM,看了不少资料,但感觉都不适合很初级的学习者。于是就萌生了写一个通俗易懂版的HMM-GMM教程,从一个音频实例说起,给大家一个感性的认识,也着重讲下作为初学者可能会感同身受的问题,不涉及到具体公式。公式的推断会给出参考资料,后续大家在研究,相信有了感性的认识之后,对这些公式的理解就不成问题了。

我们从下面这段音频说起。

在这里插入图片描述
我们把上面2秒的音频分帧,以帧长100ms,帧移100ms为例进行切分,这样上面的音频就被我们分成20帧。(ps:这里主要是为了方便说明,正常情况下帧长是25ms,帧移是10ms)

一、得到这段音频的标注

这里的标注可以是两种:
1、音素级别的标注。这是训练HMM-GMM模型所需要的标注,也就是说可以直接拿来训练的数据。
2、字节别的标注。实际上我们更多时候拿到是的字级别的标注,因为音素级别的标注工作量很大。但我们可以通过发音词典,将字级别的标注转换成音素级别的标注。这种转换没办法消除多音字的影响,但实际上不会对结果产生太大影响(这一部分可以参考kaldi的compile-train-graphs过程:http://kaldi-asr.org/doc/graph_recipe_train.html)

假设上面这段音频的标注是”你好“,那么对应的发音就是”n i2 h ao3“这四个声韵母。这里为了方便说明,我们就是用声韵母作为建模单元(ps:实际应用中是使用音素作为建模单元)。

二、对”n“、”i2“、”h“、”ao3“这4个声韵母分别使用HMM-GMM建模,使用3状态建模,如下图

在这里插入图片描述
为了方面说明,我们给每个状态加了编号。从上图我们可以看到,对于HMM的发射概率,我们是使用高斯分布函数建模的。为了方便说明,这里我们使用单高斯分布函数(虽然上图写的是GMM)
理解自环很重要,比如④这个状态,因为有了自环,它可以出现多次,因此我们可以对任意长的音频建模,这也是连续语音识别的基础。

三、对”n“、”i2“、”h“、”ao3“对应的HMM-GMM模型进行训练,得到模型参数

这一步属于HMM三个问题中的学习问题,需要用到EM算法。在开始训练之前,我们需要明确每个HMM模型的参数、模型的输入和模型的输出。

  • HMM-GMM模型的参数

    1、转移概率
    2、发射概率:因为我们使用GMM对发射概率建模,所以实际参数就是高斯分布中的均值和方差(这也是初学者容易迷糊的一个地方)。
    总结:模型参数就是转移概率、高斯分布的均值、方差

  • 模型的输入

    输入:每帧信号的MFCC特征(也可以是fbank,plp等)。具体应用中,我们通常取39维的MFCC特征。这里为了方便说明,我们取2维的MFCC特征,也就是说输入的特征空间分布是这样的:
    在这里插入图片描述

    上图中,每个黑点对应某一帧信号的MFCC特征。我们需要使用这些特征值来更新GMM的参数,具体怎么更新后续会讲。

  • 模型的输出

    输出:每一帧属于"n"、“i2”、“h”、"ao3"中的某一个状态(3状态)的概率。

同时,需要额外说明的是:虽然我们有了每段音频对应的标注,但实际上HMM-GMM模型的学习过程并不是”有监督的“。因为监督学习任务应该是:“对于每一个输入,都有对应的label”。而语音识别的任务比”传统的监督学习“复杂一点。因为我们拿到的标注是音素级别的。比如上面那段音频,它只告诉我们对应的标注是"n"、“i2”、“h”、“ao3”,并没有告诉我们每一帧(即每一个输入)对应的label是啥?那么应该怎么训练呢,相信是很多初学者的困惑。

对于这种无监督的任务,我们EM算法来进行训练(更专业的术语叫嵌入式训练,embedding training。也就是说把“n”、“i2”、“h”、“ao3”对应的HMM模型嵌入到整段音频中进去训练,因为我们不知道音频的哪一段是对应“n”、“i2”、“h”、“ao3”。嵌入式训练本质还是EM算法的思想):

  • step1:初始化对齐。以上面的20秒音频为例,因为我们不知道每一帧对应哪个声韵母的某个状态,所以就均分。也就是说1-5帧对应"n",6-10帧对应"i2",11-15帧对应"h",16-20帧对应"ao3"。同时"n"又有三个状态,那么就把1-2帧分给状态①,3-4帧分给状态②,第5帧分给状态③。“i2”、“h”、"ao3"亦如此。这里每个状态只是用单高斯描述,如果是混合高斯,还需要进一步用k-means算法来对每个高斯分量初始化。
    初始化完成后,该段音频对应的HMM模型如下:
    在这里插入图片描述

  • step2:更新模型参数。
    1、转移概率:通过上图,我们可以得到①->①的转移次数,①->②的转移次数等等。然后除以总的转移次数,就可以得到每种转移的概率,这是一个统计的过程,无他。
    2、发射概率:即均值和方差。以状态①的均值和方差为例,由上图我们可以知道第1帧和第2帧对应状态①。假设第1帧的MFCC特征是(4,3),第2帧的MFCC特征的MFCC特征是(4,7)。那么状态①的均值就是(4,5),方差是(0,8)

  • step3:重新对齐。根据step2得到的参数,重新对音频进行状态级别的对齐。这一步区别于step1的初始化,step1的初始化我们是采用粗暴的均匀对齐,而这一步的对齐是根据step2的参数进行对齐的。这里的对齐方法有两种:a、硬对齐:采用维特比算法;b、软对齐:采用前后向算法。
    经过重新对齐后,可能变成这样(第2帧已经不对应状态①了):
    在这里插入图片描述

  • step4:重复step2和step3多次,直到收敛。

这一个过程就是EM算法。step1和step3对应E步,step2对应M步。

这一过程涉及到的公式可以参考爱丁堡的语音识别课件http://59.80.44.98/www.inf.ed.ac.uk/teaching/courses/asr/2018-19/asr03-hmmgmm-handout.pdf

到这里,我们就从宏观层面把语音识别的HMM-GMM建模过程讲完了。为了方便讲解,我们这里是用单音素为例。实际应用中,通常使用三音素,但过程几乎是一样的,只是三音素多了一步决策树聚类,具体可以参考我之前的博文:kaldi中基于决策树的状态绑定

欢迎转载,转载请注明出处,谢谢!

Hmm在语音识别中的应用(一)

博文 来自: quheDiegooo

语音识别-HMM

阅读数 653

没有更多推荐了,返回首页