MATLAB 实现基于 GMM-HMM的语音识别系统
MATLAB 实现一个基于 GMM-HMM(高斯混合模型-隐马尔可夫模型) 的语音识别系统。
这是一个经典而完整的语音识别框架,虽然现今已被深度学习模型(如 DNN-HMM)大幅取代,但理解 GMM-HMM 对于掌握语音识别的根本原理至关重要。
一、为什么是 GMM-HMM?
语音识别有两个基本问题需要解决:
- 可变性 (Variability):同一个音素(如 /a/),由不同人、在不同语境下发出,其声学特征(MFCC)是不同的。如何建模这种多样的、连续的特征?
- 解决方案:GMM (高斯混合模型)。一个 GMM 可以非常灵活地拟合一个音素(或更小的单位,如状态)所对应的多种可能的声学特征分布,就像一个“软聚类”。
- 时序性 (Temporal Structure):语音是一个时序信号。音素之间如何连接?一个单词的发音有其特定的音素序列。
- 解决方案:HMM (隐马尔可夫模型)。HMM 完美地描述了“状态”之间的跳转规律(如从音素A切换到音素B)以及每个状态“发射”出某种观测特征(如MFCC向量)的概率。
GMM-HMM 的结合方式:
- HMM 负责建模语音的时序动态特性(状态跳转)。
- GMM 作为 HMM 的观测概率密度函数(B(x)),负责建模在某个特定状态下,观察到某个声学特征向量 x 的概率。
二、 系统组成与流程
一个完整的 GMM-HMM 语音识别系统包含以下核心模块和流程,其工作流程可分为训练和识别两大部分:
flowchart TD
A[语音波形] --> B[特征提取<br>MFCC, ΔMFCC, ΔΔMFCC]
B --> C{识别流程}subgraph Training[模型训练流程]direction TBTData[训练语音库] --> TFeat[特征提取]TFeat --> TInit["初始化模型<br>(Flat Start)"]TInit --> TTrain[Baum-Welch算法<br>训练GMM-HMM参数]TTrain --> TModel[声学模型库<br>(音素/词HMM)]
endTModel --> CC --> D[计算状态后验概率<br>(Viterbi或前后向算法)]
D --> E[搜索最优路径<br>(Viterbi解码)]
E --> F[输出识别结果<br>(词序列)]
三、 关键步骤详解与 MATLAB 实现要点
1. 特征提取:MFCC (Mel-Frequency Cepstral Coefficients)
这是最核心的特征,模拟人耳听觉特性。
步骤:预加重 -> 分帧加窗 -> 短时傅里叶变换 -> Mel滤波器组 -> 取对数 -> DCT变换。
MATLAB 实现:
% 使用 Voicebox 工具箱或 MATLAB 的 Audio Toolbox
% 方法 1: 使用 Audio Toolbox (R2019a+)
% audioIn = audioread('speech.wav');
% coeffs = mfcc(audioIn, fs, 'LogEnergy', 'Ignore');% 方法 2: 使用 Voicebox (非常经典,需自行下载添加到路径)
% [mfcc_vec, ~] = melfcc(speech_vec, fs, 'wintime', 0.025, 'hoptime', 0.01, 'numcep', 13);
% deltas = deltacc(mfcc_vec); % 计算一阶差分(Delta)
% deltas2 = deltacc(deltas); % 计算二阶差分(Delta-Delta)
% feature_vector = [mfcc_vec; deltas; deltas2]'; % 组合成39维特征向量% 实际应用中,更推荐使用 Kaldi 等工具提取或成熟的代码包。
2. 模型训练:Baum-Welch 算法
这是一个 EM 算法,用于在只有观测序列(特征向量)的情况下,迭代地估计 HMM 的参数 λ = (A, B, π)。
MATLAB 实现:
MATLAB 的 Statistics and Machine Learning Toolbox 提供了 hmmtrain
函数。但需要注意,它默认的观测概率是离散的。为了处理连续的语音特征,我们需要连续观测HMM。
关键:你需要为 hmmtrain
提供一个自定义的函数来计算观测概率,这个函数内部就是 GMM。
% 伪代码/思路概述
% 1. 初始化模型:为每个HMM状态分配一个GMM
for state = 1:num_states% 使用 kmeans 对属于该状态的所有特征向量进行初始聚类[idx, C] = kmeans(features_for_state, num_mixtures); % 初始化GMM:设置每个高斯的均值、协方差和权重gmm_models{state} = gmdistribution(C, ...);
end% 2. 定义自定义的观测概率函数
% 这个函数接受一个序列和模型参数,返回每个观测点属于每个状态的概率(通过GMM计算)
% prob = obs_prob_fcn(sequence, current_model)% 3. 调用 hmmtrain 进行训练
[estimatedTransition, estimatedEmissions] = hmmtrain(...feature_sequence, ... % 观测序列initial_guess_T, ... % 初始转移矩阵猜測@obs_prob_fcn, ... % **自定义的连续观测概率函数**'MaxIterations', 50, ...'Tolerance', 1e-4);
更常见的做法是直接使用 Kaldi 或自己编写完整的 Baum-Welch 重估公式(涉及前向算法、后向算法),而不是依赖 hmmtrain
。
3. 识别解码:Viterbi 算法
找到最可能产生观测序列 O 的状态序列(对于识别,就是词序列)。
MATLAB 实现:
% 使用 hmmviterbi 函数
[likely_state_sequence] = hmmviterbi(...test_feature_sequence, ... % 待识别的观测序列estimatedTransition, ... % 训练好的转移矩阵 A@obs_prob_fcn, ... % **同样需要自定义的连续观测概率函数 B(x)**
);% 但 hmmviterbi 返回的是状态序列。
% 完整的识别需要一个大循环,对词典中的每个词(对应一个HMM)计算其生成观测序列的概率,
% 然后找到概率最大的那个词。这需要构建一个所有词的复合大HMM。
推荐代码 基于GMM-HMM的语音识别程序 www.youwenfan.com/contentcsg/53383.html
四、 简化代码框架(概念性)
由于完整实现非常复杂,这里提供一个高度简化的概念性框架,用于理解流程。
%% 基于GMM-HMM的孤立词识别(极度简化版)
% 假设:只有一个词,其HMM有3个状态(不包括起止状态)% 1. 准备训练数据(多个该词的发音)
all_mfccs = cell(1, num_utterances);
for i = 1:num_utterances[speech, fs] = audioread(sprintf('word%d.wav', i));all_mfccs{i} = my_mfcc_extractor(speech, fs); % 假设自己实现了MFCC提取函数
end% 2. 初始化模型
num_states = 3;
num_mixtures = 4; % 每个GMM由4个高斯分量组成% 2.1 初始化转移矩阵 A (严格从左至右,可跳过自环和下一个状态)
A = [0.6 0.4 0.0 0.0;0.0 0.6 0.4 0.0;0.0 0.0 0.6 0.4;0.0 0.0 0.0 1.0]; % 最后一行是终止状态% 2.2 初始化每个状态的GMM
GMMs = cell(1, num_states);
for s = 1:num_states% 将所有训练数据中(粗略对齐)属于状态s附近的特征收集起来% 这里简化:随机分配或简单分段features_for_state_s = [];for utt = 1:num_utteranceslen = size(all_mfccs{utt}, 1);seg_start = round((s-1)/num_states * len) + 1;seg_end = round(s/num_states * len);features_for_state_s = [features_for_state_s; all_mfccs{utt}(seg_start:seg_end, :)];end% 使用GMM拟合这些特征GMMs{s} = fitgmdist(features_for_state_s, num_mixtures, ...'CovarianceType', 'diagonal', 'Regularize', 1e-5);
end% 3. 定义自定义观测概率函数
% 此函数需要计算观测序列seq中每个时间点上的特征向量属于每个状态GMM的概率
obs_prob_fcn = @(seq) get_obs_prob(seq, GMMs, num_states);% 4. 训练(使用hmmtrain进行少量迭代,调整A和GMM的均值/权重/协方差)
% ... 此处省略复杂的Baum-Welch重估过程,通常需要自己实现 ...% 5. 识别
[test_speech, fs] = audioread('test_word.wav');
test_mfcc = my_mfcc_extractor(test_speech, fs);% 5.1 计算观测概率
B = obs_prob_fcn(test_mfcc);% 5.2 Viterbi解码
[~, state_path] = viterbi_log(A, B); % 建议使用对数版本的Viterbi以防下溢% 根据状态路径的终点概率或最终得分,判断是否识别为该词