☰
DEAP与SEED-IV的EEG情绪识别:通道级SVM与小波特征融合
2026/11/4 2:48:44 网站建设 项目流程

简介:一份面向脑机接口与情感计算方向学习者、尤其适合毕业设计选题参考的PDF论文,围绕DEAP与SEED-IV两个公开EEG数据库开展情绪检测研究。论文采用离散小波变换将预处理信号分解为五个频段,提取功率、能量、微分熵与时域特征,并构建通道级SVM分类器及通道组合策略识别二维模型中的情绪状态;DEAP四类分类率为74%、86%、72%、84%,SEED-IV为79%、76%、77%、74%。资源包仅含1个PDF文件,大小约701KB,轻量便于查阅与引用。已有347人学习,可作为情感计算入门、EEG特征工程与SVM分类实验设计、论文写作及复现思路的参考材料,帮助读者快速把握公开数据集的实验流程、特征选择与结果对比框架;文中对相关公开数据集的梳理与实验设置说明,也便于对照理解频段划分、通道选择对识别效果的影响。

1. 需求与选型:为什么情绪识别毕设要把数据源放在 DEAP 和 SEED-IV 上

做一份基于 EEG 的情绪检测毕设,答辩现场第一个被追问的往往不是模型,而是数据从哪来。自己拉十几个同学戴头环采数据,流程看着完整,一旦被问「标签谁定的、试次之间留没留清洗期、受试者间差异怎么算」,多半答不上来。所以我定题时习惯先把数据源固定在公开库上:DEAP 提供 32 名受试者、32 通道、500Hz 的脑电,每人 40 段一分钟视频试次;SEED-IV 提供 64 通道、1000Hz、15 名受试者分三天做的 3 次会话,合计 45 份会话数据。这套方案要做的事很明确——在效价与唤醒构成的二维模型上把情绪切成 HAHV、HALV、LALV、LAHV 四类,用离散小波变换抽出五个频带的特征,交给通道级 SVM 分类后再做通道融合。它适合手里没有采集设备、又必须跑出能写进论文的指标的情感计算或脑机接口方向毕设与课设。

2. 总体方案设计:从原始脑电到四象限情绪的六段流水线

整条链路可以拆成数据获取、事件切分、预处理、特征提取、通道级分类、通道融合六段,前后严格串行。之所以强调「串行」,是因为这条链路上任何一段的参数没锁死,最后四分类的准确率就没法解释——你分不清是特征好还是标签泄漏。我一般会在动手写代码之前,把每一段的输入输出形状、采样率、通道数先写成一张约定表,后面所有脚本都按这张表对齐。

2.1 数据获取与事件切分:两套库的协议差异必须先摆平

DEAP 的采集协议是:受试者观看 40 段时长各一分钟的音乐视频,同步记录 32 通道脑电,采样率 500Hz,采集完成后由受试者对自己的效价、唤醒度做自评,数据集再依据情绪轮给出预标注。SEED-IV 的协议不同:15 名受试者在不同的三天各做一次会话,每次会话观看 24 段约两分钟的影片片段,用 64 通道设备以 1000Hz 采样,因此名义上是 15 名受试者,实际可用的是 45 份会话数据。这个「受试者数 ≠ 会话数」的差别,是后面划分训练集时最容易搞混的地方。

采样率的选择有一条硬性下限:脑电的有效成分集中在 5Hz 到 70Hz,采集设备的采样率必须高于 150Hz 才不会混叠,DEAP 的 500Hz 和 SEED-IV 的 1000Hz 都远高于这条线。方案里我通常把 SEED-IV 降采样到 500Hz 与 DEAP 对齐,好处是两套库可以共用同一套滤波参数和同一套小波分解层号,省掉大量条件分支,代价是丢掉 250Hz 以上的成分——对情绪识别这种分钟级的慢变任务,这部分信息本来也用不上。

