別深度學(xué)習(xí)模型選型與實(shí)戰(zhàn):從CNN到BiGRU)
簡介本資源面向腦電信號(hào)處理與情緒識(shí)別方向的研究者及深度學(xué)習(xí)初學(xué)者提供一套覆蓋主流架構(gòu)的EEG情緒識(shí)別模型實(shí)現(xiàn)方案適用于DEAP、SEED等經(jīng)典腦電數(shù)據(jù)集的建模與實(shí)驗(yàn)驗(yàn)證。壓縮包共74個(gè)文件包含57個(gè)Python核心腳本含BiGRU、LSTM、CNN、GCN、DNN、RNN等23種模型實(shí)現(xiàn)、9個(gè)MATLAB輔助工具用于信號(hào)預(yù)處理與可視化、3個(gè)說明文檔txt/md格式及1個(gè)LICENSE文件整體僅153KB輕量易部署。已有2231人下載學(xué)習(xí)代碼結(jié)構(gòu)清晰從原始EDF文件讀取、時(shí)頻特征提取、標(biāo)簽映射到模型訓(xùn)練與評(píng)估全流程閉環(huán)每個(gè)模型均配有獨(dú)立可運(yùn)行模塊及數(shù)據(jù)處理適配邏輯顯著降低復(fù)現(xiàn)實(shí)驗(yàn)門檻。1. 腦電情緒識(shí)別到底在識(shí)別什么收到一份名為“腦電情緒識(shí)別所用到深度學(xué)習(xí)模型 EEG models(BiGRU,lstm,cnn,gcn,dnn,rnn等等).zip”的資源光看文件名就知道這是一份打包整理好的深度學(xué)習(xí)模型集合專門用于腦電情緒識(shí)別。做這個(gè)方向的同學(xué)省去了從零搭模型的痛苦但說實(shí)話如果不知道這些模型各自的脾氣和適用場景直接拿來跑大概率會(huì)踩坑。腦電情緒識(shí)別的基本邏輯并不復(fù)雜受試者佩戴腦電帽采集頭皮表面的電位變化信號(hào)這些信號(hào)經(jīng)過預(yù)處理后喂給深度學(xué)習(xí)模型最終輸出情緒類別或情緒維度分?jǐn)?shù)。工業(yè)界和學(xué)術(shù)界使用最多的情緒模型是valence-arousal二維模型valence代表愉悅度arousal代表喚醒度每個(gè)維度和特定情感狀態(tài)相關(guān)。我見過不少剛?cè)腴T的同學(xué)上來就直接套用CNN或者LSTM跑完發(fā)現(xiàn)準(zhǔn)確率上不去然后懷疑數(shù)據(jù)有問題。其實(shí)更可能的原因是模型選擇和腦電信號(hào)的特性不匹配。腦電信號(hào)的核心特點(diǎn)是多通道通常是32導(dǎo)或64導(dǎo)、強(qiáng)噪聲工頻干擾、眼電肌電偽跡、個(gè)體差異大不同人的腦電特征分布差異明顯、時(shí)序依賴強(qiáng)情緒狀態(tài)是一個(gè)隨著時(shí)間演化的過程。這些特性決定了模型選型的基本方向需要處理時(shí)序依賴所以RNN、LSTM、BiGRU這類循環(huán)結(jié)構(gòu)有天然優(yōu)勢(shì)需要處理空間特征所以CNN、GCN這類能捕捉通道空間關(guān)系的模型也有用武之地需要處理時(shí)序和空間特征的聯(lián)合表征所以混合模型、注意力機(jī)制成為近年來的主流。先說結(jié)論這份zip里打包的DNN、CNN、RNN、LSTM、BiGRU、GCN六類模型幾乎覆蓋了腦電情緒識(shí)別的主流技術(shù)路線每一類都有它存在的道理也都有各自的局限性。下面我逐個(gè)展開說包括每個(gè)模型的原理、在腦電情緒識(shí)別中的具體應(yīng)用方式、代碼實(shí)現(xiàn)要點(diǎn)以及我在實(shí)際操作中遇到的坑和對(duì)應(yīng)的解決方案。2. 六類模型逐個(gè)拆解原理、選型與腦電適配性2.1 DNN最樸素的基線模型但別小看它DNNDeep Neural Network通常指全連接深度神經(jīng)網(wǎng)絡(luò)。在腦電情緒識(shí)別中DNN通常作為基線模型存在——當(dāng)你要驗(yàn)證一個(gè)新模型的有效性時(shí)需要先跑一個(gè)DNN用它作為對(duì)比基準(zhǔn)確保新模型不是因?yàn)閿?shù)據(jù)泄漏或者隨機(jī)因素才表現(xiàn)良好。但DNN在腦電任務(wù)中的表現(xiàn)往往不會(huì)太差。原因在于腦電特征經(jīng)過預(yù)處理后如提取功率譜密度、微分熵特征本身已經(jīng)具有較強(qiáng)的可分性DNN只需要做一個(gè)非線性映射就能達(dá)到不錯(cuò)的分類性能。我在DEAP數(shù)據(jù)集上實(shí)驗(yàn)用DNN在valence二分類上能達(dá)到78%左右的準(zhǔn)確率這個(gè)數(shù)字并不丟人。DNN的實(shí)現(xiàn)要點(diǎn)輸入層維度等于特征維度隱藏層通常用2-3層每層128-256個(gè)神經(jīng)元激活函數(shù)用ReLU輸出層用softmax做分類。Dropout設(shè)置在0.3到0.5之間防止過擬合。import torch.nn as nn class EEGDNN(nn.Module): def __init__(self, input_dim, hidden_dim256, num_classes2): super(EEGDNN, self).__init__() self.fc1 nn.Linear(input_dim, hidden_dim) self.fc2 nn.Linear(hidden_dim, hidden_dim // 2) self.fc3 nn.Linear(hidden_dim // 2, num_classes) self.dropout nn.Dropout(0.4) self.relu nn.ReLU() def forward(self, x): x self.relu(self.fc1(x)) x self.dropout(x) x self.relu(self.fc2(x)) x self.dropout(x) x self.fc3(x) return xDNN的局限性也明顯它假設(shè)輸入特征之間相互獨(dú)立無法顯式建模通道之間的空間關(guān)系和時(shí)間順序依賴。所以當(dāng)特征本身包含時(shí)序信息時(shí)比如直接輸入原始腦電波形片段DNN的效果就會(huì)明顯下降。2.2 CNN自動(dòng)提取空間特征的主力CNN在腦電情緒識(shí)別中的應(yīng)用稍微和圖像處理有些區(qū)別。圖像CNN處理的是像素矩陣空間關(guān)系是像素的二維排列腦電信號(hào)的“空間關(guān)系”是指電極通道的物理位置分布。這種差異決定了腦電CNN必須做適配不能直接套用現(xiàn)成的圖像分類網(wǎng)絡(luò)。兩種主流的適配方案第一種把EEG信號(hào)轉(zhuǎn)換為二維圖像輸入CNN。常用方法是把每個(gè)通道的時(shí)域信號(hào)堆疊成二維矩陣一維是時(shí)間點(diǎn)另一維是通道數(shù)相當(dāng)于把多通道EEG排列成了一個(gè)“偽圖像”。這種方法實(shí)現(xiàn)簡單但忽略了電極之間的實(shí)際物理距離。第二種使用1D CNN把每個(gè)通道當(dāng)作一個(gè)獨(dú)立的時(shí)間序列用一維卷積沿時(shí)間方向提取特征。卷積核在時(shí)間維度上滑動(dòng)能夠捕捉局部時(shí)間模式比如某個(gè)時(shí)間窗口內(nèi)的波形形態(tài)變化。這種方法更符合腦電信號(hào)的物理意義我實(shí)際使用中傾向于這種方式。import torch.nn as nn class EEG1DCNN(nn.Module): def __init__(self, n_channels, n_samples, num_classes2): super(EEG1DCNN, self).__init__() self.conv1 nn.Conv1d(n_channels, 64, kernel_size9, padding4) self.conv2 nn.Conv1d(64, 128, kernel_size7, padding3) self.conv3 nn.Conv1d(128, 256, kernel_size5, padding2) self.pool nn.MaxPool1d(4) self.relu nn.ReLU() self.dropout nn.Dropout(0.5) self.flatten_len (n_samples // 64) * 256 self.fc nn.Linear(self.flatten_len, num_classes) def forward(self, x): # x shape: (batch, channels, samples) x self.relu(self.conv1(x)) x self.pool(x) x self.relu(self.conv2(x)) x self.pool(x) x self.relu(self.conv3(x)) x self.pool(x) x x.view(x.size(0), -1) x self.dropout(x) x self.fc(x) return x做通道排列時(shí)建議參考國際10-20系統(tǒng)的電極分布順序來排不要隨意打亂這樣卷積核才能學(xué)到有物理意義的空間模式。CNN在腦電情緒識(shí)別中通常能達(dá)到80%左右的準(zhǔn)確率但如果只是單純的CNN沒有注意力機(jī)制或者循環(huán)結(jié)構(gòu)模型對(duì)情緒這種隨時(shí)間動(dòng)態(tài)變化的過程建模能力有限后續(xù)引入CNN注意力或CNN-LSTM混合架構(gòu)是更好的選擇。2.3 RNN最原始的時(shí)序建模方案RNN循環(huán)神經(jīng)網(wǎng)絡(luò)是處理序列數(shù)據(jù)最樸素的結(jié)構(gòu)。它的核心思想是網(wǎng)絡(luò)在每個(gè)時(shí)間步接收當(dāng)前輸入和上一個(gè)時(shí)間步的隱藏狀態(tài)輸出新的隱藏狀態(tài)形成一個(gè)循環(huán)。這個(gè)隱藏狀態(tài)理論上可以攜帶過去所有時(shí)間步的信息但現(xiàn)實(shí)中因?yàn)樘荻认栴}RNN對(duì)長序列的記憶能力非常有限。在腦電情緒識(shí)別中單純的RNN使用頻率已經(jīng)不高主要原因是腦電信號(hào)通常以250Hz甚至更高的采樣率記錄一段幾秒鐘的片段就包含上千個(gè)時(shí)間點(diǎn)。我們按時(shí)間窗口切分后每個(gè)樣本的序列長度依然很長。RNN在處理這種長序列時(shí)會(huì)出現(xiàn)兩個(gè)問題一是梯度消失導(dǎo)致前面時(shí)間步的信息無法傳遞到后面二是訓(xùn)練速度慢難以并行化。但了解RNN依然有價(jià)值。我在調(diào)試LSTM和BiGRU時(shí)經(jīng)常會(huì)回到RNN的最基礎(chǔ)公式去理解梯度傳導(dǎo)路徑很多調(diào)參思路都是從這個(gè)最樸素的模型里推出來的。如果項(xiàng)目對(duì)精度要求不高、序列長度較短RNN也可以作為快速驗(yàn)證的選項(xiàng)。2.4 LSTM長時(shí)序依賴的解決方案LSTM長短期記憶網(wǎng)絡(luò)是RNN的改進(jìn)版專治RNN的梯度消失問題。LSTM的核心創(chuàng)新是引入了門控機(jī)制輸入門控制哪些新信息進(jìn)入記憶單元遺忘門控制哪些舊信息被丟棄輸出門控制記憶單元中的哪些信息輸出到隱藏狀態(tài)。通過這種精細(xì)控制LSTM可以跨越很長的時(shí)間步保持關(guān)鍵信息。在腦電情緒識(shí)別中LSTM的價(jià)值在于情緒狀態(tài)不是瞬時(shí)的而是持續(xù)一段時(shí)間的穩(wěn)定狀態(tài)。LSTM能夠從腦電信號(hào)中提取這種隨時(shí)間延續(xù)的情緒狀態(tài)變化模式。例如愉快情緒可能持續(xù)5秒鐘其中的腦電特征在時(shí)間軸上表現(xiàn)為一種增強(qiáng)的alpha頻段活動(dòng)LSTM能夠在多個(gè)時(shí)間步上累積學(xué)習(xí)到這種模式。LSTM的兩種主要使用方式第一種直接輸入原始腦電時(shí)間序列。每個(gè)時(shí)間步輸入一個(gè)通道向量所有電極在某一時(shí)刻的采樣值LSTM逐步處理整個(gè)序列最后取最后一個(gè)時(shí)間步的隱藏狀態(tài)進(jìn)行分類。第二種先提取每個(gè)時(shí)間窗口的特征再把這些特征序列輸入LSTM。這種方式更常見因?yàn)樘卣餍蛄械木S度較低LSTM的收斂速度更快效果也通常更好。import torch.nn as nn class EEGLSTM(nn.Module): def __init__(self, input_size, hidden_size128, num_layers2, num_classes2): super(EEGLSTM, self).__init__() self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, bidirectionalFalse, dropout0.3 ) self.fc nn.Linear(hidden_size, num_classes) def forward(self, x): # x shape: (batch, seq_len, input_size) out, (h_n, c_n) self.lstm(x) # 取最后一個(gè)時(shí)間步的隱藏狀態(tài) out out[:, -1, :] out self.fc(out) return out在DEAP數(shù)據(jù)集上LSTM通常能達(dá)到82%-85%的準(zhǔn)確率驗(yàn)證集上的表現(xiàn)比較穩(wěn)定。但LSTM的訓(xùn)練速度較慢尤其是雙向LSTM參數(shù)量會(huì)翻倍。如果硬件資源有限可以考慮減少num_layers或者h(yuǎn)idden_size不要一開始就堆大模型。2.5 BiGRU雙向建模腦電情緒識(shí)別的常用主力BiGRU雙向門控循環(huán)單元是GRU的雙向版本。GRU是LSTM的簡化版把LSTM的三個(gè)門壓縮成了兩個(gè)門更新門和重置門參數(shù)量更少訓(xùn)練速度更快在很多任務(wù)上效果與LSTM相當(dāng)甚至更好。BiGRU則是在時(shí)間軸上同時(shí)進(jìn)行正向和反向傳播把兩個(gè)方向的隱藏狀態(tài)拼接起來從而捕捉上下文信息。為什么BiGRU在腦電情緒識(shí)別中表現(xiàn)不錯(cuò)我自己的理解是情緒狀態(tài)在時(shí)間上的表現(xiàn)并不是單向線性發(fā)展的某個(gè)時(shí)刻的腦電特征可能同時(shí)受到之前和之后腦電活動(dòng)的影響。例如看到一個(gè)令人愉悅的畫面后腦電活動(dòng)的變化不會(huì)瞬間發(fā)生而是在幾百毫秒內(nèi)逐漸變化使用雙向結(jié)構(gòu)可以讓模型同時(shí)利用前后時(shí)間段的信息更準(zhǔn)確地判斷當(dāng)前時(shí)刻的情緒狀態(tài)。在實(shí)際操作中BiGRU的輸入方式和LSTM完全一致區(qū)別只在模型結(jié)構(gòu)。定義BiGRU模型時(shí)只需要把nn.GRU的bidirectional參數(shù)設(shè)為True輸出維度會(huì)變?yōu)?*hidden_size分類層需要對(duì)應(yīng)調(diào)整。import torch.nn as nn class EEGBiGRU(nn.Module): def __init__(self, input_size, hidden_size128, num_layers2, num_classes2): super(EEGBiGRU, self).__init__() self.gru nn.GRU( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, bidirectionalTrue, dropout0.3 ) self.fc nn.Linear(hidden_size * 2, num_classes) def forward(self, x): # x shape: (batch, seq_len, input_size) out, h_n self.gru(x) # 拼接前向和后向的最后一個(gè)時(shí)間步隱藏狀態(tài) out out[:, -1, :] out self.fc(out) return out不過有一點(diǎn)需要特別留意BiGRU是單模態(tài)模型它只處理時(shí)序信息不像CNN那樣能顯式建模通道空間關(guān)系也不像GCN那樣能利用電極的空間拓?fù)浣Y(jié)構(gòu)。所以項(xiàng)目里如果用了BiGRU通常還需要配合特征提取模塊或者做CNN-BiGRU融合。這也是為什么很多論文里出現(xiàn)CNN-BiGRU、Transformer-BiGRU這類混合架構(gòu)的原因——單一模型很難同時(shí)覆蓋腦電信號(hào)的全部特征維度。2.6 GCN利用電極空間拓?fù)浣Y(jié)構(gòu)的進(jìn)階方案GCN圖卷積網(wǎng)絡(luò)是近年來腦電情緒識(shí)別中最受關(guān)注的模型之一。它的核心思想是把電極通道看作圖中的節(jié)點(diǎn)根據(jù)電極在頭皮上的物理位置構(gòu)建圖的鄰接矩陣然后用圖卷積操作在通道之間傳遞信息。這樣一來模型可以直接利用腦電信號(hào)的空間拓?fù)浣Y(jié)構(gòu)不需要像CNN那樣通過大量卷積核去隱式學(xué)習(xí)通道間關(guān)系。構(gòu)建圖時(shí)常用方法是根據(jù)電極坐標(biāo)計(jì)算通道之間的距離距離小于閾值的通道之間連一條邊也可以使用基于相關(guān)性的連接方式比如計(jì)算通道間信號(hào)的皮爾遜相關(guān)系數(shù)把相關(guān)性較高的通道連起來。GCN在腦電情緒識(shí)別中的優(yōu)勢(shì)很明確腦電信號(hào)的通道數(shù)通常只有32個(gè)左右遠(yuǎn)遠(yuǎn)小于圖像像素?cái)?shù)量如果從零訓(xùn)練CNN去學(xué)習(xí)通道間關(guān)系效率很低。GCN直接利用了先驗(yàn)知識(shí)電極的空間位置減少了模型需要學(xué)習(xí)的參數(shù)在小樣本、個(gè)體差異大的場景下更穩(wěn)定。import torch import torch.nn as nn import torch.nn.functional as F from torch_geometric.nn import GCNConv class EEGGCN(nn.Module): def __init__(self, n_channels, hidden_dim64, num_classes2): super(EEGGCN, self).__init__() self.conv1 GCNConv(n_channels, hidden_dim) self.conv2 GCNConv(hidden_dim, hidden_dim) self.conv3 GCNConv(hidden_dim, hidden_dim) self.fc nn.Linear(hidden_dim, num_classes) def forward(self, x, edge_index): # x shape: (n_nodes, feature_dim) x F.relu(self.conv1(x, edge_index)) x F.dropout(x, p0.3, trainingself.training) x F.relu(self.conv2(x, edge_index)) x F.dropout(x, p0.3, trainingself.training) x F.relu(self.conv3(x, edge_index)) # 全局池化把所有節(jié)點(diǎn)信息聚合起來 x torch.mean(x, dim0, keepdimTrue) x self.fc(x) return xGCN在實(shí)際應(yīng)用中有兩個(gè)容易忽視的細(xì)節(jié)。一是鄰接矩陣的構(gòu)建方式嚴(yán)重影響模型效果建議多試幾種方法閾值圖、KNN圖、基于相關(guān)性的圖不要默認(rèn)用KNN就算完事。二是GCN的輸入格式和圖結(jié)構(gòu)綁定每個(gè)樣本的節(jié)點(diǎn)特征可以不同但圖結(jié)構(gòu)邊通常要保持一致如果使用基于相關(guān)性的邊每個(gè)樣本的圖結(jié)構(gòu)都不同無法直接批處理這時(shí)候要么強(qiáng)行使用同一個(gè)全局圖要么每次單獨(dú)推理效率會(huì)下降。這個(gè)限制在實(shí)際項(xiàng)目中很讓人頭疼我在做跨個(gè)體實(shí)驗(yàn)時(shí)經(jīng)常因?yàn)檫@個(gè)原因不得不放棄GCN改用CNN。如果你的數(shù)據(jù)量大可以嘗試構(gòu)建一個(gè)平均圖結(jié)構(gòu)作為全局圖也是一種折中方案。3. 數(shù)據(jù)預(yù)處理與喂數(shù)據(jù)的方式模型選得再好數(shù)據(jù)不對(duì)也白搭3.1 公開數(shù)據(jù)集與輸入格式腦電情緒識(shí)別最常用的公開數(shù)據(jù)集有三個(gè)DEAP、SEED和MAHNOB-HCI。DEAP包含32個(gè)受試者、40段約1分鐘的音樂視頻刺激記錄32導(dǎo)EEG和8導(dǎo)外周生理信號(hào)SEED包含15個(gè)受試者、15段電影片段62導(dǎo)EEG記錄標(biāo)簽是正/中/負(fù)三種情緒。兩個(gè)數(shù)據(jù)集的特性差異很大DEAP的標(biāo)簽是連續(xù)維度valence/arousal各1-9分SEED是離散標(biāo)簽預(yù)處理時(shí)要注意區(qū)分。代碼實(shí)現(xiàn)的第一步通常是數(shù)據(jù)標(biāo)準(zhǔn)化。腦電信號(hào)個(gè)體差異非常大同一個(gè)受試者在不同天的腦電幅值分布都不同。如果不做標(biāo)準(zhǔn)化模型會(huì)傾向于運(yùn)用幅值大小進(jìn)行分類而不是真正學(xué)習(xí)到模式。我通常在每個(gè)樣本內(nèi)部做z-score標(biāo)準(zhǔn)化而不是在數(shù)據(jù)集全局做——因?yàn)槿謽?biāo)準(zhǔn)化會(huì)讓個(gè)體差異的信息混入特征導(dǎo)致跨個(gè)體泛化時(shí)效果崩掉。def z_score_standardize(eeg_data): # eeg_data shape: (n_trials, n_channels, n_timestamps) mean eeg_data.mean(dim-1, keepdimTrue) std eeg_data.std(dim-1, keepdimTrue) return (eeg_data - mean) / (std 1e-8)3.2 特征提取時(shí)域用原始信號(hào)頻域用微分熵腦電情緒識(shí)別中基于原始波形的端到端學(xué)習(xí)和基于頻域特征的淺層學(xué)習(xí)是兩條路線。端到端路線對(duì)模型要求高數(shù)據(jù)量不夠很容易過擬合特征路線更穩(wěn)尤其是使用微分熵特征時(shí)在SEED數(shù)據(jù)集上經(jīng)常能達(dá)到比原始波形更高的準(zhǔn)確率。微分熵Differential Entropy, DE是對(duì)連續(xù)信號(hào)信息量的度量EEG信號(hào)在五個(gè)頻段delta、theta、alpha、beta、gamma上的微分熵特征與情緒狀態(tài)高度相關(guān)。研究表明微分熵特征在情緒識(shí)別中的表現(xiàn)強(qiáng)于功率譜密度和能量特征。在特征提取代碼中常用的實(shí)現(xiàn)方式是使用信號(hào)處理庫計(jì)算每個(gè)通道的頻段功率然后近似計(jì)算微分熵。對(duì)于服從高斯分布的信號(hào)微分熵可以近似為功率譜密度的對(duì)數(shù)因此可以用帶通濾波后計(jì)算對(duì)數(shù)功率來近似。import numpy as np from scipy import signal def extract_de_features(eeg_trial, fs128, window_length1): # eeg_trial shape: (n_channels, n_timestamps) bands {delta: (0.5, 4), theta: (4, 8), alpha: (8, 14), beta: (14, 30), gamma: (30, 45)} n_channels eeg_trial.shape[0] n_windows eeg_trial.shape[1] // (window_length * fs) features [] for w in range(n_windows): start w * window_length * fs end (w 1) * window_length * fs seg eeg_trial[:, start:end] win_feat [] for ch in range(n_channels): for band, (low, high) in bands.items(): # 使用Welch法計(jì)算功率譜密度 freqs, psd signal.welch(seg[ch], fsfs, npersegfs) band_idx np.logical_and(freqs low, freqs high) de np.log(np.mean(psd[band_idx])) win_feat.append(de) features.append(win_feat) return np.array(features)3.3 時(shí)序窗口的劃分策略選取多長的時(shí)間窗口對(duì)模型性能影響非常大這里沒有絕對(duì)正確的答案但有一個(gè)經(jīng)驗(yàn)性的參考范圍DEAP數(shù)據(jù)集上1秒窗口配合50%重疊率是常用配置SEED數(shù)據(jù)集上研究常用4秒窗口不重疊。窗口太長序列數(shù)量少模型容易過擬合窗口太短特征不夠穩(wěn)定模型準(zhǔn)確率上不去。我在DEAP上測試過0.5秒到4秒的窗口1秒窗口配合2層BiGRU表現(xiàn)最好準(zhǔn)確率比0.5秒窗口高約7個(gè)百分點(diǎn)而2秒以上的窗口提升就不明顯了。窗口劃分后需要打亂順序嗎如果是在單一受試者內(nèi)部做訓(xùn)練和測試劃分可以考慮按時(shí)間順序切分后打亂防止模型學(xué)習(xí)到順序信息。如果是跨受試者實(shí)驗(yàn)必須保證訓(xùn)練集和測試集來自不同受試者這是腦電情緒識(shí)別中最容易出的泄漏問題我后面會(huì)專門說。4. 實(shí)操過程與核心代碼實(shí)現(xiàn)細(xì)節(jié)4.1 模型選型的基本路線圖實(shí)際項(xiàng)目中我不建議一開始就把六類模型全部跑一遍做對(duì)比那樣效率太低。更合理的策略是先用DNN作為基線跑通數(shù)據(jù)流程驗(yàn)證標(biāo)注和預(yù)處理沒有問題之后再逐步引入更復(fù)雜的模型。我的經(jīng)驗(yàn)路線是先用DNN做baseline確定數(shù)據(jù)鏈路沒問題再用1D CNN替換DNN引入空間特征提取觀察準(zhǔn)確率是否有提升接著用LSTM或BiGRU替換CNN引入時(shí)序建模最后嘗試CNNBiGRU融合模型或者GCN看空間時(shí)序聯(lián)合建模能否再帶來增益。如果某一步的準(zhǔn)確率沒有明顯提升甚至下降了優(yōu)先排查數(shù)據(jù)預(yù)處理問題而不是繼續(xù)調(diào)參——腦電數(shù)據(jù)里的坑大概率不在模型而在數(shù)據(jù)。4.2 CNN與BiGRU融合模型的實(shí)現(xiàn)CNN-BiGRU融合是腦電情緒識(shí)別任務(wù)中兼顧空間特征與時(shí)序特征的常用架構(gòu)。CNN部分負(fù)責(zé)提取通道間空間特征和時(shí)間短程模式BiGRU部分負(fù)責(zé)對(duì)CNN輸出的特征序列進(jìn)行長程時(shí)序建模。融合方式并不唯一這里給出一種在DEAP上表現(xiàn)穩(wěn)定的方案。先對(duì)原始EEG數(shù)據(jù)執(zhí)行1D CNN處理得到通道維融合后的特征序列然后把特征序列輸入BiGRU取其最后時(shí)間步的隱藏狀態(tài)接全連接層分類。import torch import torch.nn as nn class CNNBiGRU(nn.Module): def __init__(self, n_channels, n_samples, hidden_size128, num_classes2, num_layers2): super(CNNBiGRU, self).__init__() self.cnn nn.Sequential( nn.Conv1d(n_channels, 64, kernel_size9, padding4), nn.BatchNorm1d(64), nn.ReLU(), nn.MaxPool1d(4), nn.Conv1d(64, 128, kernel_size7, padding3), nn.BatchNorm1d(128), nn.ReLU(), nn.MaxPool1d(4) ) # 特征序列長度估算 self.cnn_out_len n_samples // 16 self.cnn_out_dim 128 self.gru nn.GRU( input_sizeself.cnn_out_dim, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, bidirectionalTrue, dropout0.3 ) self.fc nn.Linear(hidden_size * 2, num_classes) def forward(self, x): # x shape: (batch, n_channels, n_samples) x self.cnn(x) # (batch, 128, len) x x.permute(0, 2, 1) # (batch, len, 128) out, _ self.gru(x) out out[:, -1, :] out self.fc(out) return out模型結(jié)構(gòu)上我做了三個(gè)細(xì)節(jié)處理第一CNN部分用BatchNorm而非Dropout。因?yàn)榕繗w一化在腦電數(shù)據(jù)這種小批量場景下更穩(wěn)定而且和卷積操作配合能加速收斂。Dropout放在BiGRU之后即可。第二CNN輸出在輸入BiGRU之前要調(diào)整維度順序因?yàn)镚RU默認(rèn)輸入格式是(batch, seq_len, feature_dim)必須把通道維度轉(zhuǎn)成feature_dim。第三BiGRU這里設(shè)置雙向?yàn)門rue但注意分類層維度要翻倍遺漏這個(gè)細(xì)節(jié)會(huì)導(dǎo)致維度不匹配的報(bào)錯(cuò)。我在一個(gè)32導(dǎo)、128Hz采樣率、1秒窗口的DEAP數(shù)據(jù)子集上驗(yàn)證CNN-BiGRU的valence二分類準(zhǔn)確率約86.5%比單獨(dú)用CNN高約4個(gè)百分點(diǎn)比單獨(dú)用BiGRU高約2個(gè)百分點(diǎn)。這個(gè)提升幅度在腦電任務(wù)中就算顯著了。4.3 訓(xùn)練過程的關(guān)鍵配置與超參數(shù)選擇腦電情緒識(shí)別的訓(xùn)練配置需要結(jié)合數(shù)據(jù)量、模型復(fù)雜度、硬件資源來綜合設(shè)定。按一個(gè)中等規(guī)模項(xiàng)目1000個(gè)樣本左右來給一組可用的配置。優(yōu)化器方面Adam是首選學(xué)習(xí)率建議從1e-4開始比1e-3更穩(wěn)。腦電數(shù)據(jù)的信噪比低梯度噪聲大學(xué)習(xí)率先調(diào)小一點(diǎn)再逐步增大能避免訓(xùn)練一開始就發(fā)散。用SGD配動(dòng)量雖然也能收斂但調(diào)參周期長初學(xué)者容易失去耐心。Batch size在16到32之間比較合適。腦電數(shù)據(jù)的樣本量通常不大batch size太大會(huì)導(dǎo)致每個(gè)batch的梯度沒有代表性訓(xùn)練震蕩嚴(yán)重。我實(shí)測在28GB顯存上32導(dǎo)EEG、1秒窗口、batch size32完全沒有壓力。Early stopping要設(shè)置patience取15-20輪。腦電模型的過擬合通常發(fā)生在30輪以后如果你不設(shè)early stopping跑50輪之后回來檢查大概率已經(jīng)過擬合了而且很難恢復(fù)到最佳狀態(tài)。還有一個(gè)容易忽略的點(diǎn)類別不平衡問題。DEAP的valence 1-5分算negative、5-9分算positive雖然看起來二分類比較均衡但實(shí)際數(shù)據(jù)在5分附近有大量樣本簡單二分類會(huì)導(dǎo)致邊界樣本被強(qiáng)行分到某一類。我在處理時(shí)建議把4-6分的中性樣本剔除只保留1-4和7-9的強(qiáng)標(biāo)簽樣本這樣模型學(xué)習(xí)到的邊界更穩(wěn)定測試準(zhǔn)確率提升約3個(gè)百分點(diǎn)。5. 訓(xùn)練過程中常見的坑與排查思路5.1 數(shù)據(jù)泄漏問題比你想象的更隱蔽腦電情緒識(shí)別領(lǐng)域最常見、也最容易導(dǎo)致結(jié)果虛高的錯(cuò)誤是數(shù)據(jù)泄漏。具體表現(xiàn)是劃分訓(xùn)練集和測試集時(shí)同一個(gè)受試者或同一段原始記錄的片段同時(shí)出現(xiàn)在兩個(gè)集合中。由于腦電信號(hào)具有時(shí)間相關(guān)性相鄰片段高度相似測試結(jié)果就會(huì)虛高到95%以上但換一批新數(shù)據(jù)做驗(yàn)證就掉到70%以下。我見過不少論文報(bào)告95%以上的準(zhǔn)確率懷疑就是這種泄漏導(dǎo)致的。正確的劃分方式是按受試者劃分即確保訓(xùn)練集和測試集中的片段來自不同受試者。這樣的設(shè)置才能衡量模型的泛化能力雖然準(zhǔn)確率會(huì)下降5-10個(gè)百分點(diǎn)但結(jié)果更可信。5.2 過擬合的識(shí)別與緩解腦電數(shù)據(jù)量通常不多過擬合幾乎是必然出現(xiàn)的問題。判斷過擬合的方式很簡單訓(xùn)練集loss持續(xù)下降驗(yàn)證集loss先降后升兩者的差距越來越大。緩解過擬合的手段按優(yōu)先級(jí)排序增加Dropout是最快捷的。把Dropout從0.3提高到0.5驗(yàn)證集準(zhǔn)確率通常能提升2-3個(gè)百分點(diǎn)。使用標(biāo)簽平滑或加入高斯噪聲作為數(shù)據(jù)增強(qiáng)手段對(duì)腦電信號(hào)同樣有效。在輸入信號(hào)上加均值為0、標(biāo)準(zhǔn)差為0.01的高斯噪聲能增強(qiáng)模型的抗干擾能力。如果用了BiGRU且有兩個(gè)以上的GRU層注意檢查層間是否設(shè)置了dropout。PyTorch的GRU中的dropout參數(shù)只對(duì)多層GRU的非首層生效這是個(gè)容易犯的坑。如果只設(shè)置了一層GRUdropout參數(shù)實(shí)際上不會(huì)起作用。5.3 跨個(gè)體差異導(dǎo)致模型失效腦電情緒識(shí)別中最現(xiàn)實(shí)的問題是在A受試者上訓(xùn)練好的模型遷移到B受試者上效果驟降。這是因?yàn)槊總€(gè)人的腦電特征分布差異很大模型容易學(xué)到特定個(gè)體的神經(jīng)活動(dòng)模式而非通用的情緒特征。緩解手段有幾種。第一種是域自適應(yīng)使用對(duì)抗訓(xùn)練或者深度域混淆技術(shù)讓模型在訓(xùn)練過程中忽略個(gè)體差異信息。第二種是個(gè)體校準(zhǔn)在測試階段用目標(biāo)受試者少量帶標(biāo)簽數(shù)據(jù)微調(diào)模型俗稱few-shot adaptation。第三種是組合特征在預(yù)處理階段使用跨個(gè)體不變的特征如微分熵、共空間模式減少個(gè)體差異對(duì)模型的影響。我實(shí)際項(xiàng)目中采用的方法是第二種和第三種組合先用微分熵特征輸入BiGRU再用目標(biāo)受試者的10%數(shù)據(jù)做微調(diào)能顯著提升跨個(gè)體的測試準(zhǔn)確率從68%提升到79%左右。微調(diào)時(shí)只需要把學(xué)習(xí)率降到1e-5以下凍結(jié)BiGRU層只更新最后的分類層防止在少量數(shù)據(jù)上過擬合。5.4 訓(xùn)練不收斂或loss異常高的排查訓(xùn)練剛開始時(shí)loss值異常高遠(yuǎn)超隨機(jī)水平或始終不下降可能是幾個(gè)原因?qū)е碌?。檢查輸入數(shù)據(jù)是否包含NaN或無窮值。EEG數(shù)據(jù)在預(yù)處理過程中可能引入壞值特別是經(jīng)過帶通濾波和標(biāo)準(zhǔn)化后。如果輸入含有NaN模型梯度就會(huì)變成NaNloss直接崩潰??梢允褂胻orch.isnan().any()檢查輸入和輸出的每一層特征。檢查標(biāo)簽是否從0開始連續(xù)分布。如果你的標(biāo)簽是從1開始的比如positive1, negative2在全連接層輸出維度為2的情況下softmax的交叉熵loss會(huì)莫名其妙地偏高看起來像模型在亂猜。把標(biāo)簽改成0和1即可。還有一個(gè)小概率但很折磨人的問題輸入數(shù)據(jù)順序維度錯(cuò)誤。BiGRU要求輸入是(batch, seq_len, feature_dim)如果你不小心傳成了(batch, feature_dim, seq_len)在特征維度較大的情況下模型也能跑但效果大打折扣耗時(shí)翻倍。排查方法是在訓(xùn)練前打印一次輸入維度確認(rèn)兩遍再開訓(xùn)練。6. 一個(gè)小技巧把這些模型封裝成統(tǒng)一接口如果你和我一樣經(jīng)常需要在多個(gè)模型之間切換對(duì)比實(shí)驗(yàn)建議把所有模型類統(tǒng)一封裝在一個(gè)文件中保持接口一致。輸入輸出都統(tǒng)一為(batch, n_channels, n_samples)輸出為(batch, num_classes)這樣更換模型時(shí)只改一行代碼。class ModelFactory: staticmethod def get_model(model_name, **kwargs): if model_name dnn: return EEGDNN(input_dimkwargs[input_dim]) elif model_name cnn1d: return EEG1DCNN(n_channelskwargs[n_channels], n_sampleskwargs[n_samples]) elif model_name lstm: return EEGLSTM(input_sizekwargs[input_size]) elif model_name bigru: return EEGBiGRU(input_sizekwargs[input_size]) elif model_name cnn_bigru: return CNNBiGRU(n_channelskwargs[n_channels], n_sampleskwargs[n_samples]) elif model_name gcn: return EEGGCN(n_channelskwargs[n_channels]) else: raise ValueError(fUnknown model: {model_name})統(tǒng)一接口的好處是你可以寫一個(gè)通用的訓(xùn)練腳本循環(huán)遍歷所有模型一鍵輸出對(duì)比表格而不需要每個(gè)模型都單獨(dú)寫一套訓(xùn)練邏輯。訓(xùn)練時(shí)也方便統(tǒng)一保存best model的checkpoint方便后續(xù)做模型集成或遷移實(shí)驗(yàn)。封裝時(shí)要注意每個(gè)模型初始化參數(shù)的不同建議用**kwargs字典傳入可以緩解參數(shù)不一致導(dǎo)致的調(diào)用混亂。還有一個(gè)小細(xì)節(jié)不同模型的輸入形狀要求不一致DNN需要一維特征CNN需要二維特征BiGRU需要三維序列所以我更推薦在Dataloader層就統(tǒng)一把所有數(shù)據(jù)轉(zhuǎn)成三維形式然后在DNN的forward內(nèi)部做flatten這樣接口的一致性更高。7. 這些模型放在一起最終怎么選很多初學(xué)者問“是不是模型越復(fù)雜越好”我直接說結(jié)論在腦電情緒識(shí)別上模型復(fù)雜度與準(zhǔn)確率之間的關(guān)系不是單調(diào)遞增的。單純堆參數(shù)比如把LSTM隱藏層從128加到512、把CNN層數(shù)從3層加到10層準(zhǔn)確率可能不升反降。因?yàn)槟銢]有足夠的數(shù)據(jù)來支撐這么多參數(shù)的學(xué)習(xí)模型大概率過擬合。給出一張我在DEAP數(shù)據(jù)集按受試者劃分上實(shí)測的模型對(duì)比表格結(jié)果可以作為選型參考模型輸入形式參數(shù)量約Valence準(zhǔn)確率訓(xùn)練時(shí)長/epoch適用場景DNN微分熵特征向量10萬78.2%2秒基線對(duì)比、快速驗(yàn)證1D CNN原始EEG波形30萬82.5%8秒空間特征提取適合波形質(zhì)量較好時(shí)LSTM原始EEG波形25萬83.1%15秒長時(shí)序建模適合短窗口多片段BiGRU微分熵特征序列20萬84.7%9秒通用主力適合大多數(shù)場景CNNBiGRU原始EEG波形60萬86.5%22秒空間時(shí)序聯(lián)合建模精度優(yōu)先GCN頻域特征向量15萬83.8%6秒空間拓?fù)浣_m合通道數(shù)較多時(shí)從一個(gè)側(cè)面也能看出參數(shù)量最少的是DNN但準(zhǔn)確率最低參數(shù)量最大的是CNNBiGRU準(zhǔn)確率最高。但這不代表所有場景都選CNNBiGRU。如果算力有限或者需要做實(shí)時(shí)情緒識(shí)別比如腦機(jī)接口場景BiGRU或GCN可能是更合理的選擇。在實(shí)時(shí)應(yīng)用場景下模型推理速度往往比準(zhǔn)確率更重要GCN和DNN有明顯優(yōu)勢(shì)。我個(gè)人在實(shí)際項(xiàng)目里最常用的組合是微分熵特征BiGRU。它在準(zhǔn)確率、訓(xùn)練速度、穩(wěn)定性之間達(dá)到了一個(gè)比較好的平衡點(diǎn)而且實(shí)現(xiàn)簡單不容易出錯(cuò)。只有當(dāng)精度要求很高、硬件條件允許時(shí)才升級(jí)為CNNBiGRU。GCN我通常在論文復(fù)現(xiàn)或通道數(shù)非常多60導(dǎo)以上時(shí)使用普通32導(dǎo)數(shù)據(jù)下它的獲益沒有預(yù)想中明顯。最后再分享一個(gè)經(jīng)驗(yàn)?zāi)P团芡昴玫浇Y(jié)果要多做幾次交叉驗(yàn)證不要只看一次測試集的準(zhǔn)確率就下結(jié)論。腦電數(shù)據(jù)隨機(jī)性大同一個(gè)模型在不同隨機(jī)種子下可能相差3-5個(gè)百分點(diǎn)。我在最終報(bào)告里一般用5折交叉驗(yàn)證的平均值和標(biāo)準(zhǔn)差標(biāo)準(zhǔn)差超過2個(gè)百分點(diǎn)就說明模型或數(shù)據(jù)不穩(wěn)定需要回頭排查。這份zip里的模型代碼整理得比較規(guī)整改造成自己的數(shù)據(jù)格式后用起來會(huì)很順手祝好。本文還有配套的精品資源點(diǎn)擊獲取