
简介本资源是一套面向计算机及相关专业本科生的毕业设计实战项目聚焦脑电信号EEG四分类任务采用CNN与Transformer融合架构实现高精度建模专为毕设选题、课程设计及AI医疗交叉实践者打造。压缩包共31个文件含23个Python核心脚本覆盖数据预处理、模型定义、五折交叉训练、CAM可解释性分析、t-SNE可视化等全流程、2个Excel权重与通道统计表、2个MATLAB数据生成脚本、1个PyTorch模型权重.pth文件、1个README说明文档及配套工具模块整体18.46MB结构清晰、模块解耦小白可依序运行。已有63人学习下载项目经导师指导并获99分高分评审代码完整、注释详尽附带脑电热力图、AUC曲线、箱线图等可视化脚本提供从原始信号到分类决策的端到端可复现方案。1. 项目概述与核心价值又到了一年一度的毕业季最近不少计算机、生物医学工程甚至电子信息专业的学弟学妹来找我问得最多的就是关于“基于深度学习的脑电信号分类”这个方向的毕业设计怎么做。特别是当“Transformer”这个在NLP和CV领域大杀四方的模型开始被引入到时间序列信号处理中时很多同学既兴奋又迷茫。兴奋的是这听起来就是个高大上、紧跟前沿的课题迷茫的是脑电信号EEG本身噪声大、个体差异显著Transformer又是个“数据饕餮”本科阶段有限的资源和数据真的能玩得转吗我当年本科毕设做的就是EEG情绪识别踩过不少坑。今天我就以“基于Transformer的脑电信号分类系统”这个热门选题为例结合一个典型的“CNNTransformer”混合框架源码从头到尾拆解一遍。这个项目的核心价值在于它没有盲目堆砌最前沿的复杂模型而是采用了一种非常务实且有效的架构用CNN卷积神经网络作为“前端特征提取器”来捕捉脑电信号的局部时空特征再用Transformer作为“后端上下文建模器”来学习这些特征之间的长程依赖关系。这种组合拳既利用了CNN在图像/信号局部特征提取上的成熟优势又引入了Transformer强大的全局建模能力非常适合处理EEG这种具有高维时空特性的信号。对于本科生来说这个框架思路清晰代码结构相对模块化既有足够的创新点又具备较高的可实现性是平衡“课题新颖性”和“完成度”的绝佳选择。2. 核心架构设计为什么是CNNTransformer在深入代码之前我们必须先搞清楚这个混合架构的设计哲学。很多同学一上来就想着用纯Transformer处理EEG结果往往训练困难、效果平平。这里面的“为什么”是关键。2.1 脑电信号的独特性与处理挑战脑电信号是从头皮表面记录到的神经元群电活动它本质上是高维的时间序列数据。以常用的64通道脑电帽为例每一秒的数据就是一个64空间维度x 采样率如250Hz时间维度的矩阵。它的主要挑战在于信噪比极低有用的神经活动信号微伏级淹没在各种生理伪迹眼动、肌电、心电和环境噪声中。高维且冗余通道多但相邻通道信号高度相关存在大量冗余信息。非平稳性信号的统计特性会随时间变化同一个人在不同时间、不同状态下记录的EEG也会有差异。个体差异性大不同人的头皮结构、脑电节律特征差异显著导致模型泛化困难。直接将这些原始信号扔给Transformer就像把一本没有章节、没有标点的天书扔给一个语言模型它很难从中学习到有效的模式。因为Transformer的核心——自注意力机制虽然擅长建立全局关联但它本身不具备对输入数据的局部归纳偏置Inductive Bias。它需要模型自己从数据中学习“什么是局部特征”这对于数据量有限的EEG任务来说效率太低。2.2 CNN作为特征提取器的必然性这就是CNN登场的原因。CNN的卷积核天生就是为捕捉局部模式而设计的。在EEG的语境下时间维度卷积可以看作是一个滑动窗口用来提取特定频率节律如Alpha波8-13Hz或瞬态事件如事件相关电位ERP的特征。一个尺寸为1, 采样率*0.1的卷积核就能学习到持续100毫秒左右的波形模式。空间维度卷积可以学习不同电极通道之间的空间拓扑关系。例如一个覆盖中央区C3, C4, Cz的二维卷积核可以捕捉运动想象任务中对侧脑区的活动变化。实操心得在毕设中我们通常不会直接用2D卷积处理EEG图像虽然也有这种方法更常见的是使用1D卷积沿时间轴或混合使用1D和2D卷积。一个经典且有效的起点是使用深度可分离卷积Depthwise Separable Convolution它先对每个通道独立进行时间卷积再用1x1卷积进行通道融合参数量小效率高非常适合作为特征提取的基石。2.3 Transformer作为上下文建模器的优势经过CNN的“精加工”我们得到的不再是原始的、嘈杂的电压序列而是一系列更具判别性的“特征图”或“特征序列”。此时再将这些特征序列输入Transformer就非常合适了。序列化输入我们可以将CNN输出的特征图在时间维度上展平或者将不同通道的特征向量拼接形成一个特征序列。每个序列元素token都包含了某个时间片段或某个特征层面的信息。自注意力机制Transformer的自注意力层允许序列中的任意两个“token”直接交互。这意味着模型可以自动发现“前额叶theta波增强”与“枕叶alpha波抑制”之间的远距离关联这种关联可能对应着特定的认知状态如工作记忆负载。位置编码由于自注意力本身是置换不变的不关心顺序我们需要加入位置编码Positional Encoding来注入序列的顺序信息。对于EEG这种严格按时序发生信号位置编码至关重要。为什么不是纯Transformer如前所述纯Transformer需要海量数据来学习基础的局部特征而EEG数据标注成本极高公开数据集规模有限通常只有几十到几百个被试。CNNTransformer的混合架构实际上是将特征提取CNN和关系建模Transformer这两个任务解耦让每个模块各司其职大大降低了数据需求提升了训练效率和模型稳定性。这是一种非常经典的“分而治之”的深度学习设计思想。3. 系统源码深度解析与实操要点接下来我们以一个典型的PyTorch实现为例深入这个“CNNTransformer”分类系统的源码核心。假设我们的任务是四分类如放松、专注、焦虑、疲劳使用BCI竞赛IV 2a数据集22通道250Hz每个试次4秒。3.1 数据预处理模块一切的基础数据预处理的质量直接决定了模型性能的天花板。这部分代码往往被忽视但却是实战中的重中之重。import numpy as np import scipy.signal as signal from sklearn.preprocessing import StandardScaler class EEGDataProcessor: def __init__(self, lowcut4.0, highcut40.0, sfreq250, notch_freq50.0): 初始化处理器 :param lowcut: 带通滤波下限频率 (Hz) :param highcut: 带通滤波上限频率 (Hz) :param sfreq: 采样频率 :param notch_freq: 陷波滤波频率 (用于去除工频干扰) self.sfreq sfreq self.lowcut lowcut self.highcut highcut self.notch_freq notch_freq # 创建滤波器系数 self._create_filters() def _create_filters(self): # 带通滤波器 (巴特沃斯4阶) nyq 0.5 * self.sfreq low self.lowcut / nyq high self.highcut / nyq self.b, self.a signal.butter(4, [low, high], btypeband) # 陷波滤波器 (用于去除50Hz工频干扰) self.notch_b, self.notch_a signal.iirnotch(self.notch_freq, 30, self.sfreq) def process_single_trial(self, raw_eeg): 处理单个试次数据 [channels, time_points] # 1. 去趋势 (移除线性漂移) detrended signal.detrend(raw_eeg, axis1) # 2. 陷波滤波 (去除工频干扰) notch_filtered signal.filtfilt(self.notch_b, self.notch_a, detrended, axis1) # 3. 带通滤波 (提取特定频段如Theta, Alpha, Beta) band_filtered signal.filtfilt(self.b, self.a, notch_filtered, axis1) # 4. 重参考 (常用平均参考) avg_ref band_filtered - np.mean(band_filtered, axis0, keepdimsTrue) # 5. 标准化 (按通道进行消除幅度差异) scaler StandardScaler() normalized scaler.fit_transform(avg_ref.T).T # 转置以适应sklearn接口 # 6. 降采样 (可选如果原始采样率过高) # resampled signal.resample(normalized, new_num_points, axis1) return normalized注意事项与避坑指南滤波器的选择filtfilt函数实现了零相位滤波避免了相位失真这对后续分析至关重要。巴特沃斯滤波器通带平坦是EEG处理的常用选择。重参考平均参考是常用方法但要根据具体实验范式选择。例如研究听觉诱发电位可能选择乳突参考。标准化务必按通道进行标准化axis1因为不同通道的阻抗和信号幅度差异很大。如果对整个试次的所有数据做全局标准化会破坏通道间的相对关系。批处理在实际训练中我们需要对整个数据集进行预处理并保存为.npy或.h5格式避免在每次训练迭代时重复计算这是提升训练速度的关键。3.2 CNN特征提取器实现这里我们设计一个轻量但有效的CNN模块它接收预处理后的EEG数据[batch_size, channels, time_points]。import torch import torch.nn as nn import torch.nn.functional as F class EEGFeatureExtractor(nn.Module): def __init__(self, input_channels22, feature_dim128): super().__init__() # 第一层时间维度卷积捕捉局部时间模式 self.temporal_conv nn.Sequential( nn.Conv1d(in_channelsinput_channels, out_channels64, kernel_size31, padding15, stride2), nn.BatchNorm1d(64), nn.ELU(inplaceTrue), nn.Dropout(0.3), nn.Conv1d(64, 128, kernel_size15, padding7, stride2), nn.BatchNorm1d(128), nn.ELU(inplaceTrue), nn.Dropout(0.3), ) # 第二层空间维度卷积使用1x1卷积模拟通道间的空间关系 self.spatial_conv nn.Sequential( nn.Conv1d(in_channels128, out_channels256, kernel_size1), nn.BatchNorm1d(256), nn.ELU(inplaceTrue), nn.Dropout(0.4), ) # 自适应池化将不同长度的时序压缩为固定长度的特征向量 self.adaptive_pool nn.AdaptiveAvgPool1d(output_size1) # 全连接层输出特征维度 self.fc nn.Linear(256, feature_dim) def forward(self, x): # x shape: [batch, 22, 1000] (假设4秒数据250Hz采样) temporal_features self.temporal_conv(x) # - [batch, 128, 250] spatial_features self.spatial_conv(temporal_features) # - [batch, 256, 250] pooled self.adaptive_pool(spatial_features).squeeze(-1) # - [batch, 256] features self.fc(pooled) # - [batch, feature_dim] return features核心设计解析大卷积核第一层卷积核大小为31约125ms是为了能覆盖EEG中一些基本的节律周期如Alpha波的一个周期约100ms。步长为2实现了下采样降低了后续计算量。1x1卷积nn.Conv1d的kernel_size1在这里充当了“通道混合器”或“空间滤波器”的角色。它学习的是不同特征通道由上一层时间卷积生成之间的权重关系这在一定程度上模拟了电极空间关系的建模。自适应池化AdaptiveAvgPool1d(1)将时间维度压缩为1无论输入时间点多长输出都是[batch, channels]的向量。这确保了特征向量的维度固定便于输入后续模块。Dropout位置在卷积后、激活函数后使用Dropout是常见做法有助于防止过拟合。随着网络加深Dropout率可以适当增加从0.3到0.4。3.3 Transformer编码器适配与序列构建CNN提取出的特征向量是全局的、概括性的。为了用Transformer建模其内部更细粒度的依赖关系我们需要将这个特征向量“还原”或“构造”成一个序列。class EEGTransformerEncoder(nn.Module): def __init__(self, feature_dim128, num_heads8, num_layers4, dim_feedforward512, dropout0.1, num_classes4): super().__init__() self.feature_dim feature_dim # 将CNN特征向量“扩展”为序列。这里我们使用一个可学习的“片段嵌入”矩阵。 # 假设我们将特征拆分成4个片段token。 self.num_tokens 4 self.token_embedding nn.Linear(feature_dim // self.num_tokens, feature_dim) # 可学习的位置编码 self.position_embedding nn.Parameter(torch.randn(1, self.num_tokens, feature_dim)) # Transformer编码器层 encoder_layer nn.TransformerEncoderLayer( d_modelfeature_dim, nheadnum_heads, dim_feedforwarddim_feedforward, dropoutdropout, activationgelu, batch_firstTrue # 使用 (batch, seq, feature) 格式 ) self.transformer_encoder nn.TransformerEncoder(encoder_layer, num_layersnum_layers) # 分类头 self.cls_token nn.Parameter(torch.randn(1, 1, feature_dim)) self.norm nn.LayerNorm(feature_dim) self.classifier nn.Linear(feature_dim, num_classes) def forward(self, cnn_features): # cnn_features shape: [batch, feature_dim (128)] batch_size cnn_features.size(0) # 1. 将特征向量拆分成多个token # 例如将128维特征拆成4个32维的片段 tokens cnn_features.view(batch_size, self.num_tokens, -1) # - [batch, 4, 32] # 2. 对每个token进行线性嵌入投影到feature_dim维度 token_embeddings self.token_embedding(tokens) # - [batch, 4, 128] # 3. 添加[CLS] token用于最终分类 cls_tokens self.cls_token.expand(batch_size, -1, -1) # - [batch, 1, 128] embeddings torch.cat((cls_tokens, token_embeddings), dim1) # - [batch, 5, 128] # 4. 添加位置编码 pos_emb self.position_embedding[:, :embeddions.size(1), :] # 注意位置编码要与拼接后的序列长度匹配 # 我们预先定义的位置编码是4个token现在序列是5个1个CLS4个特征需要扩展或截断。这里简单扩展第一维。 if pos_emb.size(1) embeddings.size(1): # 为CLS token补充一个位置编码可以学习或置零 cls_pos torch.zeros(1, 1, self.feature_dim, deviceembeddings.device) pos_emb torch.cat((cls_pos, self.position_embedding), dim1) embeddings embeddings pos_emb # 5. 通过Transformer编码器 transformer_output self.transformer_encoder(embeddings) # - [batch, 5, 128] # 6. 取[CLS] token对应的输出作为序列表示 cls_output transformer_output[:, 0, :] # - [batch, 128] # 7. 分类 logits self.classifier(self.norm(cls_output)) # - [batch, num_classes] return logits关键点与变体讨论序列构建策略这是将CNN特征适配到Transformer的关键。除了上述“拆分特征向量”的方法还有更复杂的方式多尺度特征图切片如果CNN输出的是二维特征图[batch, channels, time]可以沿时间轴切成多个重叠或非重叠的片段每个片段作为一个token。使用可学习的查询向量类似DETR用一组可学习的向量作为查询query与CNN特征图进行交叉注意力生成token序列。[CLS] Token借鉴自BERT在序列前添加一个特殊的可学习分类token其最终的输出状态用作整个序列的聚合表示。这是一种非常有效的做法。位置编码对于EEG这种强时序信号位置编码至关重要。除了可学习的位置编码也可以使用正弦余弦固定编码。有研究尝试将电极的3D坐标作为空间位置编码效果也不错。Transformer层数与头数对于EEG这类数据量不大的任务Transformer层数不宜过深2-4层足够注意力头数8个是常用起点。过深的网络极易过拟合。3.4 模型整合与训练流程将CNN和Transformer组装起来并配置训练循环。class CNNTransformerEEGClassifier(nn.Module): def __init__(self, input_channels, num_classes, feature_dim128): super().__init__() self.feature_extractor EEGFeatureExtractor(input_channels, feature_dim) self.transformer_encoder EEGTransformerEncoder(feature_dim, num_classesnum_classes) def forward(self, x): features self.feature_extractor(x) logits self.transformer_encoder(features) return logits def train_epoch(model, dataloader, criterion, optimizer, device): model.train() running_loss 0.0 correct 0 total 0 for batch_idx, (data, labels) in enumerate(dataloader): data, labels data.to(device), labels.to(device) optimizer.zero_grad() outputs model(data) loss criterion(outputs, labels) loss.backward() # 梯度裁剪防止Transformer训练不稳定 torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() running_loss loss.item() _, predicted outputs.max(1) total labels.size(0) correct predicted.eq(labels).sum().item() epoch_loss running_loss / len(dataloader) epoch_acc 100. * correct / total return epoch_loss, epoch_acc训练技巧实录优化器选择AdamWAdam with decoupled weight decay是目前训练Transformer类模型的首选它比原始Adam更不容易过拟合。学习率通常设置得较小如3e-4或5e-5。学习率调度使用带热启动的余弦退火CosineAnnealingWarmRestarts或线性预热Linear Warmup配合余弦衰减对Transformer训练非常友好。梯度裁剪Transformer的训练中梯度可能爆炸clip_grad_norm_是标准操作范数阈值一般设为0.5到1.0。早停法Early Stopping在验证集性能连续多个epoch不提升时停止训练是防止过拟合最有效的手段之一。数据增强对于EEG可以在时域添加轻微的高斯噪声、随机缩放、随机通道丢弃模拟坏导在频域进行随机频带滤波等。数据增强能显著提升小数据集上的泛化能力。4. 实验设置、结果分析与可视化一个完整的毕设必须有严谨的实验设计和结果分析。4.1 数据集划分与评估指标数据集使用公开数据集如BCI Competition IV 2a, High Gamma Dataset等。务必遵循官方或领域内通用的被试独立subject-independent划分方式即训练集和测试集来自不同的被试这更能检验模型的泛化能力。划分比例常用8:1:1或7:2:1划分训练集、验证集和测试集。验证集用于调参和早停测试集只在最终评估时使用一次。评估指标准确率Accuracy最直观的指标。宏平均F1分数Macro-F1 Score当各类别样本数不平衡时比准确率更可靠。混淆矩阵Confusion Matrix可视化模型在哪些类别上容易混淆是分析模型短板的重要工具。科恩卡帕系数Cohen‘s Kappa考虑了随机分类的准确率在类别不平衡时比简单准确率更有参考价值。4.2 消融实验设计为了证明你设计的“CNNTransformer”架构的有效性必须进行消融实验Ablation Study。Baseline模型纯CNN模型去掉Transformer部分直接将CNN特征输入分类器。纯Transformer模型尝试用线性投影直接将原始EEG信号或简单预处理后转换为序列输入Transformer。不同特征融合方式比较你使用的“CNN特征向量-Transformer”与“CNN特征图切片-Transformer”等不同序列构建方式的效果。不同位置编码比较可学习位置编码与固定正弦编码的效果。将上述模型的性能准确率、F1在一个表格中对比可以清晰地展示每个组件带来的增益。模型变体准确率 (%)宏平均F1分数参数量 (M)训练时间 (epoch)Baseline (纯CNN)78.20.7650.85~45s纯Transformer65.40.6211.2~120sCNNTransformer (Ours)84.70.8321.5~85sOurs (w/o 位置编码)80.10.7811.5~85sOurs (w/ 固定位置编码)83.90.8251.5~85s表在BCI IV 2a数据集上的消融实验结果示例数据4.3 注意力权重可视化Transformer的可解释性是其一大优势。我们可以将自注意力权重可视化看看模型在做出分类决策时更“关注”哪些时间片段或特征片段。import matplotlib.pyplot as plt import seaborn as sns def visualize_attention(model, sample_input, layer_idx0, head_idx0): 可视化指定层、指定头的注意力权重 model.eval() with torch.no_grad(): # 前向传播并获取中间注意力权重需要修改模型forward以返回注意力 # 假设我们的transformer_encoder返回了注意力权重字典 outputs, attentions model(sample_input, return_attentionsTrue) # attentions 是一个列表每个元素是一个层的注意力矩阵 [batch, heads, seq_len, seq_len] attn_map attentions[layer_idx][0, head_idx].cpu().numpy() # 取第一个样本指定头和层 plt.figure(figsize(8, 6)) sns.heatmap(attn_map, cmapviridis, cbar_kws{label: Attention Weight}) # 设置坐标轴标签例如token 0是[CLS], 1-4是特征片段 token_labels [[CLS], Seg1, Seg2, Seg3, Seg4] plt.xticks(ticksnp.arange(len(token_labels))0.5, labelstoken_labels) plt.yticks(ticksnp.arange(len(token_labels))0.5, labelstoken_labels) plt.title(fAttention Map - Layer {layer_idx1}, Head {head_idx1}) plt.tight_layout() plt.show()通过观察注意力图你可能会发现[CLS]token与某些特征片段之间有更强的连接这可以解释为模型认为这些片段对当前分类任务更重要。这部分分析可以成为你论文中的亮点说明模型决策具有一定的可解释性。5. 常见问题、排查技巧与项目扩展5.1 训练过程中的典型问题与解决方案损失不下降或准确率波动大检查数据预处理确保数据已正确标准化没有NaN或Inf值。可视化几个样本看看波形是否正常。检查学习率学习率可能太大导致震荡或太小导致收敛慢。尝试使用学习率查找器如PyTorch Lightning中的lr_finder找到一个合适的范围。检查批大小Batch SizeBatch Size太小可能导致梯度估计噪声大训练不稳定。在显存允许的情况下适当增大。检查标签是否平衡如果类别严重不平衡考虑使用加权交叉熵损失nn.CrossEntropyLoss(weightclass_weights)。模型在训练集上过拟合很快验证集性能停滞增强正则化增加Dropout率特别是在全连接层和Transformer的FFN层后在CNN中也可以加入Dropout。使用更强的数据增强如前所述EEG的数据增强非常有效。降低模型复杂度减少Transformer的层数或注意力头数减少CNN的通道数。使用权重衰减Weight DecayAdamW优化器中的权重衰减参数是关键尝试从0.01调整到0.1。早停法耐心点设置合理的早停轮数patience。GPU内存溢出OOM减小批大小这是最直接的方法。使用梯度累积Gradient Accumulation假设你想用批大小64但内存只够16。你可以设置实际批大小为16每4个批次accumulation_steps4才更新一次梯度等效于批大小64。在loss.backward()后不立即optimizer.step()而是累积accumulation_steps次后再更新。使用混合精度训练AMPPyTorch的torch.cuda.amp可以显著减少显存占用并加速训练。检查输入数据维度确保没有无意中传入过长的序列或过大的图像。5.2 项目扩展与深入研究方向如果你的毕设完成得比较顺利想进一步提升深度可以考虑以下方向引入更先进的CNN架构将基础的CNN替换为更高效的架构如Squeeze-and-Excitation Networks (SENet)让模型可以学习通道间的注意力或者使用Temporal Convolutional Networks (TCN)来更好地捕捉长时序依赖。探索不同的Transformer变体Performer或Linformer使用线性复杂度的注意力机制降低计算开销适合处理更长的EEG序列。Informer专门为长序列预测设计其ProbSparse自注意力机制可能对捕捉EEG中的关键节律片段有帮助。跨被试学习与域自适应这是EEG分析的核心挑战。可以研究如何在训练中引入域对抗训练Domain Adversarial Training, DANN或使用元学习Meta-Learning方法让模型能快速适应新被试的少量校准数据。多模态融合如果条件允许可以尝试融合其他生理信号如眼电EOG、肌电EMG或功能性近红外光谱fNIRS构建多模态分类系统。Transformer在多模态融合方面有天然优势。部署与轻量化考虑将训练好的模型转换为ONNX格式并尝试在边缘设备如树莓派或移动端进行部署研究模型剪枝、量化等轻量化技术这对于未来脑机接口的实际应用至关重要。做这个项目的过程中我最大的体会是平衡“复杂性”和“有效性”是本科毕设成功的关键。不要一味追求最炫酷的模型而是要把每个基础环节数据预处理、特征工程、模型设计、训练技巧都做扎实。这个CNNTransformer的框架提供了一个非常好的起点它结构清晰有足够的深度供你探索和解释同时又不至于复杂到让你在调试中迷失方向。当你看到自己搭建的模型其注意力图能够隐约反映出不同认知状态下的脑电活动差异时那种成就感是无可替代的。最后务必做好代码版本管理用Git详细记录实验日志可以用TensorBoard或Weights Biases这会让你的论文写作和答辩过程轻松十倍。本文还有配套的精品资源点击获取