事件切分这一步,两套库的起点不一样。DEAP 拿到的是连续记录,需要按采集协议把每一段视频对应的 60 秒切出来,转成 mat 文件;切的时候要注意视频播放和设备打点之间通常有几秒的固有延迟,业内常见的做法是每段前后各留 3 秒余量再截取中间段,把边界上的过渡态丢掉。SEED-IV 提供的就是已经按协议切好的 mat 文件,事件是预先分离的,直接读即可。两套库最终要保证通道数一致——方案里统一取 32 个通道参与建模,SEED-IV 多出来的通道在下游不参与融合,这样通道级分类器的编号才能对得上。

注意:事件切分之后先别急着做特征,先打印一遍形状。DEAP 应该是(受试者数 × 事件数,通道数,采样点数),SEED-IV 同理。形状对不上,后面所有调参都是在错误的前提下做的。

2.2 预处理链路:三个滤器各管一段噪声

原始脑电里混着的东西可以分成三类,对应三个处理环节,顺序不能随意调换。第一类是工频干扰,来自采集现场的交流供电,50Hz 供电环境就在 50Hz 陷波,60Hz 环境改 60Hz,这一层必须放在最前面,因为陷波器是窄带的,如果先做宽带滤波,工频分量会和邻近频段混在一起,陷波就陷不干净了。

第二类是参考电极带来的共模漂移。头皮电极采集的是相对电位,不同的参考方式会让整段信号的基线完全不同。方案里统一转成平均参考:逐采样点减去全部通道的均值,把「所有电极共同抬起来的那部分」抵消掉。这一步的价值在通道融合阶段才体现出来——如果各通道还带着各自的参考偏置,通道之间的概率就没法直接平均。

第三类是脉冲型伪迹和肌电高频成分。方案用一维十阶中值滤波压掉脉冲,再用 20 阶巴特沃斯带通把信号限制在 0.1Hz 到 60Hz。零相位实现是关键,正向滤一遍再反向滤一遍,相位不失真,事件的起始时刻不会漂移。

import numpy as np from scipy.signal import iirnotch, butter, filtfilt, medfilt FS = 500 # SEED-IV 先降采样到 500,与 DEAP 对齐 def preprocess(raw, fs=FS, line_freq=50): """raw: (n_channels, n_samples) 原始脑电,返回同形状的干净信号""" # 1) 工频陷波:Q=30 对应约 1.7Hz 带宽,够窄,不会吃掉 alpha 波段 b, a = iirnotch(line_freq, Q=30, fs=fs) x = filtfilt(b, a, raw, axis=-1) # 2) 平均参考:逐采样点减去全通道均值,去掉共模漂移 x = x - x.mean(axis=0, keepdims=True) # 3) 一维中值滤波,kernel=10,压脉冲型伪迹 x = np.stack([medfilt(ch, kernel_size=10) for ch in x]) # 4) 0.1-60Hz 零相位带通,20 阶,把信号限制在脑电有效频带内 b, a = butter(20, [0.1, 60], btype='bandpass', fs=fs) x = filtfilt(b, a, x, axis=-1) return x

几个参数值得说清楚。iirnotch的 Q 值决定陷波宽度,Q 越大越窄,30 是个常用折中,既能压住工频又不会把 48Hz 附近的分量一起带走。medfilt的 kernel 取 10 是偶数,偶数窗会带来半个采样点的输出偏移,在 500Hz 下就是 1ms,对分钟级事件可以忽略;但如果后续要做锁时分析或者事件相关电位,就得改成 9 或 11 这种奇数窗,避免时间轴整体偏移。butter的阶数取 20 属于比较激进的高阶设计,好处是过渡带陡、60Hz 以上衰减快,坏处是阶数太高时数值精度会变差,filtfilt内部用的是二阶节级联实现,能缓解这个问题,但如果发现输出出现异常振荡,把阶数降到 4 到 6 再用零相位滤两遍,效果差别不大。

2.3 特征工程:34 维向量是怎么凑出来的

