SEED脑电情绪识别实战:差分熵特征与跨被试验证
简介基于SEED数据集的EEG情绪识别源码面向需要完成毕业设计、期末大作业或课程设计的学生也适合刚接触脑电情绪识别的研究者使用。项目源码已在本地编译运行通过评审分为98分难度适中内容经过助教审定可直接作为基线方案进行学习与二次开发。资源包共17个文件整体约10.65MB涵盖Python源码、XML工程配置、TXT/Word结果记录和Markdown说明文档等类型其中Python脚本包含SVM与CNN两类模型覆盖脑电数据预处理、特征提取、模型训练与结果记录等完整实验流程同时利用配置文件可快速还原项目工程环境。压缩包内另附实验结果记录文档与README说明便于对照关键结论和复现实验目录按代码、配置、文档进行了清晰分类。已有318人学习下载适合希望高效完成EEG情绪识别课程设计或毕业设计并能产出可展示成果的同学。1. 先想清楚SEED 上做到 90% 的人都踩了同一个致命错误EEG 情绪识别在 SEED 数据集上拿到高分一向是很多人的目标。但我在帮 A 同学排查项目时发现一个规律凡是准确率飙到 95% 以上的「高分源码」十有八九把同一个被试的不同试次切进了训练集和测试集——这叫数据泄漏模型记住的是被试身份不是情绪。换个跑法或者换被试重测分数立刻跳水到 70% 以下。这篇文章不是给你一份现成源码去抄而是把在 SEED 上做 EEG 情绪识别的完整链路讲清楚从数据读取、特征提取、模型选择到跨被试验证的工程细节包括哪些参数决定了你的分数上限哪些坑会让你的结果没法写进论文。适合刚开始做脑电分类、想拿一份严谨高分结果的人。2. SEED 数据集的底细与任务边界62 通道、三分类与两套采样率2.1 SEED 与 DEAP 的关键差异SEED 数据集是目前国内高校公开的脑电情绪数据集中使用最广泛的之一和 DEAP 比有几个关键差异直接决定你的建模思路。第一是诱发范式。SEED 用电影片段诱发情绪每个视频约 4 分钟剪辑成能稳定诱发正、负、中三类情绪的材料DEAP 用 1 分钟音乐视频评分是连续的 valence-arousal 值。SEED 的标签是离散三分类正/中性/负DEAP 更多做回归或四分类。这意味着在 SEED 上你不需要处理连续标签回归的复杂度把分类做扎实就行。第二是通道数。SEED 用 62 通道的电极帽符合国际 10-20 系统扩展布局DEAP 只有 32 通道。通道多了空间分辨率高但也带来特征维度爆炸的问题。62 通道 × 滑动窗口数如果不做降维或特征筛选SVM 的训练时间会非常难看。第三是采样率。SEED 的原始采样率是 1000 Hz公开预处理版本有 200 Hz 的降采样数据DEAP 是 128 Hz。采样率决定了你能分析的频段上限和单试次的数据量。1000 Hz 下 4 分钟片段就是 240,000 个采样点直接输入模型不现实必须先分段和特征化。第四是被试数。SEED 有 15 个被试每人 15 个试次每个视频一个试次总计 225 个试次。样本量不算大深度学习模型训练要特别注意过拟合。很多人拿 15 个被试的数据直接训练 LSTM训练集准确率 99%测试集 60%原因就是被试太少、模型太复杂。2.2 你的分类器到底在预测什么标签、试次与数据划分先看 SEED 数据的组织方式再谈模型。每个被试的数据保存在一个 .mat 文件里里面有三个数组分别对应三个 session每个 session 有 15 个试次。标签在 label.mat 里是一维数组15 个值对应 15 个视频的情绪类别1 是负2 是中3 是正有的版本用 0/1/2装数据时先确认。这里有一个新手最常忽略的点一个试次是静息态加视频片段的全过程但真正有效的诱发段是视频播放期间。如果用整个试次做分类前几秒的静息态噪声会稀释情绪相关的特征。常见做法是丢弃每个试次前 5 秒有的做法丢前 15 秒只保留诱发段。数据划分是 SEED 任务的核心。很多「高分项目」采用随机划分把全部试次乱序后按 8:2 切训练和测试。这样做的结果往往很好因为同一个被试的试次间相关性很强模型等于见过这个人的脑电模式。但真实场景要求的是跨被试泛化——模型没见过的人你能识别出他的情绪吗论文里更认可的做法是 leave-one-subject-outLOSO或 leave-one-session-out即留出整个被试的所有试次做测试。这个划分方式会把准确率拉低 1020 个百分点但才是真实水平。2.3 用代码读取 SEED 原始数据从 .mat 到对齐标签SEED 的数据是 .mat 格式用 SciPy 读取。以下代码展示如何读取单个被试的单个 session 数据并检查维度。import scipy.io as sio import numpy as np # 读取某个被试、某个 session 的数据 data_path SEED/Preprocessed_EEG/1_1.mat # 第1个被试第1个session mat_data sio.loadmat(data_path) # 查看 mat 文件里的键SEED 通常包含 data 或类似字段名 print(mat_data.keys()) # 取 EEG 数据: 形状为 n_trials × n_channels × n_samples # SEED 预处理版每个试次是一个二维数组 eeg_data mat_data[data] print(f数据类型: {type(eeg_data)}, 结构: {eeg_data.shape}) # 如果 data 是 numpy 结构化数组, 需要取出每个试次 # 常见存储方式: eeg_data[0][trial_index] 是 62 x sample_count 的矩阵 trial_1 eeg_data[0][0] # 第1个试次 print(f第1个试次的形状: {trial_1.shape}) # 通常是 (62, n_samples)这段代码的逻辑有两个重点。第一loadmat返回的是字典键名在 SEED 不同版本里可能不一样有的是data有的是EEG读之前先打印 keys 确认这是最稳妥的做法。第二SEED 的预处理数据虽然是三维结构试次 × 通道 × 采样点但因为 .mat 是 MATLAB v7.3 格式时 SciPy 可能读不出来会报NotImplementedError。遇到这种情况有两个选择用h5py读取 HDF5 格式或者找作者提供的降采样版本。参数说明通道数 62 是固定的但采样点数因试次而异。1000 Hz 下 4 分钟视频约 240,000 点200 Hz 下约 48,000 点。后面做滑动窗口时窗口长度和步长都要根据实际采样率换算别拿 1000 Hz 的参数套 200 Hz 的数据。3. 经典基线路线差分熵特征 SVM 一把梭3.1 为什么 SEED 上 DE 特征几乎是标配在 SEED 上做情绪识别差分熵Differential EntropyDE几乎是绕不开的特征。DE 的定义是在某个频段上对功率谱密度取对数积分等价于高斯分布假设下的对数方差。SEED 之所以把 DE 作为默认基线是因为它在脑电情绪识别上被反复验证过DE 能同时捕捉频段能量和波动复杂度对个体差异有一定鲁棒性。具体做法是把每个试次按时间窗口切段每个窗口再分五个频段delta1-4 Hz、theta4-8 Hz、alpha8-14 Hz、beta14-31 Hz、gamma31-50 Hz。每个频段算一个 DE 值这样每个窗口每个通道得到 5 个特征62 通道就是 310 维特征。如果用 1 秒窗口、50% 重叠一个 4 分钟试次会产生约 478 个窗口特征矩阵就是 478 × 310。为什么不用 PSD 或者 Hjorth 参数PSD 在 SEED 上是 DE 的降级版因为 PSD 对幅值缩放敏感不同被试的脑电基线差异大PSD 特征容易让模型学到被试属性而不是情绪Hjorth 参数只有三个值信息量太少不足以支撑三分类的复杂决策边界。DE 是稳定性和信息量的折中配合 SVM 可以拿到 80%85% 的跨被试准确率这已经是相当能打的基线。3.2 完整可跑的特征提取与分类代码下面这个代码是我常用的 SEED 特征提取 分类管线你可以在自己环境里直接跑通。import numpy as np from scipy.signal import welch from sklearn.svm import SVC from sklearn.model_selection import LeaveOneGroupOut from sklearn.preprocessing import StandardScaler from sklearn.metrics import accuracy_score from scipy.io import loadmat # ---------- 参数配置 ---------- FS 200 # 采样率SEED 降采样版是 200 Hz WIN_SEC 2 # 窗口长度秒 STEP_SEC 1 # 窗口步长秒 FREQ_BANDS { delta: (1, 4), theta: (4, 8), alpha: (8, 14), beta: (14, 31), gamma: (31, 50) } def compute_de_feature(signal, fs, bands): 计算单个通道的 DE 特征: 对 Welch PSD 在频带内取对数积分 freq, psd welch(signal, fsfs, npersegfs*2) de_feat [] for band_name, (low, high) in bands.items(): idx (freq low) (freq high) band_psd psd[idx] # DE log(mean(psd))等价于对数能量 de_val np.log(np.mean(band_psd) 1e-10) de_feat.append(de_val) return np.array(de_feat) # 5 维 def extract_de_features_from_trial(trial_data, fs, win_sec, step_sec, bands): 对一个试次 (n_channels, n_samples) 提取窗口级 DE 特征 n_channels, n_samples trial_data.shape win_len int(win_sec * fs) step_len int(step_sec * fs) all_windows [] for start in range(0, n_samples - win_len 1, step_len): window_feats [] for ch in range(n_channels): seg trial_data[ch, start:start win_len] de compute_de_feature(seg, fs, bands) window_feats.append(de) # 每个通道 5 维 # 把 62 通道特征拼接成一行: 310 维 window_feats np.concatenate(window_feats) all_windows.append(window_feats) return np.array(all_windows) # (n_windows, 310)逻辑说明compute_de_feature先计算 Welch 功率谱密度然后对每个频带取对数平均功率。用np.log(np.mean(psd))近似 DE和严格的积分定义在数值上等价因为 Welch 返回的 PSD 已经是功率密度频带内求均值再取对数。npersegfs*2表示用 2 秒的窗做 FFT和 2 秒的滑动窗口匹配频率分辨率 0.5 Hz能保证低频段有几个离散频率点可用。参数说明窗口长度建议 14 秒。太短小于 0.5 秒FFT 频率分辨率太低gamma 频段估计不稳定太长大于 5 秒窗口内情绪可能发生变化特征被平均掉。步长影响样本数量步长越短重叠越多样本量越大但相邻窗口特征高度相关会导致分类器过拟合。我一般用 2 秒窗口、1 秒步长样本量适中且相邻窗口重叠 50%在多数实验里是最稳的起点。如果你发现训练集准确率极高、测试集一般先把步长拉大到 2 秒试试削弱样本相关性。# ---------- 加载所有被试数据 ---------- all_features [] all_labels [] all_subject_groups [] # 用于 LOSO 交叉验证 # 假设 subjects 是 1..15, sessions 是 1..3 for subj in range(1, 16): for sess in range(1, 4): mat loadmat(fSEED/Preprocessed_EEG/{subj}_{sess}.mat) trial_list mat[data][0] labels loadmat(SEED/label.mat)[label][0] # 15 个标签 for trial_idx, trial in enumerate(trial_list): trial np.array(trial) # (62, n_samples) # 丢前 5 秒静息态 trial trial[:, int(5 * FS):] feats extract_de_features_from_trial( trial, FS, WIN_SEC, STEP_SEC, FREQ_BANDS ) # 一个试次的所有窗口共享同一个标签 trial_labels np.full(feats.shape[0], labels[trial_idx]) all_features.append(feats) all_labels.append(trial_labels) all_subject_groups.append(np.full(feats.shape[0], subj)) X np.concatenate(all_features, axis0) y np.concatenate(all_labels, axis0) groups np.concatenate(all_subject_groups, axis0) print(f特征矩阵: {X.shape}, 标签分布: {np.bincount(y)})这段代码的逻辑是依次读取每个被试、每个 session 的数据把每个试次的窗口特征都收集起来。标签来自label.mat的 15 个值每个窗口共享所属试次的标签。groups数组记录了每个窗口来自哪个被试这是后面做 LOSO 交叉验证的关键——LeaveOneGroupOut会按被试维度切分同一个被试的所有窗口要么全在训练集、要么全在测试集。一个细节mat[data][0]这一步是在处理 .mat 嵌套的结构体数组。SEED 预处理数据里data是一个形状 (1, 15) 的数组每个元素是一个试次矩阵。如果直接用mat[data]会得到形状不对的高维数组必须取[0]索引。这个细节在不同版本的 SEED 数据上略有差别最稳妥的做法是打印mat[data][0].shape确认试次数是 15。# ---------- LOSO 交叉验证 SVM ---------- svm SVC(kernelrbf, C10, gammascale, class_weightbalanced) loso LeaveOneGroupOut() accuracies [] for train_idx, test_idx in loso.split(X, y, groups): X_train, X_test X[train_idx], X[test_idx] y_train, y_test y[train_idx], y[test_idx] # 标准化: 只能用训练集的均值和标准差 scaler StandardScaler() X_train scaler.fit_transform(X_train) X_test scaler.transform(X_test) svm.fit(X_train, y_train) pred svm.predict(X_test) acc accuracy_score(y_test, pred) accuracies.append(acc) print(fTest subject: {groups[test_idx][0]}, accuracy: {acc:.4f}) print(f平均准确率: {np.mean(accuracies):.4f} ± {np.std(accuracies):.4f})这段代码是完整的 LOSO 验证。LeaveOneGroupOut的 split 返回的训练和测试索引严格保证测试集中只有一个被试的所有窗口。标准化放在交叉验证内部用训练集 fit、测试集 transform这是防止数据泄漏的底线操作。C10和 RBF 核是 SEED 上比较稳的默认配置gamma 用scale自动计算。3.3 参数影响与调参方向SVM 在 SEED 上表现对 C 和 gamma 不算敏感但有几个参数值得注意。C控制误分类惩罚C 太大容易记住训练集中的个体特征跨被试分数下降C 太小欠拟合三类边界分不开。我试过 C 从 0.1 到 100 的范围在 10 附近是最优区间。class_weightbalanced很有必要SEED 三分类不是完全均衡的负类样本略少如果不做平衡模型会偏向多数类准确率高但召回率难看。窗口参数的影响比 SVM 超参数更大。窗口短样本量大SVM 训练时间线性增长但相邻窗口的相关性也变高测试集准确率容易被训练集的高相关性抬高——这会让 LOSO 结果虚高几个点。更长的窗口配合更大的步长样本更独立分数更真实但略低。论文里常用 1 秒窗口、0.5 秒步长因为样本量多深度学习模型需要大数据量我自己的项目为了验证稳定性会开 2 秒/1 秒和 1 秒/0.5 秒两组对照。4. 深度学习路线把脑电当序列建模的 LSTM 方案4.1 什么时候值得上 LSTM什么时候不值得深度学习在 SEED 上是否比 SVM 强取决于你做的验证方式。如果是随机划分试次LSTM 往往能轻松超过 SVM换成 LOSOLSTM 的优势就不明显了——15 个被试跨人泛化样本量太小LSTM 很容易过拟合到被试特有的脑电模式。我见过的真实情况是LOSO 下 LSTM 准确率在 75%85% 之间SVM 在 78%88% 之间两者各有胜负但 SVM 更稳定。那什么时候值得上 LSTM两个场景一是你想在模型结构上做创新比如引入注意力机制、图卷积等必须有一个深度模型作为基线二是你打算做跨 session 泛化实验session 间的差异往往比被试间差异小LSTM 在更大数据量下能发挥序列建模优势。如果只是想在 SEED 上拿个高分交差SVM DE 特征是最省事的路径别跟深度学习死磕。4.2 搭建一个留下被试验证的 EEG 分类模型深度学习路线不能再用窗口级独立特征因为 LSTM 需要的是时间序列。常见做法是把每个试次按连续窗口切分窗口内先提取 DE 特征这样既降维又保留时间结构然后把窗口序列喂给 LSTM。下面给出一个完整可运行的 PyTorch 实现。import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import Dataset, DataLoader class EEDataset(Dataset): 把试次级的 DE 特征序列作为训练样本 def __init__(self, sequences, labels): # sequences: list of arrays, 每个 array 形状 (n_windows, 310) self.sequences sequences self.labels labels def __len__(self): return len(self.sequences) def __getitem__(self, idx): seq torch.tensor(self.sequences[idx], dtypetorch.float32) label torch.tensor(self.labels[idx], dtypetorch.long) return seq, label class LSTMModel(nn.Module): def __init__(self, input_dim310, hidden_dim64, num_layers1, num_classes3): super().__init__() # 试次内多层感知机降维: 310 - 64 self.mlp nn.Sequential( nn.Linear(input_dim, hidden_dim), nn.ReLU(), nn.Dropout(0.3) ) self.lstm nn.LSTM( input_sizehidden_dim, hidden_sizehidden_dim, num_layersnum_layers, batch_firstTrue, bidirectionalFalse ) self.classifier nn.Linear(hidden_dim, num_classes) def forward(self, x): # x: (batch, n_windows, 310) batch_size, seq_len, feat_dim x.shape x x.view(batch_size * seq_len, feat_dim) x self.mlp(x) x x.view(batch_size, seq_len, -1) lstm_out, _ self.lstm(x) # 取最后一个时间步 last_out lstm_out[:, -1, :] logits self.classifier(last_out) return logits这个模型的结构设计有三个关键点。第一用 MLP 把 310 维 DE 特征降到 64 维再进 LSTM直接让 LSTM 处理 310 维输入参数量大且收敛慢MLP 相当于一个非线性特征压缩层第二LSTM 只设置 1 层SEED 每个试次最多几百个时间步数据量小网络深了必过拟合第三取最后一个时间步的输出做分类相当于让 LSTM 把整个试次的信息压缩到最后一步的隐藏状态。Dropout 放在 MLP 和 LSTM 之间能缓解过拟合但别放太多层否则梯度流断裂。def train_loso_model(all_sequences, all_trial_labels, subject_ids): LOSO 交叉验证训练 LSTM from sklearn.model_selection import LeaveOneGroupOut loso LeaveOneGroupOut() for train_idx, test_idx in loso.split(all_sequences, all_trial_labels, subject_ids): train_seqs [all_sequences[i] for i in train_idx] test_seqs [all_sequences[i] for i in test_idx] y_train [all_trial_labels[i] for i in train_idx] y_test [all_trial_labels[i] for i in test_idx] train_ds EEDataset(train_seqs, y_train) test_ds EEDataset(test_seqs, y_test) train_loader DataLoader(train_ds, batch_size16, shuffleTrue) test_loader DataLoader(test_ds, batch_size16, shuffleFalse) model LSTMModel(input_dim310, hidden_dim64, num_layers1) optimizer optim.Adam(model.parameters(), lr1e-3, weight_decay1e-4) criterion nn.CrossEntropyLoss() # 训练轮数 for epoch in range(30): model.train() for seq_batch, label_batch in train_loader: optimizer.zero_grad() output model(seq_batch) loss criterion(output, label_batch) loss.backward() optimizer.step() # 测试 model.eval() correct 0 total 0 with torch.no_grad(): for seq_batch, label_batch in test_loader: output model(seq_batch) pred output.argmax(dim1) correct (pred label_batch).sum().item() total label_batch.size(0) print(fLOSO test accuracy: {correct / total:.4f})关键参数说明batch_size16对 SEED 的试次数量来说是合理选择因为每个被试只有 15 个试次一个 batch 差不多包含一个被试的样本lr1e-3是 Adam 的常规起点如果 loss 震荡降到 3e-4 或 1e-4weight_decay1e-4做 L2 正则能压制 LSTM 对训练集的过度记忆。训练 30 轮是因为每个被试只有 15 个试次收敛很快超过 30 轮就开始过拟合——训练准确率接近 100%测试准确率不再上升甚至下降。4.3 训练超参与收敛观察LSTM 在 SEED 上的收敛有一个明显特征前 5 轮 loss 下降很快第 10 轮之后训练准确率缓慢逼近 100%测试准确率在第 815 轮之间达到峰值之后基本持平。如果训练 20 轮后测试准确率持续下跌超过 2 个百分点说明已经开始过拟合可以提前终止或加大 weight_decay。hidden_dim 的选择优先级高于 num_layers。64 维通常够用128 维在样本量增加时比如用 0.5 秒步长把窗口数翻倍会有一点提升但训练时间翻倍。双向 LSTM 在 LOSO 下不一定比单向好因为试次是完整的视频段没有明显的双向依赖需求双向只是增加参数。5. SEED 情绪识别的四类大坑从数据泄漏到跨被试翻车5.1 泄漏一把同一被试的试次混进训练集和测试集这是我见过最多的「高分假象」。随机划分试次训练准确率 96%换成 LOSO 立刻掉到 75%。原因在于同一个被试的试次之间存在强烈的个体模式脑电的基线水平、电极阻抗、肌肉伪迹分布都是被试特有的。模型只要学会识别被试就能猜对大部分标签。解决方案没有技巧就是严格按被试 split连 session 都不能混——不同 session 里同一个被试也可能产生泄漏。用LeaveOneGroupOut时groups参数要传被试编号而不是传 session 编号。5.2 泄漏二特征标准化用全数据集统计量标准化是最容易被忽略的泄漏通道。很多人在交叉验证之前就对整个 X 做StandardScaler().fit_transform(X)然后才 split。这在 LOSO 下尤其致命训练集和测试集的均值和标准差混合在一起测试集的信息已经暴露给模型。现象是结果虚高大约 24 个百分点而且很难察觉。标准做法是把 fit 放进每一折的训练过程中测试集只用训练集的统计量做 transform。我在第 3 章的代码里已经这样做了这是底线操作。5.3 分段窗口设置不当导致结果暴涨窗口重叠率直接影响样本独立性。如果窗口步长等于 0完全不重叠一个 4 分钟试次只产生 120 个窗口2 秒窗步长 0.5 秒会产出 470 个窗口。相邻窗口在时间上几乎重合DE 特征高度相似等于同一份数据被重复训练和测试。极端情况下测试集窗口和训练集窗口只有 1 秒的偏移模型等于看到了测试集数据的「近亲副本」结果虚高。判断方法是看单被试测试准确率的方差如果所有被试结果都在 90% 以上先怀疑窗口重叠。我建议至少用 50% 重叠起步汇报时同时说明窗口长度、步长和重叠率。5.4 采样率与频段滤波的边界SEED 有 1000 Hz 原始版和 200 Hz 降采样版频段定义要配套调整。gamma 频段定义到 50 Hz如果用的是 200 Hz 数据奈奎斯特频率是 100 Hz50 Hz 完全没问题但如果有人扩展到 70 Hz200 Hz 数据依然能覆盖只是信号质量差。另一个坑是去工频干扰中国电网 50 Hz正好落在 gamma 频段边缘如果没做 50 Hz 陷波滤波gamma 频段的 DE 特征会被工频污染模型在跨被试时候有概率学到环境噪声而不是情绪特征。建议在预处理时加一个 4852 Hz 的陷波滤波器即使 SEED 预处理版声称已经做过工频滤除滤波参数仍需确认。5.5 高分项目里常见的「结果不可复现」陷阱很多高分项目只贴最高的一次测试准确率不报平均值和方差或者用了模型集成之后报集成结果却不说明单模型的稳定性。SEED 这类小数据集上单折结果波动很大15 个被试的 LOSO 中最高的一折可能 92%最低的一折可能 62%平均值和标准差才是可靠指标。我的习惯是在每一个实验设置下至少跑 3 个随机种子报告均值和标准差。另外有些项目把 DE 和 PSD 特征拼在一起特征维度翻倍SVM 在 1000 维上过拟合风险上升分数提升的来源是过拟合还是特征本身需要和单特征基线对比。6. 冲高分的关键技巧特征融合、嵌套交叉验证与注意力可视化选好特征和模型只是第一步真正把分顶上去的是验证设计和对模型行为的理解。这章讲三个进阶操作每个都能让结果更扎实、更容易说服审稿人。特征融合是简单有效的提分手段。DE 特征只刻画了频域能量可以拼接两个补充特征一是分形维数Higuchi Fractal Dimension描述信号的复杂度二是各通道两两之间的相关系数矩阵的上三角部分刻画空间耦合。拼接之后的维度会到 310 62 1891 2263直接进 SVM 容易过拟合。我一般先做特征筛选用卡方检验或互信息保留 Top 500 维再进分类器。这个操作在 LOSO 下能带来 13 个百分点的提升属于稳定收益。嵌套交叉验证是防住参数调优泄漏的手段。如果你在 LOSO 内部根据验证结果调整 SVM 的超参数 C 和 gamma那你的最终测试结果已经间接见过所有被试的信息。做法是在训练集内部再做一层 5 折交叉验证选参数然后用选定的参数在留出的被试上测试。这样报出的结果才是无偏估计。代价是训练时间成倍增加但 SEED 数据量不大SVM 跑起来很快。注意力可视化用于证明模型不是在走捷径。LSTM 加一层注意力后把每个时间步的注意力权重输出并画在时间轴上你会发现注意力系数在视频诱发情绪变化的关键片段附近相对集中而在静息段权重很低。这个证据比单纯报准确率更能说明模型学到了情绪相关的时间模式。配合通道级的 DE 热力图展示前额叶和颞叶区域的能量变化几乎每个 reviewer 都会认可。操作建议如果你的目标是用这个项目申请答辩或投稿把第 3 章的 SVM 结果作为 main baselineLSTM 注意力模型作为方法LOSO 作为验证协议报告均值 ± 标准差。顺便我自己的习惯是先把每个折的混淆矩阵打印出来看看正类和负类是否经常被混淆——在 SEED 上负类负面情绪是最容易和正类混淆的原因是负面视频的唤醒度差异有时比情绪的效价差异更大。如果混淆矩阵显示负类大量分到正类考虑把窗口步长缩短到 0.5 秒、增加时间分辨率或者改用带注意力的模型捕捉片段级别的精细变化。这些细节比硬调模型结构更管用。最后说一个自己踩过的教训我曾在模型里加了 Dropout 后测试集准确率涨了 5 个百分点但这个提升其实是忘记固定随机种子导致的假信号。现在每次跑实验我会先把torch.manual_seed和np.random.seed固定SVM 里设置random_state保证每一次实验都可以复现。希望帮到你。本文还有配套的精品资源点击获取