第1章:神经信号预处理实战——从原始脑电到干净数据
大家好,我是你们的老朋友。今天咱们来聊聊神经信号预处理这件事。
说实话,我刚入行那会儿,觉得预处理就是走个过场。直到有一次,我花了两周时间训练一个深度学习模型,结果发现性能差得离谱。排查了半天,最后发现是原始数据里有个50Hz的工频干扰没滤干净。嗯,从那以后,我再也不敢小看预处理了。
预处理说白了,就是把原始脑电信号里的「脏东西」清理掉。你想想看,头皮上采集到的信号,除了大脑活动,还有眼动、肌肉收缩、心跳、电源干扰……这些乱七八糟的东西混在一起,模型根本学不到真正有用的特征。
核心观点:预处理不是可选项,而是必选项。数据质量决定了模型性能的上限。
1.1 预处理流程总览
我个人习惯把预处理分成五个步骤,按顺序执行:
- 带通滤波——保留目标频段,去掉低频漂移和高频噪声
- 陷波滤波——干掉50Hz或60Hz的工频干扰
- 伪迹去除——干掉眼电、肌电这些大块头噪声
- 基线校正——把信号拉回零均值
- 重参考——选择合适的参考电极
下面这张图可以帮你快速理解整个流程:
1.2 带通滤波——把信号「框」在目标频段
脑电信号的能量主要集中在0.5Hz到45Hz之间。低于0.5Hz的是基线漂移,高于45Hz的基本是肌电噪声。带通滤波就是只让这个频段的信号通过。
我在项目中遇到过一个问题:滤波器的阶数选太高,结果信号边缘出现了振铃效应。后来我学乖了,一般用4阶或5阶的巴特沃斯滤波器,效果比较稳。
import mne
import numpy as np
# 读取原始数据
raw = mne.io.read_raw_fif('sample_raw.fif', preload=True)
# 带通滤波:保留0.5-45Hz
raw.filter(0.5, 45, fir_design='firwin')
print('带通滤波完成')
小技巧:滤波时建议用fir_design='firwin',它比默认的'firwin2'更稳定,尤其适合短数据段。
1.3 陷波滤波——干掉烦人的工频干扰
50Hz的工频干扰是脑电信号里最常见的噪声源。你想想看,实验室里到处都是电源线、插座、设备,这些都会产生50Hz的电磁场。陷波滤波就是专门针对这个频率的「狙击枪」。
我曾经见过一个同学,滤波参数设得太窄,结果50Hz附近的脑电活动也被干掉了。嗯,这里要注意:陷波滤波的带宽一般设0.5-1Hz就够了,太宽会损伤有用信号。
# 陷波滤波:干掉50Hz
raw.notch_filter(50, picks='eeg', fir_design='firwin')
print('陷波滤波完成')
注意:如果数据采样率低于100Hz,陷波滤波可能会失效。因为奈奎斯特频率只有50Hz,刚好卡在边界上。建议采样率至少250Hz以上。
1.4 伪迹去除——眼电和肌电的「大扫除」
伪迹去除是预处理里最考验经验的一步。眼电伪迹通常出现在前额电极,频率在0-4Hz;肌电伪迹频率高,一般在20Hz以上,而且能量大、持续时间短。
我个人习惯先用ICA(独立成分分析)来分离伪迹。ICA的原理很简单:把混合信号分解成统计独立的成分,然后手动识别并剔除那些明显是伪迹的成分。
# 使用ICA去除眼电伪迹
ica = mne.preprocessing.ICA(n_components=20, random_state=42)
ica.fit(raw)
# 自动识别眼电成分
eog_indices, eog_scores = ica.find_bads_eog(raw, ch_name='Fp1', threshold=3.0)
ica.exclude = eog_indices
# 应用ICA,去除伪迹
raw_clean = ica.apply(raw)
print(f'去除了{len(eog_indices)}个眼电成分')
避坑指南:我曾经一次性去除了太多ICA成分,结果把真正的脑电活动也干掉了。建议每次只去除1-2个最明显的伪迹成分,然后检查一下信号是否正常。
1.5 基线校正——把信号拉回零均值
基线校正说白了,就是把每个epoch(试次)的信号减去基线期的平均值。基线期一般取刺激出现前的100-200毫秒。这样做的好处是:消除不同试次之间的直流偏移,让信号在零附近波动。
# 创建epochs,基线校正
events = mne.find_events(raw, stim_channel='STI 014')
epochs = mne.Epochs(raw, events, event_id=1, tmin=-0.2, tmax=0.8,
baseline=(-0.2, 0), preload=True)
print('基线校正完成')
你想想看,如果没有基线校正,不同试次的信号起点不一样,叠加平均后会出现奇怪的波形。我刚开始做实验时就踩过这个坑,平均后的ERP波形怎么看怎么不对劲。
1.6 重参考——选择合适的参考电极
参考电极的选择直接影响信号质量。常见的参考方式有三种:
| 参考方式 | 说明 | 适用场景 |
|---|---|---|
| 平均参考 | 所有电极的平均值作为参考 | 电极数较多时(≥32导) |
| 双极参考 | 相邻电极相减 | 局部电位分析 |
| 单极参考 | 固定一个电极(如Cz)作为参考 | 传统ERP研究 |
# 重参考:使用平均参考
raw.set_eeg_reference('average', projection=False)
print('重参考完成')
我的建议:如果电极数少于32导,用平均参考效果不太好。这时候我一般用双极参考或者干脆不重参考,直接用原始参考。
1.7 完整预处理流水线
最后,我把整个预处理流程封装成一个函数,方便复用:
def preprocess_eeg(raw_path, l_freq=0.5, h_freq=45, notch_freq=50):
"""
完整的EEG预处理流水线
"""
# 读取数据
raw = mne.io.read_raw_fif(raw_path, preload=True)
# 1. 带通滤波
raw.filter(l_freq, h_freq, fir_design='firwin')
# 2. 陷波滤波
raw.notch_filter(notch_freq, fir_design='firwin')
# 3. ICA去伪迹
ica = mne.preprocessing.ICA(n_components=20, random_state=42)
ica.fit(raw)
eog_indices, _ = ica.find_bads_eog(raw, ch_name='Fp1', threshold=3.0)
ica.exclude = eog_indices
raw = ica.apply(raw)
# 4. 重参考
raw.set_eeg_reference('average', projection=False)
return raw
# 使用示例
raw_clean = preprocess_eeg('sample_raw.fif')
print('预处理完成,数据已就绪')
好了,这就是神经信号预处理的完整流程。说实话,预处理看起来步骤多,但每一步都有它的道理。你只要按照这个流程走一遍,基本能拿到干净的数据。
记住一句话:预处理做得好,模型训练没烦恼。下次咱们再聊特征提取的事。