ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

DEAP情绪识别实战:从脑电数据集下载到SVM分类的完整流程

DEAP情绪识别实战:从脑电数据集下载到SVM分类的完整流程 简介DEAP情绪识别与分类项目包sentimentclassify-master是一套完整的情绪分类工程实现面向情感计算、机器学习初学者以及心理学与信号处理交叉领域研究者。项目针对DEAP生理信号数据集覆盖数据组织、特征提取、情绪四分类喜、怒、哀、惧的完整流程。资源共38个文件以28个Java源码为核心另有5个train训练语料、项目配置文件与说明文档压缩包仅5.79MB轻量易用便于下载和二次开发。目前已有1127人学习下载。包内包含README、LICENSE、.project/.classpath等工程标配以及book、kitchen、all、dvd、electronic等多个领域的.train文本数据既能处理文本情感分类也可结合DEAP多模态生理信号进行情绪识别实验。读者可直接复用源码做特征对比、算法调优或场景迁移特别适合毕业设计、课程项目以及想深入理解情感分类技术细节的开发者。整体目录结构清晰源码与训练数据分离显著降低了使用门槛。1. DEAP情绪识别的第一道门槛先把数据集下载和格式弄清楚拿到sentimentclassify-master这份代码后先别急着训练第一步是把 DEAP 数据集下载到位并弄懂它的结构。DEAP 情绪识别是目前公开生理信号情绪数据集里最常被用作基准的一个32 名被试观看 40 段音乐视频期间同步记录 32 通道脑电与 8 路外周生理信号每段视频结束后被试按 1–9 分对 valence、arousal、dominance、liking 打分。标题里的 deap 数据集分类落到代码上就是一条固定链路下载解压、读 mat、去基线、分窗、提取特征、训练分类器、报告准确率。这篇文章把这条链路完整写一遍并把我在 8064 与 7680 采样点、标签阈值、特征提取顺序上踩过的坑一并说清适合刚拿到这份代码想复现 baseline 的读者。2. DEAP数据集下载与 mat 解析从申请到读出能训练的三维张量2.1 下载 DEAP 数据集申请审核与两种目录DEAP 并不像 CIFAR 那样挂一个地址就能直接下。它在官网以学术数据集形式发布常规下载流程是先在数据发布页填写申请表单写明单位与用途一般用学校或机构邮箱提交等审批邮件里给出下载链接。审核通常一两个工作日个别时候会被反垃圾规则吞进回收站收不到链接时记得先翻垃圾邮件。这一步没有太多技巧耐心按表单填就行。解压之后你会看到一个包含两个核心目录的文件夹。data_preprocessed_matlab是我们训练时要用的版本里面是 32 个被试各自独立的.mat文件命名从s01.mat到s32.matdata_original是原始信号目录按视频分段存放文件数量大、格式也更碎一般基线实验用不到。如果你只是想跑通 sentimentclassify-master 分类流程下载 preprocessed 目录就够原始数据那部分占的空间和折腾成本都更高。这里有个容易忽略的点preprocessed 并不等于“可以直接进模型”。它做过的预处理是下采样到 128Hz、去除 EOG 伪迹、4–45Hz 带通滤波但每个 trial 仍然包含基线片段需要我们自己切掉。我第一次看到8064这个数字时误以为是单纯 60 秒乘以 128后来才发现 8064 63 × 128多出来的 3 秒就是基线。这个细节会直接影响后续窗口提取我放到避坑章节专门讲。2.2 mat 文件里到底存了什么data 与 labels 两个字段用scipy.io.loadmat读取s01.mat输出里面有几个字段常用的只有两个import scipy.io as sio raw sio.loadmat(data_preprocessed_matlab/s01.mat) print(raw.keys()) data raw[data] # (40, 40, 8064) labels raw[labels] # (40, 4) print(data.shape, labels.shape, labels.dtype)先解释形状。data的三维分别是 trial、channel、sample32 名被试每人 40 个 trial对应 40 段音乐视频每个 trial 内是 40 个通道、共 8064 个采样点。labels是 40 行 4 列列顺序固定为 valence、arousal、dominance、liking取值 1–9分数越高代表越正向、越兴奋、越有支配感、越喜欢。40 个通道里前 32 个是 EEG 通道顺序对应 10-20 系统电极布局后面的 8 个是外围生理通道常见顺序是水平/垂直眼电、颞肌电、额肌电、皮肤电、呼吸、脉搏与体温。做纯 EEG 研究的人会只取前 32 行做多模态融合的人会保留全部。这个选择会影响特征维度也会影响最终准确率。我一般会先打印raw.keys()确认字段名因为个别版本里存在data与labels之外的附加字段不要凭记忆取。2.3 用 scipy 读取 mat 并拆出训练样本我不建议在训练循环里反复loadmat更常见的做法是写一个按被试加载的函数一次只把一个被试读进内存。下面这个函数做了两件事去掉前 3 秒基线并把数据类型转换为 float32 以省内存。import numpy as np def load_deap_subject(path, fs128, baseline_sec3): raw sio.loadmat(path) data raw[data].astype(np.float32) # (40, 40, 8064) labels raw[labels].astype(np.float32) if baseline_sec 0: data data[:, :, fs * baseline_sec:] # 丢弃前 baseline_sec 秒 return data, labels注意这里为什么用fs * baseline_sec而不是硬编码384采样率是数据集的约定但代码里一旦写死将来切换到 256Hz 版本或者其他数据集时就要到处改。用参数传进函数调用侧只要写load_deap_subject(s01.mat)就默认拿到 40 × 40 × 7680 的张量。输出 shape 这一步值得打印一次(40, 40, 7680)才是真正意义上的 60 秒试次。后续所有分窗、特征提取都建立在这个形状之上。如果发现形状是(40, 40, 7680)却仍然得到 8064 相关的错误多半是某个地方忘了切基线或忘了把 fs 换成 128。3. 跑通 deap数据集分类的最小管线加载、特征、训练一次出准确率3.1 sentimentclassify-master 这类代码库的常见目录组织sentimentclassify-master 这类仓库的目标就是让你用一串命令跑出 DEAP 上的分类结果常见组织方式是把链路拆成独立脚本数据加载放data_loader.py特征提取放feature_extractor.py模型定义与训练放train.py评估与画图放evaluate.py。它的目录结构大致是sentimentclassify-master/ data/ data_preprocessed_matlab/ src/ data_loader.py feature_extractor.py train.py evaluate.py config/ deap_config.yaml我没有必要照搬它的文件名重点是理解这个拆分逻辑数据加载与特征提取必须是两个独立阶段这样你换特征、换模型时才不用重写读取逻辑。实际运行前先确认data_loader.py里读取的路径指向你解压 DEAP 的目录这是一个最常见的起步报错路径对不上时会直接抛FileNotFoundError。3.2 最小可运行代码用单被试效价二分类跑通流程下面这段代码是把整套流程压缩到一个文件里大概 40 行跑通后再往工程化方向拆。核心思路是读一个被试、对每个 trial 提取频带特征、构造二分类标签、用 SVM 训练并输出测试准确率。# train_baseline.py import numpy as np import scipy.io as sio from scipy.signal import welch from sklearn.pipeline import make_pipeline from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split from sklearn.svm import SVC FREQ_BANDS { delta: (1, 4), theta: (4, 8), alpha: (8, 13), beta: (14, 30), gamma: (30, 45), } def extract_feature(trial, fs128): win fs * 4 feats [] for ch in trial: f, psd welch(ch, fsfs, npersegwin) for lo, hi in FREQ_BANDS.values(): mask (f lo) (f hi) de 0.5 * np.log2(psd[mask].mean() 1e-12) feats.append(de) return np.array(feats) def load_subject(path): raw sio.loadmat(path) data raw[data][:, :, 384:].astype(np.float32) labels raw[labels].astype(np.float32) return data, labels X, labels load_subject(data_preprocessed_matlab/s01.mat) X_feat np.array([extract_feature(t) for t in X]) y (labels[:, 0] 5).astype(int) X_tr, X_te, y_tr, y_te train_test_split( X_feat, y, test_size0.25, random_state42, stratifyy) clf make_pipeline( StandardScaler(), SVC(kernelrbf, C1.0, gammascale, class_weightbalanced) ) clf.fit(X_tr, y_tr) print(S01 valence ACC: %.3f % clf.score(X_te, y_te))这段代码里有几个参数值得说明。win fs * 4决定 Welch 方法里每个窗的长度窗口越长频率分辨率越高代价是窗口内的平稳性假设变弱DEAP 的 60 秒试次用 4 秒窗是常见选择。nperseg必须小于等于信号长度如果某个通道信号比窗口短welch会直接报错窗口设置超过 60 秒也会在这里翻车。0.5 * np.log2(psd[mask].mean() 1e-12)是对微分熵的简化近似。1e-12是数值保护项避免频带能量为 0 时对 0 取对数。SVM 的class_weightbalanced用于处理二分类标签不平衡DEAP 按阈值切分后往往不是严格的 1:1这个参数能防止模型整体往多数类偏。3.3 每个试次只有一个样本加滑动窗口才是正解上面的代码一次运行只有 40 个 trial训练集只有 30 个样本score的方差非常大跑十次准确率在 0.4 到 0.8 之间跳动都正常。想要稳定结果必须把每个 trial 切成长度为数秒的窗口。我一般在 4 秒窗口、2 秒步长下做滑动窗口每个 60 秒的 trial 得到约 28 个窗口。特征提取逻辑与上面相同区别只在外面多一层窗口循环。窗口之间虽然存在重叠但在情绪识别实验里这是常规做法因为相邻窗口的标签相同重叠加倍扩充样本量同时也引入了更强的时序冗余。def extract_windows(data, fs128, win_sec4, step_sec2): win fs * win_sec step fs * step_sec windows, label_idx [], [] for t in range(data.shape[0]): trial data[t] for start in range(0, trial.shape[1] - win 1, step): seg trial[:, start:start win] windows.append(extract_feature(seg)) label_idx.append(t) return np.array(windows), np.array(label_idx)注意这里label_idx先存 trial 编号再在训练前把它映射成 valence 二分类标签避免在窗口循环里反复判断。窗口之后单个被试的特征矩阵一般有 1000 行以上分类结果才谈得上稳定。这时候回来的准确率会下降一些因为样本量大了、窗口间的噪声也被算进去了但这个数字更接近真实水平。3.4 评价指标与结果口径单看准确率在 DEAP 上很容易被误导。试次少时 0.8 可以有很高偶然性试次多时 0.65 可能是很扎实的结果。更稳妥的做法是同时打印 balanced accuracy、F1 和混淆矩阵尤其是当二分类正负样本比例不是 1:1 时靠准确率判断好坏基本是玄学。classification_report可以一次给出这三项建议作为默认输出。另一个口径问题是“单被试内分类”和“跨被试分类”。单被试内随机切分模型见过同一个人的部分脑电模式准确率天然偏高跨被试用留一被试交叉验证模型要面对没见过的被试准确率会掉下来但这才是有实用价值的情绪识别。两者不能混着比第四章会专门聊模型与验证方式怎么匹配。4. DEAP 情绪识别的特征与模型选型微分熵、分窗和分类器怎么配4.1 为什么微分熵是 DEAP 上最稳的频带特征微分熵在 DEAP 上的流行不是偶然。生理信号分类里时域方差、均值这类统计特征容易受到个体基线差异影响频带能量特征与情绪效价的关联更稳定。微分熵的定义针对连续随机变量的熵在 EEG 频带近似服从高斯分布的假设下它刻画了频带能量在 log 域的不确定性数值上等于对功率谱密度取对数再做常数调整。情绪状态变化时不同频带的微分熵变化比原始幅度更能反映脑电节律的重分布。DEAP 的 4–45Hz 带通滤波决定了可用频带delta、theta、alpha、beta、gamma 这样切是标准做法我在前面代码里就是按这个频带表做的。其中 30–45Hz 的 gamma 带在 DEAP 的情绪识别论文里出现频率很高因为它与注意唤醒关系密切。建议不要省略 gamma只保留 alpha、beta 会把模型的信息来源限制在较小范围。频带边界本身也值得调。默认的(1, 4)在 128Hz 采样下分辨率足够但如果你把窗口缩到 2 秒频率分辨率降到 0.5Hzdelta 和 theta 的边界处会混入噪声。我一般先固定 4 秒窗口再确定频带表不要同时调两个变量。4.2 标签映射把 1–9 评分变成分类目标DEAP 的四类标签都是连续评分二分类是最容易入手的设定。常见阈值是 5评分大于 5 算高分组小于等于 5 算低分组。这个切法没有统一标准与 DEAP 原文使用的自我评估模型口径也有关。另一个选择是按每个被试评分的中位数切保证每类样本数量接近但高/低分组含义会因被试而异。如果目标是对齐已发表论文先读那篇论文的切分方式再照做如果要建立自己的评估协议固定 5 分阈值更容易跨被试比较。四分类一般将 valence 和 arousal 各自按阈值切成高/低组合成 (HVHA, HVLA, LVHA, LVLA) 四个象限。这类任务对特征数量要求更高单纯五频带微分熵做四分类会到 0.4 上下和随机水平没有拉开差距需要更多通道选择与更复杂的模型。如果你第一次跑就想出四分类的漂亮结果大概率会失望先把二分类做稳再一步步扩类。4.3 模型选型SVM、随机森林与浅层 CNN 的边界SVM样本量几千到两万之间表现稳定RBF 核配合标准化后的特征矩阵训练快、可解释。注意 RBF 的 C 和 gamma 默认值在情绪识别这类低信噪比任务上不一定最优建议网格搜索 C ∈ {0.1, 1, 10}, gamma ∈ {scale, 0.01, 0.001}。我用这个范围在多数单被试二分类任务里能稳定提升 2–4 个点。from sklearn.model_selection import GridSearchCV param_grid { svc__C: [0.1, 1, 10], svc__gamma: [scale, 0.01, 0.001], } grid GridSearchCV(clf, param_grid, cv5, scoringbalanced_accuracy) grid.fit(X_tr, y_tr) print(grid.best_params_, grid.best_score_)这段网格搜索直接复用 3.2 节的clf对象svc__C里的前缀是因为 clf 是 pipeline参数名要带步骤名。cv5在窗口级样本量上千时足够不需要更大的折数否则每个折训练样本太少。随机森林对特征单调性和尺度更不敏感但维度到了几百以后每棵树都要在大量特征里找分裂点训练时间和内存都会上升。它的好处是特征重要性可直接输出用来回答“哪个频带最重要”这类问题。当特征数量不足时容易过拟合需要注意限制树深度。浅层 CNN将原始窗口或频带谱图作为输入。常见的做法是把一个窗口的 32 通道数据排成 (32, 时间) 的矩阵用一维卷积沿时间维度扫后面接全局平均池化。CNN 的好处是端到端学习特征不再手工指定频带坏处是 32 个被试的个体差异大网络容易记住被试身份而不是情绪状态跨被试验证时性能会明显回落。小数据集上建议先用 SVM 跑通基线再考虑深模型。4.4 一条默认参数表第一次跑就这么设如果你不想从零调参我习惯用下面这组参数作为起点。窗口 4 秒、步长 2 秒频带按五段切特征用微分熵近似分类器用 RBF SVMC1gammascale样本权重平衡。标准化放在 pipeline 内评估指标用 balanced accuracy 而非裸准确率。这组参数在多数 DEAP 二分类任务上不会出大错。单被试 valence 分类大致落在 0.7–0.85cross-subject 会跌到 0.55–0.7如果差太多先检查标签切分和是否有数据泄漏而不是急着换模型。5. DEAP 数据集避坑与常见问题排查五条实战记录5.1 数据形状里多出来的 384 点8064 与 7680 的差异现象读取s01.mat打印 data 形状是 (40, 40, 8064)而按 60 秒乘 128Hz 期望是 (40, 40, 7680)。原因每个 trial 记录的是 3 秒基线与 60 秒刺激63 秒合计 8064 点preprocessed 文件没有把基线单独切开。工具链里一旦有人按 60 秒写死形状后面所有窗口索引都会错位。解决统一在加载后按data[:, :, fs*3:]丢弃前 3 秒再做后续处理。检查窗口提取、Welch 的 nperseg 是否超过 7680 也以这一步之后的长度为准。如果忘了去基线前 3 秒的静息态数据会稀释刺激段特征分类性能通常会有几个点下降。5.2 阈值切分失效valence 为什么不能直接拿 5 当全局阈值现象用labels[:, 0] 5切高/低分组打印标签分布发现有的被试高分组占 80%有的被试低分组占 70%模型训练后准确率很高但 F1 很差。原因DEAP 的评分 1–9 只是主观量表被试的打分习惯不同。某些人普遍打 7、8 分5 分阈值对他来说就是“负向”的而另一个人可能只打 2、3 分。全局切分在个体层面会产生严重的不平衡。解决要么按被试各自的评分中位数切要么干脆按领域先验只选评分 4 以下与 6 以上的 trial 做二分类中间段剔除。后者能减少标签噪声但样本量下降明显。选定哪种要在你的实验记录里写清楚因为不同切法得到的准确率不可直接对比。5.3 数据泄漏标准化与特征提取位置错了一格现象跨被试实验表面准确率高达 0.9换被试以后掉到 0.6反复复现找不到原因。原因把全部被试数据合并后做StandardScaler.fit再切训练测试集测试集的信息通过均值和方差进入了模型。更隐蔽的版本是在训练前用全部数据做特征选择同样属于泄漏。还有一种很容易踩的在 3.3 节做窗口切分后直接随机切分训练测试同一个 trial 的相邻窗口会同时出现在两边造成乐观估计。解决把标准化放进make_pipeline训练集 fit、测试集只 transform特征选择放到交叉验证内部或者干脆不用特征选择。窗口级切分时用GroupShuffleSplit按 trial 编号分组保证同一个 trial 的窗口不跨集合。5.4 内存爆炸32 个被试一次读完后 OOM现象先写一个循环把所有s*.mat加载到 list然后统一预处理运行到第 25 个被试时MemoryError。原因32 个被试的原始数据全部以 float64 驻留内存单个被试大约 100MB堆上还有后续特征矩阵32 份合起来轻松超过物理内存。这是一个典型的“图省事反而更慢”的操作被 OOM 打断后还得从头加载。解决使用“按被试处理”的流程一次只加载一个被试特征提取后立即丢弃原始信号释放用del data外加gc.collect()。windows 的特征矩阵保留但维度远小于原始张量。如果内存仍然紧张把特征矩阵分批写入.npy训练时再按被试读入。5.5 跨被试与被试内结果差异巨大评估协议不一致现象同一批代码论文里报 0.85自己复现只有 0.65老板质疑结果。原因论文用的可能是被试内“同一被试随机切分”协议而复现时用了“跨被试留一被试”协议。两个协议回答的问题根本不是同一个——前者证明“脑电模式在该个体身上可区分”后者证明“模型对新人可用”。解决先明确实验目标。产品化场景必须用留一被试统计报告 mean±std 并保留每个被试的单项准确率方便看个体差异。不要尝试通过调参把跨被试结果硬拉到被试内水平那是徒劳窗口重叠率再高也补不回个体差异带来的损失。6. 验证结果与进阶方向别让一次准确率骗了你6.1 让结果可信的三个验证习惯跑通基线后我会固定做三件事。第一用留一被试交叉验证重跑得到 32 个准确率看最低被试与最高被试相差多少落差超过 0.3 时优先排查该被试是否存在脑电伪迹。第二重复 5 次随机初始化报告平均与标准差避免拿单次结果当结论。第三给每个被试打印混淆矩阵确认高/低两类的错误不是集中在某一边否则标签切分可能有问题。6.2 进阶方向从频带特征走向时频融合如果单被试二分类稳定在 0.7 以上再考虑进阶。一个投入产出比很高的方向是时频融合对每个 4 秒窗口把微分熵换成短时傅里叶变换或小波时频图在时间轴与频率轴上同时保留信息然后再送入 CNN。另一个方向是叠加 DEAP 的 8 路外周生理特征做多模态输入皮肤电与呼吸对 arousal 的区分度比脑电更直接。做这两个方向时记得保持同一个评估协议否则换特征带来的提升会被验证方式的变化淹没。这几年做 DEAP 情绪识别我最深的教训是数据集下载与格式解析占了前期一半的坑分类器反而是最不花时间的部分。先把固定协议定下来再谈模型复杂度结果才经得起推敲。希望帮到你。本文还有配套的精品资源点击获取
返回列表