资讯详情

资讯详情

语音情感识别实战:Keras实现LSTM、CNN、SVM与MLP多模型对比

简介面向语音情感识别入门与进阶开发者这份基于Keras的项目源码完整实现了LSTM、CNN、SVM、MLP四种模型兼容Python3.8与Keras/TensorFlow2环境。压缩包内含49个文件大小约70.31MB主体包括Python脚本、yaml/json配置、h5/m模型权重和csv特征文件并提供了opensmile与librosa两套特征提取方案支持三分类、六分类、七分类等常见语音情感任务。已有242人学习下载适合快速搭建基线系统、对比传统机器学习与深度学习方法效果的研究者。包内目录按功能划分清晰预处理、模型定义、训练与预测均独立成模块自带checkpoints预训练权重开箱即用且预留自定义数据集接口便于替换音频后重新训练评估。1. 语音情感识别不只是分类这个Keras项目帮你把LSTM、CNN、SVM、MLP一次跑通拿到一批带情绪的语音文件比如“愤怒”“开心”“悲伤”第一反应往往是搭个神经网络直接分类。但实际跑过一两次就会撞上两个麻烦一是音频特征怎么提才稳定二是模型选型怎么比才算公平。这个开源项目把整条链路都封装好了——从opensmile和librosa两种特征提取到LSTM、CNN1D、MLP、SVM四个基线模型再到训练、预测、checkpoints管理全部通过yaml配置串联。你不用改一行代码就能在6类或7类情感数据集上复现实验还可以替换成自己的数据做对比。适合刚接触语音情感识别的研究生、算法工程师也适合想快速验证“特征模型”组合效果的人。以下内容基于项目源码逐层拆解每一节都能直接照着操作。2. 特征工程先行用librosa和opensmile把音频变成模型能吃的特征语音情感识别里特征决定上限模型只是逼近这个上限。这个项目同时支持两种主流特征提取方案opensmile提的是官方标准特征集librosa提的是MFCC等手工特征。为什么要两套因为论文实验里经常需要对比“复杂特征简单模型”和“简单特征复杂模型”的差异。2.1 两种特征提取路线为什么项目要同时给librosa和opensmileopensmile是一个开源音频特征提取工具项目里用的是IS10特征集INTERSPEECH 2010 Paralinguistic Challenge的特征集它会一次性输出1582维统计特征包括MFCC、F0、语音能量、过零率等的均值、方差、峰度等。打开生成的csv文件你会看到每一行是一段音频每一列是一个统计量。这种特征的优点是很适合SVM这类传统机器学习模型因为维度虽高但特征和标签的相关性比较强。librosa走的则是另一条路先把音频按帧分出来一般帧长25ms、帧移10ms然后对每一帧计算40维MFCC再叠加delta和delta-delta最后按时间轴拼接成二维矩阵。这样做出来的特征保留了时间顺序适合LSTM、CNN1D这类序列模型。项目中librosa提的特征会被保存成.p文件也就是Python的pickle格式里面存的是(n_frames, n_features)维度的数组。注意opensmile的特征是整段音频一个向量而librosa的特征是一个时间序列两者在模型输入维度上的区别很大。2.1.1 特征对比与选型逻辑我在实际项目中一般这样选如果数据量少每类只有几百条opensmileSVM更容易收敛如果数据量充足每类几千条librosaCNN1D或LSTM能学到更细的模式。这个项目把两种特征都预先处理好了所以你可以用同一套训练代码分别喂给不同模型直接看对比结果。注意opensmile特征的csv文件里标签列需要和音频路径对应项目里utils/files.py会帮你把数据集文件夹结构映射成标签。2.2 配置文件yaml怎么读configs/example.yaml的字段拆解所有特征提取的输入输出都由yaml配置文件控制。项目里configs/目录下有针对不同模型的svm.yaml、mlp.yaml、lstm.yaml、cnn1d.yaml但特征提取阶段它们共享一套字段。一个典型的配置文件长这样# configs/lstm.yaml data_root: data/Ravdess # 数据集根目录子文件夹按情感类别分 save_dir: features/7-category # 特征保存目录按类别数区分 feature_type: librosa # 或 opensmile model_type: lstm # 只影响训练阶段 n_mfcc: 40 # librosa 的 MFCC 维度 frame_length: 25 # 帧长毫秒 frame_shift: 10 # 帧移毫秒 max_len: 160 # 序列最大帧数超出截断不足补零 opensmile_config: IS10_paraling.conf # opensmile 的特征集配置 target_sr: 16000 # 统一采样率这里的data_root指向数据集的根目录每个情感类别是一个子文件夹比如happy/、angry/。save_dir是提取后特征的输出目录项目里分了3-category、6-category、7-category方便训练不同难度的任务。feature_type决定调用extract_feats/librosa.py还是extract_feats/opensmile.py。max_len很关键因为不同音频长度不一样神经网络需要固定输入维度所以超过设定帧数的部分直接截掉不足的部分在时间维上补零。2.2.1 路径和类别配置注意save_dir和data_root里的类别数必须匹配。比如你的数据只有3类情绪却把save_dir写成features/7-category后面训练时标签索引会错乱。我一般会在数据集里单独放一个label.txt按文件夹名字典序排序保证训练和测试用同一套编码。还要留意target_sr如果原始音频是44.1kHz统一重采样到16kHz能减少计算量同时保留语音主要频率范围0-8kHz。2.3 运行preprocess.py从wav到.p和.csv的全流程特征提取入口是preprocess.py命令行只需指定配置文件python preprocess.py --config configs/lstm.yaml脚本执行时首先遍历data_root下所有子文件夹得到音频路径列表和对应标签。对于librosa模式它会用librosa.load读取音频重采样到target_sr再调用librosa.feature.mfcc计算MFCC最后把原始特征、标签、路径一起存成.p文件。对于opensmile模式它会调用opensmile的python接口每一段音频提取一个1582维的行向量写入csv。我自己跑的时候发现两个高频报错一是opensmile环境没装好报找不到SMILExtract二是max_len设太短导致大部分长音频被截断损失信息。第一个问题需要提前确认opensmile能正常调用第二个问题可以在preprocess脚本里加一行统计打印输出所有音频帧数分布然后按95%分位数设置max_len。另外如果是用librosa需要注意librosa版本和n_mfcc参数的一致性版本升级偶发滤波器组初始化的差异影响实验复现。2.3.1 命令行参数和常见报错preprocess.py还支持覆盖yaml里的字段比如临时改保存路径python preprocess.py --config configs/lstm.yaml --save_dir features/6-category但我不建议频繁这样用因为配置别名多了容易乱。最好的方式是为每个实验复制一个独立yaml文件。检查特征是否提取成功看save_dir下是否生成train.p和test.p如果脚本内部随机划分或一个features.p。常见错误还有“FileNotFoundError: [Errno 2] No such file or directory”这通常是因为data_root路径写错或数据集目录里混入了.DS_Store等非音频文件建议预处理前用utils/files.py里的clean_dataset函数过滤掉非音频文件。3. 模型训练LSTM、CNN、SVM、MLP四个基线如何用一套代码训练特征准备好后训练阶段就是模型间的大乱斗。这个项目的优点是训练入口统一不管你选哪个模型只要改配置文件里的model_type剩下的事情交给train.py。3.1 模型家族划分base.py、ml.py、dnn模块各自负责什么models/目录下有三个核心文件base.py定义了通用的训练/验证循环、checkpoint保存逻辑ml.py封装了SVM和MLP这两个“非深度”模型dnn则放LSTM和CNN1D的Keras实现。为什么要把SVM和Keras深度模型放在一起因为这里SVM用的是sklearn的SVC而MLP可以直接用Keras的Dense层叠出来。项目在models/base.py里做了抽象所有模型都实现fit()、predict()、save()三个接口这样训练脚本可以统一调用。看dnn模块你会发现LSTM模型不是直接接全连接而是先经过TimeDistributed层做帧级别的特征变换。常见做法是这样的输入形状是(batch, max_len, n_mfcc)先经过一维卷积降维再送入LSTM层最后取最后一个时间步的输出接softmax。这样做的原因是原始MFCC序列每一帧的信息冗余度高先卷积能压缩局部模式。而CNN1D模型则直接对时间维做卷积通过多个卷积池化层提取局部情感特征。3.2 训练入口train.py的参数解析训练命令同样简单python train.py --config configs/cnn1d.yaml也可以额外指定--gpu 0或--epochs 50这样的参数。解读一下train.py内部流程首先加载configs/对应的yaml根据feature_type找到对应的.p或.csv特征文件然后按照预设比例默认8:2划分训练集和测试集。这里有个重要细节划分时不能直接train_test_split整个特征数组因为同一个人的音频可能跨情感类别如果随机划分同一个人会同时出现在训练集和测试集造成数据泄漏。项目在utils/files.py里提供split_by_group方法按说话人ID分组划分这点值得注意。对于LSTM和CNN1Dtrain.py会调用models/dnn.py里的build_model函数。模型结构由yaml中的参数控制比如lstm的units、dropoutcnn1d的filters、kernel_size。训练使用Adam优化器初始学习率0.001配合ReduceLROnPlateau——当验证集loss连续三个epoch不降时学习率降为原来的0.5。这个细节能有效避免训练后期震荡。3.3 配置svm.yaml、mlp.yaml、lstm.yaml、cnn1d.yaml的差异这四个文件除了model_type各自特有的参数差别很大。下面这张表是我从项目源码里拆出来的常用字段对比参数svm.yamlmlp.yamllstm.yamlcnn1d.yamlfeature_typeopensmilelibrosalibrosalibrosa核心模型SVC(C1, kernelrbf)3层Dense2层LSTM3层Conv1D输入维度固定1582维向量展开的MFCC序列时间步×特征数时间步×特征数关键调参项C、gammahidden_units、batch_sizeunits、return_sequencesfilters、kernel_size训练轮数不适用508080归一化StandardScalerStandardScaler直接归一化直接归一化注意一个容易踩的坑SVM和MLP使用opensmile提的统计特征时需要提前做StandardScaler否则C和gamma的搜索范围会失真。而LSTM和CNN1D的输入是时间序列通常也用全局均值方差归一化但归一化参数只在训练集上计算然后用同一套参数处理测试集测试集不能被“看”到均值方差。项目里checkpoints/中保存了SCALER_LIBROSA.m和SCALER_OPENSMILE.m这两个文件就是干这个用的。3.3.1 模型结构示例与参数说明以lstm.yaml为例典型的结构在models/dnn.py里长这样# models/dnn.py 中 LSTM 模型构建逻辑 from tensorflow.keras.layers import Input, LSTM, Dense, Dropout, Bidirectional, TimeDistributed def build_lstm(input_shape, num_classes, units128, dropout0.3): inputs Input(shapeinput_shape) # (None, max_len, n_mfcc) # 先做一个时间分布的卷积将40维MFCC压缩到32维减少计算量 x TimeDistributed(Dense(32, activationrelu))(inputs) x Bidirectional(LSTM(units, return_sequencesFalse, dropoutdropout))(x) x Dropout(dropout)(x) outputs Dense(num_classes, activationsoftmax)(x) return inputs, outputs这里的input_shape由max_len和n_mfcc决定比如(160, 40)。Bidirectional表示双向LSTM能同时利用时间步前后的上下文——在语音情感识别任务里某段语音的愤怒情绪可能受前一段平静语调的影响所以双向比单向效果好。return_sequencesFalse表示只返回最后一个时间步的输出。如果你要堆叠两层LSTM第一层需要设置return_sequencesTrue否则第二层接不到完整的时间序列。3.4 训练后的checkpointsh5与json文件说明训练完成后checkpoints/目录下会生成多个文件命名规则是{MODEL}_{FEATURE}_{DATASET}。例如LSTM_LIBROSA_IS10.h5和LSTM_LIBROSA_IS10.json。其中.h5是Keras的HDF5权重文件.json是模型结构描述。你可以用tf.keras.models.model_from_json读取结构再load_weights赋值。注意CNN1D_OPENSMILE_IS10.h5这种情况表示用opensmile特征训练的卷积网络说明作者做了交叉组合实验——卷积也能吃统计特征只是要reshape成2D。还有.m结尾的是SVM模型的pickle文件或joblib备份SCALER_*.m是归一化器。4. 预测与验证用训练好的模型对单条音频做情感分类模型训练好之后最终要落地到单条音频的预测。predict.py就是干这个的它与训练脚本解耦可以加载任何已保存的checkpoint。4.1 predict.py的调用方式预测时先要用和训练时完全相同的特征提取流程把音频转成特征向量或序列再喂给模型。命令格式是python predict.py --config configs/lstm.yaml --model checkpoints/LSTM_LIBROSA_IS10.h5 --audio test.wav脚本内部会做这些事读取configs/lstm.yaml里的feature_type和max_len等参数用对应的特征提取器处理test.wav。如果是librosa特征它会输出一个(1, max_len, n_mfcc)的数组其中时间维小于max_len的部分历史补零如果是opensmile特征则输出(1, 1582)的向量并利用保存的SCALER做标准化。然后调用模型预测返回每个类别的概率。4.2 模型与标量文件怎么对应这里有个容易忽略的环节预测时除了加载模型权重还要加载对应的SCALER。项目里predict.py会从模型文件名中解析出LIBROSA或OPENSMILE自动去checkpoints/里找SCALER_LIBROSA.m或SCALER_OPENSMILE.m。如果你自己重命名了模型文件预测脚本可能匹配不到标量文件。我一般会把标量文件名也写进yaml配置里例如scaler_path: checkpoints/SCALER_LIBROSA.m然后修改predict.py优先读取这个字段。如果没有标量直接预测大概率所有输出概率都在0.4~0.6之间看不出明显优势——这就是没有标准化的典型症状。4.3 验证模型性能混淆矩阵和plot.py除了单条预测项目还提供了utils/plot.py脚本用于生成混淆矩阵和训练曲线。命令行调用python utils/plot.py --history history.json --cm confusion_matrix.npy --output result.png其中history.json是训练过程中每个epoch的loss和accuracy记录confusion_matrix.npy是在测试集上计算的混淆矩阵。我习惯交叉验证后先看每类别的recall而不是只看整体accuracy因为情感数据往往不平衡——“中性”样本总是比“惊讶”多整体准确率会被主导类别带偏。如果某一类recall特别低查看混淆矩阵能知道它最容易被错分成哪一类比如“恐惧”经常被误判为“悲伤”这说明特征上两者太接近可能需要更多f0相关的特征来区分。5. 进阶把项目扩展到自己的数据集和更多情感类别基础流程跑通后你肯定会想用自己的数据集。这个项目的扩展性不错但有几个地方必须按它的约定来。5.1 整理数据集utils/files.py的目录约定utils/files.py里有整理数据集的辅助函数。它的核心假设是每个情感类别一个文件夹文件夹名就是标签。比如data/my_dataset/ ├── happy/ │ ├── audio_001.wav │ ├── audio_002.wav ├── sad/ │ ├── audio_003.wav写一个简单的遍历脚本检查所有音频能否正常读取import os from glob import glob import librosa for wav in glob(data/my_dataset/*/*.wav): try: y, sr librosa.load(wav, sr16000) if len(y) 1600: # 过滤过短音频 print(ftoo short: {wav}) except Exception as e: print(ferror: {wav}: {e})如果你的标签是文件名而非文件夹名需要先改utils/files.py里的get_label_from_path函数。例如文件名“123_happy_456.wav”这种格式你可以用wav.split(_)[1]提取情感标签。5.2 修改配置从3-category到7-category的迁移项目features/里已经预设了3类、6类、7类的保存目录。换成7类时只要把data_root指向含7个子文件夹的数据集save_dir改成features/7-category预处理和训练脚本会自动识别类别数。但要小心原数据集里的7类可能和你自己的标签集不一致比如原项目用的是RAVDESS的8类但只用其中7类假如你下载了完整RAVDESS需要排除“语气平静”这一类否则类别数对不上。修改yaml中的data_root后建议先跑一次preprocess.py观察save_dir下生成的标签集合是否符合预期。5.3 调参建议LSTM的序列长度与CNN1D的核大小调参时先看max_len。如果设置过小长音频的尾段被截断可能丢失情感爆发点设置过大大部分样本补零太多模型容易过拟合到零区域。常见做法是统计所有音频的帧数分布取95%分位数的值作为max_len。对于CNN1Dkernel_size通常设为3或5。因为音频帧之间的上下文相关性大约在几十毫秒内kernel_size5对应50ms的跨度已经能捕捉短时韵律变化。更大的核容易模糊局部模式。5.4 踩坑记录训练集测试集划分、过拟合和特征归一化最后抖几个我实际踩过的坑。第一划分数据集时一定要按说话人ID分割否则模型记住了说话人身份而非情感特征跨人测试效果会非常差。项目里utils/files.py提供split_by_group建议优先使用。第二LSTM在网络深处容易过拟合尤其在少于10小时语音的数据集上此时把dropout从0.2调到0.5并把recurrent_dropout设为0.2效果立竿见影。第三用opensmile特征跑SVM时如果训练集准确率接近100%但测试集很低先把C降一个数量级再看是不是gamma太大。第四注意checkpoints/里已提供的模型是针对特定特征维度的如果你改了n_mfcc旧模型不能直接加载报shape不匹配需要重新训练。第五语音情感识别数据量通常不够大可以考虑用plot.py生成的混淆矩阵分析错误样本看看是不是噪声环境导致——比如有背景音乐时“平静”容易被误判为“愉快”这时可以尝试加谱减降噪预处理再提特征。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →