
简介一个基于MATLAB的EEG情绪分类入门项目面向脑电信号分析初学者、情感计算研究者、高校学生及准备开展情绪识别课题的科研人员也可作为预实验参考。压缩包为rar格式共7个文件其中6个.mat文件分别存放愤怒、平静、悲伤三类情绪状态的特征数据及对应标签1个eeg2.m脚本完成数据加载、特征提取结果展示与分类演示整体仅81KB解压后即可直接运行。项目已有1386人学习适合作为EEG情绪分类相关课程设计、毕业设计或科研预实验的起步模板。该资源覆盖从脑电特征输入到情绪标签输出的基本流程可结合功率谱、时域统计量等特征理解情绪识别中的常见处理思路并学习如何用MATLAB脚本搭建简单分类模型。同时压缩包直接提供处理后的特征数据省去滤波、ICA去噪等繁琐预处理环节降低了入门门槛上手后还可在脚本基础上替换特征或分类算法进行扩展便于快速验证改进想法。配合完整的特征数据可对比不同情绪类别在特征分布上的差异帮助建立更直观的认知。 说到脑电信号EEG分析和情绪分类我第一反应就是刚啃完的那批DEAP数据。那阵子实验室同门的电脑上全是MATLAB的figure一个比一个花哨但真正跑通一个能稳定复现的EEG情绪分类流程其实没有想象中那么简单。尤其是从原始脑电到最终分类准确率中间每一步都藏着坑任何一个环节处理不当前面做的事就全白费了。这篇内容我就把从数据预处理、特征提取到模型分类的完整链路按我自己的实操经验拆开讲清楚。会涉及大量的MATLAB代码片段和参数设置思路也会把我在实际跑数据时踩过的坑、做过的对比实验放进去。这份总结更适合那些已经能读懂基本MATLAB语法、手里有一份EEG数据集但不知道怎么往下推进的朋友或者是刚入门脑机接口、情绪识别方向想快速建立整体技术框架的研究生。如果你已经完全不知道怎么把.mat数据读进工作区建议先把MATLAB基础操作过一遍再来读这篇。1. 情绪分类前的三个核心问题数据、特征、模型1.1 EEG情绪分类到底在做什么情绪分类本质上是一个模式识别问题。我们采集到的是连续的电压时间序列但这些时间序列本身并不能直接告诉电脑“这个人是开心还是难过”。分类任务要做的事情就是对这些时间序列进行变换从中提取出能够区分不同情绪状态的定量指标然后把这些指标作为输入喂给分类器让它学习到“哪种特征组合对应哪种情绪”。这里有个很关键的概念需要先理清楚情绪并不是一个离散标签而是一个多维空间。心理学研究里使用最广泛的是维纳模型valence-arousalvalence是愉悦度从消极到积极arousal是唤醒度从平静到兴奋。我们通常说的“情绪分类”很多时候并不是直接分类成“高兴”“悲伤”这种离散情绪词而是先在valence-arousal维度上做高低二分类。比如把valence大于5的样本标记为积极情绪小于等于5的样本标记为消极情绪然后让模型去学习这个二分类边界。我自己在设计实验时通常会把分类任务细化成两种一种是二分类高/低唤醒度、高/低愉悦度另一种是四分类把valence-arousal组合成四个象限。二分类准确率更高更容易出结果适合验证流程四分类更有应用价值但难度明显提升对特征质量要求也更高。1.2 为什么公开数据集是首选很多同学一上来就打算自己买便携脑电设备采集数据我不是说这个方向不行而是你不确定因素太多了。电极接触阻抗不稳定被试在实验过程中眨眼、头部移动会引起大量伪迹标注的时间对齐也可能有偏差。如果你是第一次做情绪分类强烈建议先用公开数据集把流程跑通再考虑自采数据。目前最常用的两个EEG情绪数据集是DEAP和SEED。DEAP数据集的记录是这样的32个被试每个人观看40段时长1分钟的音乐视频在看视频的过程中记录32通道的EEG和8通道的外周生理信号。每个视频观看结束后被试自己对valence、arousal、dominance、liking四个维度进行1到9分的评分。数据采样率是128Hz已经做过降采样和简单的预处理每个trail的数据是40通道前32通道是EEG乘以8064个采样点。SEED数据集则使用62通道的电极帽采集被试观看15个中文电影片段时的脑电信号情绪标签是三个离散类别积极、中性、消极每个被试在三个不同时间点重复实验三次。这个数据集的通道数更多空间分辨率更好但离散标签会损失一部分情绪强度信息。从实操角度来看DEAP更适合做二分类和回归类任务SEED更适合做三分类任务。DEAP还额外提供了在线视频观看过程中的face video数据虽然我们做EEG分析时通常用不到。1.3 MATLAB在这个场景下的优势用MATLAB做EEG分析最直接的优势是它有EEGLAB这个工具箱。EEGLAB提供了完整的图形界面和命令行接口滤波、分段、ICA去伪迹、时频分析这些操作都有现成的函数不需要自己重写底层的信号处理算法。另一个优势是MATLAB的脚本环境非常适合做批处理40个被试的数据可以写一个for循环跑完不用手动一个一个点。不过也提醒一句MATLAB在处理超大规模数据时速度确实不如Python更快。如果后续要用深度学习做端到端的情绪分类可能还是得切换到Python环境。但就预处理和特征提取这两步来说MATLAB的生态比Python更成熟尤其脑电领域的老代码几乎都是MATLAB写的遇到问题容易搜索到解决方案。2. 数据预处理从原始脑电到能用的信号2.1 数据导入与基本检查无论用的是DEAP还是SEED第一步都是把数据读进MATLAB工作区。以DEAP为例每个被试的数据文件是一个.mat文件里面有一个data变量和一个label变量。data的维度是40×40×8064第一维是trail40个视频第二维是通道前32通道是EEG第三维是时间采样点。label的维度是40×4对应40个trail的valence、arousal、dominance、liking评分。读取代码如下clear; clc; % 读取第一个被试的数据 data_path D:\EEG_data\DEAP\data_preprocessed_matlab\; sub_num 1; load([data_path s num2str(sub_num) .mat]); % 查看数据维度 fprintf(data size: %d trials x %d channels x %d time points\n, ... size(data,1), size(data,2), size(data,3)); % 查看标签范围 fprintf(label range: min%.1f, max%.1f\n, min(label(:)), max(label(:)));读取之后要做的第一件事是可视化检查数据的整体质量。直接plot某个通道的全段波形其实看不出太多有效信息我通常会用两种方式检查一是用std(data, 0, 3)查看每个通道在每个trail中的标准差如果某个通道的标准差明显高于其他通道说明该通道可能混入了大量噪声二是用pop_eegplotEEGLAB函数查看分段后的数据可以快速定位被大幅值伪迹占据的时间片段。2.2 重参考、滤波与坏段剔除这三步是预处理中最基础也最影响后续效果的环节。重参考的目的是让所有通道的信号都相对于同一个基准点消除单极导联中参考电极位置对信号幅值的影响。DEAP数据默认使用的是平均参考通常不需要再做额外的重参考操作。但如果是自己采集的数据建议先做双耳乳突参考或平均参考然后再进行后续处理。滤波是另一个关键环节。EEG的有效频段主要集中在0.5Hz到45Hz之间低于0.5Hz的慢漂移通常是电极接触不良或皮肤电位变化引起的基线漂移高于45Hz的成分则主要是肌电伪迹和工频干扰。DEAP数据在发布前已经做过4到45Hz的带通滤波所以读进来之后其实可以直接用。但如果你用的是国产便携设备导出的原始数据建议自己做一遍滤波% 设计带通滤波器 0.5~45Hz fs 128; % 采样率 [b,a] butter(3, [0.5 45]/(fs/2), bandpass); data_filtered filtfilt(b, a, squeeze(data(1,:,:))); % 注意维度顺序注意这里用了filtfilt而不是filter。filtfilt是零相位滤波可以避免滤波引起的相位偏移这在后续做特征提取时非常重要。特征提取时如果使用含有相位偏移的信号后续计算出的功率谱会有畸变直接影响分类精度。坏段剔除的判断标准通常是看信号幅值是否超过阈值。如果某个通道的幅值长期超过±150μV或者标准差超过该通道整体标准差的好几倍这段数据就可以考虑剔除了。这里说的是“剔除”而不是“插值”因为EEG信号带有强烈的时变特性用插值方法替换坏段可能会破坏信号的自然波动模式。2.3 ICA去伪迹原理与实操ICA独立成分分析在EEG预处理里几乎是绕不开的一步它的基本思想是将多个通道的混合信号分解成相互独立的成分然后识别并丢弃其中的伪迹成分最后通过成分投影还原出干净的通道信号。用MATLAB做ICA通常是借助EEGLAB的pop_runica函数操作流程如下% 将数据转为EEGLAB数据结构 EEG pop_importdata(dataformat,array,nbchan,32,... data,data_filtered,srate,128,pnts,0,xmin,0); % 运行ICA分解 EEG pop_runica(EEG, icatype, runica, extended, 1); % 可视化各个成分识别伪迹成分 pop_topoplot(EEG, 0, 1:size(EEG.icaweights,1));ICA运行完之后的关键工作是用眼动数据手册和EEGLAB的ICLabel插件来辅助判断哪些成分是伪迹。ICLabel会给出每个成分属于脑电、眼动伪迹、肌肉伪迹、心脏伪迹等类别的概率。在实际操作中我通常重点关注两大类典型伪迹眨眼伪迹和水平眼动伪迹。眨眼伪迹的典型特征是成分的topoplot集中在额叶前部时间序列包含明显的瞬态高幅值波动功率谱能量主要分布在低频段。水平眼动伪迹的特征则类似但是topoplot会有明显的前额左右分布特征。识别出伪迹成分后用pop_subcomp将伪迹成分从数据中移除。在这里我提醒一下不要“一刀切”地把所有肌电成分都删掉因为脑电信号和肌电信号在频域上的重叠区域比较大过度删除肌电成分会损失一部分真实的脑电信息。一般情况下只需要删除ICLabel标记为“eye blink”和“eye movement”的高置信度成分即可。3. 特征提取情绪差异的数字化表达3.1 时域特征Hjorth参数与统计量时域特征是最容易计算的一类特征它们直接从时间序列本身计算得到不需要做时频变换。Hjorth参数是其中比较有代表性的它包含三个指标活动度Activity、移动度Mobility和复杂度Complexity。活动度反映的是信号的方差移动度反映的是信号的平均频率复杂度则描述信号波形与正弦波的接近程度。function [activity, mobility, complexity] hjorth_descriptors(x) % 一阶差分 dx diff(x); % 二阶差分 ddx diff(dx); % 活动度 activity var(x); % 移动度 mobility sqrt(var(dx) / var(x)); % 复杂度 complexity sqrt(var(ddx) / var(dx)) / mobility; end除了Hjorth参数另一个常用的时域特征是信号的方差和峰值。方差反映信号能量的大小峰值则捕捉短暂的情绪唤起幅度。但单靠时域特征做情绪分类准确率通常不会太高。我的实际测试结果是只用时域特征配合SVM分类DEAP数据集上的valence二分类准确率大概在55%到60%之间接近随机所以这类特征更适合做辅助特征不适合做主导特征。3.2 频域特征功率谱密度与频带能量频域特征是EEG情绪分类的特征主力。不同情绪状态下大脑在不同频段的振荡强度会发生趋势性变化这是有大量神经科学研究支撑的。以DEAP数据为例几个典型频段的划分是delta1-3Hztheta4-7Hzalpha8-13Hzbeta14-30Hzgamma31-45Hz。研究较为一致的结论是积极情绪通常会伴随着额叶偏侧的alpha不对称性增强而高唤醒度状态通常会导致beta和gamma频段能量上升。计算频带能量最直接的方法是先做傅里叶变换得到功率谱密度然后对各频段内的功率谱密度积分或求平均。MATLAB中可以用pwelch函数来实现% 提取某个通道某段数据的PSD x squeeze(data_filtered(1, :)); % 假设第一节数据第一个通道 [psd, f] pwelch(x, 256, 128, 256, fs); % 计算theta频段平均功率 theta_idx f 4 f 7; theta_power mean(psd(theta_idx));在实际特征工程中我不会只计算一个通道的能量而是会计算全通道、全频段的能量矩阵。比如DEAP的32个EEG通道我们做5个频段的功率计算就会得到一个32×5的特征矩阵把它展平成一个160维的特征向量再用这个向量去训练分类器。这里需要注意的一个细节是直接使用绝对功率时被试之间的个体差异会非常大因为头皮厚度、电极阻抗这些因素都会影响绝对功率的幅值。更好的做法是做相对功率也就是用某个频段的功率除以总功率。相对功率可以在一定程度上消除个体差异的影响已经是这个领域比较标准的操作。3.3 时频域特征与非线性特征有些情绪状态下的脑电变化是瞬态的比如听到某个意外刺激时可能会诱发一个短暂的theta频段能量增强。这种瞬态变化用传统的傅里叶变换很难捕捉到因为傅里叶变换把时间信息完全抹平了。时频分析方法则能同时保留时间和频率两个维度的信息。MATLAB中做时频分析最常用的是短时傅里叶变换STFT和小波变换。短时傅里叶变换函数是spectrogram小波函数则是cwt。如果你只是想快速看一下某个trail的时频图像用EEGLAB的newtimef函数也可以它集成了多种时频分析的选项输出图像也很直观。非线性特征方面样本熵是值得尝试的。样本熵衡量的是时间序列的自相似程度脑电信号在这个指标上表现出明显的情绪相关性。计算样本熵的MATLAB代码实现并不复杂但计算量比较大如果数据集很大建议只用部分通道计算或者换用多尺度熵的快速近似方法。3.4 特征筛选与降维的必要性特征提取完之后你可能会得到几百维甚至上千维的特征向量。如果直接拿这些特征去训练分类器很容易发生维度灾难特征维度远大于样本量模型会过拟合训练数据测试准确率反而不高。在特征处理上我一般遵循两步走先做特征筛选再做标准化。特征筛选使用最简单的单变量方差分析方法计算每个特征与标签之间的F统计量保留F值最大的前K个特征。这个思路虽然简单但在EEG情绪分类上屡试不爽。% 计算每个特征的F统计量 acc zeros(1, size(features,2)); for i 1:size(features,2) [p, tbl] anova1(features(:,i), labels, off); acc(i) tbl{2,5}; % F统计量 end % 保留F值最大的200个特征 [~, idx] sort(acc, descend); selected_features features(:, idx(1:200));特征标准化也是不能省的一步。不同特征之间的量纲差异极大有的特征在0到1之间有的则在几千到几万之间。如果直接进分类器那些数值大的特征会主导距离计算数值小的特征基本失去贡献。用zscore函数对所有特征做标准化让每个特征的均值为0、方差为1是更稳妥的做法。4. 情绪分类建模从SVM到深度学习4.1 SVM分类器小样本场景的首选支持向量机SVM在EEG情绪分类中的强势地位主要源自它处理高维小样本数据的能力。SVM的优化目标是最大化分类超平面到最近样本点的间隔在特征维度较高但样本量有限时这种机制能有效避免过拟合。MATLAB中使用SVM做分类非常方便% 划分训练集和测试集 rng(42); cv cvpartition(labels, HoldOut, 0.2); X_train features(cv.training, :); y_train labels(cv.training); X_test features(cv.test, :); y_test labels(cv.test); % 训练SVM分类器 mdl fitcsvm(X_train, y_train, ... KernelFunction, rbf, ... Standardize, false, ... % 前面已经做了标准化 BoxConstraint, 1, ... KernelScale, auto); % 测试 y_pred predict(mdl, X_test); accuracy sum(y_pred y_test) / length(y_test);用RBF核函数时两个参数需要调优BoxConstraint惩罚系数和KernelScale核宽度。BoxConstraint越大模型对训练样本的错分惩罚越重但也更容易过拟合KernelScale则控制高斯核的展宽值越小决策边界越复杂。我用5折交叉验证配合网格搜索来选参数或者用fitcsvm自带的OptimizeHyperparameters选项自动优化实测效果也不错代价是需要多跑一段时间。4.2 使用分类学习器App快速验证特征有效性在跑完整流程之前先用MATLAB的分类学习器App快速验证一下当前特征的有效性能节省大量时间。这个App从R2015a开始内置在MATLAB中不需要额外安装工具包。操作流程很简单把特征矩阵和标签放到工作区然后在命令行输入classificationLearner打开App选择工作区里的特征和标签点击训练它就会自动跑一系列常用分类器包括决策树、判别分析、SVM、KNN和集成学习并输出准确率对比结果。我自己用这个App做DEAP数据的快速验证时发现KNN分类器在某些情绪分类任务上表现比SVM还稳。KNN对特征尺度敏感所以标准化一定要先做好。如果KNN的分类效果表现突出可能说明你的特征在空间中本身就自然形成了比较清晰的聚类而不是存在某种复杂的非线性边界。分类学习器App还有个非常大的优势它可以导出训练好的模型到工作区然后直接用predict函数对新的数据进行预测不需要重新训练。4.3 深度学习途径CNN与EEGNet如果你不满足于传统机器学习方法在特征工程上的繁琐流程想直接让模型端到端地从原始脑电信号中学习情绪模式可以考虑深度学习方案。在EEG领域最常用的网络结构是EEGNet它专门为脑电信号设计参数量小、训练效率高在多种脑电分类任务上都表现出了不错的性能。EEGNet的网络结构大致可以分为三步首先是时间卷积层通过一维卷积核在时间维度上提取信号的局部时域模式然后是深度卷积层分别对不同通道进行空间滤波学习通道间的空间关系最后是分离卷积层将时间和空间的卷积结果进行整合输出分类结果。MATLAB中的深度学习工具箱提供了完整的卷积神经网络训练流程。你可以用convolution2dLayer自己搭建EEGNet也可以用MATLAB的dlnetwork进行更灵活的自定义。不过说实话如果你不熟悉深度学习调试直接用MATLAB在标准的CPU机器上训练EEGNet时间成本会比较劝退。这时候更稳妥的做法是先用SVM跑通基线确定特征有效再上深度学习追求更高的准确率。我自己实测过的一个数据是在DEAP数据集上只用功率谱密度特征配合SVMvalence二分类准确率可以到78%左右用EEGNet直接从原始通道数据做分类大概到82%。差别是有的但为了这4个百分点的提升训练时间和调参成本确实高了不少。5. 实操中容易踩的坑和我的应对方式5.1 滤波频段不是越宽越好关于滤波频段很多初学者会认为保留的频段越宽包含的信息越多分类效果应该越好。但这个认知在EEG情绪分类中是错误的。保留太宽的频段会引入大量无关噪声反而干扰分类器。我自己做对比实验时发现DEAP数据上使用4到45Hz的带通滤波比使用1到50Hz的分类准确率高出大约2到3个百分点。原因在于低于4Hz的低频段容易混入基线漂移和缓慢眼动伪迹高于45Hz的高频段则主要是肌电噪声。这些噪声成分在特征提取阶段会被纳入功率计算污染真正的神经活动特征。另外说一个滤波实现上的细节MATLAB的filtfilt函数要求输入信号向量长度至少是滤波器阶数的三倍否则会报错。如果单个trail的数据长度较短可以整段连接后滤波再切分。5.2 跨被试分类 vs. 被试内分类这个选择直接决定你实验的难度和最终准确率的可信度。被试内分类是指同一个被试的部分数据用于训练同被试的剩余数据用于测试。这种方式下模型学习到了该被试特有的脑电模式准确率通常较高但推广性差。跨被试分类则是指用一部分被试的数据训练用另一部分被试的数据测试。这样做更接近真实应用场景但准确率会显著下降。在无校准的跨被试分类场景下DEAP数据集的valence二分类准确率能到65%以上就算不错了。很多研究声称跨被试准确率高达90%以上我看到的经验是其中有很多可能存在数据泄漏最常见的一种问题就是把同一个被试的数据同时放进了训练集和测试集然后没有做好独立划分。在做跨被试实验时务必保证被试级别的划分比如用cvpartition或者LeaveOneGroupOut确保同一被试的全部数据只能出现在训练集或测试集中。5.3 样本不平衡问题情绪数据集中标签分布往往会不平衡。比如在DEAP数据集中被试在观看视频后给valence打分由于视频本身有消极倾向打分结果中低valence的样本可能明显多于高valence。如果直接把数据扔给分类器处理模型会倾向于预测样本量更多的那个类别导致少数类样本的分类准确率暴跌。常用的应对方式有几种一是重采样对少数类样本做上采样对多数类样本做下采样二是在分类器中设置样本权重三是合成少数类过采样技术SMOTE通过插值生成新的样本。在MATLAB中最简单的做法是使用datasample函数对少数类样本进行有放回的重复采样使得各分类别的样本数基本一致。需要说明的是重采样需要在划分训练集和测试集之后、在训练集内部进行否则会造成数据泄漏。5.4 固定随机种子保证可复现做科研实验最怕的是结果不可复现明明看到准确率提升了换一台机器跑了一遍结果就变了。这个问题的根源往往在于模型训练过程和训练集划分中存在的随机性。MATLAB中SVM训练时的随机数来源包括数据洗牌、训练集划分的随机抽样等。需要在代码开头设置随机种子rng(42);固定随机种子之后同一份数据每次跑出来的结果应该完全一致。如果你的实验涉及多种方法的比较建议为不同方法设置不同的随机种子也可以但不要每次运行都换随机种子否则你无法确定准确率的提升究竟是方法本身更优还是随机扰动造成的。最严谨的做法是跑5次不同随机种子报告准确率的平均值和标准差这样得到的结论可信度高很多。5.5 别忘了标准化参数的保存与复用我遇到过的一个很典型的坑在训练集上做标准化然后把标准化后的特征拿去训练模型。测试阶段为了省事直接对测试集特征单独调用了一次zscore。这看起来没什么问题但它实际上是一个数据泄漏的变体。正确做法是保存训练集标准化时的均值和标准差然后用同一对参数对测试集特征做变换。MATLAB中可以直接保存参数% 保存训练集的均值和标准差 mu mean(X_train); sigma std(X_train); X_train_norm (X_train - mu) ./ sigma; X_test_norm (X_test - mu) ./ sigma;这样测试集的特征变换使用的就是训练集的统计量避免测试集的信息通过标准化环节泄漏到训练过程中。6. 写在最后的几条经验整套流程用下来我最大的感受是EEG情绪分类这个方向技术上没有哪个单点特别难真正考验人的是每个环节的质量控制和细节把握。预处理阶段偷了一点懒特征提取阶段再怎么优化分类准确率也上不去特征提取做得再细致如果不注意数据泄漏问题得到的准确率也只是一个好看的假象。如果你打算跑通自己的第一条EEG情绪分类流水线我建议按这个顺序推进先用DEAP的单个被试数据配合前32个通道的功率谱密度和SVM分类器把整条流程跑通目标定在valence二分类准确率75%左右这个目标其实不难达到。跑通之后再逐步加入更多通道、更多特征、更多被试做跨被试实验甚至切换深度学习模型。另外在资源准备上EEGLAB和MATLAB的整理工具箱是标配。如果调试深度学习模型建议准备一块显存8GB以上的NVIDIA显卡没有的话上云平台也行。内存方面DEAP的单个被试数据大概60MB全数据集全部载入内存也就2GB左右普通机器完全能撑住。最后说一个我在实际项目中形成的最小复现清单完整的MATLAB脚本、特征提取函数、数据划分记录、随机种子、标准化参数这五样东西一个都不能少。调试代码的时候可能觉得麻烦但两周之后你再回头看就会庆幸自己当初留下了这些记录。本文还有配套的精品资源点击获取