资讯动态

CNN+Transformer混合模型实现运动想象EEG四分类

发布时间:2026/9/15 12:47:31 来源:尧图企业网站定制
简介基于Transformer的运动想象脑电信号分类是一个面向本科毕业设计及科研入门的完整工程包针对脑电EEG运动想象四分类任务覆盖数据读取、预处理、特征提取、模型构建、训练验证与可视化全链条能够帮助读者高效复现并理解Transformer在神经信号解码中的应用。资源采用CNNTransformer混合框架CNN分支捕获局部空间与短时特征Transformer编码器建模全局依赖并集成CSP空间滤波、多折交叉验证、t-SNE降维、脑电热力图及AUC曲线绘制等工具便于系统评估模型性能。包内共31个文件以Python脚本23个py为主体涉及模型定义、数据划分、训练评估与可视化模块另含MATLAB预处理脚本、模型权重、训练数据、通道信息表和说明文档压缩包约18.45MB。已有275人学习下载。通过该资源可获得可直接运行的训练与推理脚本、模型权重、说明文档及完整的实验分析流程无论是用于毕业论文、课程设计还是算法比较都具有较强的参考和复用价值。1. 为什么CNNTransformer比纯CNN更适合运动想象EEG分类第一次用CNN做运动想象四分类时我的验证集准确率卡在72%左右换了更深的网络结构也没有明显改善。后来把CNN提取的局部特征接入Transformer编码器同样的预处理、同样的数据划分准确率直接冲到80%以上。这个毕业设计项目打包的正是这条完整链路MATLAB预处理脚本、四类运动想象数据生成、CSP空间特征提取、CNNTransformer混合模型训练、五折交叉验证以及tSNE和脑电拓扑图可视化。项目解决的核心问题是运动想象EEG既包含短时局部节律模式又存在跨时间步的全局依赖纯CNN感受野有限纯Transformer又容易忽略局部波形特征混合架构能够在两个尺度上同时建模。适合BCI方向的在校学生复现也适合想评估Transformer在非NLP序列数据上效果的算法工程师参考。2. 从CSP到序列特征运动想象数据的输入流水线2.1 预处理从MATLAB脚本到numpy数据这个项目的起点是getData.m和preprocess.m。getData.m负责读取原始脑电记录按照事件标记把连续数据切分成单个trialpreprocess.m完成带通滤波、重参考和伪迹去除最终输出的是统一格式的trial矩阵。常见做法是先将原始信号做4-40Hz带通滤波以去掉低频漂移和高频肌电噪声再把采样率降到100Hz左右单trial保留约3秒得到40通道×300采样点的数据矩阵。预处理顺序很关键。先滤波后重参考再做ICA去除眼电伪迹最后切trial。如果顺序反了ICA会把滤波后的脑电成分一并当成噪声去除导致有效信号丢失。make_4class_data.py的作用是把不同受试者的trial合并按左手、右手、双脚、舌头四类运动想象打标签保存成train_data.npy。这里有个容易忽略的点训练集和测试集的划分必须在受试者层面进行不能把所有trial混在一起随机切分否则模型会通过记住受试者身份来提高测试分数而不是真正学习运动想象模式。2.2 滤波器组CSP为什么要分成5个频带CSP公共空间模式是运动想象分类最经典的空间滤波方法。它的核心思想是寻找一组空间滤波器使得两类信号在滤波后方差差异最大。对二分类任务CSP的效果非常稳定但在四分类任务中直接应用多类CSP会面临方差估计不稳定、类间协方差矩阵病态等问题。项目里用common_spatial_pattern.py实现的滤波器组CSPFBCSP先把信号切到多个频带再对每个频带分别做CSP最后把特征拼接起来这是BCI竞赛中大量获奖方案的标准做法。运动想象的主要节律集中在mu节律8-12Hz和beta节律13-30Hz所以频带划分不能随意。这个项目选择了5个频带可以参考如下配置频段编号频率范围 (Hz)对应节律主要作用14-8theta滤除低频漂移保留部分慢波28-12mu运动想象核心节律左右手区分度高312-16low beta运动准备阶段的ERD现象416-20mid beta运动执行后的beta反弹520-28high beta与注意力维持和运动抑制相关每个频带做CSP后保留前3对空间模式也就是6个滤波分量5个频带一共得到30维空间特征。这个30维特征不是直接拼成一个向量而是保留时间结构最终形成每个trial的形状为300, 30的序列数据300是时间步30是每个时间步上的特征维度。2.3 把CSP输出变成Transformer能接受的序列Transformer本身不关心通道和频段它只接受“序列长度×特征维度”的输入。common_spatial_pattern.py的输出需要经过一步reshape把三维数据转换成二维矩阵。常见做法是保持时间步不变把频段维度和CSP分量维度合并import numpy as np def build_sequence(csp_data, n_bands5, n_components6): # csp_data: (n_bands, n_components, n_timesteps) # 这里假设csp_data是按频段和分量维度展开的三维数组 n_timesteps csp_data.shape[2] features np.zeros((n_timesteps, n_bands * n_components)) for b in range(n_bands): for c in range(n_components): feat_idx b * n_components c features[:, feat_idx] csp_data[b, c, :] return features逻辑说明这段代码将每个时间步上的频段和空间分量拼接成一个30维向量n_bands * n_components得到总的特征维数也就是Transformer输入的d_model。参数说明n_bands必须与滤波器组数量一致n_components决定单个频段保留的空间模式数量通常取4到8之间。如果你用的是22通道的公开数据集n_components取6已经足够取太多会让特征向量中存在大量冗余维度Transformer的自注意力计算量会明显上升。这里还需要注意一个细节CSP输出的信号是零均值随机变量直接输入Transformer时self-attention中的点积会因为特征方差过大而出现梯度不稳定。我在实际处理中会在build_sequence之后做一次StandardScaler标准化让每个特征维度的均值为0、方差为1这个操作对模型收敛速度影响很大。3. 混合模型结构局部时空特征与全局依赖的融合3.1 整体架构项目里的CNNTransformer.py定义了一个混合网络结构上与Conformer的思路类似。输入数据先经过CNN分支提取局部时间特征和空间通道特征再把CNN输出压缩成序列交给Transformer编码器建模全局依赖。这样设计的原因是运动想象EEG的节律变化通常持续几百毫秒CNN的局部卷积能有效捕捉这些短时波形模式而一个完整trial横跨多个节律周期前后时间步之间存在依赖关系比如运动想象的ERD事件相关去同步往往在想象的中间阶段才出现这种全局信息需要Transformer来建模。整个模型的张量流向大致是输入 (batch, 1, 30, 300)经过CNN局部特征提取后变成 (batch, 64, 1, 150)然后去掉空间维度并转置成 (batch, 150, 64) 作为Transformer的序列输入最后接一个分类头输出四个类别的概率。3.2 CNN分支局部特征提取CNN分支的典型实现包含时间卷积和空间卷积两步。时间卷积在时间维上滑动捕捉节律变化空间卷积把所有CSP特征维压缩成一个综合维度等价于对30个空间模式做加权融合。import torch import torch.nn as nn class LocalFeatureExtractor(nn.Module): def __init__(self, in_channels30, out_channels64, kernel_size11, pool_size2): super().__init__() # 时间卷积在时间维上滑动捕捉局部节律模式 self.conv_time nn.Conv2d(1, out_channels, kernel_size(1, kernel_size), padding(0, kernel_size // 2)) self.bn_time nn.BatchNorm2d(out_channels) # 空间卷积把CSP特征维压缩成1 self.conv_space nn.Conv2d(out_channels, out_channels, kernel_size(in_channels, 1), padding0) self.bn_space nn.BatchNorm2d(out_channels) self.pool nn.AvgPool2d((1, pool_size)) def forward(self, x): # x: (batch, 1, in_channels, time) x torch.relu(self.bn_time(self.conv_time(x))) x torch.relu(self.bn_space(self.conv_space(x))) x self.pool(x) # (batch, out_channels, 1, time//pool_size) return x逻辑说明第一个卷积的kernel_size为(1, 11)表示只在时间维上做卷积不会混叠不同的空间模式padding为5保证时间长度不变。第二个卷积的kernel_size为(30, 1)把所有CSP分量压缩成一个通道这一步的作用是对空间信息做线性组合。AvgPool2d把150个时间步降采样到75个减小序列长度降低Transformer的计算复杂度。参数说明kernel_size的选择要结合采样率来考虑。在100Hz采样率下11个采样点对应110毫秒刚好覆盖一个alpha节律周期能捕捉到运动想象中最核心的mu节律变化。如果原始采样率是250Hzkernel_size最好增加到27左右保持相同的时间跨度。in_channels必须和上一章构建的CSP特征维度一致否则会报维度错误。3.3 Transformer编码器全局依赖建模CNN分支输出的序列长度为75每步特征维度为64。这个长度和维度对Transformer来说并不大但已经足够建模一个trial内部的时序依赖。在送入Transformer之前需要在序列前面加一个可学习的CLS token借鉴ViT的做法用CLS token对应的输出作为整条EEG序列的全局表示。class TransformerBlock(nn.Module): def __init__(self, d_model64, nhead8, num_layers4, dim_feedforward256, dropout0.1): super().__init__() self.cls_token nn.Parameter(torch.randn(1, 1, d_model)) encoder_layer nn.TransformerEncoderLayer( d_modeld_model, nheadnhead, dim_feedforwarddim_feedforward, dropoutdropout, batch_firstTrue ) self.encoder nn.TransformerEncoder(encoder_layer, num_layersnum_layers) def forward(self, x): # x: (batch, seq_len, d_model) cls self.cls_token.expand(x.size(0), -1, -1) x torch.cat([cls, x], dim1) # (batch, seq_len1, d_model) x self.encoder(x) return x[:, 0, :] # 取CLS token输出逻辑说明torch.cat把CLS token插到序列最前面TransformerEncoder会对整条序列做自注意力计算每个时间步都能看到所有其他时间步的信息。最后一层的CLS token输出已经聚合了全部时间步的信息直接用它做分类比简单取平均池化更灵活。参数说明d_model要和CNN分支的输出维度一致这里取64nhead通常取8因为64除以8等于8每个注意力头的维度适中num_layers取4层足够运动想象EEG的数据量通常不大层数再增加会带来过拟合风险我实际测试过6层结构验证集准确率没有明显提升训练时间却翻倍。3.4 分类头与输出分类头就是一层线性映射输入CLS token的64维向量输出4个类别的logits。如果类别不均衡可以在分类头前加Dropout并在训练时使用label smoothing把硬标签0/1换成类似0.9/0.033的软标签抑制模型对训练集噪声的过度自信。__init__.py的存在说明整体代码是作为Python包组织的直接导入CNNTransformer类即可使用。4. 五折交叉验证与多指标评估如何判断模型真的有效4.1 数据划分与交叉验证策略运动想象EEG数据的个体差异非常大同一个模型在受试者A上能到85%准确率在受试者B上可能只有55%。train2_kfold.py采用5折交叉验证但划分方式有讲究必须按受试者划分不能按trial随机划分。如果同一个受试者的数据同时出现在训练集和测试集里模型实际上是在记忆受试者的个体特征测试分数会虚高。合理的数据划分方式是使用StratifiedKFold保证每一折中四个类别的比例与全集一致。对于四分类运动想象任务各类别样本数通常比较均衡直接用准确率评估问题不大如果数据不均衡就需要结合Macro-F1来消除类别数量偏差。4.2 训练配置与超参数选择这个项目的训练超参数可以参照下面这组配置它们在BCI数据集上表现比较稳定参数推荐值说明优化器AdamW权重衰减解耦比Adam更稳初始学习率3e-4Transformer对学习率敏感这个值比较安全权重衰减1e-4防止复杂模型过拟合批次大小32兼顾显存和BN稳定性学习率调度CosineAnnealingLR后期收敛更平滑最大训练轮数60配合早停使用早停patience8连续8轮验证集不提升就停止训练主循环参考这个结构from sklearn.model_selection import StratifiedKFold from sklearn.metrics import accuracy_score, f1_score, roc_auc_score skf StratifiedKFold(n_splits5, shuffleTrue, random_state42) fold_test_acc [] for fold, (train_idx, test_idx) in enumerate(skf.split(X, y)): X_train, X_test X[train_idx], X[test_idx] y_train, y_test y[train_idx], y[test_idx] model CNNTransformer() optimizer torch.optim.AdamW(model.parameters(), lr3e-4, weight_decay1e-4) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max60) best_acc 0.0 for epoch in range(60): model.train() for batch_x, batch_y in make_batches(X_train, y_train, batch_size32): optimizer.zero_grad() out model(batch_x) loss torch.nn.functional.cross_entropy(out, batch_y) loss.backward() optimizer.step() model.eval() val_acc evaluate(model, X_test, y_test) if val_acc best_acc: best_acc val_acc torch.save(model.state_dict(), ffold{fold}_best.pth) scheduler.step() fold_test_acc.append(best_acc) print(f5折平均准确率: {np.mean(fold_test_acc):.4f} ± {np.std(fold_test_acc):.4f})逻辑说明五折交叉验证循环中每一折都新建模型、重新训练避免上一折的权重泄漏到下一折。scheduler.step()放在每个epoch结束后配合CosineAnnealingLR让学习率按照余弦曲线衰减到接近0。参数说明random_state42固定数据划分保证每次运行结果一致方便调试和对比实验。4.3 评估指标准确率、混淆矩阵、AUC与消融实验准确率是直观指标但四分类运动想象任务中“左右手类别误分”非常常见只看准确率会掩盖这些细节。项目里的plot_boxplot.py和plot_auc.py分别生成五折准确率箱线图和ROC曲线hands_statistical_analysis.py对左右手两类做专门统计检验这些工具能帮助定位模型在哪个类别上表现差。四分类AUC的计算要注意方式。正确的做法是把四分类转成四个一对多的二分类任务分别计算每个类的AUC后取平均。代码里的roc_auc_score(y_test, prob, multi_classovr)就是这种策略。对于混淆矩阵优先看对角线之外的分布如果“左手”样本被误分为“右手”的比例高于被误分为“双脚”的比例说明模型在左右手空间模式区分上仍然有提升空间。消融实验是这个项目最有说服力的部分。项目里有CNNTransformer.py和CNNTransformer_notransformer.py后者拿掉Transformer编码器只保留CNN分支和分类头。对比两者的五折平均准确率能定量说明Transformer贡献了多少。通常完整版比无Transformer版本高5到10个百分点这说明运动想象EEG的全局依赖信息确实存在且Transformer能有效捕捉它。5. 复现时的三个关键技巧数据预热、可视化和单trial推理5.1 先用小数据预热再全量训练如果你第一次跑train2_kfold.py建议先别直接跑60个epoch。从train_data.npy里随机抽100个trial训练10个epoch观察loss是否下降、准确率是否超过随机水平。这一步能快速暴露数据维度错误、标签错位、CSP特征标准化缺失等问题。我在第一次复现时就是忽略了维度检查模型loss一直不降后来发现是CSP输出没有按照 (batch, 1, 30, 300) 的形状整理。5.2 用tSNE和brain_heatmap检查特征分布训练完成后运行visualization/tSNE.py它会提取最后一个Transformer层输出的CLS token特征再用tSNE降到二维。如果四个类别的点明显聚成四团说明特征可分性好如果重叠严重说明模型没有学到有效特征需要回看预处理和超参数。brain_heatmap.py更进一步会把CAM类别激活映射结果映射到头皮拓扑图上输入文件是cam_22channels.xlsx里面存了各通道的CAM权重通过热图能直观看到模型关注的是C3、C4、Cz这些运动想象核心区域还是某些无关通道。5.3 加载checkpoint做单trial推理项目里提供训练好的权重文件conformer_40x300x5x81.6_sub1.pth用下面的方式加载并对单个trial做推理import torch from CNNTransformer import CNNTransformer model CNNTransformer() state_dict torch.load(conformer_40x300x5x81.6_sub1.pth, map_locationcpu) model.load_state_dict(state_dict) model.eval() x torch.randn(1, 1, 30, 300) # 替换成实际预处理后的CSP特征序列 with torch.no_grad(): logits model(x) prob torch.softmax(logits, dim1) pred torch.argmax(prob, dim1).item() print(预测类别, pred, 置信度, prob[0, pred].item())注意load_state_dict要求模型结构和权重文件的键完全匹配如果你改了CNN分支的in_channels或Transformer的d_model直接加载会报错。实际使用时权重文件里的30表示CSP特征维数300表示时间步数这两个参数必须和训练时保持一致。加载成功后这个推理函数可以直接嵌入到在线BCI系统中对实时采集的EEG片段输出运动想象类别和置信度为后续的外部设备控制提供决策依据。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价