首页 / 资讯中心 / 文章详情

基于EEG的情绪分类:SVM在DEAP与SEED-IV上的可行路线

基于EEG的情绪分类:SVM在DEAP与SEED-IV上的可行路线 ★ FEATURED ARTICLE
简介基于 DEAP 与 SEED-IV 数据库的 EEG 情绪检测研究论文聚焦二维情绪模型下的内心情绪识别适合脑机接口、情感计算方向的在校生及科研人员参考。文中采用离散小波变换提取五个频段从功率、能量、微分熵和时域等维度构造特征并设计通道级 SVM 分类器配合通道组合器完成情绪状态判定在 DEAP 上四分类准确率达到 74%、86%、72%、84%在 SEED-IV 上为 79%、76%、77%、74%实验设置与结果数据完整便于对比复现。资源包共 1 个 PDF 文件大小约 701KB为期刊全文排版可离线阅读、标注也适合作为毕业设计算法选型和论文写作的参考底稿。该资源已有 346 人学习下载对需要快速梳理基于公开脑电数据集做情绪分类任务、掌握小波特征与 SVM 建模流程的读者尤为实用。1. 基于 EEG 的情绪分类从 DEAP、SEED-IV 到 SVM 的最短可行路线情绪检测不是新鲜事但当你真正把 DEAP 或 SEED-IV 里的原始脑电读进内存遇到的第一组词会是小样本、高噪声、标签主观。很多团队第一反应是直接上深度学习然后被几十个受试者的样本量和动辄几小时的训练卡住相比之下用 SVM 在几十维到几百维特征上先跑通一个可解释的判别器是复现论文基准、快速验证特征设计最稳妥的路。本文按“数据 → 预处理 → 特征 → 调参与避坑”的顺序给出一条能从头跑完的情绪检测最小路径。DEAP 和 SEED-IV 是情绪识别里被引用最多的两个公开脑电数据库。DEAP 提供 32 名受试者、32 通道 EEG标签是效价、唤醒、支配、喜好四类评分SEED-IV 则是 62 通道的四分类情绪数据每个受试者分成多个 session。两者的通道数、基线设计、试次个数完全不同把代码从一套搬到另一套时最容易出问题。文章适合三类人准备复现情绪分类论文的、用公开数据做课程设计或毕业设计的、以及想快速判断 SVM 在脑电任务上值不值得投入的工程师。看完后你能直接搭起一套不泄漏、可复现、结果可信的 SVM 分类流程也知道哪些参数值得调、哪些地方是坑。2. 先读懂两个公开数据集DEAP 和 SEED-IV 的结构差异与预处理两个数据集虽然都叫“情绪脑电”但内部组织方式差异很大。DEAP 以试次为单位、每个受试者一个文件标签是连续评分SEED-IV 以 session 为单位、情绪是离散类别。先用代码把结构摸清楚再谈特征和模型。2.1 DEAP 的数据组织32 通道、40 试次与效价唤醒标签DEAP 的原始包里每个受试者对应一个.mat文件内部通常是data和labels两个数组。data的形状一般是(40, 40, 8064)40 个试次、40 个通道、8064 个采样点。8064 这个数字来自 3 秒静息基线加上 60 秒情绪诱发视频采样率 128Hz计算方式是63 × 128 8064。40 个通道里前 32 个是 EEG 通道后面几个是眼电、肌电等外围信号特征提取前要把它们分开。import scipy.io as sio # 按常见文件命名方式读取受试者1不同来源命名可能不同 raw sio.loadmat(deap/s01.mat) data raw[data] labels raw[labels] # data: (40, 40, 8064)labels: (40, 4) n_trials, n_channels, n_points data.shape print(trials:, n_trials, channels:, n_channels, points:, n_points) print(label columns: valence, arousal, dominance, liking) eeg data[:, :32, :] # 只保留前32个EEG通道 peripheral data[:, 32:, :]很多版本的 DEAP 文件中data和labels的键名略有差别可能是data、labels也可能是嵌套结构。load 之后先打印.keys()和 shape比直接瞎索引省时间。标签列顺序一般是效价、唤醒、支配、喜好做二分类时最常用的是效价和唤醒按阈值 5 把 1 到 9 的评分切成高低两类。这种“每受试者一个数组”的组织方式决定了后面交叉验证时要特别小心同一个人的 40 个试次不能同时散落在训练集和测试集里否则模型学到的很可能不是情绪而是这个人特有的脑电模式。这一点在后面的避坑章节会专门展开。2.2 SEED-IV 的组织62 通道、四分类情绪与多 session 结构SEED-IV 的设计思路和 DEAP 不一样。它采集 62 个通道的 EEG采样率更高情绪标签是快乐、悲伤、恐惧、中性四类每个受试者分多个 session 采集session 之间隔一段时间用来测试跨时间稳定性。数据文件常见的是按受试者和 session 拆分每个文件里是已经切好试次的矩阵。import scipy.io as sio # SEED-IV 单个 session 数据文件常见结构 mat sio.loadmat(seed_iv/eeg_1_1.mat) # 不同版本键名不一样先看结构再取数 print([k for k in mat.keys() if not k.startswith(__)]) # 常见结构: (n_trials, n_channels, n_points) # 每个 trial 里前几秒可能是静息或提示画面SEED-IV 六十二个通道本身就说明一个问题通道数翻倍特征维度也翻倍但每个试次带来的样本量并没有成倍增加。直接用全部通道的原始信号做 SVM特征维度很容易冲到几千需要靠频段特征把维度压下来这一点放到第三章详细说。另外 SEED-IV 的多 session 设计经常被忽略。同一个受试者三次 session 的数据如果混在一起当成普通样本等于默认三天里脑电分布完全一样但实际并非如此。做跨 session 验证时正确的做法是按受试者、按 session 分别划分而不是把所有数据扔进同一个矩阵里 shuffle。2.3 时间轴与基线处理3 秒静息到底该不该保留DEAP 每个试次前 3 秒是静息基线后 60 秒是情绪诱发过程。主流做法是把前 3 秒去掉只用后 60 秒做分析。原因很直白情绪刺激呈现之前那段时间里信号反映的是受试者的即时状态而不是目标情绪。如果保留基线模型会学到“观众还没开始看视频时的状态”和效价标签之间的关系这在实验设计上说不通。fs 128 baseline_sec 3 start_point baseline_sec * fs # 384 analysis eeg[:, :, start_point:] # 只保留 60s 诱发段 # 如果需要做差分/相对基线特征 baseline_mean np.mean(eeg[:, :, :start_point], axis2, keepdimsTrue) eeg_relative analysis - baseline_mean处理基线的另一个常见做法是计算“相对功率”把诱发段各频带功率除以基线段对应频带功率再取对数。这个做法试图消除受试者当天的整体脑电水平差异但对情绪分类的提升并不是稳定的有时反而会因为基线段的估计方差过大带来噪声。我一般先把绝对特征跑一版再试相对特征哪个在验证集上稳定就用哪个不要一上来就叠加太多变换。基线处理没有统一答案不同论文的取舍也不一样。关键是你在方法部分写清楚了自己怎么处理审稿人和复现者才能照做。把“去 3 秒基线”作为默认把“保留基线做差”作为对比实验是最省事也最不容易被挑毛病的组合。3. 特征工程把原始脑电变成 SVM 能吃的向量SVM 本身不关心信号是什么它只吃固定维度的数值向量。脑电的原始采样点维度太高、噪声太重直接拼成一维向量往往效果很差。这一步需要把每个试次转换成一组低维、有物理意义的特征。3.1 频带划分与微分熵为什么 DE 特征在情绪任务里这么常用情绪脑电研究里最常用的特征不是时域均值而是各个频带的能量。频带沿用睡眠分期的习惯划分为 delta、theta、alpha、beta、gamma 五段但情绪任务里 alpha、beta、gamma 的区分度通常更强。简单的做法是计算每个频带的功率谱密度均值也就是 band power进一步的做法是计算微分熵也就是 DESEED 系列论文里大量使用这个特征。DE 在数学上是“把信号近似看成高斯分布后对其概率密度函数做微分熵运算”工程实现时可以简化为对频带内功率取对数。这个变换的好处有两个一是压缩了功率的动态范围避免个别幅值大的通道主导距离计算二是让特征分布更接近对称SVM 的 RBF 核在度量距离时会更稳。import numpy as np from scipy.signal import welch def band_de(signal, fs128.0): 计算五个频带的微分熵近似值log(带内平均功率) bands { delta: (1, 4), theta: (4, 8), alpha: (8, 13), beta: (13, 30), gamma: (30, 45), } result {} for name, (lo, hi) in bands.items(): freqs, pxx welch(signal, fsfs, npersegfs) mask (freqs lo) (freqs hi) band_power np.mean(pxx[mask]) result[name] np.log(band_power 1e-12) # 加小常数防零 return resultnpersegfs表示用 1 秒窗口做 Welch 谱估计这样低频段也能有不少谱线参与平均不会只有三五根频点撑起一个带。1e-12是防止全零信号取对数时出现负无穷。函数返回的是五个频段的 DE 近似值对于一个试次最终特征是把“通道 × 频段”全部串起来32 通道 × 5 频段就是 160 维。需要说明的是严格 DE 公式里还有0.5 * log(2πeσ²)这样的常数项和系数。对 SVM 来说特征向量所有维度同时乘一个常数、加一个常数不会改变相对距离所以工程上直接取log(band_power)是等价近似。如果你要写进论文建议在方法部分写清楚“采用 log 功率近似微分熵”避免被同行挑数学细节。3.2 特征矩阵拼装按试次切片别把窗口维度搞混一个试次有 60 秒信号可以整段算一个特征向量也可以切成多个短窗口各算一个特征向量。整段计算的优点是特征稳定缺点是一个试次只能贡献一个样本40 个试次太少SVM 很容易欠拟合。我一般先用 1 秒不重叠窗口切分每个试次得到 60 个样本模型有更多数据可学。def trial_to_features(eeg_trial, fs128.0, win_sec1.0): 把一个试次(通道×时间)切成多个窗口每个窗口生成 通道×频段 特征 channels, n_points eeg_trial.shape win_len int(win_sec * fs) n_windows n_points // win_len features [] for i in range(n_windows): seg eeg_trial[:, i*win_len:(i1)*win_len] vec [] for ch in range(channels): de band_de(seg[ch], fs) vec.extend([de[delta], de[theta], de[alpha], de[beta], de[gamma]]) features.append(vec) return np.array(features) # (n_windows, channels*5)窗口长度是一个超参数。1 秒窗口能提供大量样本但某些情绪成分在时间上可能不是稳定持续的太短的窗口会让特征噪声变大。0.5 秒到 2 秒都有人用我用 1 秒作为默认值然后在验证集上试一下 2 秒。别小看这个参数它在 DEAP 上往往能带来 3 到 5 个百分点的准确率差异。3.3 归一化与特征选择先切训练集再拟合变换器SVM 对特征尺度非常敏感。RBF 核的距离公式里量级大的维度会直接压过量级小的维度所以必须先做标准化。最容易被忽视的是标准化只能看着训练集算均值和方差再把这个均值和方差用到验证集上。如果先对全量数据做标准化再做交叉验证就是一种温和的数据泄漏会高估模型表现。from sklearn.preprocessing import StandardScaler # X_all 形状 (n_samples, n_features) # 正确做法: 先切训练/测试再 fit 标准化 X_train, X_test, y_train, y_test train_test_split( X_all, y_all, test_size0.2, random_state42, stratifyy_all ) scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test) # 只 transform不 fit特征选择方面全通道全频段拼出来的特征维度并不算高SVM 本身也不是特别怕冗余特征我在实际任务里很少做复杂的特征选择。如果某个通道明显有坏数据比如全零、方差极小直接删掉通道比任何算法都有效。一个简单的做法是计算每个特征维度的方差把方差为 0 的维度去掉这能避免某些数据文件里缺失通道导致的报错。4. SVM 情绪分类落地核函数、网格搜索与交叉验证特征准备好之后SVM 的使用反而相对直接。情绪分类是小样本、非线性边界的问题RBF 核是默认选择但在某些设定下线性核更稳。关键在于参数怎么设、验证怎么切。4.1 选 RBF 还是线性核先看样本量和特征维度RBF 核有两个重要参数C和gamma。C控制误分类惩罚越大越容易过拟合gamma控制单个样本的影响半径越大决策边界越复杂。在几百个样本、一两百维特征的设定下RBF 核的拟合能力足够但也很容易过拟合尤其是gamma偏大的时候。线性核没有gamma只有C更容易调。对 DEAP 二分类任务线性核往往能拿到和 RBF 差不多的准确率因为特征本身已经被频段聚合过非线性边界带来的提升有限。对 SEED-IV 这种四分类任务RBF 通常略好一些但优势也不是压倒性的。from sklearn.svm import SVC # RBF 核示例放缩和模型组合成 pipeline pipe Pipeline([ (scale, StandardScaler()), (svm, SVC(kernelrbf, class_weightbalanced, decision_function_shapeovo, random_state42)) ])decision_function_shapeovo的意思是多分类时使用 one-vs-one 策略这是 SVM 原生策略结果会比 one-vs-rest 在类别不均衡时更稳定。class_weightbalanced会自动按类别频率加权四分类情绪里如果某个类别样本偏少这个参数能防止模型全体预测到多数类。4.2 网格搜索与交叉验证按受试者分组是关键网格搜索参数时交叉验证的划分方式比网格本身更重要。先看两个错误做法一是把所有受试者的窗口混在一起直接 KFold这是最常见的虚高来源二是把每个受试者单独训练测试得到指标后再平均这是“受试者相关”的设定结果通常偏高。真正有意义的是“跨受试者”设定一个受试者的所有窗口只能出现在训练集或者测试集不能两边都出现。实现上可以用GroupKFold把受试者编号当作 group。from sklearn.model_selection import GroupKFold, GridSearchCV # groups 是每个样本所属的受试者编号 groups subject_ids_for_each_window param_grid { svm__C: [0.1, 1, 10, 100], svm__gamma: [0.001, 0.01, 0.1, 1, scale], } gkf GroupKFold(n_splits10) # 10折同一受试者不会同时进训练和测试 grid GridSearchCV( pipe, param_grid, cvgkf, scoringf1_macro, n_jobs-1 ) grid.fit(X_all, y_all, groupsgroups) print(grid.best_params_, grid.best_score_)GroupKFold的 10 折会把所有受试者分成 10 组每折拿走一组做测试。这样评估的是“新受试者”的表现更接近真实使用场景。需要提醒的是如果窗口来自同一个用户且重叠过多跨受试者分组还不够还要保证同一个试次的两个重叠窗口不跨训练测试集这个在避坑章里细说。4.3 评价指标只看准确率会骗自己DEAP 二分类任务里多数论文报告准确率但正负样本其实可能不是严格 5050只看准确率容易被占比高的一类拉高。SEED-IV 四分类任务更是这样如果四类样本数量不完全一致宏平均 F1 比准确率更能反映真实水平。from sklearn.metrics import classification_report, confusion_matrix y_pred grid.predict(X_test) print(classification_report(y_test, y_pred, digits3)) print(confusion_matrix(y_test, y_pred))classification_report会同时给出每个类别的精确率、召回率、F1以及宏平均和加权平均。我一般看两行宏平均 F1 和各类别的召回率。如果某一个情绪类别的召回率明显低于其他类别比如 SEED-IV 里悲伤和中性经常互相混模型整体准确率再高也不适合上线使用。公开数据集上DEAP 二分类的 RBF 或线性 SVM 基准在 80% 到 87% 左右SEED-IV 四分类在 70% 到 80% 之间具体数值受特征窗口、通道选择、是否跨受试者影响很大。如果跑出来的结果明显高于这个区间先怀疑评估流程是否有泄漏而不是沾沾自喜。5. 基于 EEG 的情绪 SVM 避坑指南五个必看陷阱和排查方法这一章专门写我在这类任务里最常见的五个坑。每条按现象、原因、解决方式展开希望能帮你省下反复试错的时间。5.1 全量标准化引发的数据泄漏现象交叉验证准确率 92%看起来非常漂亮但换到独立受试者上只有 74%前后差距大到不合常理。原因是把StandardScaler放在切分之前对全量数据 fit 了均值和方差。测试集的统计信息混进了训练过程模型相当于间接看到了测试集的尺度分布。解决方式把标准化放进Pipeline让GridSearchCV或者手动切分在每一折内部重新 fit。只要标准化器不在交叉验证循环外部对整个X调用fit这个泄漏就能避免。排查时直接看代码里有没有对X_all调scaler.fit。5.2 同一受试者的窗口同时出现在训练集和测试集现象个人相关验证准确率 88%跨受试者验证只有 64%。原因很典型没有使用分组交叉验证同一个人的 40 个试次、60 个窗口被随机打散训练集里学到的个体脑电特征直接帮助了测试集。这不算模型学会了情绪而是学会了认人。解决方式用GroupKFold并把受试者编号传入groups。如果目标是新受试者泛化这是最低要求。论文里要在方法部分明确写“受试者独立划分”否则审稿人大概率会质疑结果。5.3 滑动窗口重叠造成的样本重复现象用 1 秒窗、0.5 秒步长切窗后准确率上升了 2 到 3 个百分点但细看发现相邻窗口共享一半信号。原因是重叠窗口生成高度相似样本训练集和测试集之间可能存在近似重复模型等于看到了模板匹配。解决方式步长大于或等于窗口长度即采用不重叠窗口。如果一定要重叠窗口来增广必须保证任意一个原始试次的全部窗口都落在同一侧也就是分组划分时以试次为粒度而不是以窗口为粒度。这一点在写代码时很容易漏掉。5.4 类别不均衡时盲目追求准确率现象四分类任务里中性类别样本占比 40%其他三类各 20%模型全部预测中性就能拿到 40% 准确率。调参后准确率提了但混淆矩阵里有两类几乎不预测。原因是没看每类别召回率也没用类别均衡机制。解决方式给SVC设置class_weightbalanced评价指标改用f1_macro。网格搜索的 scoring 也应该设为f1_macro否则参数选择过程本身会偏向占比高的类别。5.5 随机数种子对结果影响过大现象同一套代码跑三次准确率分别是 81.2%、76.5%、79.8%差异大到没法比较两个特征方案的好坏。原因是数据切分、模型初始化里的随机数没有固定少数受试者的划分变化对几百个样本的模型影响很大。解决方式所有可能引入随机的地方都固定random_state包括train_test_split、SVC、GridSearchCV。在论文里报告多次种子下的均值加减标准差而不是只报最好一次。我现在做这类实验的习惯是直接固定random_state42跑完整套流程然后换一个种子整体重跑一遍两次结论一致才敢写进文档。6. 从最小实验起步把 SVM 情绪分类跑出可信结果如果你只想先跑通一版不需要从头写特征工程。假设你已经按第三章的方式得到了特征矩阵X_all、标签y_all和受试者编号groups下面这个脚本可以直接作为实验起点。import numpy as np from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.svm import SVC from sklearn.model_selection import GroupKFold, cross_val_score X_all np.load(features.npy) y_all np.load(labels.npy) groups np.load(subject_ids.npy) pipe Pipeline([ (scale, StandardScaler()), (svm, SVC( kernelrbf, C10, gammascale, class_weightbalanced, random_state42 )) ]) cv GroupKFold(n_splits10) scores cross_val_score( pipe, X_all, y_all, groupsgroups, scoringf1_macro, cvcv, n_jobs-1 ) print(F1 macro: %.3f ± %.3f % (scores.mean(), scores.std()))跑完这版之后我有三个习惯性的验证动作。第一打印混淆矩阵看哪些类别互相混淆如果悲伤和中性大面积互相串说明特征里区分这两个类别的信息不足优先加时域特征或者换频段权重。第二对比线性核和 RBF 的宏平均 F1如果差距小于 1 个百分点我会选线性核因为可解释性更好、参数更少。第三统计每个受试者的预测结果看看是不是集中在某几个人特别差这通常是通道质量或个体基线差异导致的。作为第一人称的教训我在早期做 DEAP 二分类时某次实验结果跑到 89%当时第一反应是调参有效后来发现窗口重叠加全量标准化两个问题叠加折半之后真实水平只有 82% 左右。从那以后我对每个实验步骤都先问一句“这一步有没有可能让测试集信息提前泄漏”再往下走。情绪检测这个方向本身并不缺模型复杂度缺的是用同一套标准流程对比不同特征时的确定性。希望这篇笔记能帮你在 DEAP 或 SEED-IV 上少走几个弯路把精力留在真正值得研究的问题上。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站