
1. 为什么EEG分类值得单独写一篇体系化梳理脑电信号分类这个方向我前前后后跟了快六年从最早拿SVM加手工特征跑二分类到后来用CNN做端到端再到现在折腾图神经网络建模电极间拓扑关系踩过的坑比跑通的实验多得多。身边不少刚入门的同学经常问我同一个问题EEG分类到底有多少条技术路线为什么论文里的方法五花八门自己上手却总是复现不出来。这个问题的根源在于EEG分类不是一个单一任务它下面挂着好几种截然不同的子问题每种问题的数据形态、标签结构、评价方式都不一样用同一套思路去套必然翻车。所谓EEG数据分类说白了就是给一段脑电信号打上标签。这个标签可以是这段信号来自想象左手运动还是右手运动可以是这个受试者此刻处于清醒还是困倦状态也可以是这段波形属于癫痫发作期还是间歇期。任务不同但底层逻辑是相通的先把原始的高维时序信号压缩成有判别力的表示再用分类器把这个表示映射到标签空间。区别只在于这个压缩和映射是用手工规则做的还是用神经网络学出来的。这篇文章适合三类人看。第一类是刚进实验室的研究生需要快速建立EEG分类的全景认知知道有哪些流派、各自适合什么场景。第二类是做工程落地的开发者手上有脑电设备想把分类模型部署到实际产品里需要了解不同方案的算力开销和鲁棒性差异。第三类是从其他领域转过来的深度学习从业者熟悉CNN、RNN、GNN这些工具但不清楚它们在EEG上的特殊约束。我会把分类体系拆开讲透每个分支都配上原理说明、实操要点和我自己踩过的坑尽量做到看完就能动手。2. EEG分类体系的全景拆解与流派划分2.1 按任务类型划分从二分类到多任务学习EEG分类最粗的一层划分是按任务类型走。最基础的是二分类比如运动想象里的左右手区分、癫痫检测里的发作与不发作区分。这类任务数据相对好标注评价指标也简单准确率和AUC基本能说明问题。往上一层是多分类典型的是运动想象的四分类左手、右手、双脚、舌头或者情绪识别里的效价-唤醒度多级分类。多分类的难点在于类间边界模糊尤其是相邻类别在频域上的差异可能只有几个赫兹模型很容易混淆。再往上是多标签和多任务。多标签指的是一个样本同时属于多个类别比如一段脑电既包含疲劳特征又包含注意力下降特征。多任务则是同时预测多个相关但不同的目标比如一边做运动想象分类一边做伪迹检测。这类任务在近两年的论文里越来越多因为实际应用场景很少是单一标签的。我做过一个睡眠分期加异常检测的联合模型共享底层特征提取器两个任务头分别输出效果比单独训两个模型好不少因为底层特征被迫学到了更通用的表示。2.2 按信号处理层级划分从手工特征到端到端第二层划分是按处理层级走这也是传统方法和深度学习方法的分水岭。传统路线是预处理-特征提取-特征选择-分类器四段式。预处理负责去噪和滤波特征提取负责从时域、频域、时频域挖出统计量特征选择负责降维去冗余最后丢给SVM、随机森林或者LDA做分类。这条路线的优势是可解释性强每个特征都有明确的生理意义比如alpha波功率、Hjorth参数、样本熵。劣势是特征工程极度依赖领域知识换一个任务可能整套特征都要重做。深度学习路线则是端到端原始信号或者经过最简预处理的信号直接喂进网络特征提取和分类一起学。CNN负责抓局部时频模式RNN负责抓时序依赖GNN负责抓电极间的空间关系。这条路线的优势是省去了手工特征设计的麻烦理论上能学到人想不到的表示。劣势是数据需求量大而EEG恰恰是典型的小样本问题一个受试者做几百次trial就算多了跨受试者数据又存在巨大的分布差异。我个人的经验是数据量小于五百个样本时传统方法加好的特征往往比深度学习更稳。数据量上千且做了充分的跨受试者对齐后深度学习才开始显现优势。这个阈值不是绝对的但方向是对的。2.3 按模型架构划分CNN、RNN、GNN三条主线第三层划分是按模型架构走这也是当前论文里最活跃的部分。CNN系的核心思想是把EEG当成图像来处理常见做法是把多通道信号转成二维矩阵一个轴是时间一个轴是通道然后套用图像分类里的卷积核。更讲究一点的做法是先做时频变换得到频谱图再对频谱图做卷积。CNN的优势是局部模式提取能力强对高频振荡和瞬态波形的捕捉很到位。RNN系的核心思想是把EEG当成序列来处理利用循环结构建模时间依赖。LSTM和GRU是主力因为它们能缓解长序列的梯度消失问题。RNN适合处理那些状态随时间演变的信号比如情绪渐变、疲劳累积。但RNN的训练速度慢并行性差在长序列上容易遗忘早期信息。GNN系是近几年的新贵核心思想是把电极当成图的节点电极间的相关性当成边用图卷积来聚合空间信息。这个思路很符合EEG的物理本质因为脑电本身就是空间上相互影响的。GNN的难点在于图结构怎么定义是用固定的距离图还是用数据驱动学出来的自适应图。我试过用皮尔逊相关系数建图也试过用可学习的邻接矩阵后者在小数据集上容易过拟合前者更稳但不够灵活。2.4 三条路线的对比与选型建议维度CNN系RNN系GNN系擅长建模局部时频模式长时序依赖空间拓扑关系数据需求中等较大中等偏大训练速度快慢中等可解释性中等较低较高典型场景运动想象、癫痫检测情绪识别、疲劳监测跨通道分析、源定位辅助主要风险过拟合、通道顺序敏感梯度问题、并行性差图结构设计依赖经验选型的时候不要迷信某一条路线。我见过太多论文把三种架构堆在一起号称混合模型结果消融实验一做发现提升全来自某一个分支。实际项目里先跑通最简单的CNN基线再根据任务特点决定要不要加RNN或GNN这个顺序最稳妥。3. 核心细节解析从原始信号到分类标签的关键环节3.1 预处理去噪、滤波与重参考EEG信号的信噪比低得令人发指这是所有从业者的共识。原始信号里混着眼电、肌电、心电、工频干扰还有电极接触不良带来的漂移。预处理做不好后面模型再花哨也是白搭。去噪的常规流程是先做带通滤波通常保留0.5到45赫兹因为大部分有意义的脑电活动都在这个范围。工频干扰用陷波滤波干掉50赫兹或60赫兹看地区。眼电伪迹的处理是个分水岭。简单做法是用阈值法直接剔除幅度超标的段但这样会丢数据。讲究一点的做法是用独立成分分析把眼电成分分离出来再减掉或者用回归方法把眼电参考通道的影响扣掉。我试过用ICA效果不错但计算量大而且成分的挑选需要人工判断自动化程度低。后来转向用自适应滤波速度快但残留较多。现在有些论文用深度学习做伪迹去除思路是训一个自编码器把干净信号重建出来这个方向值得关注但还没到完全成熟的阶段。重参考这一步经常被新手忽略。默认的参考电极选择会直接影响信号的空间分布常用的有耳垂参考、乳突参考、平均参考。平均参考在通道数多的时候比较稳但会引入全局的共模噪声。我的习惯是先用平均参考跑一遍如果分类效果不理想再换回乳突参考对比。注意预处理阶段千万不要做过度滤波。我见过有人把带通卡到8到30赫兹结果把慢波和gamma波全滤没了分类器只能靠噪声做判断交叉验证准确率虚高但实际部署一塌糊涂。3.2 特征工程时域、频域、时频域与空间域传统路线的核心在特征。时域特征包括均值、方差、偏度、峰度、过零率、Hjorth参数。这些特征计算快但对非平稳信号描述能力有限。频域特征主要是各频段的功率谱密度delta、theta、alpha、beta、gamma五个经典频段可以算绝对功率也可以算相对功率。相对功率对个体差异更鲁棒我一般优先用相对功率。时频域特征用短时傅里叶变换或者小波变换得到能同时保留时间和频率信息。小波变换在EEG上用得更多因为它的多分辨率特性适合分析瞬态成分。常用的母小波有Daubechies和Morlet前者适合检测突变后者适合分析振荡。选择哪个母小波没有标准答案我通常拿几个候选跑一遍交叉验证看哪个效果好。空间域特征包括共空间模式、拉普拉斯算子、通道间相关性。共空间模式在运动想象任务里是经典方法核心思想是找一组空间滤波器让两类信号的方差比最大化。这个方法的数学推导有点绕但实现起来不复杂很多工具箱都有现成的。通道间相关性可以构建功能连接矩阵这个矩阵既可以作为特征喂给分类器也可以作为GNN的输入图。3.3 深度学习模型的关键设计选择用CNN处理EEG时第一个要决定的是输入形态。是把原始时序直接卷还是先转成时频图再卷。直接卷时序的做法叫一维卷积计算量小但感受野有限。转成时频图用二维卷积感受野大但计算量翻倍。我一般先用一维卷积做快速验证效果不够再上二维。第二个要决定的是通道维度的处理方式。一种做法是把通道当成卷积核的深度让网络自己学通道间的组合。另一种做法是先对每个通道独立卷积再用一个空间卷积层聚合。前者参数少但可能忽略空间结构后者参数多但表达能力强。运动想象任务里后者通常更好因为左右手想象的空间分布差异是核心判别信息。RNN这边LSTM和GRU的选择其实没那么关键GRU参数少一点训练快一点LSTM在超长序列上略稳。真正关键的是序列切分方式。是把整个trial当成一个序列还是切成多个时间窗分别预测再投票。前者上下文完整但序列长后者训练样本多但丢失了跨窗依赖。我倾向于用滑动窗加注意力机制让模型自己决定哪些窗重要。GNN这边图结构的定义是命门。固定图用电极间的物理距离或者功能连接阈值来建边简单但不够灵活。自适应图让网络学一个邻接矩阵灵活但容易过拟合。折中方案是用物理距离图作为先验再叠加一个可学习的残差。这个思路我在一个跨受试者情绪识别项目里用过比纯固定图提升了三个百分点左右。3.4 标签处理与数据增强EEG数据的小样本特性决定了数据增强不是可选项而是必选项。常用的增强手段包括加高斯噪声、时间平移、幅度缩放、通道丢弃、频段扰动。加噪声的幅度要控制好太大就淹没有效信号了我一般按信号标准差的百分之五到十来加。时间平移要注意别把标签对应的关键段移出窗口。还有一个容易被忽略的点是标签平滑。EEG的标签往往带有噪声尤其是靠受试者自我报告得到的情绪标签边界样本的标签可信度低。用硬标签训练会让模型对这些样本过度自信。标签平滑把硬标签换成软标签能缓解这个问题。我通常把平滑系数设在0.1左右太高会让模型学不到判别边界。4. 实操过程从零搭建一个EEG分类流水线4.1 环境准备与数据加载我习惯用Python做原型开发核心依赖是MNE负责信号处理PyTorch负责建模scikit-learn负责传统方法和评价指标。环境配置这块miniconda建虚拟环境最省心避免包版本冲突。MNE的安装直接pip就行但要注意它依赖的scipy和numpy版本太新太旧都可能出问题。数据加载阶段公开数据集是入门的好选择。运动想象有BCI Competition IV 2a癫痫检测有CHB-MIT情绪识别有DEAP和SEED。这些数据集的格式各不相同MNE提供了不少读取接口但有些需要自己写解析脚本。加载后第一件事是检查采样率、通道名、事件标记是否对齐。我踩过一次坑事件标记的时间戳和信号时间轴差了半个采样周期导致切出来的trial全部偏移分类准确率一直在随机水平徘徊查了两天才发现是这个问题。import mne import numpy as np raw mne.io.read_raw_gdf(dataset.gdf, preloadTrue) raw.filter(0.5, 45., fir_designfirwin) events, event_id mne.events_from_annotations(raw) epochs mne.Epochs(raw, events, event_id, tmin-0.2, tmax0.8, baseline(-0.2, 0), preloadTrue) X epochs.get_data() # shape: (n_epochs, n_channels, n_times) y epochs.events[:, -1]这段代码是标准起手式。tmin和tmax的选择取决于任务运动想象一般取提示后0.5到2.5秒因为运动想象的ERD现象有延迟。baseline用提示前的一段做基线校正能去掉个体间的直流偏移差异。4.2 传统基线CSP加LDA的快速验证在碰深度学习之前我一定会先跑一个CSP加LDA的基线。原因很简单如果这个基线能到百分之七十以上说明数据本身有可分性深度学习有发挥空间。如果基线只有百分之五十几那要么是预处理有问题要么是任务本身太难这时候上深度学习大概率也是白费。CSP的实现用MNE的CSP类取前几个空间滤波器。LDA用scikit-learn的默认配置就行。交叉验证用分层K折折数取五或者十。这里有个细节CSP的滤波器必须在训练折上拟合不能在全量数据上拟合后再切分否则就是数据泄露。我见过不少论文犯这个错误报出来的准确率高得离谱实际复现差一大截。from mne.decoding import CSP from sklearn.discriminant_analysis import LinearDiscriminantAnalysis from sklearn.pipeline import Pipeline from sklearn.model_selection import StratifiedKFold, cross_val_score clf Pipeline([(CSP, CSP(n_components4, regNone, logTrue)), (LDA, LinearDiscriminantAnalysis())]) cv StratifiedKFold(n_splits5, shuffleTrue, random_state42) scores cross_val_score(clf, X, y, cvcv, n_jobs1) print(scores.mean())4.3 深度学习模型搭建以CNN为例CNN的搭建我倾向于从简到繁。第一版就用两三个卷积块加全局平均池化加全连接别一上来就堆残差连接和注意力。卷积核大小在时间维度上取采样率的四分之一左右比如250赫兹采样就取64这样能覆盖一个完整的alpha周期。通道维度上第一层卷积核深度等于通道数让网络自己学空间滤波。import torch import torch.nn as nn class EEGNet(nn.Module): def __init__(self, n_channels, n_times, n_classes): super().__init__() self.conv1 nn.Conv2d(1, 16, (1, 64), padding(0, 32)) self.bn1 nn.BatchNorm2d(16) self.conv2 nn.Conv2d(16, 32, (n_channels, 1)) self.bn2 nn.BatchNorm2d(32) self.pool nn.AvgPool2d((1, 4)) self.drop nn.Dropout(0.5) self.fc nn.Linear(32 * (n_times // 4), n_classes) def forward(self, x): x x.unsqueeze(1) x torch.relu(self.bn1(self.conv1(x))) x torch.relu(self.bn2(self.conv2(x))) x self.pool(x) x self.drop(x) x x.flatten(1) return self.fc(x)这个结构是EEGNet的简化版原论文里还有深度可分离卷积我这里为了讲清楚原理先省略。训练的时候用Adam学习率从千分之一开始batch size取32或者64。早停的耐心值设十到十五个epochEEG数据很容易过拟合训练损失还在降但验证损失已经抬头的时候就得停。4.4 跨受试者迁移的处理跨受试者是EEG分类最头疼的问题。同一个模型在受试者A上准确率百分之九十换到受试者B可能掉到百分之六十。原因是每个人的脑电幅值、频率分布、头型尺寸都不一样。处理这个问题有几条路。一是做数据对齐用欧氏对齐把每个受试者的协方差矩阵对齐到单位矩阵这个操作简单但效果显著我基本是标配。二是做域适应用对抗训练让特征提取器学到的表示在不同受试者间不可区分。三是做微调用目标受试者的少量标注数据调整模型最后几层。欧氏对齐的实现不复杂核心是对每个trial的协方差矩阵做白化变换。MNE里没有现成的函数得自己写但代码量不大。我实测下来光这一步就能把跨受试者准确率提升五到十个点性价比极高。提示跨受试者实验里测试受试者的数据绝对不能参与任何形式的训练包括标准化参数的拟合。我见过有人用全量数据算均值和方差再做标准化这属于典型的数据泄露报出来的结果没有参考价值。5. 常见问题与排查技巧实录5.1 准确率卡在随机水平怎么办这是新手最常遇到的问题。排查顺序我一般是这样的。先看标签对不对齐事件标记和信号时间轴是否一致这个用肉眼画图就能查。再看预处理有没有把有效信号滤掉把滤波前后的信号画出来对比。然后看数据有没有做基线校正没做基线校正的话直流漂移会主导分类。最后看交叉验证有没有数据泄露CSP和标准化这类依赖数据的操作必须在训练折内拟合。如果以上都没问题那可能是任务本身的可分性就低。这时候可以降级任务比如四分类改二分类看看准确率有没有提升。如果二分类也上不去那大概率是数据质量问题检查电极阻抗记录看看有没有通道全程饱和或者全是噪声。5.2 模型过拟合的识别与缓解过拟合的典型表现是训练准确率一路涨到九十九验证准确率在六七十徘徊。缓解手段按优先级排第一是加数据增强这是最根本的。第二是加Dropout和权重衰减Dropout率从0.5开始试权重衰减从万分之一开始。第三是减小模型容量把卷积核数量减半或者去掉一层。第四是早停这个最简单但最有效。我个人的经验是EEG任务里模型参数量控制在十万以内比较稳超过五十万基本必过拟合除非数据量上万。所以别盲目堆大模型小模型加好特征往往更实用。5.3 训练不收敛的排查清单现象可能原因排查方法损失一直是NaN学习率太大或输入有NaN降学习率检查数据损失不降学习率太小或初始化差调学习率换初始化损失震荡batch size太小增大batch size验证损失先降后升过拟合早停、加正则准确率不涨但损失降标签噪声或类别不平衡检查标签分布类别不平衡在EEG里很常见比如癫痫检测里发作段远少于非发作段。处理方法是加类别权重或者用focal loss。我一般先用加权交叉熵简单有效。如果效果不够再上focal loss但focal loss的超参数比较敏感调起来费劲。5.4 部署阶段的性能优化模型训好了要部署到实际设备上这时候算力和内存就是硬约束。优化手段包括模型量化、剪枝、知识蒸馏。量化把浮点权重转成定点速度能快两三倍精度损失通常在一个点以内。剪枝去掉不重要的连接能减小模型体积。知识蒸馏用大模型教小模型适合对延迟敏感的场景。我做过一个嵌入式部署的项目原始模型在服务器上跑一次推理要五十毫秒量化加剪枝后降到八毫秒精度只掉了零点五个点。这个优化幅度在实时脑机接口场景里是决定性的因为超过二十毫秒的延迟用户就能感觉到卡顿。6. 我在这条路上踩过的几个坑第一个坑是迷信复杂模型。刚入门的时候觉得模型越深越好把ResNet搬过来改改就用结果在小数据集上过拟合得一塌糊涂。后来回归简单结构反而效果更好。EEG分类的核心矛盾是样本少而维度高模型复杂度必须和数据量匹配。第二个坑是忽略个体差异。早期做跨受试者实验直接把所有人的数据混在一起训结果模型学到的是受试者身份而不是任务相关特征。后来加了欧氏对齐和域适应才把这个问题缓解。现在我做任何跨受试者实验第一件事就是检查模型有没有在偷学受试者特征方法是看混淆矩阵里有没有按受试者聚类的模式。第三个坑是评价指标选错。EEG数据经常类别不平衡这时候准确率是有欺骗性的。一个把所有样本都判为多数类的模型也能有百分之八九十的准确率。所以我现在一律看AUC和F1必要时看混淆矩阵。如果是多分类看宏平均F1而不是微平均。第四个坑是复现论文时不看细节。很多论文的方法部分写得含糊超参数、预处理步骤、数据划分方式都一笔带过。直接照搬往往复现不出来。我的做法是先找有没有开源代码没有的话就发邮件问作者要预处理后的数据或者详细配置。实在不行就自己按合理猜测补全然后在论文里注明这是复现尝试而非严格复现。EEG分类这个方向工具在变模型在变但底层逻辑没变理解信号、尊重数据、控制复杂度。把这三点做好了不管用CNN还是GNN都能做出靠谱的结果。