言语信息:基于HMM+GMM的单个词语音识别

实验内容

基于有标签的数字语音数据集,通过GMM+HMM模型完成单个词语音识别的分类任务。

实验思路

基于GMM+HMM的语音识别框架可以简述为下图的过程:
Alt text
其可以被大致分割为特征提取GMMHMM三部分,下面就这三部分一一展开叙述。

特征提取

对单个词进行语音识别,仍然需要首先提取待分类语音段的MFCC特征。

根据上课所学知识,对于单个词的语音进行识别,首先需要将输入的音频信号转化为语音特征MFCC、即梅尔频率倒谱系数(Mel Frequency Cepstrum Coefficient, MFCC),而MFCC的生成流程如下图所示:
Alt text

简而言之,要获得一段语音信号的MFCC:

  • 首先需要对输入的信号进行欲加重(Pre-Emphasis)以凸显高频段的共振峰;
  • 其次对语音信号进行分帧和加hamming或hanning窗(Window),从而使每帧信号两端衰减至接近0;
  • 再通过快速傅立叶变换(FFT)将时域信号转换到频域,得到语音信号的能量分布;
  • 之后将能量谱通过梅尔尺度的三角形滤波器组对频谱进行了平滑化(Mel-Filter Bank),以得到符合人耳听觉习惯的声谱,通常取对数(log)将单位转化为db;
  • 经离散余弦变换(DCT)后最终得到梅尔倒谱系数,即MFCC,通常为13维信号,一般会加上其一阶微分(delta)和二阶微分(delta-delta)信号共同组成39微特征作为最终的MFCC组成

GMM + HMM

在获得语音信号的MFCC特征后,接下来需要完成:将帧识别为状态、把状态组合成音素、把音素组合成单词三步骤。

关于基于音素的声学模型,这里引用一段说明[2]:

“HMM是对语音信号的时间序列结构建立统计模型,将其看作一个数学上的双重随机过程:一个是用具有有限状态数的Markov链来模拟语音信号统计特性变化的隐含(马尔可夫模型的内部状态外界不可见)的随机过程,另一个是与Markov链的每一个状态相关联的外界可见的观测序列(通常就是从各个帧计算而得的声学特征)的随机过程。”在单词词典(lexicon)中,根据每个单词的发音过程,以音素作为隐藏节点,音素的变化过程构成了HMM状态序列。每一个音素以一定的概率密度函数生成观测向量(即MFCC特征向量)。在GMM-HMM中,用高斯混合函数去拟合这样的概率密度函数。

简而言之,GMM即为多个高斯分布的叠加,而HMM则是具有隐藏节点和可见节点的马尔可夫过程,音素在马尔可夫状态转移过程中涉及到的从属概率和转移概率问题可以通过GMM来解决,如此二者便结合为了GMM+HMM模型。

在GMM+HMM模型中,HMM需要解决的有三个问题:

  • Likelihood:一个HMM生成一串observation序列x的概率(the Forward algorithm)
    Alt text
  • Decoding:给定一串observation序列x,找出最可能从属的HMM状态序列(the Viterbi algorithm)
    Alt text
  • Training:给定一个observation序列x,训练出HMM参数(the EM algorithm)
    Alt text

GMM需要完成如下的功能:

  • 对于每个帧,得到其属于每个状态的概率
  • 在给定input sequence的情况下,计算出一系列状态转移的概率

实验过程

实验环境

  • 系统:Mac OS X
  • 语言:python3.6.5
  • requirements:
    • numpy
    • scipy.io.wavfile: 用于wav数据读取
    • python_speech_features: 用于生成MFCC特征
    • hmmlearn: 调用GMMHMM模型
    • sklearn.externals: 存储、加载模型

项目结构

本工程主要有三大主体部分:

  • 数据集的加载及分割
    • 通过generate_wav_label(wavpath)函数来实现数据集的加载,主要通过os.walk(wavpath)遍历wavpath的下层文件并依次收集。
    • 通过split_dataset(wavdict, labeldict)函数将数据集分割为训练集wavdict_train, labeldict_train和测试集wavdict_test, labeldict_test
  • MFCC特征获取
    • 通过python_speech_featuresmfcc函数实现特征的获取,这里选取mfcc,d_mfcc,d2_mfcc组合为39维的MFCC特征
  • GMMHMM模型构建
    • 考虑到效率问题,这里使用hmmlearn.hmm.GMMHMM来构建我们需要的模型,其中解码算法选择viterbi,初始化参数使用默认的kmeans产生
    • 对于0~9的十分类问题,需要分别为每个数字构建起对应的GMMHMM模型,即需要分别构造10个模型来实现十分类需求

运行步骤

项目的整体数据流图如下所示:
Alt text

实验结果

数据集

本次实验的数据集来源于助教提供的中英文0~9语音,其中英文数据包括0~9的各20条实例,共计200条数据。