特征决定了 SVM 的天花板。方案里把特征分成三组,加起来正好 34 维,这个数字不是随手定的,它对应的是每个通道在每个事件上产出的一个定长向量,后面 32 个通道分类器每个都吃同样长度的输入。

第一组是时域统计特征,12 维:均值、均方根、标准差、一阶差分、归一化一阶差分、二阶差分、归一化二阶差分、峰度、偏度、方差,再加上 Hjorth 参数里的活动度(用方差表示)和移动性、复杂性。移动性和复杂性这两个量本质上是在刻画信号的变化速率和它偏离正弦的程度,对情绪这种「慢变但有节律」的信号比较敏感。

第二组是频域或小波域特征,5 个频带各 4 个特征,共 20 维:频带能量、功率谱密度、微分熵、平均频带功率。微分熵按DE = 0.5 * log2(2 * pi * e * sigma^2)计算,它对频带内的功率分布比对绝对幅值更敏感,是脑电情绪识别里被验证过很多次的特征。

第三组是补充特征,2 维:Hurst 指数和排列熵。Hurst 指数刻画长程相关性,排列熵刻画序列的复杂度,这两个量在情绪唤醒度变化时会有响应,加进来是为了给 SVM 提供一点非线性的判别信息。

特征组具体特征维数
时域统计均值、RMS、标准差、一阶差分、归一化一阶差分、二阶差分、归一化二阶差分、峰度、偏度、方差、移动性、复杂性12
小波/频域(5 频带各 4 项)频带能量、功率谱密度、微分熵、平均频带功率20
补充特征Hurst 指数、排列熵2
合计34

五个频带的划分沿用经典定义:delta 1-3Hz、theta 4-7Hz、alpha 8-13Hz、beta 14-30Hz、gamma 31-60Hz。注意这些边界和 8 层小波分解得到的系数层不是一一对齐的,具体怎么对上,下一章会给出层号映射表。

特征是按通道分别算的,算完之后每个通道单独存一份矩阵。这一步看起来啰嗦,但它是整个方案的核心设计:不做通道拼接,而是让每个通道独立成模型,把「通道间怎么组合」这件事推迟到决策层去解决,而不是在特征层硬拼成一个 32×34 的长向量。后者的问题在于不同通道的量纲差异极大,拼接后做标准化会互相污染,而通道独立建模天然规避了这一点。

3. 通道级 SVM 的落地:32 个分类器怎么训、怎么融合

这一章是全篇最「可抄」的部分。方案的核心不是某一个精巧的模型,而是「一个通道一个分类器,最后投票」这个结构。它带来的直接好处是并行、可解释、单通道失效时不会拖垮整体;代价是训练成本乘以 32,以及融合策略需要单独设计。

3.1 8 层 db8 小波分解与五频带特征计算

离散小波变换的任务是把预处理后的宽带信号拆成五个频带。方案用 db8 小波做 8 层分解,得到一组近似系数 cA8 和 8 组细节系数 cD8 到 cD1,每一层对应一个倍频程。采样率 500Hz 时,各层的频率范围是这样的:

系数层频率范围(Hz)对应频带
cD1125-250超出脑电范围,丢弃
cD262.5-125超出脑电范围,丢弃
cD331.25-62.5gamma
cD415.6-31.25beta
cD57.8-15.6alpha
cD63.9-7.8theta
cD71.95-3.9delta
cA80-1.95delta 低频端,与 cD7 合并

映射关系不是严格的整数对齐,比如 beta 定义是 14-30Hz,而 cD4 覆盖 15.6-31.25Hz,差一点点。这个偏差在小波分解里是固有的,因为倍频程划分和人为定义的频带边界本来就不是一套体系。工程上的处理方式是认下这个偏差,在论文里写清楚「采用 cD4 近似 beta 频带」,而不是硬去凑一个完全对齐的分解层数。

