资讯动态

DEAP数据集情感识别实战:EEG预处理、特征提取与跨被试评估指南

发布时间:2026/10/22 13:38:09 来源:尧图企业网站定制
简介面向情感识别研究者与入门者这份基于DEAP数据集的处理与分类代码包解决了从原始生理信号到特征提取、降维及SVM识别的完整流程搭建问题。资源共19个文件以13个Matlab脚本为核心覆盖数据读取loadData.m、makeDEAPstruct.m、电极映射getElectrodeMapping.m、频段特征构建freqBands.m、extractFeaturesFromPSD.m、受试者特征聚合与分类ClassifySVM.m另含4个asv自动备份、1份README说明与gitignore文件压缩包整体仅11KB便于快速阅读和移植。已有2501人学习适合需要通过代码结构复现DEAP情感识别实验的Matlab用户到手后可直接对照脚本梳理从原始数据到SVM分类的完整步骤也可基于现有特征工程与降维函数做二次改造。1. DEAP数据集到底是什么为什么情感识别入门绕不开它做情感识别最头疼的往往不是模型而是数据。DEAP数据集正是这个场景里使用频率最高的公开生理信号库32名被试各观看40段一分钟音乐视频同步记录32导脑电和8路外周生理信号每段视频结束后由被试自己对效价、唤醒度、优势度和喜爱度打分由此得到一套带情感标注的生理信号样本。这个结构让DEAP成了情感识别领域的事实基准——论文对基准、课程做设计、入门练手几乎都从它开始。它要解决的核心问题是给你一份带真实情感标签的EEG数据让你把预处理、特征提取和分类模型的整条链路跑通。适合想入门情感识别、做课程项目或验证新模型的从业者。下面按一条可复现的路径展开先讲数据格式再做预处理和特征最后给模型评估与踩坑记录。2. 先拿到数据再说DEAP文件结构、.mat加载与形状确认下载解压之后第一步不是写特征提取而是把文件结构摸清楚。DEAP的预处理数据以 .mat 格式分发这种格式在 Python 里用 scipy 就能读但数据组织方式有合并版和单被试版两种搞混了后面全盘皆输。2.1 合并文件与单被试文件怎么选先看懂 data 和 labels 两个字段DEAP 预处理数据通常有两种分发形式一种是所有被试打包在一个文件里另一种是按被试拆成32个文件。两者内容一致只是组织方式不同具体差别看下表。版本文件示例data 形状labels 形状适用场景合并版data_preprocessed_matlab.mat(32, 40, 40, 8064)(32, 40, 4)想一次加载全部被试单被试版s01.mat 到 s32.mat(40, 40, 8064)(40, 4)内存有限、按被试逐一处理先解释四个维度的含义。以合并版为例data 的 shape 依次是被试数、试次数、通道数、采样点数32名被试、每名40个试次所以前两维是32和40通道维是40其中前32个是脑电通道按国际10-20系统排布后8个是外周生理信号大致涵盖肌电、眼电、皮电、呼吸、脉搏和体温采样点维是8064对应128Hz采样率下63秒时长这63秒由3秒静息基线和60秒视频组成。labels 的最后一维4表示效价、唤醒度、优势度、喜爱度四个评分取值都在1到9之间。这里有个非常容易踩的误解很多人以为 labels 是视频本身的情感标签。实际上预处理文件里存的是每位被试自己看完后的主观打分不是视频的群体平均评分。如果想让所有被试共用一个标签需要去读单独的评分文件而直接用数据包里的 labels训练出来的模型学习的是个体真实感受这恰恰是情感识别更关心的目标。2.2 用Python加载DEAP的.mat文件并确认shapeimport scipy.io as sio import numpy as np mat sio.loadmat(data_preprocessed_matlab.mat) data mat[data] # 合并版预期 (32, 40, 40, 8064) labels mat[labels] # 合并版预期 (32, 40, 4) print(data 形状:, data.shape, dtype:, data.dtype) print(labels形状:, labels.shape, 取值范围:, labels.min(), labels.max())加载完成后我建议立刻做三个自检。第一打印 data.dtype正常是 float64全量加载约占用3.3GB内存如果你的机器只有16GB内存还开着浏览器这一步就会卡到怀疑人生。第二确认 labels 最小值是1、最大值是9说明是原始打分而不是已经二值化过的标签如果 min 是0、max 是1说明你下到了别人二次处理过的版本后面阈值逻辑全部要改。第三看 data.shape 最后一个维度是不是8064如果不是先做 squeeze 去掉单例维度再看很多 .mat 文件会多出一两个长度为1的维度直接 reshape 会搞乱数据。如果你是单被试版把加载路径换成 s01.matdata 的形状是 (40, 40, 8064)前两维分别是试次和通道没有被试维。这时处理逻辑要套一层 for 循环逐个被试加载、处理、释放内存压力小得多。提示如果 loadmat 报错且错误信息里出现 v7.3 或 HDF5说明文件是 Matlab 新格式。DEAP 常规 .mat 文件用 scipy 直接读没问题但网盘二次打包的版本偶尔会变成新格式那就要改用 h5py访问方式从 mat[data] 变成 f[/data]并且返回的是 dataset 对象要先取 [:] 再转 numpy 数组。2.3 转成npy缓存实验前先花两分钟后面省几十分钟EEG 实验的特点是反复改参数、反复重跑每次从头 loadmat 一个几GB的数组非常浪费。我一般会在第一次加载成功后立刻转成 npy 缓存后面所有实验直接读缓存。# 第一次加载完成后执行一次之后注释掉 X data.astype(np.float32) Y labels.astype(np.float32) np.save(deap_X.npy, X) np.save(deap_Y.npy, Y) # 之后每次实验这样加载 X np.load(deap_X.npy, mmap_moder) Y np.load(deap_Y.npy, mmap_moder)参数说明astype(np.float32) 把 float64 降成 float32精度对后续特征计算完全够用内存从3.3GB直接降到1.65GBmmap_moder 是只读内存映射加载时不会把整个数组物理读进内存而是用到哪块读哪块配合按被试循环的实验非常合适。注意 mmap 模式下的数组不能原地修改需要先取切片或拷贝再操作。如果特征提取代码改了、需要重新生成特征直接删掉 npy 重跑缓存那一步即可不用动原始 .mat。3. 预处理三步基线扣除、带通滤波与通道顺序预处理决定了后续特征的天花板。这一步的目标是把原始信号里的个体差异、工频干扰和通道顺序问题一次处理干净让后面的特征提取在一个稳定的信号基础上进行。常见做法是三步扣基线、带通滤波、拆通道。3.1 三秒静息基线为什么必须扣不扣基线模型记住的是人不是情绪每个试次的8064个采样点里前384个也就是3秒乘以128Hz是静息基线。这一小段记录的是被试在视频开始前的状态它存在的意义是给后续60秒的情感响应提供一个参照基准。EEG 的绝对幅值受个体差异影响非常大有的人静息时枕区 alpha 波幅值天生就高有的人状态紧张时额区 beta 持续偏高。如果直接拿整段信号做特征模型真正学到的很可能不是这段视频引发了什么情绪而是这段信号来自哪个人。这也是为什么很多新手第一次跑 DEAP 准确率异常高——模型在做变相的被试识别。标准做法是把基线当作零点对每个通道先计算前384个采样点的均值再从整个试次中减去。这样后续特征描述的是看视频相对于静息状态的变化量个体差异被大幅压制。另一类做法是在频域扣基线用基线段的功率谱逐频点去减试次功率谱效果更精细但实现复杂我一般先用时域均值减法简单稳定对二分类任务足够。3.2 滤波参数怎么定EEG四个频段与4-45Hz带通EEG 的特征基本都按频段组织。DEAP 预处理后的采样率是128Hz奈奎斯特频率64Hz所以能用的分析范围最高到45Hz左右。常用频段划分如下。频段频率范围常见关联theta4-8 Hz放松、困倦、情绪唤醒alpha8-13 Hz静息、放松、注意力抑制beta13-30 Hz活跃思考、紧张、警觉gamma30-45 Hz高级认知加工、情绪强度滤波前先想清楚目的。如果只做情绪二分类一个4-45Hz的带通就够了它能去掉1Hz以下的慢漂移和运动伪迹也能把45Hz以上的肌电干扰压下去同时保留四个频段的完整信息。如果后面要做特定频段分析再分别滤波不要一开始就把信号切成多份费内存不说还会让流程变得难排查。工频干扰是50Hz正好落在这个带通之外所以多数情况下不需要额外陷波如果发现50Hz附近还有明显尖峰再单独加一个陷波器。3.3 完整预处理函数从原始试次到干净信号from scipy import signal def preprocess_trial(trial, fs128, baseline_sec3, band(4, 45)): trial: 一个试次的信号形状 (通道数40, 采样点数8064) n_base fs * baseline_sec # 384 个采样点 base_mean trial[:, :n_base].mean(axis1, keepdimsTrue) trial trial - base_mean # 每个通道分别扣基线均值 b, a signal.butter(4, band, btypebandpass, fsfs) trial signal.filtfilt(b, a, trial, axis1, padlen150) return trial这段代码里的参数都值得记一下。butter 阶数取4阶数越高过渡带越陡但相位畸变越大4阶在脑电里是常用折中点。filtfilt 做的是零相位滤波正向滤一遍、反向再滤一遍把相位失真抵消掉这是 EEG 预处理里几乎必选的做法不要用 lfilter 替代否则波形会整体偏移。band 是 (4, 45)下限不能设成0否则直流漂移和运动伪迹都会留下。padlen150 是边缘延拓长度filtfilt 默认在边缘补零补得不够时试次前后几百个点会出现明显振铃经验值是滤波器阶数的30到40倍4阶滤波器给150左右比较稳妥。处理完记得把通道拆开data 的前32行是脑电后8行是外周信号。只做EEG实验就取 [:32]做多模态融合才保留全部40通道。通道顺序问题我在实际项目里吃过亏——有次特征维度对不上排查半天发现是代码直接取了数据尾部当脑电结果真正用的是肌电和皮电模型当然学不出稳定的情绪模式。4. 特征提取与建模从每试次8064个采样点到情绪二分类预处理完每个试次还是 (40, 8064) 的原始信号分类器不能直接吃这种输入。下一步是把一段高维时序信号压缩成低维特征向量再配上标签训练模型。常见做法是功率谱密度和差分熵模型先用 SVM 和随机森林打底。4.1 Welch功率谱密度特征每通道每频段一个值功率谱密度 PSD 是EEG情感识别里最基础的特征。它回答的问题是每个通道的信号能量主要分布在哪些频率上。Welch 法是最常用的 PSD 估计方式本质是把信号切段、各段做FFT、再取平均降低单次FFT的方差。freqs, psd signal.welch(clean_trial, fs128, nperseg512, noverlap256) bands {theta: (4, 8), alpha: (8, 13), beta: (13, 30), gamma: (30, 45)} feat_parts [] for name, (lo, hi) in bands.items(): mask (freqs lo) (freqs hi) feat_parts.append(psd[:, mask].mean(axis1)) # 每个通道一个均值 feature np.concatenate(feat_parts) # 40通道 × 4频段 160维参数说明nperseg512 表示用512个点做一次FFT对应4秒窗频率分辨率等于 fs 除以 nperseg算下来是0.25Hz频段边界能正好落在整数频率上noverlap256 是50%重叠60秒信号能切出约29个窗重叠越多功率估计越平滑但计算量也线性上涨。psd 的形状是 (通道数, 每个通道的频点)对频段范围取平均后每个通道得到4个代表值40通道拼起来就是160维特征只用EEG就取前32通道得到128维。这里有个细节PSD 不同频段的数值量级差异很大gamma 段功率普遍比 theta 低几个数量级。后续接 SVM 时必须靠标准化拉齐量级接树模型时不敏感但无论如何都不要把原始频谱直接拼成长向量喂给模型——维度高、噪声大、解释性差这是新手最容易犯的偷懒写法。4.2 差分熵与标签二值化1-9分量表怎么切成高低类PSD 之外DEAP 情感识别里另一个高频特征是差分熵 DE。它衡量信号在某个频段上的复杂度对脑电的平稳段识别效果不错。工程上有个简化公式假设频段内信号近似服从高斯分布差分熵等于 0.5 乘以 log(2πeσ²)其中 σ² 就是该频段的平均功率所以可以直接从 PSD 推出来。eps 1e-10 de_feature 0.5 * np.log(2 * np.pi * np.e * (freq_band_power eps))实际使用中很多人直接取 log(频段功率) 作为特征效果差别不大。注意功率必须加保护值 eps否则某些通道功率接近0时 log 会输出负无穷整条特征直接报废。这个细节不贵但能救一整个实验。标签处理同样关键。labels 是1到9的连续打分做分类任务需要二值化。最常见的切法是打分大于等于5归为高一类、小于5归为低一类对效价和唤醒度分别切。但这个固定阈值有个问题每个人的打分习惯不同有人全程打7以上有人集中在4到6固定阈值5会制造严重的类别不平衡。更稳的做法是按被试分别取中位数做阈值def binarize_by_subject(labels_subject): labels_subject: 当前被试的标签形状 (40, 4) th np.median(labels_subject, axis0) # 每类一个阈值 return (labels_subject th).astype(int)逻辑说明中位数阈值保证了每个被试内部正负样本基本均衡模型不用面对8比2这种极端分布。代价是语义从绝对高低变成相对这个人而言的高低但对情绪识别任务来说个体相对变化本来就是更合理的目标因为情绪的生理表现本来就因人而异。4.3 基线模型怎么搭SVM和随机森林的参数选择特征有了、标签有了接下来搭第一个能出结果的基线模型。常见做法是先跑两个RBF 核 SVM 和随机森林两者用 sklearn 的默认参数微调就能得到可用结果。from sklearn.svm import SVC from sklearn.ensemble import RandomForestClassifier from sklearn.preprocessing import StandardScaler from sklearn.pipeline import make_pipeline svm make_pipeline(StandardScaler(), SVC(kernelrbf, C1.0, gammascale)) rf RandomForestClassifier(n_estimators200, max_depth8, random_state42)参数说明SVM 的 C 控制误分类惩罚C 值太大会过拟合个体特异性模式C1.0 起步比较稳gammascale 让 sklearn 根据特征维度自动确定核宽比手动设 gamma0.1 之类的固定值可靠得多。随机森林的 n_estimators200、max_depth8树太深在128维或160维特征上很容易记住单个被试的试次模式深度限制在8左右泛化更好。两个模型跑出来的准确率如果都落在65%到80%区间说明特征提取和标签管线基本正常。如果有人报告90%以上的准确率先别羡慕去查他的数据划分是不是泄漏了这是下一章的主题。5. DEAP情感识别避坑指南五个高频坑与排查方法下面五条按出现频率排过序前两条几乎每个第一次跑DEAP的人都会中招后面三条属于看起来对、细想不对的隐蔽问题。每一条都按现象、原因、解决三步给可以直接对照排查。5.1 坑1基线没扣干净准确率虚高到不敢信现象某同学复现实验直接对原始8064点提特征SVM 二分类准确率上到88%远超公开论文水平。后来把基线扣除加上准确率掉到72%。原因原始信号里个体静息状态的绝对功率占主导模型本质上在学这个人是谁而不是这个人的情绪是什么。被试和标签之间存在相关性模型借这条捷径把准确率抬了上去。解决严格按3.3节的流程做基线扣除并且对每个试次单独计算基线均值绝不能把整段数据统一减一个全局均值。排查时可以做一个对照实验把 labels 随机打乱再训练如果准确率仍然明显高于50%说明管线里存在依赖标签顺序的泄漏特征或预处理一定有问题。5.2 坑2同一个被试的试次同时进训练和测试跨被试直接翻车现象把所有试次按8比2随机划分训练集里出现被试A的35个试次测试集里出现被试A剩下的5个试次测试准确率78%。换成训练集只包含30个被试、测试集是被试A和B的划分准确率直接掉到55%。原因同一个被试的脑电模式高度相似模型见过该被试的部分数据后对剩余数据就作弊了。这是情感识别里最典型的数据泄漏形式比特征泄漏隐蔽得多因为从代码上你很难看出逻辑问题。解决以被试为分组单位划分数据用 GroupKFold 或 LeaveOneGroupOut而不是默认的 KFold。跨被试泛化才是这类任务的实际目标被试内划分的结果只能说明模型记住了个体不能说明它能识别情绪。我自己的习惯是实验一开始就用按被试划分省得后面改评估逻辑时手忙脚乱。5.3 坑3标准化用了全量统计量测试集信息提前泄漏现象先对全部样本的每个特征计算均值和方差再用这个标准化器分别处理训练集和测试集模型在测试集上表现异常好但换一批新数据部署时效果大幅下滑。原因全局统计量里包含了测试集的信息模型在训练时相当于间接看到了测试分布的均值和方差。这在 PSD 特征上尤其明显因为不同被试的功率量级差异大标准化统计量稍有偏差特征分布就整体偏移。解决标准化器只能拟合训练集测试集必须用训练集的均值和方法做转换。sklearn 里用 make_pipeline 串起 StandardScaler 和分类器再套交叉验证能天然避免这个坑这也是4.3节坚持用 pipeline 的原因。排查方法很简单把训练和测试分开标准化后准确率如果明显下降基本就是这里漏了。5.4 坑4标签阈值固定取5类别不平衡导致指标失真现象某次实验用固定阈值5切唤醒度正负样本比接近8比2模型全部预测多数类也能有80%准确率但 F1 只有0.4。原因DEAP 被试对唤醒度的打分普遍偏高固定阈值5对打分习惯偏高的被试不公平制造了天然的不平衡分布。解决改用4.2节的中位数阈值按被试切或者把任务改成回归直接预测1到9的连续分数。评估指标至少同时看准确率、F1、混淆矩阵三项准确率单指标在类别不平衡时没有任何说服力。5.5 坑5只看准确率不看混淆矩阵模型大概率在偷懒现象训练时监控准确率一路涨到85%以为收敛良好。打印混淆矩阵发现模型把82%的样本都预测为效价低剩下13%刚好预测对准确率才显得高。原因标签不平衡时准确率不是训练过程最直接的优化信号模型学到全猜多数类这种偷懒解在训练集上表现不差在真实场景里毫无用处。解决每个实验结束都打印混淆矩阵和按类别的 F1。如果某一类的召回率明显偏低先回查标签阈值和数据划分再考虑类别加权参数比如 SVC(class_weightbalanced) 或随机森林的 class_weightbalanced_subsample。打印混淆矩阵是我在所有EEG实验里的固定自检步骤花两分钟就能避免半天无效调试。6. 从实验到实用跨被试验证与滑动窗口在线推理技巧前面所有流程都是按试次为单位做离线分类但真实情感识别要面对的是连续信号流。最后补两个让实验更接近实用的技巧跨被试评估和滑动窗口推理。6.1 跨被试评估LeaveOneGroupOut 才是论文级验证from sklearn.model_selection import LeaveOneGroupOut from sklearn.metrics import accuracy_score, f1_score # 每个被试40个试次共1280个样本groups 标记每个样本属于哪个被试 groups np.repeat(np.arange(32), 40) logo LeaveOneGroupOut() accs, f1s [], [] for train_idx, test_idx in logo.split(features, labels_bin, groupsgroups): clf make_pipeline(StandardScaler(), SVC(C1.0, gammascale)) clf.fit(features[train_idx], labels_bin[train_idx]) pred clf.predict(features[test_idx]) accs.append(accuracy_score(labels_bin[test_idx], pred)) f1s.append(f1_score(labels_bin[test_idx], pred)) print(LOSO 平均准确率:, np.mean(accs), F1:, np.mean(f1s))这段逻辑是每次留出一个被试的全部40个试次做测试其余31个被试训练重复32次。报出来的平均准确率和标准差才是能写进报告、能跟论文对比的数字。随机划分的数字再好看审稿人和面试官都会追问一句跨被试结果如何。6.2 滑动窗口推理把60秒切成2秒一片在线识别才成立线下实验用整段60秒提特征没问题但真实场景里不可能等一段视频放完才给出情感判断。常见的做法是把试次切成2秒窗口、1秒步长对每个窗口独立预测最后投票得出试次级结论。def sliding_windows(trial, win_len2*128, hop1*128): trial: (40, 8064)返回 (窗口数, 40, win_len) n trial.shape[1] windows [] for start in range(0, n - win_len 1, hop): windows.append(trial[:, start:start win_len]) return np.stack(windows) # 使用示例每个窗口提特征、预测再把所有窗口的预测做多数投票 win_preds [svm.predict(feature_from_window(w)) for w in sliding_windows(trial)] trial_pred np.bincount(np.array(win_preds).astype(int)).argmax()参数说明win_len256个采样点即2秒hop128个采样点即1秒60秒信号能切出59个窗口。窗口越短实时性越好但频率分辨率会变差因为单窗FFT的频点变稀窗口越长特征越稳但起步延迟越大。在线场景一般从2秒窗开始调效果不行再往4秒加。这里有个值得记住的 trade-offWelch特征的 nperseg 不能超过窗口长度如果窗口是2秒、256个点nperseg 最多取256。所以在线系统的特征设计和离线系统往往是两套参数不能直接复用。我现在的习惯是每接到一个EEG情感识别任务先把数据管线按这套流程完整跑一遍加载、缓存、扣基线、滤波、提特征、按被试交叉验证最后打印混淆矩阵和F1。基线数字跑出来之后再决定要不要上深度学习、要不要做多模态融合。这套流程帮我挡掉了大部分翻车现场也希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