去年年底我把EEGnet的论文从头到尾啃了一遍,代码也照着Keras官方实现跑通了。说实话,刚接触这个模型的时候,读论文里的Figure 2是一脸懵的:一张网络图上画了Conv2D、DepthwiseConv2D、SeparableConv2D三种卷积层,乍一看都叫卷积,实际干的事情完全不同。我当时最大的困惑就是“为什么不能直接用一层普通卷积搞定”,以及“这三个操作分别在脑电信号上做了什么”。后来自己动手把每一层的前向传播过程、特征图尺寸变化、参数量都画了出来,才真正理解这套设计的精妙之处。
这篇内容就是想把当时画图搞懂的整个过程整理出来,把EEGnet的三种卷积层操作拆开讲清楚,从脑电信号的特点、每种卷积的计算逻辑,到完整尺寸推演和复现时容易踩的坑,一次讲透。如果你正在做脑机接口、情绪识别、睡眠分期这类任务,或者只是想在小型时序数据集上找一个参数少、效果稳的基线模型,这篇应该能帮你省下不少看代码和猜图的时间。
1. 脑电信号为什么难处理:频段、通道、样本量三个暴击
1.1 脑电信号的“三维组织方式”
先别急着看网络结构,得先搞清楚EEGnet的输入到底长什么样,否则后面讲卷积操作的时候很容易对不上号。
一段原始脑电信号,放在数组里看是二维的:形状是(C, T),其中 C 是电极通道数,T 是时间采样点数。举个例子,64导联的脑电帽,采样率250Hz,采集2秒数据,得到的就是(64, 500)的矩阵。但在神经网络里,这个二维矩阵通常还要扩展一维,变成(1, C, T)或者(C, T, 1),多出来的这一维是卷积神经网络的“通道维”。这里特别容易绕晕:脑电的“通道”和卷积网络的“通道”是两个概念。EEGnet输入里的1是卷积网络的通道数,C和T反而被当成类似图像的“空间尺寸”来处理。
为什么这么组织?因为EEGnet的做法是把脑电信号当作“伪图像”来处理:时间维像图像的宽度,电极通道维像图像的高度,卷积网络的通道维为1。这样一来,所有成熟的2D卷积操作都能直接复用,不需要写特殊的1D网络结构。理解这一点,后面看三种卷积层的kernel尺寸就顺了。
脑电信号本身的特性也给建模出了难题。一是信噪比极低,几微伏到几百微伏的幅值里混着肌电、眼电、工频干扰;二是有效信息分散在多个频段,比如α波8-13Hz、β波13-30Hz,不同任务激活的频段不一样;三是空间模式复杂,某个认知状态往往对应多个电极的协同变化,单看某一个通道很难判断。传统做法是先做带通滤波,再用CSP(公共空间模式)做空间滤波,最后提取特征喂给分类器。EEGnet的巧妙之处在于,它把“频段特征提取”和“空间特征提取”直接设计成了卷积层,让网络自己学,这就是为什么它有三种不同的卷积层操作。
1.2 紧凑模型是BCI落地绕不开的路
脑电建模还有一个客观限制:样本量太少。做一次脑电实验,能采到的有效 trial 通常只有几十到几百个,这和ImageNet那种百万级数据集完全不是一个量级。如果直接上一个ResNet这样的大模型,大概率是严重过拟合——训练集损失降到底,验证集一塌糊涂。
EEGnet在论文里反复强调的关键词是“compact”,紧凑。整个模型的可训练参数量才几千个(后面第3章会具体算),却能在一堆BCI竞赛数据集上取得和当时很多复杂模型相当甚至更好的效果。这种“小模型也能干活”的特性,恰恰是BCI实时系统最需要的。嵌入式设备、在线解码、反馈实验,都对延迟和显存有要求,模型越小,部署越现实。
所以EEGnet的设计哲学可以概括成一句话:用最少的参数,把脑电信号里最核心的“时间节律”和“空间模式”提取出来。三种卷积层正好各自承担一部分工作,下面一章就逐个拆解。
2. 三种卷积层图解:时间卷积、空间深度卷积、可分离卷积各在干什么
2.1 第1种操作:时间卷积(Conv2D),先沿时间轴扫一遍
EEGnet里的第一种卷积操作,论文图中标注为Conv2D,实际是时间卷积:卷积核尺寸是(1, 64),只在时间维上滑动,不做任何跨通道操作。这一步的作用是提取每个电极通道上的时间节律模式,类似带通滤波,但比固定滤波器更灵活。
我当年画图时,第一步就是把三维特征图画成一个小长方体,然后让卷积核沿着时间轴慢慢滑动:
输入特征图:(1, C, T) 例如 (1, 64, 128) 卷积核: (1, 64) —— 高度是1,宽度是64 步长: 1 padding: same,时间维长度不变 时间轴 → ┌──────────────────────────────────────────┐ │ 通道1 ──┐ │ │ 通道2 ──┤ 这个卷积核只在时间方向滑 │ │ ... │ 每次覆盖连续64个采样点 │ │ 通道C ──┘ 在当前电极通道上做加权求和 │ └──────────────────────────────────────────┘这里有个容易忽略的点:虽然输入有C个电极通道,但这个时间卷积核的高度是1,它是在每个电极通道上独立做卷积的。也就是说,64个电极通道共享同一套时间卷积核(论文默认F1=8,即8个不同的时间卷积核),每个核可以看作一个“可学习的带通滤波器组”。如果采样率是128Hz,64个采样点就是0.5秒的信号长度,正好覆盖几个完整周期的α波;如果采样率是250Hz,同样的0.5秒需要125个采样点,这一步就涉及后面第4章说的参数联动调整。
为什么不能用普通的全连接层或者1D卷积代替?因为2D卷积配合(1, 64)这个尺寸,可以让不同电极通道共享同一套滤波器参数。脑电的节律特征(比如α波、β波)在所有电极上形态是相似的,共享参数一方面大幅减少模型参数,另一方面也让模型具备一定的跨通道泛化能力。这一步的输出形状是(F1, C, T),其中F1=8,相当于在每个电极上得到了8组“频率特征图”。
2.2 第2种操作:空间深度卷积(DepthwiseConv2D),跨通道融合
第二种卷积层是DepthwiseConv2D,在EEGnet里也叫空间卷积。它的卷积核尺寸是(C, 1),也就是说,卷积核的高度等于全部电极通道数C,宽度只有1。
这一步可以把前面得来的8组时间特征图,在空间方向上进行融合。为了画清楚,我把它理解成对每个时间特征图单独做一次“跨电极加权组合”:
输入特征图:(F1=8, C=64, T=128) 卷积核: (C=64, 1) —— 高度覆盖全部电极,宽度为1 对第1个特征图:把当前时刻所有64个电极的值加权求和,得到一个值 对第2个特征图:同样操作,但权重不同 ... 因为是depthwise卷积,每个特征图独立计算,互不干扰 电极维 ┌──────────────────────┐ │ 电极1 ─┐ │ │ 电极2 │ 卷积核高=64 │ │ ... │ 一次覆盖全部 │ │ 电极64 ┘ │ └──────────────────────┘ ↓ (16, 1, T) —— 所有电极信息融合成一个空间单元这个操作思想上是向CSP(公共空间模式)靠拢的。CSP的核心就是寻找一组空间滤波器,让两类任务下信号的方差差异最大化。EEGnet用深度卷积替代了手动设计的空间滤波器,让网络根据任务loss自动学习每个电极的权重组合。但值得注意的是,它用的是深度卷积,不是普通卷积:普通卷积会同时融合所有输入特征图通道,而深度卷积对每个输入通道单独做卷积,输出通道数和输入通道数是倍数关系。这里论文默认depth_multiplier=2,所以输入8个特征图,输出变成8 * 2 = 16个特征图。为什么要2倍?给模型多一点空间特征表达能力,相当于每个时间特征图可以学出两个不同的空间滤波模式。
这一步过后,原来(8, 64, 128)的特征图变成了(16, 1, 128):电极维度被“压扁”了,所有通道信息融合成了一个综合的空间表达。这也是EEGnet最关键的一步,后面再接时间维度的处理就不再关心具体是哪个电极了。
2.3 第3种操作:可分离卷积(SeparableConv2D),深度可分离降参
第三种卷积层叫SeparableConv2D,可分离卷积。这个层在EEGnet里放在第二个block中,用来进一步提取短时的时序特征,同时把特征图的通道数整理成更紧凑的表示。
可分离卷积拆成两步:先做深度卷积,再做1x1逐点卷积。以EEGnet默认参数为例,深度卷积核尺寸是(1, 16),在时间方向上滑动,每个特征图独立处理;然后接一个核尺寸(1, 1)的逐点卷积,把16个特征图在通道维度上加权融合,输出 F2=16 个新特征图。
第一步:深度卷积(DepthwiseConv2D) 输入 (16, 1, 32) → 卷积核 (1, 16) → 输出 (16, 1, 32) 每个特征图只做自己时间轴上的短时卷积,不跨通道 第二步:逐点卷积(Conv2D 1x1) 输入 (16, 1, 32) → 卷积核 (1, 1) → 输出 (16, 1, 32) 这一步把16个特征图的结果在通道维上做线性组合,实现跨特征图融合为什么非要用这种“拆两步”的卷积?直接用一个普通的(16, 1, 16)卷积核不是更简单吗?问题出在参数量上。假设输入特征图通道数是16,输出也是16,核尺寸在时间方向是16:
- 普通卷积的参数量:16(输出通道)× 16(输入通道)× 1 × 16 = 4096 个权重
- 可分离卷积的参数量:深度卷积部分 16(输入通道)× 1 × 16 = 256,逐点卷积部分 16(输出通道)× 16(输入通道)× 1 × 1 = 256,合计512个权重
参数量直接缩小到原来的八分之一。在脑电这种小样本场景下,这种压缩很有价值。而且从效果上看,深度卷积负责在每个特征图上提取局部时间模式,逐点卷积负责把不同特征图的信息组合,分工明确,表达能力和普通卷积接近,但需要拟合的参数少得多。
到了这一层,三种卷积操作就齐了。可以直观地总结一下:时间卷积负责“频段筛选”,空间深度卷积负责“通道融合”,可分离卷积负责“短时特征提取+特征重组”。三者组合起来,几乎对应了脑电信号处理里经典的“滤波→空间滤波→特征分类”pipeline,只不过全部变成了可学习的模块。
3. 把三种卷积串起来:完整前向传播尺寸推演
3.1 输入、池化、Dropout这些“配角”也不能忽略
论文图里还有几个容易被忽略的操作:BatchNorm、ELU激活、平均池化、Dropout。这些不是卷积层,但直接决定了网络能否训练起来、尺寸怎么变化。
BatchNorm放在每个卷积层之后,作用是稳定训练。脑电数据不同trial之间的幅值差异很大,同一个被试不同时段的数据分布也可能漂移,BatchNorm能强制把特征分布拉回均值为0、方差为1附近,让模型不用花大力气适应幅值波动。EEGnet论文里的ELU激活函数也值得一提,ELU在负数区域不是硬截断,而是平滑过渡,对脑电这种包含大量微弱负向波动的信号来说,能保留更多信息。
池化层用的是 AveragePooling2D,分别发生在第一个block末和第二个block末。第一个block后池化核是(1, 4),把时间维压缩4倍;第二个block后池化核是(1, 8),再压缩8倍。两次池化一共把时间维压缩32倍。这意味着如果输入时间长度是128,经过两次池化后时间维只剩4。这种大幅压缩不是随便设计的,而是为了让最后分类层看到的特征具有平移不变性,同时把序列信息压缩成相对稳定的统计量。其实对于BCI任务,分类器并不需要知道某个波形精确出现在第几个采样点,只要知道“大概在这个时间窗内有这种模式”就够了。
Dropout放在每个block的池化之后,默认0.25,作用是缓解小样本下的过拟合。它相当于在训练时随机丢弃一部分特征,迫使网络学到更鲁棒的模式,而不是过度依赖某几个特征图。
3.2 用一张尺寸表核对自己的实现
为了确保自己对模型没有理解偏差,我把所有层的输入输出尺寸和参数量列成了一张表,以输入(1, C=64, T=128)、默认参数 F1=8、D=2、F2=16 为例。这张表后来也成了我复现时排查维度的主要参考。
| 层操作 | 核尺寸/参数 | 输出形状 | 参数量(约) |
|---|---|---|---|
| Input | - | (1, 64, 128) | 0 |
| Conv2D | (1, 64), F1=8 | (8, 64, 128) | 520 |
| BatchNorm | - | (8, 64, 128) | 16 |
| DepthwiseConv2D | (64, 1), depth_multiplier=2 | (16, 1, 128) | 1040 |
| BatchNorm | - | (16, 1, 128) | 32 |
| ELU | - | (16, 1, 128) | 0 |
| AveragePooling2D | (1, 4) | (16, 1, 32) | 0 |
| Dropout(0.25) | - | (16, 1, 32) | 0 |
| SeparableConv2D | (1, 16), F2=16 | (16, 1, 32) | 560 |
| BatchNorm | - | (16, 1, 32) | 32 |
| ELU | - | (16, 1, 32) | 0 |
| AveragePooling2D | (1, 8) | (16, 1, 4) | 0 |
| Dropout(0.25) | - | (16, 1, 4) | 0 |
| Flatten | - | 64 | 0 |
| Dense | N_classes | N_classes | 64×N+10 |
整理完这张表,有几个体验值得拿出来说:
第一,DepthwiseConv2D的核尺寸是(64, 1),但这里的64是电极通道数,如果你换了导联数,比如14导联的消费级设备,这个核高度也要改成14,否则维度就对不上。很多复现代码报维度错误,原因就在这里。第二,池化操作作用于时间维,第一个block后时间维从128变32,第二个block后从32变4,如果原始窗口长度不是4的倍数也不是8的倍数,最后一个Flatten的输出维度就不是64,Dense层的输入维度也要跟着改。第三,整个卷积部分的参数量加起来才2100左右,对比普通CNN动辄几十万上百万的参数,EEGnet确实是“小而能打”。
4. 复现EEGnet的调参笔记:为什么代码跑通但效果不对
4.1 采样率和时间卷积核长度必须联动
这是我在复现时踩的第一个坑。论文写的是时间卷积核长度64,我直接套在自己的250Hz采样数据上,结果验证集准确率一直上不去。后来一想,64个采样点在128Hz采样率下是0.5秒,但在250Hz下只有0.256秒,能覆盖的完整脑电节律周期变少了,相当于“滤波器”的频带分辨率完全变了。
正确的做法是:时间卷积核长度跟着采样率等比缩放。比如原设计在128Hz下核长64,你的数据是256Hz,核长就可以调整为128,保持0.5秒的物理时间窗。第二个block里的(1, 16)时间卷积核同理。采样率差距不大时也可以先不调整,但要意识到模型提取的节奏模式已经和论文设定不同了,这会影响最终效果。
另外,时间窗口长度也要注意。EEGnet的两个池化层会把时间维压缩32倍,如果输入时间长度是128,最后时间维只剩4,还有足够的可区分信息。如果窗口太短(比如只有64个采样点),第二个池化后时间维只剩2,信息丢失严重;如果窗口太长(比如1000个采样点),最后Flatten的维度会很大,分类层参数变多,小样本下容易过拟合。我的经验是,在保持时间分辨率的前提下,原始窗口长度调到128到256之间相对好用,既能覆盖多个节律周期,又不会让分类层过于臃肿。
4.2 训练设置、batch size和BN的问题
EEGnet论文里使用的是Adam优化器,学习率方面论文给的设置比较激进,实际复现时我更建议从0.001起步。脑电数据本身噪声大,学习率太高容易在训练初期就震荡,学习率太低又容易卡在局部最优。我自己习惯配合学习率衰减或者early stopping,一旦验证集损失连续几个epoch不下降就调低学习率或者直接停止。
Batch size这个参数在EEGnet里比在其他模型里更需要小心,因为BatchNorm对batch的统计量很敏感。BCI实验数据量少,如果batch size只有8或16,BatchNorm计算的均值和方差波动很大,训练和推理时的统计量不一致,会让验证集结果忽高忽低。我有一次把batch size从32调到64,验证集准确率从82%稳定到了86%,原因就在这里。如果确实因为显存或数据量限制只能用很小的batch,可以考虑把BatchNorm换成GroupNorm或者LayerNorm,这类不依赖batch统计量的归一化方式在少样本脑电数据上往往更稳。
Dropout的值也值得调一下。论文默认是0.25,但这个值是针对它当时的训练数据量和任务难度设定的。如果你的分类任务很简单(比如二分类,且两类差异很大),Dropout可以降到0.1甚至关掉;如果类别很多且数据量寥寥,适当提高到0.5能明显缓解过拟合。建议把这个当超参来对待,而不是一直用默认值。
4.3 分类头和损失函数怎么换
EEGnet论文里的分类头是Flatten加Dense加Softmax,适用于多分类任务。实际使用时,这里可以灵活改:
- 二分类任务:把Dense层输出改成1个节点,配合Sigmoid和二元交叉熵损失,训练更稳。
- 连续值回归(比如疲劳程度、注意力水平):把Dense改成1个节点的线性输出,损失函数换成MSE。
- 想在特征层做领域自适应:可以从Flatten之后那一层(64维特征)引出分支,接上对抗判别器。这个64维特征本质上是原始脑电信号经过充分时空压缩后的浓缩表示,做迁移学习效果不错。
我试过在EEGnet的Flatten层后面接一个小的全连接层(比如64→32→N)来提升非线性表达能力,在小数据集上有一点提升,但代价是参数量增加,过拟合风险也上升。要不要加,建议用验证集准确率说话,不要盲目堆结构。
还有一个常被忽略的细节:类别不平衡。BCI数据里不同类别的trial数经常不一样,特别是“静息态”这类容易采集的类别,数量往往比任务态多不少。如果不做处理,模型会偏向多数类。建议先按类别统计trial数,再决定用加权损失还是过采样解决。
5. 从EEGnet里反推出来的通用经验与后续扩展
5.1 这套设计对其他生理信号同样适用
把EEGnet拆透之后,我最大的收获其实不在脑电本身,而是它给了一整套处理“低样本量时序信号”的通用范式:先用窄核在时间维度提取局部节奏模式,再用跨通道卷积融合多传感器信息,然后用深度可分离卷积继续提特征并控制参数量,最后用全局池化或大跨度池化压缩成分类特征。
这套范式不只能用在EEG上。心电信号(ECG)的多导联数据、肌电信号(EMG)的多通道记录、甚至可穿戴设备里加速度计和陀螺仪的多轴时序数据,结构上都类似:通道数不多、样本量有限、信噪比一般、时间维度上的局部模式是关键。我后来在一个多通道心电分类任务里,几乎原封不动地沿用了EEGnet的block设计,只是把时间卷积核长度按照采样率做了调整,效果比之前手搓的1D CNN基线好不少,而且训练速度快得多。
这里面最值得学习的是“深度卷积”的使用思路。很多人在处理多通道时序数据时,习惯一上来就把所有通道拼成一个大的输入向量。EEGnet用空间深度卷积提示了一个更合理的方式:先在每个通道上分别提取局部特征,再在融合阶段把通道信息组合起来,“分而治之”既减少参数,又更符合物理含义。
5.2 如果想进一步压参/提精度,往哪里发力
EEGnet已经很紧凑,但如果你面对的数据量比论文里的BCI竞赛数据还少,还可以从几个方向继续压参。一是把F1从8降到4,D从2降到1,模型容量直接砍一半,适合只有几十个trial的极限小样本场景。二是把第二个block的SeparableConv2D替换成单纯的逐点卷积,减少短时特征提取的开销,如果任务本身对精细时序模式不敏感,效果损失不大。三是把Dense分类层改成GlobalAveragePooling加一个全连接点,直接去掉Flatten之后的向量展平参数。
反过来,如果数据量足够,想提精度,可以在时间卷积这一层做多尺度改进:同时用(1, 32)、(1, 64)、(1, 128)三种不同长度的卷积核并行提取短、中、长三种时长的节奏模式,然后拼在一起再送入空间卷积层。这类多尺度时间卷积在脑电上的提升通常比较明显,毕竟不同脑电节律的频率范围跨越很大,一个固定核长很难同时照顾到θ波和γ波。
还有人在EEGnet的基础上引入注意力机制,在空间卷积之后加一个通道注意力模块,自动加权不同特征图的重要性。从我的实验经验看,注意力在通道数较多(大于16)时收益更明显,对于最紧凑的EEGnet配置帮助有限,可以把它当作数据量充足时的锦上添花。
最后分享一个我自己一直在用的组合拳:把EEGnet当作特征提取器,先在大规模公开数据上预训练,然后在你的目标任务数据上冻结卷积层、只微调分类头。由于EEGnet参数量极少,这种迁移策略即使在目标数据只有几十个trial时也能work,是我在几个脑电项目里最稳妥的基线方案。