import numpy as np import pywt BANDS = {'delta': ('cA8', 'cD7'), 'theta': ('cD6',), 'alpha': ('cD5',), 'beta': ('cD4',), 'gamma': ('cD3',)} def band_signals(seg, wavelet='db8', level=8): """seg: (n_channels, n_samples);返回 {频带: 该频带的时域波形}""" coeffs = pywt.wavedec(seg, wavelet, level=level, axis=-1) # wavedec 返回顺序: [cA8, cD8, cD7, ..., cD1],索引 0 是近似系数 names = ['cA8'] + [f'cD{i}' for i in range(level, 0, -1)] idx = {n: i for i, n in enumerate(names)} n_samples = seg.shape[-1] out = {} for band, need in BANDS.items(): keep = [np.zeros_like(c) for c in coeffs] for name in need: keep[idx[name]] = coeffs[idx[name]] # 只保留目标层的系数,其余置零后重构,得到该频带的纯净波形 rec = pywt.waverec(keep, wavelet, axis=-1) out[band] = rec[..., :n_samples] # waverec 输出可能比原信号长,截断 return out def diff_entropy(x): """微分熵:对高斯假设下的频带功率分布的熵估计""" var = np.var(x, axis=-1) + 1e-12 return 0.5 * np.log2(2 * np.pi * np.e * var)

waverec重构出来的长度可能比原信号多一两个采样点,这是小波重构的边界效应,必须截断回去,否则后面拼特征矩阵时会出现长度不一致。这个坑很小但很致命——如果只对部分事件触发,特征矩阵就会出现行数莫名其妙少几行的情况,而且报错信息完全指不到这里。diff_entropy里加的那个极小量1e-12是为了防止某个频带在某些通道上恰好全零导致 log 发散,实际数据里很少出现,但一旦出现就是 NaN 传染整行。

每个频带算出波形之后,四项特征依次计算:频带能量取波形平方和,平均频带功率取能量除以采样点数,功率谱密度走 Welch 法,微分熵用上面的函数。五项频带乘以四项特征正好 20 维,加上 12 维时域和 2 维补充特征,凑齐 34 维。

3.2 训练集划分与 SVM 超参数选择

划分方式直接决定准确率的可信度。方案按受试者划分:DEAP 的 32 名受试者里,22 名用于训练,10 名用于测试;SEED-IV 的 45 份会话数据里,32 份用于训练,13 份用于测试。这个划分是「跨受试者」的,比随机划分严格得多,也更能反映真实部署场景——新用户戴上设备,模型没见过他,还能不能分对。

按方案给的数字,DEAP 训练集的特征矩阵是 880×34(22 名受试者 × 40 个事件),SEED-IV 是 742×34。后面这个数字值得单独拎出来说:32 份会话 × 24 个事件应该是 768 行,实际是 742,差 26 行。这种情况在真实数据里很常见,原因通常是某些会话的个别事件因为记录长度不足或标记缺失被丢弃,也可能是同一受试者跨会话的事件做了去重。处理原则是:不要为了让数字好看去补齐,而是查清楚缺在哪,在论文里写明白。

SVM 在通道级的使用方式是这样:每个通道单独训练一个四分类器,输入是该通道上所有事件的 34 维特征,输出是四类的后验概率。核函数选 RBF,因为脑电特征和情绪标签之间的关系明显非线性,线性核在这种任务上通常要低好几个点。

