简介:情绪识别是情感计算与脑机接口领域的核心议题,其目标是通过分析脑电信号等生理数据推断人的情感状态,在人机交互、心理健康监测、智能推荐等场景中具有重要价值。该任务的基本原理在于,不同情绪状态会引发脑电信号在频段能量、复杂度等特征上的可测差异,因此需要从原始信号中提取有效特征并构建分类模型。公开数据集DEAP为研究者提供了标准化的验证平台,但其数据解析与预处理环节常成为实践门槛。本文围绕DEAP数据集,系统梳理了从数据下载、格式解析、去基线滤波、微分熵特征提取到SVM分类的完整工程链路,并针对维度读取错误、数据泄漏、类别不均衡等常见问题给出了可复现的解决方案。适合正在入门情感计算、脑电信号分类,或计划利用公开数据集快速搭建基线的学生与工程师参考。 做情绪识别这个方向的人,几乎绕不开DEAP数据集。我第一次跑通整个sentimentclassify项目时,卡得最久的不是模型选型,反而是数据下载和解析。DEAP数据集下载下来是32个.dat文件,没有任何小白的导引文档,稍不留神维度读错,后面全部白干。这篇文章就把我完整跑通“DEAP数据下载 → 预处理 → 特征提取 → 情绪分类”的整个实操过程写出来,包括每个环节的取舍理由和踩坑记录,适合正在做情感计算、脑电信号分类、或者想拿公开数据集练手的学生和工程师参考。
1. 项目整体设计与技术路线
1.1 这个项目到底在解决什么问题
sentimentclassify的核心任务很直接:给定一段脑电信号,判断受试者当时的情绪状态是高唤醒还是低唤醒、是积极还是消极。听起来像二分类小任务,实际落地时链条很长,数据下载、格式解析、滤波去噪、特征提取、模型训练和评估,每一步都决定最终准确率。
DEAP(Database for Emotion Analysis using Physiological Signals)是目前情感计算领域引用最广的公开数据集之一,由伦敦大学玛丽女王学院等机构发布。它包含32名受试者观看40段一分钟音乐视频时的生理信号记录,每段视频结束后受试者从效价(valence)、唤醒度(arousal)、支配度(dominance)、喜欢度(liking)四个维度给自己打分,分值1到9。这个标注方式让研究者可以自由地把任务定义为二分类、三分类甚至回归,灵活性很高。
这个项目适合谁?首先是刚进入脑机接口或情感计算方向的研究生,需要一条能快速跑通的基线流程;其次是想在公开数据集上验证新特征或新模型的工程师;最后是那些想系统学一遍生理信号处理全流程的入门者。看懂这篇文章,你至少能独立完成一次 “数据到准确率” 的完整实验。
1.2 为什么选DEAP而不是其他情感数据集
情感计算公开数据集其实不少,最常被拿来对比的是DEAP、SEED和AMIGOS。SEED是国内上海交通大学发布的脑电情感数据集,采用电影片段诱发,优点是脑电通道质量高、有多次实验;缺点是样本量偏少,跨被试泛化难度大。AMIGOS是后来的多模态数据集,包含脑电、心电、面部视频等,模态丰富但早期版本标签缺失问题比较明显。
DEAP能做“街车”级基准,主要有三个原因:
- 样本量适中,32受试者×40段视频=1280个有效样本,足够训练传统机器学习模型,也够小规模深度学习模型使用;
- 模态覆盖完整,包含32通道脑电和8通道外周生理信号(眼电、肌电、皮电、呼吸、体温等),可以单独用EEG,也可以做多模态融合;
- 基线结果公开,学术界有大量可对比的论文结果,你跑出来的70%、80%准确率能直接对着文献自查。
当然DEAP也有明显短板:128Hz采样率偏低,对gamma频段以上的信息保留有限;跨受试者个体差异大,很多人在这上面做域自适应研究。但这些缺点不影响它作为入门和基线验证的首选。
1.3 技术路线的核心取舍
我采用的路线是经典的“信号处理+传统机器学习”管线,而不是一上来就端到端深度学习。完整链路是:
数据下载 → 数据解析 → 去基线 → 带通滤波 → 分窗特征提取 → 归一化 → SVM分类 → 交叉验证评估。
这套方案有三个优势。
第一,可解释性强。脑电特征和情绪状态之间的对应关系可以被逐项检验,比如高唤醒度状态下gamma频段能量是否显著升高,这种分析在论文里比神经网络的隐层输出更有说服力。
第二,调试方便。管线每一段都可以单独输出可视化,数据加载错了、滤波参数不对、特征计算偏差,都能快速定位到具体环节。
第三,计算资源友好。传统特征+SVM在普通笔记本上几分钟就能跑完一轮实验,而深度模型动辄几小时,前期探索完全没必要上重武器。
当然,深度学习后续肯定要试。我的建议是先把传统基线打到稳定,再考虑用EEGNet或LSTM去提升,这样性能对比才站得住脚。
2. DEAP数据集下载与预处理实操
2.1 数据集申请、下载与文件结构
DEAP数据集不是直接公开下载的,需要到官方页面填写申请表格,说明姓名、单位、用途等基本信息,然后等待审核邮件。审核通过后,你会收到下载地址和访问密码。
官网提供两个版本:原始版本data_original和预处理版本data_preprocessed_matlab。原始版本是512Hz采样率、未做降噪的完整记录,适合做精细信号分析;预处理版本已经降采样到128Hz、去除眼电伪迹、应用了4-45Hz带通滤波,更适合快速实验。第一次跑通流程,强烈建议直接用预处理版本,省掉大量信号清理的麻烦。
下载后解压,目录里应该是32个.dat文件,命名规则是s01.dat到s32.dat,每个文件对应一个受试者。还有一个单独的participant_ratings.dat文件,存放所有受试者的标签数据。另外官方提供一份README,里面写了详细的文件格式说明,这一步千万别跳过,我第一次就是没看文档,拿着.dat文件当纯二进制读,维度完全错乱。
2.2 解析.dat文件:从Matlab风格数据到NumPy数组
预处理版本的.dat文件实际是Matlab的MAT文件格式,可以直接用scipy.io.loadmat读取。每个受试者文件里是一个四维数组,形状为(40, 40, 8064)。
我来拆解这个维度:
- 第一个40:受试者观看的40段实验视频;
- 第二个40:每段视频记录的40个信号通道,其中前32个是脑电通道,遵循国际10-20系统,后8个是外周生理信号;
- 8064:采样点数,等于128Hz采样率乘以63秒总时长。
这里有个非常容易踩的坑:63秒不是有效的刺激时长。DEAP实验设计是3秒基线 + 60秒音乐视频播放 + 若干秒评分时间,官方预处理文件保留了前63秒的信号,其中前3秒是静息基线。如果你把全部8064个采样点都拿去计算特征,基线信号会污染情绪相关的分段。
标签存储在participant_ratings.dat,读取后是(32, 40, 4)的数组,分别对应32个受试者、40段视频、4个评分维度。评分范围1到9,对应效价、唤醒度、支配度、喜欢度。
下面是数据加载的核心代码:
import numpy as np from scipy.io import loadmat def load_subject_data(data_path): """ 加载单个受试者的DEAP预处理数据 返回: eeg信号 (40, 40, 8064), 无标签 """ mat = loadmat(data_path) # 去掉Matlab文件自带的元信息键,拿到真正的信号键 key = [k for k in mat.keys() if not k.startswith('__')][0] data = np.array(mat[key], dtype=np.float32) print(f"受试者数据形状: {data.shape}") return data def load_ratings(rating_path): """ 加载所有受试者的评分标签 返回: (32, 40, 4) 的数组,最后一维是 val/aro/dom/lik """ mat = loadmat(rating_path) key = [k for k in mat.keys() if not k.startswith('__')][0] ratings = np.array(mat[key], dtype=np.float32) print(f"标签数据形状: {ratings.shape}") return ratings一个小经验:loadmat返回的字典里包含__header__、__version__、__globals__这些元信息键,取数据键时用列表推导式过滤掉比较好,不要硬编码键名,因为不同来源的MAT文件键名可能不一样。
2.3 预处理链路:去基线、滤波与分段
拿到原始信号后,第一步是去基线。方法很简单,每个通道减去前3秒(384个采样点)的平均值,或者把前384个点整体切掉。强烈建议切掉而不只是做零均值化,因为前3秒的静息状态包含大量与情绪无关的背景活动,留在信号里反而干扰分类。
第二步是滤波。官方预处理版本虽然已经做过4-45Hz带通滤波,但如果你想从原始版本开始,或者想针对特定频段做二次研究,这一步仍要自己做。情绪识别研究中,脑电有效信息主要集中在theta(4-8Hz)、alpha(8-12Hz)、beta(12-30Hz)、gamma(30-45Hz)几个频段,45Hz以上的高频成分多为肌肉伪迹,截止频率设在这里很合理。我用的是4阶巴特沃斯滤波器,配合filtfilt做零相位滤波,避免相位偏移影响特征时序。
滤波和切段的核心代码:
from scipy.signal import butter, filtfilt def remove_baseline(trial_signal, fs=128, baseline_sec=3): """ 去除前几秒的基线信号 trial_signal: (40, 8064) 单个受试者的全部通道 """ baseline_points = baseline_sec * fs return trial_signal[:, baseline_points:] def apply_bandpass(signal, low=4, high=45, fs=128, order=4): """ 对信号执行零相位带通滤波 signal: 任意形状,最后一维是时间轴 """ b, a = butter(order, [low, high], btype='bandpass', fs=fs) filtered = filtfilt(b, a, signal, axis=-1) return filtered处理后信号的有效部分变成60秒,即7680个采样点。这个分段方式贯穿整个特征提取流程,后面所有特征都在这60秒上计算,这一点要保持一致。
2.4 标签映射与训练样本组织
DEAP的评分是1到9分,做分类任务时首先要决定类别阈值。最常见做法是以5分作为分界线,评分大于等于5记为高唤醒/积极效价,小于5记为低唤醒/消极效价。DEAP设计视频筛选时已经特意选择了能诱发不同情绪状态的片段,所以按5分切分后两个类别在整体上基本均衡。
def ratings_to_binary(ratings, score_column, threshold=5.0): """ 将1-9分评分二值化 score_column: 0=valence, 1=arousal, 2=dominance, 3=liking 返回: 0/1 标签数组 """ scores = ratings[:, :, score_column] labels = (scores >= threshold).astype(int) return labels组织训练样本时有一个经典问题:每名受试者只有40个样本,直接用全部数据训练一个通用模型,你得到的是“跨受试者模型”,分类难度大、方差高;如果单独为每名受试者训练模型,你又面临样本量不足的问题。我的做法是两种实验都做:先跑跨受试者基线,把32个受试者当作同一分布的数据混合训练,看全局效果;再跑受试者无关的交叉验证,模拟实际应用中面对新用户的情况。两种模式的准确率差异通常很大,论文里一定要写清楚自己做的是哪一种,不然别人复现时会一头雾水。
3. 特征提取与情绪分类实现
3.1 微分熵特征为什么是DEAP上的常青树
脑电特征种类很多,常见的有时域统计特征(均值、方差、峰度)、频域功率谱密度、小波系数、Hjorth参数等。在DEAP上我用了两套特征做对比,一套是微分熵(Differential Entropy, DE),另一套是功率谱密度(PSD),最终主线用微分熵,因为它在多篇文献中都被验证是脑电情绪识别最稳定的手工特征之一。
微分熵是香农信息熵在连续变量上的推广。对满足高斯分布的随机变量,计算微分熵可以得到一个非常漂亮的解析式:
DE = 0.5 × log(2πeσ²)
其中σ²是信号在某个频段上的方差。这个结论意味着:如果一个信号的包络近似服从正态分布,那么它的微分熵就等于信号本身能量(方差)的对数函数,计算量小,物理含义清晰。
实际提取微分熵特征时,做法是先对每个通道做频带滤波,把脑电切到theta、alpha、beta、gamma四个子频段,然后分别计算每个频段滤波后信号的方差,再套公式取对数,得到一个通道×频带对应的特征值。
归一化方面有个细节:不是对所有特征整体做z-score,而是每个受试者、每个频段分别做标准化,因为不同人的脑电幅值基线差异很大,全局标准化会让个别高幅值受试者主导模型训练。
3.2 完整特征提取与分类代码
下面给出一个可直接运行的完整示例,覆盖从单受试者数据到SVM分类的完整流程。
import numpy as np from scipy.signal import butter, filtfilt from sklearn.svm import SVC from sklearn.model_selection import cross_val_score from sklearn.preprocessing import StandardScaler from sklearn.pipeline import make_pipeline # 频带定义 BANDS = { 'theta': (4, 8), 'alpha': (8, 12), 'beta': (12, 30), 'gamma': (30, 45), } def band_filter(signal, low, high, fs=128, order=4): b, a = butter(order, [low, high], btype='bandpass', fs=fs) return filtfilt(b, a, signal, axis=-1) def extract_de_features(trial_signal, fs=128): """ trial_signal: (32, 7680) 已去基线的EEG信号 返回: (32 * 4,) 的微分熵特征向量 """ features = [] for ch_idx in range(trial_signal.shape[0]): channel = trial_signal[ch_idx] for band_name, (low, high) in BANDS.items(): filtered = band_filter(channel, low, high, fs) variance = np.var(filtered) de_value = 0.5 * np.log(2 * np.pi * np.e * variance + 1e-8) features.append(de_value) return np.array(features) # 假设已经加载了 data 和 labels # data: (40, 40, 7680),labels: (40,) 二分类标签 def build_feature_matrix(data): X = [] for trial_idx in range(data.shape[0]): eeg_trial = data[trial_idx, :32, :] # 只取前32个EEG通道 feature_vector = extract_de_features(eeg_trial) X.append(feature_vector) return np.array(X) X = build_feature_matrix(data) y = labels # 使用标准化的SVM分类器,5折交叉验证 clf = make_pipeline( StandardScaler(), SVC(kernel='rbf', C=1.0, gamma='scale', class_weight='balanced') ) scores = cross_val_score(clf, X, y, cv=5, scoring='accuracy') print(f"5折交叉验证准确率: {scores.mean():.4f} ± {scores.std():.4f}")有几个地方要展开说。
class_weight='balanced'这行不能省。虽然DEAP整体类别分布接近均衡,但单独一个受试者的40段视频里,高低唤醒的比例可能偏向某一边,加上这个参数能自动修正类别权重,防止小样本场景下模型偏向多数类。
特征计算里的1e-8是防止方差为0导致log(0)报错。脑电信号就算静息状态也几乎不可能方差为0,但浮点运算下极端情况仍然存在,加一个小常数成本极低,属于写代码留余量的习惯。
gamma='scale'是sklearn的自动缩放模式,它会根据输入特征数量调整gamma值,比手动指定gamma更适合特征维度较高的场景。
3.3 分类模型选型与参数细节
分类器我用过很多种,在DEAP上实测下来,SVM的径向基核版本表现最稳,接近70%-75%的准确率区间。随机森林也能跑,但需要更多调参,而且在小样本下容易过拟合。深度学习方面,我用LSTM做过序列分类,也用过1D CNN直接吃原始波形,效果不是不好,只是对计算资源和训练时间的要求和传统方法不在一个量级。
为什么SVM在DEAP上表现好?一个重要原因是样本量小。1280个样本(全部受试者混合)对应128维特征,传统SVM的软间隔机制不容易过拟合,而且RBF核能够捕捉特征之间的非线性关系。另一个原因是脑电特征在高维空间里往往呈现较清晰的类别边界,SVM擅长处理这种“中等维度、边界分明”的问题。
SVM的三个关键超参数:C控制误分类惩罚,C太大容易过拟合,C=1是稳妥起点;gamma控制RBF核的作用范围,scale模式自动按1/(特征数量×特征方差)计算,适合特征尺度差异大的情况;kernel='rbf'是默认首选,线性核在小样本上容易出现欠拟合。
如果数据集换成SEED或你自己的采集数据,参数需要重新调节,不要直接照搬。调参顺序建议是:先用默认参数跑出基线,再画学习曲线判断是过拟合还是欠拟合,最后用网格搜索微调C和gamma。
3.4 结果评估与基线数据对照
评估指标不能只看准确率,特别是类别不均衡的时候。DEAP本身还算均衡,但单受试者模型下完全可能出现某类样本只有10个的情况,此时F1分数比准确率更有参考意义。我习惯同时输出准确率、F1和混淆矩阵,表格化保存。
在我自己的实验里,DE特征+SVM在跨受试者、二分类效价任务上大约能到71%左右,唤醒度任务约69%。这个数字基本符合目前DEAP传统方法的报告区间。如果模型准确率只有55%上下,先不要怀疑模型,大概率是数据或特征环节出了问题,排查方向见第4部分。
需要特别提醒的是,如果你想和论文结果做对比,务必看清楚对方用的是“单被试内随机划分”还是“跨被试划分”。同一个数据集、同一套特征,这两种实验设置的结果可能相差10个百分点以上。随机划分让同一受试者的不同视频同时进入训练集和测试集,模型容易记住受试者个体特征,虚高不少;跨被试划分才更接近真实场景。
4. 常见问题与调优经验实录
4.1 数据加载与格式转换容易踩的坑
第一个坑是loadmat版本问题。Matlab R2014以后默认保存格式是 v7.3,也就是HDF5格式,scipy的loadmat读不了。DEAP官网的预处理文件是老版本格式,直接用没问题,但如果你自己用新版Matlab二次保存过,加载时就会报错。遇到这种情况,改用h5py读取,注意HDF5读出来的数据维度是反的,需要做一次转置。
第二个坑是数据类型。loadmat读出来的数组默认是float64,而DEAP预处理文件本身是浮点数,不会溢出,但当你构造全数据集时,(1280, 40, 8064)的float64数组直接占用约3.3GB内存。建议加载后就转成float32,内存直接减半,对分类精度几乎没有影响。
第三个坑是硬编码键名。不同渠道下载的DEAP文件内部键名可能因为处理工具不同而略有差异,我都是用过滤__前缀的方式动态找键,避免踩死。
4.2 特征与训练环节的隐藏陷阱
最大的隐藏陷阱是数据泄漏。我见过很多初学者的代码,在交叉验证之前就对全量特征做了标准化,然后在每一折训练时再用这份标准化参数。这相当于让模型在训练阶段“看到”了测试集的分布,交叉验证结果虚高,但换到真实新数据上立刻打回原形。正确做法是用sklearn.pipeline把标准化和分类器封装在一起,让每一折交叉验证都在训练子集上重新fit标准化器。
第二个陷阱是特征计算的顺序。很多教程把滤波放在特征提取之前就全部做完,但如果你的特征是基于频带滤波之后的信号计算方差,那滤波操作本身就应该在特征提取函数内部对每个频段单独完成,而不是先做一次全频带滤波再算特征。两者的物理意义完全不同。
第三个陷阱是死盯着准确率不看分布。DEAP的受试者评分主观性很强,同样是高唤醒度的标签,有人打7分有人打5分,类别边界本身是模糊的。这种场景下单独提高分类阈值或复杂度,收益很有限,不如先看混淆矩阵里是哪一类容易被混淆,再针对性地做类别加权或样本重采样。
4.3 分类准确率卡在50%左右的排查思路
准确率在50%附近的实验结果基本等于随机猜测,这种情况下模型和数据都有嫌疑。我整理了排查顺序:
第一步检查标签对齐。确保第n条样本的特征来自第n段视频,评分文件的行列索引对不对。可以把某几个trial的标签打印出来,人工抽查几个高分视频和低分视频的特征均值,看是否有趋势差异。
第二步检查基线是否去除。如果保留了前3秒基线,特征会被静息信号稀释,情绪相关的频段差异被平均掉,准确率会明显下降。
第三步检查类别均衡。打印标签分布,如果某个受试者全部视频都是“高唤醒”,那测试集准确率再高也没有意义,改用F1评估。
第四步做特征可视化。把DE特征用t-SNE降到2维,按标签着色,如果两类点在图上完全重叠,说明特征根本无法区分情绪状态,这时再换模型也没用,应该回头检查特征提取公式或频段选择。
第五步做模型自检。用随机打乱的标签训练同一个模型,如果准确率仍然在50%附近,说明特征管线本身没有问题,问题出在标签或特征的对齐上。
4.4 一张表总结避坑清单
我把实操中遇到的高频问题整理成表格,方便排查时对照。
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| loadmat报错或键找不到 | MAT文件是v7.3格式 | 改用h5py读取并对转置 |
| 内存不足 | 全量数据用float64保存 | 转成float32,逐受试者加载 |
| 准确率接近50% | 标签对齐错误或基线未去除 | 检查标签索引,确认去除前3秒 |
| 训练准确率98%、测试55% | 标准化时数据泄漏 | 用pipeline封装标准化和分类器 |
| 单受试者模型效果忽高忽低 | 样本量太少且类别不均衡 | 加class_weight,用F1评估 |
| 结果和论文对不上 | 实验设置不同(被试内/跨被试) | 明确实验协议,对比同协议结果 |
5. 写在最后的实操体会
把这个流程完整跑通之后,我最深的感受是:DEAP上真正决定成败的不是模型,而是数据处理的每一处细节。同样的原始数据,去不去基线、特征在哪个频段计算、标准化是不是放在交叉验证外,都会带来几个百分点的差异。我第一次只花一个晚上就让SVM准确率从53%提到了68%,没有改任何模型,做的就是上述这些预处理修正。
如果你接下来想继续深入,我建议三个方向。第一,把DE特征换成时频图,用CNN或EEGNet直接学习空间和时间特征,这是目前EEG深度学习的主流路线。第二,在跨受试者任务上尝试域自适应方法,比如对抗训练或特征对齐,DEAP是这类方法最常用的验证平台。第三,把后8路外周信号也加进来做多模态融合,EEG加皮电加肌电的组合往往比单用EEG更接近真实情绪状态。数据已经在本地了,下一步就看你想往哪个方向掘进。
本文还有配套的精品资源,点击获取