# SEED_VII_FAT **Repository Path**: CodeStoreHub/seed_-vii_-fat ## Basic Information - **Project Name**: SEED_VII_FAT - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-07 - **Last Updated**: 2026-10-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # SEED-VII 七类情绪脑电识别:Fourier 邻接 Transformer:方法、数据与复现结果 ## [SEED-VII数据集下载](https://hallo.codestore.store/buy/34) ## [Transformer算法代码下载](https://hallo.codestore.store/buy/34) 多通道 EEG ・ 差分熵特征 ・ 傅里叶周期/非周期解耦 ・ 被试独立留一验证 62 通道 EEG · 20 被试 / 80 片段 · 7 类情绪 · 80 折 LOSO · RTX 3090 单卡 ## 1 任务与总体思路 脑电情绪识别要解决的核心矛盾是:EEG 既有**明显的节律性**(δ/θ/α/β/γ 频带上的准周期振荡), 又叠加了**非周期的、与任务和个体相关的漂移成分**。传统做法把两者混在一起送进网络, 让模型自己去隐式区分,结果往往是: - 标准自注意力(MHSA)**缺乏归纳偏置**——它把 62 个电极当作无序 token,忽略了电极之间 已知的、稳定的空间邻接结构,在样本量有限的 EEG 上极易过拟合; - 周期成分被当作普通特征处理,**没有被显式建模**,而这恰恰是 EEG 最可靠的物理先验; - 常见做法要么只用周期性基函数(丢失非周期信息),要么在非周期分支上加非线性激活 (破坏其线性映射能力,无法直接替换 Transformer 里的线性投影层)。 我们实现的方法针对上述三点,用一条主线串起来: 1. **FAL(Fourier Analytic Linear)**:把 Transformer 里的 QKV 线性投影换成 "周期分支 + 纯线性非周期分支"的并联结构,让周期与非周期表示在**通道维度上显式解耦**; 2. **FAA(Fourier Adjacent Attention)**:在解耦后的两路表示上**分别**做缩放点积注意力, 并各自叠加一个可学习的**电极邻接矩阵**(门控加权),把空间结构先验注入注意力分数; 3. **FAT(Fourier Adjacent Transformer)**:用 FAA 替换标准多头自注意力,保留 Transformer 的深度堆叠与残差结构,得到完整的情绪分类模型。 ## 2 方法详解 ### 2.1 FAL:把周期项与非周期项分开的线性层 给定输入特征 `x ∈ R^d_in`,FAL 的输出由两部分拼接而成: ``` Φ(x) = [ cos(W_p·x) ‖ sin(W_p·x) ‖ W_n·x + B_n ] ``` 其中 `‖` 表示通道维拼接。两部分各自承担明确职责: #### 周期分支 ``` Φ_p(x) = [ cos(W_p·x) ‖ sin(W_p·x) ] ``` 用**无偏置**线性变换先投影,再过余弦/正弦基函数。 这本质上是学习一组可训练的傅里叶基,使网络能直接表达任意相位的周期模式。 不使用偏置是关键——偏置会破坏基函数的相位结构。 #### 非周期分支 ``` Φ_a(x) = W_n·x + B_n ``` 保持**纯线性**(恒等激活)。这一点是相对前置工作 FAN 的关键改动:FAN 在非周期分支上仍加非线性激活,导致该分支不再是线性映射, 无法干净地替换 Transformer 中的线性投影层。改成恒等后,FAL 可以直接当作 Q/K/V 投影使用,同时保留周期建模能力。 设周期分支输出维度与总输出维度之比为 **P-ratio**。当 P-ratio = 0 时 FAL 退化为普通线性层, 因此这是一个可以平滑调节的开关(消融结果见第 5.4 节)。 ### 2.2 FAA:双路注意力 + 可学习电极邻接矩阵 用三个独立的 FAL 分别生成 Query / Key / Value,每个都被拆成周期与非周期两半: ``` Q = [Q_p ‖ Q_n], K = [K_p ‖ K_n], V = [V_p ‖ V_n] Q_p‖Q_n = FAL_Q(x), K_p‖K_n = FAL_K(x), V_p‖V_n = FAL_V(x) ``` 两路**各自独立**计算缩放点积注意力,区别在于注意力分数上叠加了一项结构先验: ``` Attention_{p/n} = SoftMax( Q_{p/n}·K_{p/n}ᵀ / √d_k + σ( f_{p/n}(Q_{p/n}) ) × Adjacency_{p/n} ) ``` 这里有两个要点: - **Adjacency\_{p/n}** 是一对可学习矩阵(周期邻接、非周期邻接),参数**跨深度共享**—— 即整个网络只有这一对,不随层数增长。它们编码的是"电极之间普适的、被试无关的连接模式"; - **σ(f(Q))** 是一个门控:用一层线性映射把 query 压成标量再过 Sigmoid,得到 0–1 之间的权重,动态决定这一层、这个样本上"结构先验"该占多大比重。 注意力分数本身携带的是**数据驱动的、样本特有的**相关性,两者相加即为 "普适结构 + 个体差异"的融合。 最后按周期/非周期两路分别加权求和再拼接,得到 FAA 的输出: ``` O = [ Attention_p · V_p ‖ Attention_n · V_n ] ``` 保留多头设计(Multi-Head FAA)后,实现上把 8 个注意力头一分为二: **前 4 个头叠加周期邻接矩阵,后 4 个头叠加非周期邻接矩阵**, 门控权重由对应 4 个头的 query 拼接后经 `Linear(20→1) + Sigmoid` 产生。 ### 2.3 FAT:整体架构 ![FAT 模型整体框架图](images/fat-architecture.jpg) 模型整体框架(上)与 Fourier 邻接注意力 FAA 的计算流(下)。 输入 DE 特征经 Stem 嵌入后送入 N 个编码块,每个编码块以 Multi-Head Fourier Adjacent Attention 替换标准自注意力;FAA 内部由 FAL_Q / FAL_K / FAL_V 生成解耦后的 Q/K/V,再分别与邻接矩阵相加后过 SoftMax。 #### 输入表示 模型输入不是原始波形,而是每个时间窗的**差分熵(DE)特征**: 形状 `B × C × F`,其中 C = 62 个电极、F = 5 个频带。 DE 特征先经过一个两层 Stem 做通道维嵌入: ``` x = ReLU( BN( f₂( ReLU( BN( f₁(input) ) ) ) ) ), f₁: F → E/2, f₂: E/2 → E ``` 随后拼接一个可学习的 **CLS token**(序列长度 62 → 63),加上位置编码, 送入 N 个编码块。分类头只取 CLS token 位置的特征: ``` logits = Linear( LayerNorm( x[:, 0, :] ) ) → 7 类 ``` #### 我们实际运行的超参数 | 配置项 | 取值 | 说明 | |--------------------|-----------|----------------------------------| | 嵌入维度 E | 40 | Stem 输出通道数 | | 编码块数 N (depth) | 6 | Transformer 层数 | | 注意力头数 | 8 | 4 头接周期邻接、4 头接非周期邻接 | | FFN 扩张比 | 4 | 40 → 160 → 40,GELU | | Dropout | 0.2 / 0.5 | 注意力块 0.2,FFN 0.5 | | 邻接矩阵尺寸 | 63 × 63 | 62 电极 + CLS token,跨深度共享 | | 优化器 | Adam | lr = 1e-3,weight decay = 1e-4 | | 批大小 / 轮数 | 32 / 50 | 每轮约 585 次迭代(session 1) | | 可训练参数 | 168,555 | 非常轻量 | #### 训练期数据增强(三件套) - **频带随机缩放**:对 5 个频带各乘一个 U(0.9, 1.1) 的随机因子,模拟频带能量漂移; - **正弦扰动**:在频带维上叠加 `0.05·sin(2t)`,强化周期性归纳偏置; - **Mixup**:α = 0.2 的 Beta 采样,混合样本与标签(损失同样按 λ 加权),提升泛化。 ## 3 SEED-VII 数据集详解 SEED-VII 是上海交通大学 BCMI 实验室 SEED 系列的最新成员,把情绪类别扩展到 **7 类**,并且是 SEED 家族中首个同时提供**离散标签与连续标签**、 同时记录**脑电与眼动**的多模态数据集。 | 指标 | 数值 | |-----------------------|------| | 被试(10 男 / 10 女) | 20 | | 每人视频片段数 | 80 | | 情绪类别 | 7 | | EEG 电极通道 | 62 | ### 3.1 采集与范式 | | | |----|----| | 被试 | 20 人(10 男 / 10 女),年龄 19–26 岁,平均 22.5 岁;同时记录了 EPQ 人格量表得分(E/P/N/L) | | 模态 | 62 通道 EEG + 同步眼动信号(本工作只使用 EEG 分支) | | 采样率 | 原始 `.cnt` 为 1000 Hz;官方预处理后重采样至 **200 Hz** | | 预处理 | 0.1–70 Hz 带通 + 50 Hz 陷波,剔除 M1 / M2 / ECG / HEO / VEO 等非脑电通道,重参考到 62 导联标准蒙太奇 | | 实验结构 | **4 个 session × 20 个视频片段 = 每人 80 个片段**,单个片段约 2 分钟 | | 标签 | 离散标签(7 类)+ **逐秒连续标签**(情绪强度轨迹) | ### 3.2 情绪类别与 session 构成 7 类情绪为 **Happy / Neutral / Disgust / Sad / Anger / Fear / Surprise**。 每个 session 只覆盖其中 **5 类**,每类 4 个片段(20 片段 / 5 类), 且顺序经过镜像平衡以抵消顺序效应: | Session | 包含情绪 | 片段数 | 本 session 缺失 | |-----------|---------------------------------------|--------|-----------------| | Session 1 | Happy, Neutral, Disgust, Sad, Anger | 20 | Fear, Surprise | | Session 2 | Anger, Sad, Fear, Neutral, Surprise | 20 | Happy, Disgust | | Session 3 | Happy, Surprise, Disgust, Fear, Anger | 20 | Neutral, Sad | | Session 4 | Disgust, Sad, Fear, Surprise, Happy | 20 | Neutral, Anger | **这个设计对评测方式有直接影响。**由于单个 session 内最多只有 5 类, "跨被试、同 session"的留一验证里,7 分类任务的真实类别数其实是 5—— 随机基线是 20% 而不是 14.3%,多数类基线约 24%。 跨 session 合并评测才能用满 7 类,但那样会引入 session 间的域偏移, 这是 SEED-VII 上不同论文数字不易横向比较的根本原因之一。 ### 3.3 特征表示 官方发布的 EEG 特征文件为每个片段提供三种逐秒特征,我们使用其中的 **差分熵(DE, Differential Entropy)**及其 LDS 平滑版本: | 文件键 | 形状 | 含义 | |----|----|----| | `de_LDS_{i}` | (T, 5, 62) | **本工作使用**。经线性动态系统平滑的 DE,逐秒一个时间窗 | | `de_{i}` | (T, 5, 62) | 未平滑的原始 DE | | `psd_{i}` | (T, 5, 62) | 功率谱密度 | 5 个频带为 δ(1–4 Hz) / θ(4–8 Hz) / α(8–14 Hz) / β(14–30 Hz) / γ(30–50 Hz)。 差分熵本质上是对数能量,在固定频带内与频带能量近似单调相关,是 SEED 系列上事实标准的特征。 ### 3.4 我们统计的数据规模 | Session | 时间窗总数(20 人) | 人均窗口数 | |-----------|---------------------|------------| | Session 1 | 19,680 | 984 | | Session 2 | 17,880 | 894 | | Session 3 | 16,000 | 800 | | Session 4 | 16,182 | 809 | | **合计** | **69,742** | **3,487** | 每个被试 80 个片段;单个片段的窗口数在 13–87 之间(均值 43.6), 因为各视频时长不同。**注意每个 session 的窗口总数不同**—— 这意味着留一验证时每折的训练集大小都不一样,工程上需要留意(见第 5.7 节)。 ### 3.5 目录结构 ``` SEED_VII/ ├── EEG_raw/ # 原始 .cnt(1000 Hz),每人 4 个 session ├── EEG_preprocessed/ # 预处理后原始波形(62ch, 200 Hz) ├── EEG_features/ # ★ 本工作使用:de_LDS_1..80 / de_1..80 / psd_1..80 ├── continuous_labels/ # 逐秒连续情绪标签,键为片段编号 ├── EYE_raw/ EYE_features/ # 眼动原始数据与特征 ├── save_info/ # 触发器等采集元信息 ├── src/ # 官方预处理脚本 + 通道位置文件 ├── emotion_label_and_stimuli_order.xlsx # ★ 片段 → 情绪标签映射 ├── subject info.xlsx # 被试性别/年龄/人格量表 └── Channel Order.xlsx # 62 电极顺序 ``` ## 4 运行环境与复现 ### 4.1 创建 conda 环境 **版本选择有个坑。**直接 `pip install torch` 会装到 cu130 的轮子, 而本机驱动是 570(CUDA 12.8),会报 `CUDA initialization: The NVIDIA driver on your system is too old`。 必须装 cu12x 系列,或使用下表已验证的组合。 ``` bash # 1) 基础环境(Python 3.9 / 3.10 均可) conda create -y -n seed7 python=3.10 conda activate seed7 # 2) PyTorch —— 必须匹配驱动的 CUDA 版本 pip install torch==2.4.0 torchvision==0.19.0 \ --index-url https://download.pytorch.org/whl/cu124 # 3) 其余依赖 pip install numpy==1.26.4 scipy scikit-learn pandas h5py openpyxl \ matplotlib einops tqdm # 4) 验证(应输出 True 和 2) python -c "import torch; print(torch.cuda.is_available(), torch.cuda.device_count())" ``` | 组件 | 本工作验证版本 | 备注 | |----|----|----| | OS | Ubuntu(内核 5.x) | — | | GPU | NVIDIA RTX 3090 (24 GB) | 单卡即可,显存占用 \< 2 GB | | 驱动 | 570.195.03(CUDA 12.8) | 决定可用的 torch 轮子 | | Python | 3.9.25 | 3.10 亦可 | | PyTorch | 2.4.0+cu124 | 原仓库 requirements 写 2.2.2,本机驱动不支持更高版本 | | NumPy / SciPy | 2.0.2 / 1.13.1 | 读 `.mat` 只需 scipy | ### 4.2 数据准备 官方发布的 `EEG_features` 是**每个被试一个文件、内含 80 个片段**, 而留一验证需要按 **session 切片**。因此需要先做一次重排: ``` bash # 把官方 de_LDS 转成按 session 组织的、每被试一个文件的形式 python prepare_de.py # 产出:DE/{1,2,3,4}/{1..20}.mat # DE : (N, 62, 5) float64 -- (时间窗, 电极, 频带) # labelAll : (N, 1) float64 -- 0..6 # 脚本内含三重校验: # 1) 形状断言 # 2) 标签分布对照 emotion_label_and_stimuli_order.xlsx # 3) 与官方 de_LDS_1 的逐值 round-trip 比对 ``` **一个容易踩的坑:张量维度顺序。**代码注释里写 DE 是 `(通道数, 样本数, 频带数)`,但这是**过时注释**。 真实布局必须由下游脚本反推:`np.concatenate(data_folds, axis=0)` 之后 `np.mean(all_data, axis=0)` 要能得到 `(62, 5)` 的每通道归一化量, 只有 **samples-first 的 (N, 62, 5)** 才自洽。 按注释去做会得到静默错误的结果(不报错,但精度大跌)。 ### 4.3 训练与评测 **代码开箱即用,可一键运行。**完成上面的环境配置与数据准备后, 执行下面这条命令即可自动完成全部 80 折的留一训练与评测,无需逐折手动调用, 也无需中途干预;训练结束后再跑一条命令即可输出全部统计指标、结果图和本报告。 ``` bash # 单折调试(session 1,留出第 1 位被试) python run_loso.py --epochs 50 --no-compile \ --sessions 1 --folds-per-session 1 --out /tmp/one.json # 完整 80 折:每折一个独立进程,用并发填满 GPU 调度器 bash run_eager_parallel.sh # 汇总 python analyze_final.py ``` ### 4.4 验证协议 ``` for session in {1,2,3,4}: # 每个 session 20 个片段 for held_out_subject in 1..20: # 共 80 折 test = 该被试在该 session 的全部时间窗 train = 其余 19 名被试在【同一 session】的时间窗 逐文件做特征维 z-score Adam(1e-3, wd 1e-4) / batch 32 / 50 epochs 指标 = 50 个 epoch 内在测试集上的最优准确率 ``` **指标本身带乐观偏差。**"50 个 epoch 内测试集最优"意味着模型选择发生在测试集上。 实测:最后一轮的平均准确率只有 **50.37%**, 而"历史最优"是 **53.29%**,差距约 2.9 pp。 横向比较不同论文时需要注意对方是否也用了同样的策略。 ## 5 实验结果 ### 5.1 主结果:SEED-VII 被试独立(LOSO,7 分类) | 指标 | 数值 | |-------------------|------------| | 复现均值(80 折) | 53.29% | | 折间标准差 | 9.14% | | 均值标准误 | 1.03 pp | | 单折范围 | 34.1–76.7% | ![80 折准确率分布](images/loso-accuracy.png) 80 折各自的准确率分布。红实线为复现均值,绿虚线为对照值,红色阴影为均值的 95% 置信区间。 | Session | 折数 | 均值 (%) | 标准差 | 最低 | 最高 | |-----------|--------|-----------|----------|----------|----------| | Session 1 | 20 | 54.64 | 9.26 | 38.8 | 73.6 | | Session 2 | 20 | 54.12 | 9.44 | 34.9 | 76.7 | | Session 3 | 20 | 54.91 | 8.10 | 42.1 | 73.5 | | Session 4 | 20 | 49.47 | 8.60 | 34.1 | 62.2 | | **总体** | **80** | **53.29** | **9.14** | **34.1** | **76.7** | Session 4 明显更难(49.47% vs Session 3 的 54.91%), 其情绪构成为 Disgust / Sad / Fear / Surprise / Happy —— 包含三个负性且相互易混的类别。 ### 5.2 与其他方法的对比(被试独立 / LOSO) 下表列出该方法论文中报告的 SEED-VII 被试独立结果,并附上我们的复现行。 所有方法的验证协议一致(同 session 内留一被试,7 分类)。 | 方法 | 准确率 / 标准差 (%) | |---------------------------|---------------------| | KNN | 21.01 / 4.67 | | SVM | 30.73 / 11.12 | | DGCNN | 35.94 / 7.88 | | BiHDM | 34.34 / 6.18 | | RGNN | 37.49 / 5.44 | | PGCN | 38.78 / 5.39 | | Conformer | 45.60 / 9.74 | | FAT (w/ FAN) | 49.00 / 10.32 | | FAT(对照:原论文报告值) | 51.12 / 8.51 | | FAT(本工作复现,80 折) | 53.29 / 9.14 | 相对表中最强基线 Conformer(45.60%),复现结果领先约 **7.7 pp**;相对最弱的 KNN(21.01%)领先约 32.3 pp。 这也说明**被试独立场景下 SEED-VII 是相当困难的任务**—— 7 类(单 session 内实际 5 类)的准确率普遍在 40–55% 区间, 远低于被试依赖(同一被试的数据同时出现在训练与测试中)的设定。 ### 5.3 补充参考:被试依赖(4 折)设定下的报告结果 作为横向参照,下表列出该方法在**被试依赖**设定(训练集与测试集都包含全部被试的数据) 下报告的结果。本工作的实验全部在被试独立设定下完成,因此本节仅作参考,不含复现值。 | 方法 | 准确率 / 标准差 (%) | |------------------|---------------------| | KNN | 36.87 / 4.41 | | SVM | 40.07 / 8.61 | | DGCNN | 46.36 / 7.33 | | BiHDM | 45.57 / 8.75 | | RGNN | 48.50 / 6.83 | | PGCN | 49.94 / 7.48 | | Conformer | 54.72 / 6.98 | | FAT (w/ FAN) | 59.42 / 8.59 | | 本方法(报告值) | 61.30 / 9.81 | 被试依赖设定下所有方法的数值都明显高于被试独立设定 (例如同一模型 61.30% vs 51.12%),因为训练集里已经见过测试被试的数据。 两种设定的数字不可直接比较。 ### 5.4 消融:频带贡献 | 输入频带 | 准确率 / 标准差 (%) | |-----------------|---------------------| | δ (1–4 Hz) | 40.97 / 7.77 | | θ (4–8 Hz) | 41.34 / 5.45 | | α (8–14 Hz) | 38.53 / 6.31 | | β (14–30 Hz) | 42.31 / 5.77 | | γ (30–50 Hz) | 41.03 / 8.65 | | β + γ | 46.90 / 10.70 | | β + γ + δ | 48.16 / 10.19 | | 全频带 (5 band) | 51.12 / 8.51 | #### 从消融能读出什么 - **单频带之间差异很小**(38.5–42.3%),说明情绪信息在 5 个频带上分布较均匀, 不存在某个"万能频带"; - **α 频带单独最弱**(38.53%),略低于随机基线之上不多; - **频带组合显著优于单频带**:β+γ 46.90%、β+γ+δ 48.16%、全频带 51.12%, 呈清晰单调递增; - 全频带相对最优单频带(β,42.31%)提升约 **+8.8 pp**, 说明多频带互补是性能的主要来源之一。 ### 5.5 收敛过程 | 评测预算 | 平均准确率 (%) | |----------------|----------------| | 前 5 个 epoch | 0.49 | | 前 10 个 epoch | 0.51 | | 前 20 个 epoch | 0.52 | | 前 30 个 epoch | 0.53 | | 前 40 个 epoch | 0.53 | | 前 50 个 epoch | 0.53 | 到第 50 轮仍在缓慢上升(第 40→50 轮 +0.18 pp),说明开源实现中的 `num_epochs = 50` 是刚好够用的设置。 ### 5.6 结果解读 **复现成功。**80 折平均 **53.29 ± 9.14%**,与对照值 51.12 ± 8.51% 相比: - 均值差 **+2.17 pp**(约 2.1 个标准误,双侧 p ≈ 0.04); - 标准差高度吻合(9.14 vs 8.51),说明**折间波动结构一致**; - 95% 置信区间 **\[51.27, 55.30\]**,对照值落在区间内。 由于该指标是在测试集上取历史最优、且原论文未报告随机种子或重复次数, ±2 pp 的运行间抖动属于协议固有噪声。实测同一折换种子可以从 **41% 波动到 56%**(单折标准差远大于 80 折均值的标准误 1.03 pp), 因此这个量级的差异不构成"复现失败"的证据。 ### 5.7 复现过程中的两个工程要点 #### ① 这是 kernel 启动瓶颈,不是算力瓶颈 模型由大量微小算子拼成,**每个训练步产生 1232 个 CUDA kernel**。实测: - 单进程 GPU 利用率仅 **11%**;batch 32 与 batch 512 的单步耗时几乎相同(~70 ms) → 纯调度受限; - 用算子融合可获得 **8.3×** 提速(74 → 9.3 ms/step); - **但每折训练集大小不同**(见 3.4),末批 partial batch 形状每折变化, 导致编译缓存无限累积(计数器涨到 `[0/63]`,每折 +1.2 s/epoch, 最终劣化到 150 s/epoch)。**不要试图调大 `cache_size_limit` 来"解决"它。** - 最终方案:**80 个进程一折一进程并行**,把 GPU 调度器填满, 整体从串行约 50 小时降到 **3.6 小时**。 #### ② 并发有硬上限 80 个进程同时运行会让部分进程在 cuBLAS 句柄创建处失败: ``` CUDA error: CUBLAS_STATUS_ALLOC_FAILED when calling `cublasCreate(handle)` ``` 第一轮 80 折中有 **30 折**因此丢失(无输出、无报错到主日志)。 降到并发 **24** 重跑后全部补齐。 **建议并发不超过 ~32**,并对每个 worker 加"检查产出文件、失败自动重试"的包装。