资讯动态

基于GMM的语音识别系统:MATLAB实现与核心原理解析

发布时间:2026/10/17 8:51:52 来源:尧图企业网站定制
很多朋友一听到“语音识别”四个字第一反应就是神经网络、端到端、大模型。但如果往根上刨语音识别真正普及的经典底座其实是一套非常朴实、也非常优雅的概率统计框架。最近我在做一个小型孤立词识别项目标题就叫“基于高斯混合模型GMM的语音识别系统MATLAB实现与核心原理”这里头没有用任何深度学习的组件纯粹靠GMM把数字0到9的发音给认出来。这篇文章就把整个项目怎么从原理走到落地的过程完整拆一遍包括特征怎么提、GMM怎么训、EM算法到底在干什么、MATLAB代码怎么组织、以及我踩过的那些坑。想动手写一个自己的语音识别Demo、或者想搞懂统计声学模型底层逻辑的同学可以把它当成一份带注释的“作业参考答案”来看。要用GMM做语音识别首先要明确一个边界GMM本身不是一个时序模型它描述的是“一帧特征长什么样”的概率分布。语音是一段随时间变化的信号所以这个系统最适合的场景是孤立词识别、小词汇量指令识别或者配合HMM隐马尔可夫模型去做连续语音识别中的观测概率建模。我这次做的是孤立词识别好处是结构清晰、训练简单能把GMM的核心逻辑完整暴露出来非常适合用来理解语音识别最底层的数学直觉。1. 项目定位与整体技术路线1.1 这个系统到底在解决什么问题先说说我在这个项目里定义的任务输入一段录音内容是中文数字“0”到“9”中的一个系统判断它到底是哪个数字。从表面上看这像是一个分类问题把音频映射到10个类别之一。但实际上它比普通的图像分类要恶心不少因为语音不是一张静态的像素图而是一长串随时间和声带状态连续变化的数据。同样一个“三”不同的人说出来长度不一样、基频不一样、共振峰位置不一样、语速不一样甚至同一个人在不同情绪下说出来的声学特征都有明显差异。GMM干的事情就是针对每个类别训练出一个概率密度函数用来回答一个问题假如已知这串特征来自“三”这个类别那么观测到当前这段特征的概率有多大到了识别阶段我处理完测试音频提取出特征序列然后分别计算它在10个GMM模型下的对数似然哪个模型给出的似然最高就判定为哪个数字。有一点需要提前讲清楚GMM假设观测样本之间相互独立。这意味着我输出的每一帧特征都被当成一个独立样本不考虑句子内部的时序依赖。这当然是简化但这恰恰是入门语音识别最好的切入点。等你把GMM这套东西完全玩明白了再去接触HMM-GMM、GMM-UBM说话人识别或者直接跨到端到端模型都会有很扎实的底子。1.2 都2025年了为什么还要选GMM而不是端到端模型每次我拿这个项目跟别人聊总有人问现在随便拿个开源框架训一个深度学习模型识别准确率秒杀GMM为什么还要费力去研究它我的回答很简单因为GMM能把“特征空间里概率分布长什么样”这件事用可视化、可解释的方式展现出来而神经网络本质上是一个难以直接理解的高维函数逼近器。另一个很实际的原因是训练成本和硬件门槛。训练一个GMM的EM算法在普通CPU上跑完只需要几十秒到几分钟数据量要求也远低于深度模型。它非常适合做教学、做原型验证也适合嵌入式场景里快速训练一个跟设备相关的模型。像我这次用的MATLAB环境一把循环就能写完EM不需要装深度学习工具箱不需要GPU不需要调学习率连CUDA都跟你没关系。还有一点容易被忽略GMM是最简单的一类生成式模型。它能告诉你每个特征的分布中心在哪、离散程度如何、哪些维度之间相关性强。这些信息在做特征分析、模型剪枝、异常检测的时候都很有价值。比如我后来分析混淆原因就是靠观察两个类别GMM的均值向量和协方差对角元素直接定位到哪个频段的特征区分度不够。这个能力是黑盒分类器给不了的。1.3 系统结构与模块划分整个系统的数据流是典型的“前端特征提取 后端统计建模”结构。音频输入之后第一步是预处理包括预加重、分帧、加窗目的是把声门激励和唇辐射带来的高频衰减补偿回来同时把连续信号切成一帧一帧的短时平稳片段。第二步是特征提取从每一帧里计算MFCC系数得到一组低维、去相关、符合人耳听觉特性的特征向量。第三步是训练阶段把同一类别的所有特征帧收集起来用EM算法迭代估计GMM参数。第四步是识别阶段对测试语音同样提特征然后计算每个类别的对数似然取最大类别作为输出。我建议你把这个流程牢牢记在心里因为不管是GMM、HMM-GMM还是深度学习方法前端部分几乎都是这套路子。Feature extraction做得好不好往往比分类器选什么对最终准确率影响更大。我后面会把每一层细节都拆开讲。2. 语音特征提取MFCC为什么是标准答案2.1 从波形到特征预加重、分帧、加窗很多人第一次接触语音识别就被“预加重、分帧、加窗”这三个词劝退了。其实它们一点都不神秘只是为了把一段粗糙的波形整理成适合建模的样本集合。首先说预加重。人在发声时声门波的能量大致按6dB/倍频程的斜率衰减也就是说高频部分的能量天然偏低。如果不做处理后续MFCC计算时高频段的特征会被低频淹没。预加重本质上就是一个高通滤波器我在这里用了一阶差分% 预加重系数通常取0.95~0.98 y_filtered filter([1, -0.97], 1, x);这段代码的意思是把当前样本减去前一个样本的0.97倍这会让信号的相对变化部分被放大高频能量得到补偿。然后是分帧。语音信号是时变的但在20~30毫秒这个尺度内可以近似看成平稳信号。所以我用25毫秒的帧长、10毫秒的帧移把整段信号切成很多个小块相邻帧之间有一半以上的重叠。这里必须给新手提个醒帧移小于帧长不是闲着没事干而是为了让相邻帧之间平滑过渡避免特征序列在时间轴上发生突变。frame_len 25/1000 * fs; % 每帧采样点数 frame_shift 10/1000 * fs; % 帧移采样点数 frame_number floor((length(x) - frame_len) / frame_shift) 1; frames zeros(frame_number, frame_len); for i 1:frame_number start_idx (i-1)*frame_shift 1; frames(i,:) y_filtered(start_idx : start_idxframe_len-1); end分完帧之后必须加窗否则截断处会出现频谱泄漏本来应该集中在某个频率上的能量会散到旁瓣去。常用的窗函数有汉明窗、汉宁窗。我选汉明窗因为它的主瓣宽度和旁瓣衰减在语音识别场景中表现均衡window hamming(frame_len, periodic); frames frames .* repmat(window., size(frames,1), 1);这里有个容易忽略的细节MATLAB里hamming默认是对称窗我加了periodic参数因为后续要做FFT分析周期窗能避免IDFT后的边缘不连续问题。2.2 MFCC计算流程与MATLAB实现MFCC是目前语音识别和说话人识别领域最经典的特征。它提取的核心思路是人耳对频率的感知不是线性的低频区域分辨率高高频区域分辨率低。于是MFCC先把信号的频谱映射到Mel刻度上再取对数、做DCT最终得到一组近似解相关的系数。完整流程分四步。第一步对每一帧加窗后的信号做FFT取幅度谱的平方得到功率谱。第二步将功率谱通过一组Mel滤波器组得到每个频带的能量。第三步对每个频带的能量取对数。第四步对对数能量做离散余弦变换取前若干个数作为MFCC系数。function mfcc_feat compute_mfcc(x, fs) % MFCC参数设置 num_filters 26; % 滤波器个数 num_ceps 13; % 取前13维系数 frame_len 25/1000 * fs; frame_shift 10/1000 * fs; % 预加重 x filter([1, -0.97], 1, x); % 分帧加窗 n_frames floor((length(x)-frame_len)/frame_shift) 1; frames zeros(n_frames, frame_len); for i 1:n_frames start_idx (i-1)*frame_shift 1; frames(i,:) x(start_idx:start_idxframe_len-1); end frames frames .* hamming(frame_len, periodic).; % FFT和功率谱 NFFT 512; power_spectrum abs(fft(frames, NFFT, 2)).^2; % Mel滤波器组 mel_filter design_mel_filterbank(fs, NFFT, num_filters); mel_energies power_spectrum * mel_filter; % 对数压缩 log_mel_energies log(mel_energies eps); % DCT mfcc_feat dct(log_mel_energies); mfcc_feat mfcc_feat(:, 1:num_ceps); end这里design_mel_filterbank是自实现的Mel滤波器组构造函数。它首先要定义Mel刻度公式Mel频率和普通频率之间的关系是mel 2595 * log10(1 f/700)。然后在这个刻度上均匀放置三角滤波器。很多老代码会直接调用melbank函数新版MATLAB里它已经被移除所以我会建议自己写一个也不复杂。我自己习惯用26个滤波器组这个数量不算多但经实验足够支撑10个数字的孤立词识别。如果你觉得识别率偏低可以尝试加大到40但训练特征维度也会跟着涨GMM参数估计所需的样本量同样要增加。2.3 特征拼接与归一化只提取13维静态MFCC识别时总感觉抓不住语音的动态变化。语音识别里的常见做法是把13维MFCC和一阶差分、二阶差分拼起来组成39维的特征向量。差分系数相当于语音特征在时间轴上的“速度”和“加速度”能捕捉到声学特征的动态变化趋势。% 计算一阶差分 delta zeros(size(mfcc_feat)); delta(2:end, :) diff(mfcc_feat, 1, 1); % 计算二阶差分 delta2 zeros(size(delta)); delta2(2:end, :) diff(delta, 1, 1); % 拼接 feat_all [mfcc_feat, delta, delta2];不过我说实话在这个项目的背景下加差分特征带来的收益没有想象的那么大大约只提升了两三个百分点的识别率而特征维度从13翻到39GMM参数数量暴涨训练时间也变长了。我最后保留了差分是因为在噪声环境下差分特征能略微提高鲁棒性。如果你只是为了快速跑通只保留静态13维就够了。特征归一化也很重要。不同录音的音量差异会导致MFCC的幅度分布不一致所以在训练之前我会对每个维度做均值方差的标准化。注意这里必须用训练集统计出来的均值和标准差去归一化测试集千万不要混在一起算否则会把测试集信息泄漏到训练过程中得到虚高的识别率。3. GMM核心原理与MATLAB实现3.1 高斯混合模型是怎么描述语音的GMM的出发点很简单单高斯分布只能描述一个中心、一种聚集程度的数据。但人的语音特征分布是典型的“多峰”形态。比如“四”这个数字包含高频的擦音段和较低的元音段特征空间里会呈现出好几个聚集区域。这时你用单个高斯去拟合结果就是所有数据被平均到一个中心既描述不好擦音区域也描述不好元音区域似然很低。GMM的做法是准备K个高斯分量每个高斯有自己的均值向量和协方差矩阵同时有一个权重表示这个分量在所有样本中的占比。整个模型变成这些高斯分量的加权求和$$p(x) \sum_{k1}^{K} \pi_k \cdot \mathcal{N}(x \mid \mu_k, \Sigma_k)$$你可以把GMM想象成“特征空间里有好几座山每座山是一个高斯山的高度由权重决定”。我最初的实验里K8也就是用8座高斯山去描述每个数字的特征分布。这里有一个特别重要的点需要解释清楚每个高斯分量其实在某种程度上对应了语音中一个“隐状态”。比如某个分量可能主要描述清辅音部分的特征另一个分量描述元音部分的特征。通过EM算法模型自己会学会把数据划分到不同分量上我们不需要手工标注每一帧到底属于哪一类。3.2 EM算法期望与最大化GMM的参数不能直接求导拿解析解因为每个样本来自哪个分量是一个未知的隐变量。EM算法就是专门处理这种含隐变量问题的迭代优化算法。整个训练过程可以这样理解我先随便给出一组GMM参数的初始值然后反复执行两步。E步固定当前参数对每个特征样本计算它落在每个分量上的后验概率相当于“猜测这个样本属于哪个高斯分量”M步用这些后验概率作为权重重新计算每个分量的均值、协方差和混合权重让似然函数在这组新的参数下更大。重复这个过程直到对数似然的变化量小于预设阈值。% 单类GMM训练的EM核心循环 max_iter 50; log_likelihood_old -inf; for iter 1:max_iter % E步计算后验概率矩阵 N x K resp zeros(N, K); for k 1:K resp(:, k) weight(k) * mvnpdf(data, mu(k,:), sigma(:,:,k)); end resp resp ./ sum(resp, 2); % M步更新参数 Nk sum(resp, 1); weight_new Nk / N; mu_new zeros(K, D); sigma_new zeros(D, D, K); for k 1:K mu_new(k, :) sum(resp(:, k) .* data, 1) / Nk(k); diff_mat data - mu_new(k, :); sigma_new(:, :, k) (resp(:, k) .* diff_mat) * diff_mat / Nk(k); end % 更新参数 weight weight_new; mu mu_new; sigma sigma_new; % 计算当前对数似然 log_likelihood sum(log(sum(resp, 2) eps)); if abs(log_likelihood - log_likelihood_old) 1e-4 break; end log_likelihood_old log_likelihood; end这段代码里我故意用了最朴素的方式实现它很好理解但性能不是最优。如果你的训练数据特别多建议用gmfit函数或者统计工具箱里的fitgmdist它在数值稳定性上做了很多优化比如防止协方差矩阵奇异、防止某分量退化。实际项目里能用现成的函数就不要自己手写但原理一定要懂否则出了问题你根本不知道怎么排查。有一点我必须强调EM算法对初始值非常敏感。如果初始K个高斯分量全堆在同一个位置迭代半天可能只更新出一个有效分量其他分量权重变成0模型退化成一个单高斯。所以在EM之前我会先用K-means对整个训练数据做聚类把聚类中心作为GMM的初始均值再用各簇内样本的协方差作为初始协方差。3.3 混合数选择与协方差约束GMM超参数里对结果影响最大的是混合数K。K太小模型拟合能力不够描述不了语音特征的多峰分布K太大模型复杂度上去了但每个分量的训练样本变少容易发生过拟合。我针对10个数字做过一组对照实验K从2、4、8、16、32往上加结论是最优区间在8到16之间。协方差矩阵的处理方式同样关键。理论上每个高斯分量可以拥有独立的满协方差矩阵但这对训练样本量要求很高。比如39维特征、8个分量满协方差矩阵需要估计39*40/2 ≈ 780个参数8个分量就有6240个参数要估计。如果每类数字只有几百帧特征直接用满协方差大概率得到奇异矩阵。所以我实际使用的是对角协方差矩阵也就是说假设每个特征维度之间条件独立。这当然损失了一部分相关性信息但对绝大部分语音识别工程而言对角协方差在效果和稳健性之间是最划算的我的实验也证实了这一点。% 使用对角协方差训练GMM for k 1:K sigma(:, :, k) diag(var(data, 1, 1)); end3.4 GMM训练代码解析训练逻辑本身不复杂读取所有类别为c的训练音频文件对每个文件提MFCC特征把所有帧拼接成一个大的特征矩阵然后对这个矩阵执行一次GMM训练得到一个类专属的GMM模型。整个过程可以用一个循环完事。gmm_models cell(10, 1); for digit 0:9 feat_all collect_features(digit, train_files, train_labels); gmm_models{digit1} train_gmm(feat_all, K, diag); endcollect_features函数负责遍历该类别的所有训练音频提取静态和动态MFCC特征最后返回一个大矩阵。train_gmm负责执行高斯混合模型参数估计输出一个包含权重、均值、协方差的结构体。这里有一个工程细节不同的录音片段长度差异很大有的说一遍“零”可能0.4秒有的话唠一点的能到0.8秒。所以每类的总帧数会不同。我在训练时不会因为帧数差异做任何加权这是世界通用的常规做法因为每类数字的先验概率本来就不同。另外一个我特别想提醒的坑如果你的训练集只有一个人发音那么这套模型本质上就是一个“特定人识别器”。换一个人来测试识别率可能掉得惨不忍睹。因为这个人的音色、语速、基频都已经写死在GMM的均值向量里了。正规做法是录多个人的声音混在一起训练每个数字至少10个以上不同人的样本这样GMM才能学到“类别共性”而不是“某个人说话的特征”。4. 模型训练与识别解码全过程4.1 数据准备与标注数据是一切实验的前提。我用的数据包括若干位发音人录制的数字0到9每人每个数字录了多遍采样率统一为16kHz、16bit单声道WAV。录音环境在普通的室内有一些轻微的底噪。数据必须严格切分训练集和测试集。我的做法是按人划分保证训练集和测试集里没有同一个人同一条语音的重复。也就是说训练集里出现了某人的录音测试集里还可以出现这个人没录过的新样本。这样做能避免“记忆”效应测出来的结果更真实。如果你把同一条录音既放训练又放测试识别率能虚高到99%但没有任何实用价值这不是能力问题这是作弊。表如下是我常用的数据划分方式数据角色内容说明数量训练集用于估计GMM参数也就是EM迭代数据720条8人×10数字×9遍验证集用于调整K值、特征维度等超参数不参与训练90条新录制测试集最终评估识别效果全程不接触180条多人的新数据4.2 训练流程设计训练流程并不复杂但有一个关键点容易被新手忽略GMM描述的特征必须是“同一类别的共性特征”而不是“每个录音片段的专属特征”。所以我把同一类别下所有文件的特征全部拼起来一起训练而不是对每个文件各自训一个GMM再把GMM参数平均。后者看着合理实则大错特错因为不同录音长度不同各自GMM的分量对应关系是错乱的平均参数毫无意义。训练之前还有一步对特征做标准化。我用训练集各维的均值和标准差对特征做归一化然后训练GMM。这里必须把训练集的均值和标准差保存下来测试阶段直接使用相同数值。如果测试时重新计算测试集的统计量相当于给模型泄漏了测试集的分布信息结果一样会被污染。训练过程中还可加一个条件如果某类数据量特别少可以适当减少混合数。比如“8”这个音在某些语速下发音非常短特征帧数比“零”少了一半给K16的GMM会导致分量快速退化。我的策略是按类别做一次基础的帧数统计低于阈值的类别自动把K下调一档效果比统一用固定K要好一些。4.3 识别解码与似然比对识别阶段是整个系统的临门一脚。测试音频输入后先做和训练阶段完全相同的前端处理预加重、分帧、加窗、提取MFCC、拼接差分、用训练集的统计量做归一化。然后进入核心决策部分。对每一类数字的GMM计算该测试音频每一帧特征的对数似然把所有帧的对数似然求和再除以帧数得到平均对数似然。选平均对数似然最大的类别作为识别结果。除以帧数非常关键因为有的词音长长有的词音长短如果不归一化长音频的似然天然占优识别结果会整体偏向音量长、帧数多的类别。% 识别单个测试文件 function predict_label recognize_one(file_path, gmm_models, norm_params) feat compute_mfcc(file_path, fs); feat apply_norm(feat, norm_params); n_frames size(feat, 1); log_likelihoods zeros(10, 1); for digit 0:9 LL 0; for i 1:n_frames for k 1:K LL LL log(gmm_models{digit1}.weight(k)) ... logmvnpdf_single(feat(i,:), gmm_models{digit1}.mu(k,:), ... gmm_models{digit1}.sigma(:,:,k)); end end log_likelihoods(digit1) LL / n_frames; end [~, predict_label] max(log_likelihoods); predict_label predict_label - 1; end这段代码里我用了logmvnpdf_single这个函数避免直接用mvnpdf也算完又取log导致数值下溢。实际计算时log高斯密度公式展开后无非是二次项加常数项完全可以把矩阵求逆运算提前到训练阶段做好。否则测试时每帧每个分量都做一次矩阵求逆计算量会非常扎眼几十条测试数据跑下来就开始冒汗了。我实际测试了识别速度10个类、K8、每帧39维特征单条0.5秒的语音大约几百帧MATLAB下识别耗时在几十毫秒量级。这个速度完全可以做到准实时。5. 实验结果与参数调优记录5.1 基线实验结果我的基线配置是训练集720条MFCC静态13维加一阶差分和二阶差分共39维K8对角协方差EM迭代最大50次收敛阈值1e-4。在这个配置下测试集180条音频的整体识别准确率大概在91.7%左右。乍一看还行但仔细看混淆矩阵问题还是很明显。预测实际0123456789017000000001101800000000200161000100300116000100400101700000500001152000600000216000700010001700800000000180910000000017最容易混淆的是“5”和“6”这俩韵母部分都带高元音特征且语速稍快时元音时长缩短区分度明显下降。“2”和“3”的混淆则是因为两者以卷舌音开头辅音段特征非常接近GMM没办法把辅音段的差异拉开。5.2 混合数与特征维度的影响我后来针对K值做了几轮实验。固定其余条件不变K4时准确率87.2%K8时91.7%K16时92.2%K32时又掉回88.9%。这类曲线在统计分类器里极其典型K太小欠拟合K太大过拟合。对这个小样本任务来说8到16是一个合理的区间。特征维度的实验结果也很有意思。只用静态13维MFCC准确率88.4%加上一阶差分后提高到91.1%再加上二阶差分只到91.7%。二阶差分的边际收益已经非常微弱但训练时间涨了不少因为特征维度越大每次M步协方差估计的计算量越高。如果你的目标是快速复现建议直接13维或者26维就好性价比高很多。5.3 时间信息缺失的补偿手段GMM最大的缺点是“只管帧、不管顺序”。为了补偿时间信息缺失我在项目里做了一个轻量级的扩展把一条语音的MFCC特征先用向量量化方式聚类成几个“时序块”再统计每个块里的特征分布把块间的转移频次作为额外特征输入到GMM中。但这本质上已经长得很像HMM了所以项目做了两三天就停了主要目的是确认“GMM丢失了多少时序信息”。我的结论是在孤立词识别里单纯用GMM足够应付大多数情况因为每个词的发音在声学特征上本身就有许多独特之处。但到了连续语音、大词汇量、多说话人场景GMM不做时序建模是不行的必须跟HMM结合。如果有同学想往下深入我建议把HMM-GMM这套联合训练弄明白那才是经典语音识别最核心的一块拼图。6. 常见问题与排错经验6.1 对数似然出现NaN或者Inf这是GMM新手必踩的坑。对数似然变成NaN绝大部分原因是协方差矩阵奇异或趋近奇异。特征中有某个维度的方差接近0或者训练样本太少而特征维度过高在高斯密度计算中就会出现sqrt(det(2*pi*sigma))为Inf或0进而导致log为NaN。解决办法有这么几个。最立竿见影的是强制协方差矩阵加一个小的对角扰动比如reg_covar1e-6。在MATLAB中就是sigma sigma 1e-6 * eye(D)。另外就是改用对角协方差并检查数据里是否存在全零列——比如某一段录音静音过长提取出的MFCC某维基本恒定为0标准化后依然会出现方差为0的风险。我排查这类问题的方法是打印每个GMM分量的协方差对角值凡是出现数量级小于1e-10的元素就说明那一维特征分量为零或者被归一化到几乎为零要检查前端特征提取。6.2 识别结果总是偏向某一个类别这种现象通常不是因为“那类模型特别好”而是因为某一个类别的训练帧数特别多导致其GMM的对数似然天然偏高。虽然我在识别阶段对总帧数做了归一化但不同类别的模型在相同输入下仍然会有绝对对数似然水平的系统性差异。解决办法是给各类模型的似然值做一个校准。我采用的方法是在验证集上统计每个类别模型的平均对数似然然后为每个类别算一个减掉的偏置项让验证集上每个类别的平均加速接近一致。这其实就是简单的先验补偿。实际效果很明显怪异偏向问题立刻消失。6.3 更换说话人后识别率断崖式下跌换人之后的识别率能跌到60%以下属于特定人模型的正常现象。GMM的均值向量包含了训练说话人的声道长度和音色信息新说话人的共振峰位置整体平移之后特征分布会偏移出原有高斯的有效覆盖范围。解决思路是扩充训练集说话人数量或者在特征层面做说话人归一化。在实践中最直接的做法是录音环节就覆盖多个男女声、不同年龄段并且在训练时把每个人的录音条数基本对齐防止主流音色占据主导。如果想更进一步可以把MFCC通过声道长度归一化VTLN或者用倒谱均值减CMS消除录音通道和说话人带来的静态偏移。CMS实现起来很便宜就是减掉整条语音特征各维度的均值在我的实验里光这一招就让跨说话人测试准确率提升了将近五个百分点强烈建议你加上。6.4 快速自检清单现象优先排查项训练过程NaN协方差奇异、特征恒定、初始化不当训练收敛太慢迭代次数太少、K值过大、没有用K-means初始化识别偏向一类帧数不均、未校准、先验不平衡换人识别率低训练集说话人少、未做CMS、特征归一化参数错误耗时严重膨胀特征维度过高、预测时重复求逆、K值设定过大最后一个工程建议整个项目里要养成“每个过程都留一个可视化”的习惯。训练完GMM后把前两维特征散点图和GMM等高线叠加打印出来看一下能帮助你快速判断EM是否收敛到了合理位置。我在做K8的实验时就是通过可视化发现其中一个分量的均值点悬在了两个簇之间的低密度区域顺着查才发现是初始化随机种子没设好导致EM掉进局部最优。这种问题只看准确率数字是完全发现不了的。我自己的实操体会是GMM这个模型看起来很简单但背后牵扯的隐性工程问题非常多。从数据划分、特征归一化、协方差约束到EM初始化任何一个环节的马虎都会在最终准确率上狠狠报复你。当你把这些问题一个个都解决完你对“模型鲁棒性”这几个字的理解会变得完全不一样。这个项目的完整代码核心其实也就几百行MATLAB但我建议你读代码的时候不要上来就跑先把每一步的原理在纸上画一遍否则只是一个调包侠遇到问题完全无从下手。如果后面有时间我打算把HMM-GMM融合进来替换掉独立的GMM分类器那样系统就能支持连续词序列识别了这个坑到时候再出一篇详细记录。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