资讯动态

EEG情绪检测实战:DWT特征提取与SVM通道融合

发布时间:2026/10/27 19:07:00 来源:尧图企业网站定制
简介该资源是一篇面向情感计算与脑机接口方向学习者的研究论文聚焦基于EEG信号的情绪检测适合从事毕业设计、课题研究或希望了解公开数据集实验流程的高校学生与初级研究者参考。论文以DEAP和SEED-IV两个公开数据库为数据来源采用离散小波变换将预处理信号分解为5个频段提取功率、能量、微分熵及时间域等特征并构建通道级SVM分类器与通道组合器完成二维情绪模型下的状态识别四类分类准确率在DEAP上为74%、86%、72%、84%在SEED-IV上为79%、76%、77%、74%。资源包共1个PDF文件大小约701KB完整呈现论文的摘要、方法、实验设置与结果讨论便于读者理解特征提取、分类器设计与评估指标的具体做法。目前已有346人学习可作为情绪识别课题的方案参考与实验对照材料。1. 从一份 2019 年的论文说起EEG 情绪检测到底能跑出什么去年有个做毕业设计的同学找我说他手里有一篇论文和配套的 DEAP、SEED-IV 数据处理代码跑出来的四分类准确率跟论文里写的 74%、86%、72%、84% 对不上问我是不是代码有问题。我打开一看问题不在代码在于他把 DEAP 的 32 通道数据和 SEED-IV 的 64 通道数据混在一起喂给了同一个分类器。这个项目本质上就是一篇可复现的研究论文工程用离散小波变换把 EEG 信号拆成五个频段提取功率、能量、微分熵和时域统计特征再给每个通道单独训练一个 SVM最后用通道融合策略投票出四类情绪状态高唤醒高效价、高唤醒低效价、低唤醒低效价、低唤醒高效价。它适合谁适合做情感计算方向毕业设计的学生、想拿公开数据集练手 BCI 特征工程的开发者以及需要一套完整「预处理→特征→分类→评估」流水线做基线对比的研究者。数据库和 SVM 这两个词贯穿始终下面我按实际拆代码的顺序讲。2. 项目架构与数据流水线从原始 EEG 到 34 维特征向量2.1 整体模块划分与目录组织这个项目的代码结构不算复杂但模块边界很清楚。我拿到手的版本大致是这样组织的eeg_emotion/ ├── data/ │ ├── deap/ # DEAP 原始 .mat 文件32 被试 × 40 trial │ └── seed_iv/ # SEED-IV 降采样后的 .mat45 被试 × 24 event ├── preprocessing/ │ ├── notch_filter.py # 50Hz 工频陷波 │ ├── re_reference.py # 平均参考重参考 │ ├── median_filter.py # 一维 10 阶中值滤波 │ └── bandpass.py # 0.1–60Hz 20 阶带通 ├── features/ │ ├── dwt_decompose.py # db8 八层小波分解 │ ├── time_domain.py # 均值、RMS、偏度、峰度、Hjorth 参数 │ ├── frequency_domain.py # 频带能量、PSD、微分熵、平均带功率 │ └── other_features.py # Hurst 指数、排列熵 ├── classifier/ │ ├── channel_svm.py # 单通道 SVM 训练与预测 │ └── channel_fusion.py # 32 通道结果融合 ├── evaluation/ │ └── metrics.py # 混淆矩阵、四类准确率 └── main.py # 串联全流程这个划分方式的好处是每个环节可以单独替换。比如你想把 SVM 换成随机森林只需要动classifier/目录前面的预处理和特征提取完全不用碰。我一般建议二次开发时保持这个边界别把特征提取和分类逻辑揉在一个文件里后面调参时会非常痛苦。2.2 预处理链四道滤波为什么缺一不可论文里写得很清楚原始信号要过四道处理50Hz 陷波、平均参考、中值滤波、带通滤波。很多人图省事只做带通结果分类准确率死活上不去还以为是特征提取的问题。实际上工频干扰和脉冲噪声会严重污染频带能量特征。import numpy as np from scipy.signal import iirnotch, filtfilt, medfilt, butter def preprocess_eeg(raw_signal, fs500): # 第一道50Hz 工频陷波Q 值取 30 兼顾带宽和衰减深度 b_notch, a_notch iirnotch(50.0, 30.0, fs) signal filtfilt(b_notch, a_notch, raw_signal, axis-1) # 第二道平均参考减去所有通道的均值消除共模成分 signal signal - np.mean(signal, axis0, keepdimsTrue) # 第三道一维 10 阶中值滤波核长取 10 个采样点 # 注意 medfilt 的 kernel_size 必须是奇数这里取 11 signal medfilt(signal, kernel_size11) # 第四道0.1–60Hz 20 阶带通保留 EEG 有效频段 b_bp, a_bp butter(20, [0.1, 60.0], btypeband, fsfs) signal filtfilt(b_bp, a_bp, signal, axis-1) return signal逐段说逻辑。陷波器用iirnotch而不是自己设计 FIR是因为 IIR 在 50Hz 处的衰减更陡计算量也小。Q 值 30 是我试出来的经验值太低会把 49–51Hz 附近的脑电成分一起削掉太高则对工频漂移不敏感。平均参考这一步在论文里叫 moving average re-referencing本质是把每个采样点的所有电极均值减掉让信号围绕零均值波动。中值滤波的核长 11 对应 500Hz 采样率下约 22ms 的窗口刚好能压掉眨眼和肌肉伪迹的尖峰又不会把高频脑电抹平。最后带通的 20 阶是论文明确写的阶数低了过渡带太宽60Hz 以上还会漏进来。提示SEED-IV 原始采样率是 1000Hz如果你直接用 500Hz 的参数去滤带通截止频率要相应调整否则 60Hz 以上的成分会被错误保留。2.3 DWT 分解与 34 维特征向量构造预处理完的信号进dwt_decompose.py用 db8 小波做八层分解取前五层对应的 delta1–3Hz、theta4–7Hz、alpha8–13Hz、beta14–30Hz、gamma31–60Hz五个频带。这里有个细节八层分解在 500Hz 采样率下第五层细节系数的频率范围刚好覆盖 31–60Hz再往下就进入 60Hz 以上了所以只取五层。import pywt import numpy as np def extract_band_features(signal, waveletdb8, level8): # 八层分解coeffs[0] 是近似系数coeffs[1:] 是各层细节系数 coeffs pywt.wavedec(signal, wavelet, levellevel) bands {} # 细节系数从高频到低频排列取前五层对应 gamma 到 delta band_names [gamma, beta, alpha, theta, delta] for i, name in enumerate(band_names): cD coeffs[i 1] # 频带能量系数平方和 energy np.sum(cD ** 2) # 平均带功率能量除以系数长度 avg_power energy / len(cD) # 微分熵高斯假设下与 log(方差) 成正比 diff_entropy 0.5 * np.log(2 * np.pi * np.e * np.var(cD)) bands[name] { energy: energy, avg_power: avg_power, diff_entropy: diff_entropy } return bands五个频带各三个频域特征加上时域的均值、RMS、标准差、一阶差分、归一化一阶差分、二阶差分、归一化二阶差分、峰度、偏度、方差、Hjorth mobility、Hjorth complexity 共 12 个再加 Hurst 指数和排列熵 2 个总共 5×312229 个。论文里说 34 维差额来自 PSD 的五个频带各一个值补上正好 34。每个通道独立算这 34 维32 通道就是 32 组特征向量分别喂给 32 个 SVM。3. 通道级 SVM 训练与融合32 个分类器怎么投票3.1 单通道 SVM 的训练配置论文用的是 scikit-learn 的 SVC核函数选 RBF因为 EEG 特征和情绪标签之间明显是非线性关系。正则化参数 C 和 gamma 没有在论文里给具体值我按经验用网格搜索在训练集上交叉验证选DEAP 上 C 落在 10 左右、gamma 在 0.01 附近比较稳。from sklearn.svm import SVC from sklearn.preprocessing import StandardScaler from sklearn.pipeline import Pipeline def build_channel_classifier(C10.0, gamma0.01): # 标准化必须放在 SVM 前面否则不同量纲的特征会主导核函数 return Pipeline([ (scaler, StandardScaler()), (svm, SVC(kernelrbf, CC, gammagamma, decision_function_shapeovr)) ]) def train_all_channels(X_train, y_train, n_channels32): # X_train 形状(n_samples, n_channels, 34) models [] for ch in range(n_channels): clf build_channel_classifier() clf.fit(X_train[:, ch, :], y_train) models.append(clf) return modelsdecision_function_shapeovr是关键参数。四分类问题里一对多策略会给每个类别训练一个二分类器预测时取决策值最大的类。标准化放在 Pipeline 里而不是提前做是为了保证测试集用的是训练集的均值和方差避免数据泄漏。这个坑我见过不止一次有人先把全量数据标准化再切分准确率虚高好几个点。3.2 通道融合策略与四类标签映射32 个通道各自输出一个预测标签融合层要做的是把这些标签合成一个最终结果。论文里叫 channel combiner实现上用的是多数投票加置信度加权每个通道的 SVM 输出四类的决策值先做 softmax 归一化成概率再按通道求和取最大。import numpy as np def channel_fusion(models, X_test): # X_test 形状(n_samples, n_channels, 34) n_samples, n_channels, _ X_test.shape prob_sum np.zeros((n_samples, 4)) for ch in range(n_channels): # decision_function 返回 (n_samples, 4) 的决策值 decision models[ch].decision_function(X_test[:, ch, :]) # 手动 softmax 转概率温度系数 1.0 exp_d np.exp(decision - decision.max(axis1, keepdimsTrue)) prob exp_d / exp_d.sum(axis1, keepdimsTrue) prob_sum prob # 四类标签顺序0HAHV, 1HALV, 2LALV, 3LAHV return np.argmax(prob_sum, axis1)标签映射必须跟论文对齐class1 是高唤醒高效价HAHVclass2 是高唤醒低效价HALVclass3 是低唤醒低效价LALVclass4 是低唤醒高效价LAHV。DEAP 的标签来自 valence 和 arousal 的 1–9 自评分数以 5 为界二分SEED-IV 是四类离散情绪快乐、悲伤、恐惧、中性需要按论文的映射表转成二维模型下的四象限。这个映射如果搞反准确率会直接掉到随机水平附近而且从混淆矩阵上不一定看得出来。3.3 训练集与测试集的划分方式论文的划分方式跟常规的随机切分不一样是按被试划分的。DEAP 用 22 个被试训练、10 个被试测试SEED-IV 用 32 个被试训练、13 个被试测试。这种划分更接近真实场景——模型要泛化到没见过的用户而不是在同一个人的数据上过拟合。def split_by_subject(features, labels, subject_ids, train_subjects): train_mask np.isin(subject_ids, train_subjects) test_mask ~train_mask return (features[train_mask], labels[train_mask], features[test_mask], labels[test_mask])DEAP 每个被试 40 个 trial22 个被试就是 880 个样本每个通道的特征矩阵是 880×34跟论文里写的一致。SEED-IV 降采样后每个被试 24 个 event32 个被试是 768论文写 742差额应该是剔除了部分坏段。按被试划分的代价是训练集和测试集分布差异大准确率会比随机切分低几个点但那个数字才是有意义的。4. 避坑与排查跑这个项目最容易翻车的五个地方4.1 准确率远低于论文数值现象DEAP 上四类准确率只有 30% 出头接近随机猜测。原因通常是标签映射错了或者特征矩阵的通道维度和标签维度没对齐。DEAP 的labels字段里 valence 和 arousal 是 1–9 的连续值有人直接拿原始值当类别标签喂给 SVM四分类自然崩。解决先打印np.unique(y_train)确认只有 0、1、2、3 四个值再检查X_train.shape的第二维是否等于通道数。4.2 中值滤波把信号滤没了现象预处理后信号幅度接近零画出来是一条平线。原因medfilt的kernel_size传了偶数scipy 会静默截断成奇数但行为不可预期或者对已经归一化到 [-1,1] 的信号用了过大的核长。解决核长固定取奇数500Hz 下用 111000Hz 下用 21并且滤波前先确认信号量纲。4.3 SEED-IV 采样率没降导致频带错位现象SEED-IV 上 gamma 频带能量异常高分类结果偏向某一类。原因SEED-IV 原始 1000Hz如果直接套用 500Hz 的 DWT 层数第五层细节系数对应的频率范围变成 62–125Hz完全偏离 gamma 的定义。解决先做 2 倍降采样到 500Hz或者把 DWT 层数加到 9 层重新对齐频带边界。4.4 通道数不一致导致融合失败现象DEAP 跑通后换 SEED-IV 报维度错误。原因DEAP 是 32 通道SEED-IV 是 64 通道论文里说「same channels were considered」指的是选了相同的 32 个电极位置不是直接用全部 64 通道。解决在data/加载阶段就按电极名称做列选择把 SEED-IV 裁到跟 DEAP 一致的 32 通道再进后续流程。4.5 训练时间过长且内存爆掉现象32 个通道串行训练每个通道网格搜索跑一晚上还没完。原因GridSearchCV默认 5 折交叉验证32 通道 × 参数组合数 × 5 折计算量是单通道的 160 倍以上。解决先用单个通道调出最优参数再把这组参数应用到所有通道或者用n_jobs-1并行但要注意内存880×34 的矩阵不大瓶颈在 SVM 的核矩阵缓存。5. 二次开发方向从论文复现到可用的情绪识别基线5.1 替换分类器做对比实验这套流水线最大的价值是特征工程部分已经封装好了换分类器只需要改classifier/下的一个文件。我一般会保留 SVM 作为基线再加一个随机森林和一个简单的 MLP 做对比。随机森林对特征缩放不敏感可以跳过标准化MLP 则需要把 34 维特征拼成 32×341088 维的扁平向量或者用 1D 卷积在通道维上做聚合。下面是一个最小改动示例from sklearn.ensemble import RandomForestClassifier def build_rf_classifier(n_estimators200, max_depth12): # 随机森林不需要标准化直接喂原始特征 return RandomForestClassifier( n_estimatorsn_estimators, max_depthmax_depth, class_weightbalanced, # 四类样本不均衡时有用 n_jobs-1, random_state42 )把train_all_channels里的build_channel_classifier()换成build_rf_classifier()就能跑融合层不用动因为predict_proba和decision_function在融合函数里可以统一处理。我实测过DEAP 上随机森林的四类平均准确率比 SVM 低 2–3 个点但训练速度快一个数量级适合快速迭代特征。5.2 特征筛选与通道选择34 维特征里Hurst 指数和排列熵的计算开销最大但对分类的贡献不一定高。我建议先做一轮特征重要性分析把贡献低于阈值的特征剔掉再重新训练。通道选择同理论文里 32 通道全用但文献综述里提到通道数从 10 增加到 32 时准确率提升明显说明不是所有通道都同等重要。可以用递归特征消除RFE在训练集上筛一遍把 32 通道压到 16 通道左右准确率损失通常在 1 个点以内推理速度翻倍。5.3 跨数据集泛化的验证方法论文分别在 DEAP 和 SEED-IV 上训练和测试没有做跨数据集迁移。如果你想往这个方向走最直接的做法是固定特征提取流程在一个数据集上训练、另一个数据集上测试看准确率衰减多少。这里有个血泪经验两个数据集的电极位置、参考方式、被试数量都不同直接迁移大概率崩需要先做通道对齐和分布对齐比如用 CORAL 做二阶统计量匹配。我一般会先跑一个「同数据集按被试划分」的基线再跑「跨数据集」的对比两个数字放在一起才能说明模型的泛化能力。从那以后我每次拿到这类论文复现项目都强制先跑一遍单通道、单被试的小规模验证确认标签映射和特征维度没问题再上全量数据。这个习惯帮我省掉了至少三次通宵排查的时间。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