
簡介面向語音情感識別入門與進階開發者這份基于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生成的混淆矩陣分析錯誤樣本看看是不是噪聲環境導致——比如有背景音樂時“平靜”容易被誤判為“愉快”這時可以嘗試加譜減降噪預處理再提特征。本文還有配套的精品資源點擊獲取