简介:资源面向语音情感识别方向的MATLAB与深度学习实践者,围绕语音情绪状态自动识别这一任务,提供一套可直接运行的算法实现,覆盖从语音信号预处理、MFCC特征提取到模型训练与评估的完整流程,适合研究生、竞赛参与者及工程研发人员快速上手与二次开发。包内共9个文件,包含5个.mat数据文件(对应愤怒、快乐、中性、悲伤、恐惧等情感样本)和4个.m源码文件(覆盖BP神经网络、PNN概率神经网络、LVQ学习向量量化等模型实现与配套脚本),压缩包约260KB,结构精简但功能链条完整。已有454人学习/下载,说明该资源在情感识别入门与算法对比中有一定参考价值。资源以MATLAB实现为主,既保留BP、PNN、LVQ等网络,也便于学习者在此基础上尝试CNN/LSTM等更复杂的深度模型;可帮助理解不同网络在语音情感分类中的效果差异,并能基于素材自行扩充实验,是可直接运行与复现的小型算法案例。
1. 从语音里读情绪,为什么偏偏用 matlab 做落地
语音情感识别这些年被反复提起,但从论文里的准确率到实际能跑的工程,中间隔着一条特征工程和训练链路的鸿沟。不少人先用 python 搭原型,等要接进已有 matlab 信号处理流程时又发现两套生态对接成本很高。如果你已经在用 matlab 做语音或振动分析,完全没必要另起炉灶——matlab 的深度学习工具箱和音频工具箱本身就能把 预处理、特征提取、LSTM 训练、精度评估 串成一条完整链路,这也是这个资源的价值所在。
这套系统解决的是“说话内容不重要、情绪才是标签”的分类问题,输入是音频片段,输出是 angry、happy、neutral 之类的离散情感类别。适合信号处理背景的工程师、做语音交互原型的算法工程师,以及想把手头音频数据快速训练成模型的在校研究者。难点不在网络结构有多深,而在特征组织方式、序列长度对齐、训练验证拆分这些小细节,下面按我拆这套系统的顺序一步步说清楚。
2. 特征提取与标签处理:MFCC、韵律特征和样本对齐
2.1 为什么首选 MFCC 而不是直接把波形丢给网络
语音情感识别的输入组织方式,直接决定模型收敛速度和最终精度。语音波形是几万赫兹的采样序列,直接喂给网络很难学到情绪层面的抽象,波形里充满基频、共振峰、噪声和环境干扰。业界和学术界都先做特征映射,MFCC(梅尔倒谱系数)是目前最通用的语音特征,它模拟人耳对频率的非线性感知,压缩后的倒谱系数能保留发音器官和情感状态相关的谱包络信息。
在 matlab 里提取 MFCC 用audioFeatureExtractor一行就能配置好,对比手动分帧、加窗、做 FFT、过梅尔滤波器组,既省代码又不容易出错。如下所示:
aFE = audioFeatureExtractor( ... 'SampleRate', fs, ... 'Window', hann(512, 'periodic'), ... 'OverlapLength', 384, ... 'mfcc', true, ... 'gtcc', true, ... 'zerocrossrate', true); features = extract(aFE, audioData);2.2 特征维度的含义与参数调整逻辑
Window决定每帧长度,hann(512, 'periodic')在 fs=16000 Hz(16 kHz 采样)下对应 32 ms 的窗长,这是语音分析里的常用配置,既能覆盖足够多的基频周期,又不至于把音节边界抹平。OverlapLength设为 384,表示帧移 128 点,即 8 ms,保证帧与帧之间平滑过渡,说话人发音过程中短暂停顿也不易断裂。这里的核心思想是时间内核的滑动窗口切分,与卷积层的局部感受野方式异曲同工。
gtcc是线性频率倒谱系数,用来补 MFCC 在低频分辨率上的不足;zerocrossrate是过零率,能辅助区分清音和浊音,对愤怒、惊讶这类高能量情绪有区分度。这三个特征拼成一个向量,每帧维度是 13 + 13 + 1 = 27。输出的features是一个帧数 × 27的矩阵。如果处理一条 3 秒的语音,16 kHz 采样下大约 368 帧,矩阵尺寸为 368 × 27,而每帧时间步长为 8 ms,这构成了后续 LSTM 输入的时间步维度。
2.3 标签体系与类别平衡策略
情感标签要以 categorical 类型进入网络,而不是字符串数组。matlab 的trainNetwork要求响应变量要么是 categorical,要么是 one-hot 编码,直接把 cell 字符串丢进去会报标签类型错误,这也是初级用户经常踩的坑之一。把标签转成 categorical 只需要一行:
labelCategorical = categorical(labelStrings);这时要注意类别的顺序:如果样本里 neutral 占一半,angry 只占一小部分,训练出来的模型会偏向多数类。常见的做法是先把每个类别样本数统计清楚,再用repmat做过采样,或用 matlab 自带的sum(Y, 1)统计后调整分类权重。这里我的经验是:当最少的类别样本数不足最大值的三分之一,先做数据增强会比调权重更有效,加一点高斯白噪声、改变音高(shiftPitch)是成本低且不破坏情绪标签的两个方法。
3. LSTM 与全连接层搭建:用 trainNetwork 组织序列训练
3.1 长短时记忆网络在语音情感识别里的定位
语音特征矩阵的维度是时间帧 × 特征维度,时间帧之间是严格有序的。愤怒的情绪往往表现为语速快、能量起伏大、基频攀升,这种趋势要靠跨帧上下文才能捕捉。LSTM 的核心机制是记忆单元和门控结构,能在一段序列里保留长期依赖信息,正好适配语音帧序列的时序建模。
对比单纯用 CNN 逐帧识别,LSTM 能感知情绪在第 1 秒到第 2 秒的发展轨迹。对比传统 HMM,LSTM 省去了状态转移矩阵的手工设计。所以在工程折中下,LSTM 是性价比最高的选择。不过也要说清楚边界:如果你想用一个 4 GB 显存的显卡跑大规模数据,LSTM 的训练效率远不如时域卷积网络 TCN,但 TCN 在 matlab 里要手工搭残差块,而 LSTM 可以直接调用工具箱层接口。
3.2 在 matlab 里搭建分层网络结构
推荐用layerGraph组织网络,比起直接数组方式,后面要加跳接或改结构时不用重写所有代码:
layers = [ sequenceInputLayer(27, 'MinLength', 50) bilstmLayer(128, 'OutputMode', 'last') dropoutLayer(0.3) fullyConnectedLayer(64) reluLayer fullyConnectedLayer(numClasses) softmaxLayer classificationLayer ]; options = trainingOptions('adam', ... 'MaxEpochs', 40, ... 'MiniBatchSize', 64, ... 'InitialLearnRate', 0.001, ... 'LearnRateSchedule', 'piecewise', ... 'LearnRateDropFactor', 0.5, ... 'LearnRateDropPeriod', 10, ... 'ValidationData', {XVal, YVal}, ... 'ValidationFrequency', 10, ... 'Shuffle', 'every-epoch', ... 'Plots', 'training-progress', ... 'Verbose', true); net = trainNetwork(XTrain, YTrain, layers, options);3.3 数据矩阵 reshape 的细节与训练选项解读
XTrain必须是numFeatures × numTimeSteps × 1 × numObservations的四维数组,注意维度顺序和图像输入不同,很多第一次用 matlab 的人会在这里被乱序坑到。XTrain(:, :, 1, i)表示第 i 条语音样本,其中每行是一个时间步的特征向量。
bilstmLayer(128, 'OutputMode', 'last')里 128 是隐藏单元数。这里OutputMode设为'last',因为最终要输出一个序列的单一标签。如果做帧级情感识别则需要设成'sequence'。dropoutLayer(0.3)让 30% 的神经元随机失活,能显著降低语音噪声导致的过拟合。InitialLearnRate取 0.001 而不是 0.01,语音特征梯度波动大,偏大的学习率会让 loss 在第 20 轮附近震荡。
LearnRateDropPeriod设为 10,意思是每 10 轮学习率乘 0.5,让模型前期大步走,后期精细调。ValidationFrequency为 10 是每 10 次迭代跑一次验证集,注意它是按迭代计数不是按轮数计数,一轮迭代等于一个 mini-batch 训练完成。
训练结束后trainNetwork返回的net直接用于后续预测,不再需要手动做特征标准化,matlab 已经在训练过程里按输入数据分布把相关参数固化到层里了。
4. 训练中的过拟合排查与超参数优化:从 loss 曲线上找蛛丝马迹
4.1 训练误差与验证误差分离,基线先跑通
模型能不能用,要看两条曲线:训练 loss 和验证 loss。如果训练 loss 持续下降、验证 loss 在中间某个点反弹,基本可以断定过拟合。在 matlab 的training-progress画布里,蓝色代表训练准确率、黑色代表验证准确率。正常情况下验证准确率会在训练准确率下方 5~10 个百分点内波动,一旦差距拉大到 15 个点以上,就要介入干预。
经典四条干预路径是:加大 dropout 比率、减少隐藏单元数量、加正则化项、提前停止。先只改 dropout 从 0.3 到 0.5,往往就能看到验证 loss 回到正常区间。不要一上来就动网络深度,语音情感数据集通常只有几百到几千条样本,加深网络会成倍放大过拟合风险。
4.2 matlab 里做超参数搜索:贝叶斯优化接管手工调参
学习率、隐藏单元数、dropout 比率这三个参数之间存在交互,手动调很容易陷入局部最优。matlab 自带bayesopt可以做贝叶斯超参数优化,替代网格搜索的暴力组合。定义优化变量时要给合理边界,比如:
optimVars = [ optimizableVariable('lstmUnits', [50 256], 'Type', 'integer') optimizableVariable('dropoutRate', [0.2 0.6], 'Type', 'real') optimizableVariable('initialLR', [0.0001 0.01], 'Type', 'real') ]; results = bayesopt(@(params) objectiveFun(params, XTrain, YTrain, XVal, YVal), ... optimVars, ... 'MaxObjectiveEvaluations', 20, ... 'AcquisitionFunctionName', 'expected-improvement-plus', ... 'UseParallel', true);optimizableVariable定义搜索空间,Type,integer确保 LSTM 单元数是整数,real允许 dropout 和学习率取连续值。objectiveFun需要你自己封装一个函数,里面完成构建网络、设置trainingOptions、训练、输出验证集 loss。expected-improvement-plus是贝叶斯优化的默认采集函数,它比probability-of-improvement多带一个探索因子,避免算法总是在已知最优附近打转。没有并行计算工具箱的机器把UseParallel关掉,否则优化器会停在等待并行池的假死状态。
4.3 数据不平衡时的处理方式与混淆矩阵评估
类别不平衡在情感数据集中非常普遍,中性语气往往占大头,恐惧和厌恶这类情绪样本难采集还难标注。训练结束后用classify(net, XTest)得到预测结果,搭配confusionchart看混淆矩阵:
YTestPred = classify(net, XTest); figure; confusionchart(YTest, YTestPred);当发现 angry 被大量误判成 happy 时,不要只在网络层面补,重点先看特征层面——两类的 MFCC 分布是否重叠过大,可以通过tsne降维后把特征散点画出来确认。特征分不开的情况下,优先提取更丰富的韵律特征包括基频 F0 和能量包络,在audioFeatureExtractor中开启pitch和rms这两个属性即可:
aFE.pitch = true; aFE.rms = true;但audioFeatureExtractor开 pitch 后,训练和预测阶段必须使用完全相同配置,否则向量长度不匹配,trainNetwork直接报维度错误。顺手把这一条写进自动化流程的配置校验里,是最稳妥的做法。
5. 短语音片段分类的置信度校准与滑动窗口投票
5.1 从预测概率到连续标签流,做一版能实时跑的分类器
模型训练完成后,你已经可以做离线文件分类。但语音情感识别的实用场景是流式的,比如客服对话中判断用户情绪是愤怒还是平静。这里不推荐一帧一帧地喂给 LSTM,因为 20 ms 的语音帧缺乏上下文,分类得分会剧烈抖动。我一般做的方案是滑动窗口拼接:取一段 1.2 秒的音频,滑动步长 0.4 秒,每个窗口做一次classify得到概率分布,相邻窗口之间做加权平均。
matlab 的predict返回的是每个类别的后验概率,等价于对 softmax 输出做了一次校准。假设一个片段的输出是[0.7, 0.2, 0.1],说明模型对“angry”的判断置信度较高;但如果输出是[0.4, 0.35, 0.25],三个类别相差不大,直接取最大值就有点冒险。设置一个 0.5 的置信度阈值,低于阈值时输出neutral,往往能显著降低通话录音里的误报率。
5.2 连续音频分块预测代码与硬投票逻辑
实际落地时用如下脚本片段处理一段长语音:
winLen = 1.2; % 窗口长度,秒 stepLen = 0.4; % 滑窗步长,秒 samplesPerWin = round(winLen * fs); samplesPerStep = round(stepLen * fs); numWins = floor((length(audioLong) - samplesPerWin) / samplesPerStep) + 1; scoreSum = zeros(1, numClasses); frameStruct = struct('windowIdx', {}, 'feature', {}, 'score', {}); for idx = 1:numWins startIdx = (idx - 1) * samplesPerStep + 1; seg = audioLong(startIdx : startIdx + samplesPerWin - 1); feat = extract(aFE, seg); feat = feat'; [scores, ~] = predict(net, {feat}); % 拼接成分类器原始输出序列 frameStruct(end+1).windowIdx = idx; frameStruct(end).feature = feat; frameStruct(end).score = scores; scoreSum = scoreSum + scores; end [~, finalLabelIdx] = max(scoreSum); finalEmotion = categories(labelCategorical{1});窗口间有重叠,每一段语音的信息被多个窗口重复利用,最终scoreSum等效于做了硬投票与软投票的折中——保留每个窗口的概率值以体现不确定度,同时用累加方式平滑掉单窗口尖刺。配合事先标定的置信度阈值来划分“确定类别”与“待确认状态”,把不能判断的片段留给上层人工复核,这套系统的可靠性会明显提升。
这种移动到分段预测的思路,本质上是把离线训练模型复用到在线推理的一种工程化解法,可以进一步和语音活动检测 VAD 结合,滤掉静音帧后再进模型,实际误报率约能再降三成。
本文还有配套的精品资源,点击获取