from sklearn.svm import SVC from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.model_selection import GridSearchCV, StratifiedKFold def build_channel_model(seed=42): """单通道分类器:标准化 + RBF-SVM,超参用分层 5 折搜""" pipe = Pipeline([ ('scale', StandardScaler()), # 34 维量纲差异大,必须先标准化 ('svm', SVC(kernel='rbf', probability=True, class_weight='balanced', # 四类样本数不均衡时兜底 random_state=seed)) ]) grid = { 'svm__C': [0.1, 1, 10, 100], # 正则强度,越大越容易过拟合 'svm__gamma': ['scale', 0.01, 0.001, 0.0001], # RBF 宽度 } cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=seed) return GridSearchCV(pipe, grid, cv=cv, scoring='accuracy', n_jobs=-1)

关于超参数,有两点血泪经验。gamma一定不要只留默认的'scale',脑电特征经过标准化后方差都是 1,'scale'退化成一个固定值,搜不到真正合适的宽度,把 0.01、0.001、0.0001 一起放进去搜,往往能差出五到八个点。class_weight='balanced'在四分类里几乎是必开项,因为四个象限的样本数天然不均,HAHV 和 LALV 通常远多于另外两类,不开的话模型会塌向多数类。probability=True会触发内部的 Platt 标定,训练时间大概翻倍,但通道融合需要概率而不是硬标签,这个开销省不掉。

3.3 通道融合:从 32 个概率到 1 个情绪标签

32 个分类器各给出一个四维概率向量,融合的任务是把它压成一个标签。最简单的是等权平均,把 32 个概率向量逐元素求均值再取 argmax。这个做法在通道质量相近时够用,但如果某些通道接触不良,噪声会拉低整体。

更稳一点的做法是按通道在验证集上的准确率加权。先在一部分训练数据上做受试者内的快速验证,拿到每个通道的单通道准确率,归一化成权重,再参与到融合里。

import numpy as np def fuse_equal(probs): """probs: (n_channels, 4) 每个通道的四类后验概率""" return int(np.argmax(np.mean(probs, axis=0))) def fuse_weighted(probs, weights): """weights: (n_channels,) 建议用单通道验证准确率减 0.25(随机基线)后截断""" w = np.clip(np.asarray(weights) - 0.25, 0, None) w = w / (w.sum() + 1e-12) return int(np.argmax(np.asarray(probs).T @ w))

权重里减去 0.25 这一步是刻意为之:四分类的随机基线就是 25%,一个通道准确率 26% 和 24% 在信息量上没区别,都接近于瞎猜,直接拿准确率当权重会让这类通道拿到不该有的票。减掉基线再截断到非负,等于把「不如瞎猜」的通道直接踢出投票。

还有一种等价于朴素贝叶斯的做法,把各通道的对数概率相加再取 argmax。它和概率平均的区别在于,对数域相加对极端值更敏感,某个通道给出接近 0 的概率时会强烈压制其他通道的意见。我一般两个都跑一遍做对比,如果对数融合明显更差,往往说明存在个别异常通道在拖后腿,这本身就是个值得排查的信号。

3.4 结果复现:两套库的准确率怎么读

按方案给出的结果,两套库的四分类准确率如下:

类别含义DEAPSEED-IV
1HAHV(高效价、高唤醒)74%79%
2HALV(高效价、低唤醒)86%76%
3LALV(低效价、低唤醒)72%77%
4LAHV(低效价、高唤醒)84%74%

这组数字有两处值得在论文里解释,而不是照抄。第一处是 DEAP 上 HALV 拿到 86% 而 LALV 只有 72%,相差 14 个百分点;第二处是 SEED-IV 四类相当平均,都在 74% 到 79% 之间。前者的典型解释是 DEAP 的标签来自受试者自评,而自评在低唤醒区间的一致性较差,不同人对「低唤醒」的理解差异更大;后者则是因为 SEED-IV 用的是影片诱发加固定标签,四类的诱发强度被设计得比较均衡。这两条解释要能自圆其说,答辩时被追问「为什么这两个库表现不一样」就有话讲。

评估指标不要只报准确率。四分类任务里,宏平均 F1 和每一类的召回率才是真正说明问题的东西,尤其当某一类样本占比很低时,准确率会被多数类撑着虚高。

from sklearn.metrics import classification_report, confusion_matrix def report(y_true, y_pred, names=('HAHV', 'HALV', 'LALV', 'LAHV')): print(confusion_matrix(y_true, y_pred)) # zero_division=0 避免某类完全没预测出来时报警告刷屏 print(classification_report(y_true, y_pred, target_names=names, digits=3, zero_division=0))

混淆矩阵要按行看而不是按列看。行是真实类别,列是预测类别,对角线上是对的比例,非对角线上能看出哪两类最容易混。情绪识别里常见的是 HALV 和 LALV 互相混——这两个类在唤醒度上分得开,但效价上都偏一侧,说明效价这个维度上的特征判别力不够,得回去看频域特征是不是被 alpha 波主导了。

4. 避坑与排查:情绪识别实验里最容易翻车的五件事

这套流程跑通不难,难的是跑出来的数字经得起问。下面五条是我见过最多、也最消耗时间的翻车点,每一条都按「看到什么现象、根子在哪、怎么改」来说。

4.1 准确率突然飙到 95% 以上

现象:某一版脚本跑出 95% 以上的四分类准确率,比方案里给的 74% 到 86% 高出一大截,兴奋完之后发现换一批数据就崩。

原因:九成是数据泄漏。最常见的是把同一个受试者的不同事件随机分到了训练集和测试集两端。同一受试者的脑电有很强的个体特征,模型只要认出「这是谁」就能顺带把标签猜对,学到的根本不是你想要的情绪模式。另一种泄漏是标准化放在了划分之前,用全量数据算均值和方差,测试集的统计信息漏进了训练过程。

解决:划分必须按受试者做,训练集和测试集的受试者不重叠,这一点在方案里已经写死了。标准化必须放进 Pipeline 里,让StandardScaler只在训练折上 fit。验证的时候对着受试者名单逐个核对,训练集和测试集的 ID 集合交集必须为空。

4.2 特征矩阵行数对不上

现象:算出来的矩阵是 742×34,但按「32 份会话 × 24 个事件」推应该是 768×34,差 26 行,代码不报错,模型照跑,直到写论文列数字时才发现。

原因:三种可能。一是部分事件因为记录长度不够被预处理阶段截掉了;二是某些会话的事件标记本身有缺失,读进来就是空的;三是脚本里有dropna之类的隐式清洗,静默丢弃了含 NaN 的行。

解决:在特征提取的循环里加计数器,每处理一个事件就累加,并且把跳过的样本和跳过原因写进日志。跑完之后拿日志和预期数量对账,对不上就逐个会话排查。这一步看着笨,但它是唯一能让你在论文里写清楚「有效样本数」的办法。

4.3 某个通道的准确率恒定在 25% 附近

现象:32 个通道分类器里,有一两个通道的测试准确率稳定落在 25%,正好是四分类的随机水平,不管怎么调参都不动。

原因:该通道的数据本身就是废的。可能是采集时电极脱落导致整段接近零,也可能是某个通道在某次会话里被设备写成了常数,或者单位和其他通道不一致(比如其他通道是微伏,它是毫伏),标准化之后变成一条几乎全零的序列。

解决:做特征之前先做一遍通道体检——逐通道算标准差、算非零采样点占比、算最大最小值。标准差低于全体中位数十分之一的通道直接标红;常数序列直接剔除。剔除之后通道数会变,融合时要按剩下的通道重新归一化权重,不能沿用原来的编号。

4.4 四类结果全倒向同一类

现象:混淆矩阵上几乎所有的预测都落在同一列,模型变成了一个常数预测器,但准确率看起来还有 40% 多,比随机高,容易误以为「模型学到了东西」。

原因:类别不平衡加上核函数的参数过小。gamma太小时 RBF 核会退化成近似线性甚至近似常数,模型没有足够的容量去区分四类;再加上多数类占比高,SVM 会找到「全预测多数类」这个局部最优。

解决:先开class_weight='balanced',再看gamma的搜索范围是不是太窄。另外要做的是分层抽样,确保每一折里四类都有代表。判断模型是不是真的塌了,看宏平均 F1——如果准确率 40% 而宏 F1 只有 0.15,基本可以确诊。

4.5 换台机器结果就复现不出来

现象:同一份代码在自己的笔记本上跑出 78%,在实验室服务器上跑出 71%,数据一样、参数一样,结果就是不一样。

原因:随机种子没锁死,GridSearchCV的cv划分、SVM 的random_state、train_test_split的shuffle任何一处没固定,结果就会浮动。另一个常被忽略的是n_jobs=-1带来的浮点归约顺序差异,并行计算时求和顺序不同,浮点结果在末位上会有微小差别,偶尔会放大成不同的 argmax。

解决:把种子写成模块级常量,所有涉及随机的接口都显式传进去;n_jobs固定成 1 或者在结果差异可接受的前提下统一用 -1。最终报告的数字要来自一次完整的、参数全锁的运行,而不是若干次运行里最好的那次。

5. 验收与答辩:怎么证明这套 SVM 方案真的跑通了

答辩时最容易被问倒的不是「你的准确率多少」,而是「你怎么知道这个数字是对的」。把这套方案交上去之前,我建议按下面四步自检一遍,每一步都能在答辩时直接变成一张幻灯片。

第一步,链路可复现。从原始 mat 文件到最终混淆矩阵,必须是一条命令能跑完的脚本,中间不能有手工改文件、手工删行的环节。我会在脚本开头打印一段环境信息:数据路径、采样率、通道数、随机种子、各阶段的样本计数。这段输出存成日志,和论文里的数字一一对应。

第二步,指标能拆解。不要只给一个总准确率。按类别给召回率,按受试者给分布,按通道给贡献度。特别是通道贡献——把 32 个通道的单通道准确率画成一张条形图,能直观看出哪些通道在起作用。这张图往往能解释很多问题,比如额区和颞区的通道普遍高于枕区,这和情绪加工的生理基础是对得上的,讲出来就是加分项。

第三步,对照实验要有。至少准备两组对照:一组用等权融合对比加权融合,一组用 RBF 核对比线性核。对照不需要多,但要能说明「为什么选这个」。如果加权融合比等权高了三个点,那这三个点就是你设计决策的价值,讲清楚它比罗列十个特征有用得多。

验收项合格线检查方式
数据划分训练/测试受试者无交集打印 ID 集合求交集
样本计数与预期一致或差异有日志逐阶段计数器
类别表现四类召回率均高于随机基线混淆矩阵按行看
融合增益加权不低于等权两组结果对比
复现性同参数两次运行结果一致锁种子后重跑

第四步,异常要能解释。方案给的两套库结果不完全一致,DEAP 的 HALV 明显高、SEED-IV 四类平摊,这种差异不是缺陷,是素材。答辩时主动提出来,说明原因,比等人问出来再临时编要强得多。

代码层面,我习惯在最后加一段一键出表的脚本,把所有指标算完直接落成 CSV 和图片,避免手抄数字时抄错。

import json import numpy as np def dump_run(config, y_true, y_pred, path='run_summary.json'): """把一次完整运行的配置和指标落盘,作为论文数字的唯一来源""" from sklearn.metrics import accuracy_score, f1_score summary = { 'config': config, # 采样率、种子、通道数、核参数 'n_train': int(config['n_train']), 'n_test': int(config['n_test']), 'accuracy': float(accuracy_score(y_true, y_pred)), 'macro_f1': float(f1_score(y_true, y_pred, average='macro')), 'per_class_recall': f1_score(y_true, y_pred, average=None).tolist(), } with open(path, 'w', encoding='utf-8') as f: json.dump(summary, f, ensure_ascii=False, indent=2) return summary

这段脚本的价值不在代码本身,而在于它把「配置」和「结果」绑在同一个文件里。论文里出现的每一个百分数,都能顺着这个文件回溯到具体是哪次运行、什么参数、多少样本。评审老师真要较真,你打开文件就能对上。

从那以后我每次交方案前,都会强制走一遍「清空输出目录、从原始数据重跑、对比日志」这套流程,哪怕时间再紧也不跳。跳过这一步省下来的两小时,往往要在答辩前一晚用通宵补回来。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询