运行结果

可以看到,对于随机选中的10条英文测试数据,模型可以成功识别出5条,acc为50%:
Alt text

参考文献

[1] 传统语音识别(GMM+HMM)
[2] GMM+HMM学习笔记

代码附录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
from python_speech_features import *
from scipy.io import wavfile
from hmmlearn import hmm
from sklearn.externals import joblib
import numpy as np
import os

import warnings
warnings.filterwarnings("ignore")

np.random.seed(10)

def generate_wav_label(wavpath):
wavdict = {}
labeldict = {}
for (dirpath, dirnames, filenames) in os.walk(wavpath):
# print(dirpath,dirnames,filenames)
for filename in filenames:
if filename.endswith('.wav'):
filepath = os.sep.join([dirpath, filename])
fileid = filepath
wavdict[fileid] = filepath

label = ''
language = wavpath.split('_')[1]
if language == 'en':
label = filepath.split('_')[-1].split('.')[0]
if language == 'ch':
label = filepath.split('/')[1]

labeldict[fileid] = label
return wavdict, labeldict

def compute_mfcc(file):
fs, audio = wavfile.read(file)
mfcc_feat = mfcc(audio, samplerate=fs, numcep=13, winlen=0.025, winstep=0.01, nfilt=26, nfft=2048, lowfreq=0,
highfreq=None, preemph=0.97)
d_mfcc_feat = delta(mfcc_feat, 1)
d_mfcc_feat2 = delta(mfcc_feat, 2)

feature_mfcc = np.hstack((mfcc_feat, d_mfcc_feat, d_mfcc_feat2))
return feature_mfcc


class Model():
def __init__(self, CATEGORY=['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], n_comp=1, n_mix=1, cov_type='full'):
super(Model, self).__init__()
# print(CATEGORY)
self.CATEGORY = CATEGORY
self.category = len(CATEGORY)
self.n_comp = n_comp
self.n_mix = n_mix
self.cov_type = cov_type

self.models = []
# self.n_mix = [4, 3, 2, 3, 2, 3, 4, 4, 2, 3]
for k in range(self.category):
# print(k)
model = hmm.GMMHMM(n_components=self.n_comp, n_mix=self.n_mix, covariance_type=self.cov_type)
self.models.append(model)

# 模型训练
def train(self, wavdict=None, labeldict=None):
for k in range(self.category):
model = self.models[k]
for x in wavdict:
if labeldict[x] == self.CATEGORY[k]:
print('k=', k, wavdict[x])
mfcc_feat = compute_mfcc(wavdict[x])
model.fit(mfcc_feat)

# 测试模型
def test(self, filepath):
result = []
for k in range(self.category):
model = self.models[k]
mfcc_feat = compute_mfcc(filepath)
re = model.score(mfcc_feat)
result.append(re)

# 选取得分最高的标签
result = np.argmax(np.array(result))
result = self.CATEGORY[result]
return result

def save(self, path="models.pkl"):
joblib.dump(self.models, path)

def load(self, path="models.pkl"):
self.models = joblib.load(path)

# 生成test_size个测试用例
def split_dataset(wavdict, labeldict, test_size=10):
nums = len(labeldict)
shuf_arr = np.arange(nums)
# print(shuf_arr)
np.random.shuffle(shuf_arr)
# print(shuf_arr)
labelarr = []
for l in labeldict:
labelarr.append(l)
wavdict_test = {}
labeldict_test = {}
wavdict_train = {}
labeldict_train = {}

for i in range(test_size):
wavdict_test[labelarr[shuf_arr[i]]] = wavdict[labelarr[shuf_arr[i]]]
labeldict_test[labelarr[shuf_arr[i]]] = labeldict[labelarr[shuf_arr[i]]]
for k in labeldict:
if k not in labeldict_test:
wavdict_train[k] = wavdict[k]
labeldict_train[k] = labeldict[k]

return wavdict_train, labeldict_train, wavdict_test, labeldict_test

if __name__ == '__main__':
dataset = "data_en_train"
wavdict, labeldict = generate_wav_label(dataset)
wavdict_train, labeldict_train, wavdict_test, labeldict_test = split_dataset(wavdict, labeldict)

models = Model()
# print("GMMHMM train:", dataset)
# models.train(wavdict=wavdict_train, labeldict=labeldict_train)
# models.save()

print("GMMHMM test:", dataset)
models.load()

TP = 0
FP = 0
for k in wavdict_test:
wav_path = wavdict_test[k]
res = models.test(wav_path)[0]
print(wavdict_test[k], res, labeldict_test[k])
if res == labeldict_test[k]:
TP += 1
else:
FP += 1
print('TP:', TP)
print('FP:', FP)
print('Acc:', TP / (TP + FP))
小手一抖⬇️