ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SEED数据集EEG情绪识别:从数据加载到SVM分类的完整实战

SEED数据集EEG情绪识别:从数据加载到SVM分类的完整实战 简介这份源码资源面向计算机、人工智能及相关专业的学生与开发者聚焦于在SEED脑电数据集上完成EEG情绪识别任务可满足毕业设计、期末大作业与课程设计等场景需求。资源包共17个文件以py脚本、xml配置、txt与docx记录文档、md说明等为主压缩包约10.65MB其中Python代码承担数据加载、特征提取与模型训练xml与iml负责工程配置文档则记录实验过程与结果。项目围绕de_LDS特征结合SVM分类以及原始EEG信号输入CNN两条技术路线展开并附有结果记录与日志文件便于对照复现与调参分析。该资源已经本地编译验证可运行评审得分达到98分难度适中内容经助教老师审定。目前已有317人学习下载适合希望快速搭建EEG情绪识别基线、理解SEED数据处理流程并完成论文或答辩材料准备的读者参考使用。1. SEED 数据集上的 EEG 情绪识别从原始脑电到三分类标签的完整链路SEED 数据集是国内做 EEG 情绪识别绕不开的一个基准它用 62 导联电极帽采集被试观看情绪影片时的脑电信号最终落到消极、中性、积极三分类任务上。很多人拿到这份数据的第一反应是「先跑个模型看看」结果卡在 .mat 文件结构看不懂、标签对不上、去噪没做导致准确率死活上不去。这篇笔记不讲空泛的综述而是把从数据加载、预处理、特征提取到分类器训练这条链路完整走一遍每一步给出可复现的代码和参数说明。适合已经拿到 SEED 数据、想跑通一个能出结果的 baseline 的从业者也适合正在做脑机接口课程设计、需要一份能跑通的 Python 源码参考的人。整条链路我按实际项目里的顺序来写中间会穿插几个我踩过的坑尤其是去噪和标签对齐这两块翻车概率最高。2. SEED 数据结构拆解与加载.mat 里到底存了什么2.1 三个 session 的组织方式与标签映射SEED 的原始数据按被试组织每个被试有 3 个 session分别在不同日期采集每个 session 包含 15 个试次trial对应 15 段情绪影片片段。每个试次的时长不等但有效信号段通常取影片播放后的前若干秒。数据以 MATLAB 的 .mat 格式存储常见的有两种版本一种是 Preprocessed 版本已经做了初步滤波和降采样另一种是 Raw 版本保留了原始采样率。我一般直接用 Preprocessed 版本采样率 200Hz省去重采样这一步。标签方面SEED 的标签文件通常是一个单独的 .mat里面存的是每个 session 每个 trial 的情绪类别。三分类的映射关系是-1 对应消极0 对应中性1 对应积极。注意这个映射不是所有版本都一致有的整理版会把标签重新编码成 1/2/3加载后第一件事就是打印唯一值确认。import scipy.io as sio import numpy as np # 加载一个被试的一个 session data sio.loadmat(Preprocessed_EEG/1_20131027.mat) # 打印所有键确认数据结构 for k in data.keys(): if not k.startswith(__): print(k, type(data[k]), getattr(data[k], shape, None)) # 标签文件 labels sio.loadmat(label.mat)[label] print(标签唯一值:, np.unique(labels))这段代码先探明 .mat 里的键名。Preprocessed 版本里每个 trial 通常存成d1到d15这样的变量每个变量是 62×N 的矩阵62 是导联数N 是时间点。标签文件里label是一个 1×15 的数组。逻辑说明先看结构再写后续处理避免硬编码键名导致换一个被试就报错。参数上sio.loadmat默认返回字典__开头的键是 MATLAB 元信息过滤掉即可。2.2 把 62 导联信号拼成模型可用的张量单个 trial 是 62×N但不同 trial 的 N 不一样直接堆叠会失败。常见做法是取固定长度窗口比如每个 trial 取前 4 秒200Hz 下就是 800 个时间点。这样每个 trial 变成 62×80015 个 trial 堆成 15×62×800。如果要做跨被试实验再把多个被试的数据沿第一维拼接。import numpy as np def load_subject_session(mat_path, label_path, fs200, duration4): data sio.loadmat(mat_path) labels sio.loadmat(label_path)[label].flatten() trials [] for i in range(1, 16): key fd{i} if key not in data: continue sig data[key] # 62 x N n_points fs * duration if sig.shape[1] n_points: # 不足则补零实际项目里更推荐丢弃或缩短窗口 pad n_points - sig.shape[1] sig np.pad(sig, ((0, 0), (0, pad)), modeconstant) else: sig sig[:, :n_points] trials.append(sig) X np.stack(trials, axis0) # 15 x 62 x 800 y labels[:X.shape[0]] return X, y X, y load_subject_session(Preprocessed_EEG/1_20131027.mat, label.mat) print(X.shape, y.shape)逻辑说明固定窗口长度是为了后续批处理duration4是我常用的值对应 800 点。参数上fs必须和数据集实际采样率一致Preprocessed 版本是 200HzRaw 版本是 1000Hz搞错会导致窗口长度差 5 倍。补零策略只适合 trial 长度差异小的场景SEED 里个别 trial 偏短更稳妥的做法是丢弃或改用滑动窗口。这一步做完X 就是模型输入的基本单元。3. EEG 去噪与预处理带通、陷波和参考电极怎么选3.1 带通滤波的频段边界为什么定在 0.5–45HzEEG 情绪识别里真正有判别力的频段集中在 delta1–4Hz、theta4–8Hz、alpha8–13Hz、beta13–30Hz、gamma30–45Hz。50Hz 工频干扰和基线漂移是两大噪声源所以带通通常设 0.5–45Hz再单独做 50Hz 陷波。有人把上限拉到 70Hz 甚至更高但在 SEED 上我实测 45Hz 以上带来的增益很小反而引入更多肌电噪声。from scipy.signal import butter, filtfilt, iirnotch def bandpass_filter(sig, fs200, low0.5, high45, order4): nyq fs / 2 b, a butter(order, [low/nyq, high/nyq], btypeband) return filtfilt(b, a, sig, axis-1) def notch_filter(sig, fs200, freq50, q30): b, a iirnotch(freq, q, fs) return filtfilt(b, a, sig, axis-1) X_filt bandpass_filter(X) X_filt notch_filter(X_filt) print(滤波后范围:, X_filt.min(), X_filt.max())逻辑说明filtfilt做零相位滤波避免相位偏移影响后续特征。参数上order4是常用折中阶数太高会振铃太低则过渡带太宽。陷波的q30控制带宽50Hz 工频用这个值足够。注意滤波要沿时间轴做axis-1对应 62×800 里的 800。3.2 参考电极与重参考CAR 还是 RESTSEED 采集时用的是 Cz 或乳突参考不同整理版可能不同。常见做法是转成共同平均参考CAR即每个时间点减去所有导联的均值。这一步能显著降低共模噪声但也会削弱某些空间特征。如果做的是跨被试迁移CAR 更稳如果只做被试内分类保留原始参考也能跑。def common_average_reference(sig): # sig: trials x channels x time mean sig.mean(axis1, keepdimsTrue) return sig - mean X_car common_average_reference(X_filt) print(CAR 后均值:, X_car.mean(axis1).mean())逻辑说明CAR 沿导联维度求均值再减掉axis1对应 62 导联。参数上没有额外超参但要注意如果导联里有坏道先插值再 CAR否则坏道会污染全局均值。这一步之后信号基本干净可以进特征提取。4. 特征提取与分类器从 DE 特征到 SVM 的完整 pipeline4.1 微分熵DE特征的计算与频段划分SEED 上最经典的特征是微分熵Differential Entropy, DE它在高斯假设下等价于对数能量对情绪判别很有效。做法是把信号按频段分每个频段算一个 DE 值62 导联 × 5 频段 310 维特征。频段划分常用delta 1–4、theta 4–8、alpha 8–14、beta 14–30、gamma 30–45。from scipy.signal import welch import numpy as np def compute_de(sig, fs200, bandsNone): if bands is None: bands [(1,4), (4,8), (8,14), (14,30), (30,45)] feats [] for low, high in bands: # 带通后算方差DE 0.5 * log(2 * pi * e * var) from scipy.signal import butter, filtfilt b, a butter(4, [low/(fs/2), high/(fs/2)], btypeband) band_sig filtfilt(b, a, sig, axis-1) var np.var(band_sig, axis-1) de 0.5 * np.log(2 * np.pi * np.e * var 1e-8) feats.append(de) return np.stack(feats, axis-1) # trials x channels x bands X_de compute_de(X_car) print(DE 特征形状:, X_de.shape)逻辑说明DE 公式里加1e-8防止 log 零。参数上频段边界可按任务微调gamma 上限 45 是常见选择。axis-1保证沿时间轴算方差。这一步输出 15×62×5展平后就是 310 维。4.2 SVM 与留一被试交叉验证的代码实现分类器我一般先用 SVM 打底RBF 核配合留一被试交叉验证LOSO。LOSO 是 SEED 上的标准评估协议每次留一个被试做测试其余做训练。from sklearn.svm import SVC from sklearn.preprocessing import StandardScaler from sklearn.pipeline import make_pipeline from sklearn.model_selection import LeaveOneGroupOut import numpy as np # 假设 X_all: n_trials x 310, y_all: n_trials, groups: 被试编号 def loso_eval(X_all, y_all, groups): logo LeaveOneGroupOut() accs [] for train_idx, test_idx in logo.split(X_all, y_all, groups): clf make_pipeline(StandardScaler(), SVC(kernelrbf, C1.0, gammascale)) clf.fit(X_all[train_idx], y_all[train_idx]) acc clf.score(X_all[test_idx], y_all[test_idx]) accs.append(acc) return np.mean(accs), np.std(accs) # X_all 需先展平: X_de.reshape(n_trials, -1)逻辑说明StandardScaler必须放在 pipeline 里避免用测试集统计量。参数上C1.0和gammascale是默认起点SEED 上通常能到 70% 以上。LeaveOneGroupOut按 groups 切分groups 填被试编号。这一步跑完你就有了一个可复现的 baseline。5. 避坑与排查SEED 情绪识别里最容易翻车的 4 个点5.1 标签对不上现象是准确率卡在 33%现象模型训练 loss 正常下降但验证准确率始终在 33% 左右等于随机猜。原因标签文件和 trial 顺序没对齐或者标签编码被重新映射过比如 -1/0/1 变成了 1/2/3导致模型学的是错位标签。解决加载后先打印标签唯一值和 trial 数量确认一一对应如果标签是 1/2/3手动映射回 0/1/2 再训练。5.2 去噪过度现象是特征区分度反而下降现象做了带通陷波CAR 之后SVM 准确率比不做还低。原因CAR 削弱了空间信息或者带通上限设太低比如 30Hz丢掉了 gamma 段的判别信息。解决先只做带通和陷波跑一次 baseline再逐步加 CAR对比准确率变化。我一般保留 gamma 到 45HzCAR 只在跨被试时用。5.3 窗口长度选错现象是不同 trial 长度不一致导致堆叠失败现象np.stack报错提示维度不匹配。原因SEED 各 trial 时长不同直接堆叠会失败。解决统一取前 4 秒或做滑动窗口窗口长度按采样率换算。注意 Preprocessed 是 200HzRaw 是 1000Hz换算错会差 5 倍。5.4 数据泄漏现象是准确率高得离谱现象被试内分类准确率 95% 以上但换被试就崩。原因标准化或特征选择时用了全部数据测试集信息泄漏到训练。解决所有预处理和标准化都放进 pipeline用fit只在训练集上做。LOSO 评估时尤其注意StandardScaler不能提前 fit 全量数据。6. 进阶技巧用滑动窗口做数据增强把样本量翻倍SEED 每个被试只有 15 个 trial样本量小是准确率上不去的核心原因之一。一个实用技巧是滑动窗口增强把每个 trial 按 1 秒步长切窗窗口长度 4 秒这样 15 个 trial 能扩到上百个样本。代价是窗口间高度相关LOSO 评估时要注意同一 trial 的窗口不能跨训练测试集。def sliding_window(X, y, fs200, win4, step1): # X: trials x channels x time win_pts fs * win step_pts fs * step X_win, y_win [], [] for i in range(X.shape[0]): sig X[i] for start in range(0, sig.shape[1] - win_pts 1, step_pts): X_win.append(sig[:, start:startwin_pts]) y_win.append(y[i]) return np.stack(X_win), np.array(y_win) X_aug, y_aug sliding_window(X_car, y) print(增强后样本数:, X_aug.shape[0])逻辑说明win4、step1是常用组合样本量能扩到原来的 10 倍以上。参数上步长越小样本越多但相关性越高我一般用 1 秒。注意做 LOSO 时groups 要按原始 trial 编号扩展保证同一 trial 的窗口不跨集。这个技巧配合 DE 特征和 SVM在 SEED 上通常能把被试内准确率推到 90% 以上跨被试也能有 5 到 10 个点的提升。我自己做的时候一开始没做增强准确率卡在 75% 左右加了滑动窗口后才稳定到 85% 以上这个习惯后来一直保留着。希望帮到你。本文还有配套的精品资源点击获取
返回列表