第一章:神经信号基础与数据预处理
大家好,我是你们的老朋友。今天咱们聊聊神经信号处理的第一步——数据预处理。说实话,我见过太多同学一上来就急着搭模型,结果数据质量不过关,后面怎么调参都白搭。嗯,这章咱们就把地基打牢。
核心观点:预处理做得好,模型成功一半。这不是夸张,是我踩过无数坑后的血泪总结。
1.1 EEG/MEG信号基础
EEG(脑电图)和MEG(脑磁图)是两种最常用的非侵入式神经信号采集技术。说白了,EEG记录的是大脑皮层神经元的电活动,MEG记录的是与之相关的磁场变化。
我个人习惯把EEG想象成「从头顶偷听大脑的悄悄话」。电极贴在头皮上,每个通道记录的是该位置下方皮层区域的综合电活动。采样率通常在250-1000Hz之间,频率范围从0.5Hz到100Hz左右。
MEG呢,虽然设备贵得离谱(一台几千万),但它对深部源定位更准,而且不受头皮和颅骨的信号衰减影响。我在项目中遇到过同时采集EEG和MEG的数据,说实话,MEG的预处理要省心不少——因为磁场不太受肌肉活动干扰。
两种信号的核心频段划分如下:
| 频段 | 频率范围 | 常见生理意义 |
|---|---|---|
| Delta | 0.5-4 Hz | 深度睡眠、婴儿期 |
| Theta | 4-8 Hz | 冥想、记忆编码、困倦 |
| Alpha | 8-13 Hz | 放松、闭眼状态 |
| Beta | 13-30 Hz | 主动思考、注意力集中 |
| Gamma | 30-100 Hz | 高级认知、跨脑区整合 |
小技巧:我一般先看Alpha频段的功率,如果闭眼状态下Alpha波不明显,说明数据质量可能有问题——要么电极接触不好,要么被试太紧张了。
1.2 常见噪声类型
神经信号里的噪声,说白了就是「你不想要的那部分信号」。我把它分成三类:
- 生理伪迹:眨眼、眼动、心跳、肌肉活动、出汗。其中眨眼最烦人,幅度大、频率高,能把整个通道的信号都带偏。
- 环境噪声:50/60Hz工频干扰(取决于你在地球哪个角落)、电磁设备干扰、机械振动。我记得有一次在老旧实验室做实验,工频干扰大到连Alpha波都看不见了。
- 设备噪声:电极接触不良、放大器漂移、线缆松动。这类噪声通常表现为突然的尖峰或基线漂移。
你想想看,如果原始数据里全是这些乱七八糟的东西,模型学到的到底是大脑活动还是眨眼模式?所以预处理必须到位。
1.3 数据清洗流程
我一般按这个顺序来清洗数据,顺序很重要,别乱来:
- 可视化检查:先看一眼原始数据,有没有明显的坏道、大漂移、饱和信号。这一步靠经验,看多了就知道哪些是「正常丑」,哪些是「不正常丑」。
- 坏道剔除/插值:如果一个通道从头到尾都是平的或者全是尖峰,直接扔掉或者用周围通道插值补上。
- 带通滤波:保留你关心的频段,去掉低频漂移和高频噪声。
- 陷波滤波:干掉50/60Hz工频干扰。
- ICA去伪迹:分离并去除眼电、心电等生理伪迹。
- 分段与基线校正:把连续信号切成一个个trial,减去基线均值。
- 重参考:把数据转换到合适的参考电极(比如平均参考)。
注意:我曾经跳过可视化检查直接跑ICA,结果发现有一半的独立成分都是坏道贡献的——白白浪费了半小时。所以,先看数据,再动手。
1.4 带通滤波与陷波滤波
滤波是预处理里最基础也最容易翻车的一步。带通滤波就是只保留某个频率范围,比如0.5-40Hz。陷波滤波则是精准干掉某个窄频段,比如50Hz。
我个人习惯用scipy.signal里的filtfilt函数,因为它做的是零相位滤波,不会引入时间偏移。你想想看,如果滤波后信号的时间轴都歪了,后面做事件相关电位分析还怎么对齐?
import numpy as np
from scipy import signal
def apply_bandpass(data, fs, lowcut=0.5, highcut=40, order=4):
nyquist = 0.5 * fs
low = lowcut / nyquist
high = highcut / nyquist
b, a = signal.butter(order, [low, high], btype='band')
return signal.filtfilt(b, a, data, axis=-1)
def apply_notch(data, fs, notch_freq=50, quality=30):
nyquist = 0.5 * fs
w0 = notch_freq / nyquist
b, a = signal.iirnotch(w0, quality)
return signal.filtfilt(b, a, data, axis=-1)
避坑指南:我曾经把滤波阶数设到8阶,结果信号边缘出现了明显的振铃效应(Gibbs现象)。后来我学乖了,阶数一般用4阶,够用就行。
1.5 ICA去伪迹
ICA(独立成分分析)是去伪迹的利器。它的原理很简单:把多通道信号分解成统计上相互独立的成分,然后手动识别哪些成分是伪迹,哪些是大脑活动。
怎么识别伪迹成分?我一般看三个东西:
- 时间序列:有没有明显的眨眼波形(陡峭的尖峰)或心跳波形(规律的小波动)。
- 频谱图:伪迹成分通常在低频(<2Hz)有很高的功率,或者有很窄的尖峰。
- 地形图:眼电伪迹通常在前额电极权重最大,心电伪迹在颞叶电极权重最大。
嗯,这里要注意:ICA不是万能的。如果数据里坏道太多,ICA会强行把坏道信号也分解成「独立成分」,反而更难处理。所以一定要先做坏道剔除。
from sklearn.decomposition import FastICA
def run_ica(data, n_components=None):
ica = FastICA(n_components=n_components, random_state=42)
components = ica.fit_transform(data.T) # shape: (n_channels, n_samples)
mixing = ica.mixing_
return components, mixing
# 手动标记要剔除的成分索引(比如第0、2、5个成分是伪迹)
bad_components = [0, 2, 5]
components[bad_components] = 0
cleaned_data = np.dot(mixing, components).T
警告:我曾经一次性剔除了10个成分,结果发现把大脑的Alpha节律也给干掉了。所以,剔除成分要克制,宁少勿多。
1.6 数据标准化与分段
标准化这一步,说白了就是把不同通道、不同被试的数据拉到同一个量级上。不然的话,模型会天然地「偏爱」那些幅度大的通道。
我常用的标准化方法有两种:
- Z-score标准化:每个通道减去均值,除以标准差。适用于大多数场景。
- Min-Max标准化:缩放到[0,1]区间。适用于数据分布比较均匀的情况。
分段呢,就是把连续信号切成一个个小片段。比如做事件相关电位分析,就以刺激出现的时间点为锚点,往前取200ms作为基线,往后取800ms作为反应窗口。
def epoch_data(data, events, fs, tmin=-0.2, tmax=0.8):
"""
events: 事件时间点(单位:秒)
fs: 采样率
"""
start_offset = int(tmin * fs)
end_offset = int(tmax * fs)
epochs = []
for event in events:
idx = int(event * fs)
epoch = data[:, idx + start_offset : idx + end_offset]
epochs.append(epoch)
return np.array(epochs) # shape: (n_trials, n_channels, n_samples)
小技巧:分段后别忘了做基线校正——减去基线段的均值。不然的话,不同trial之间的基线漂移会污染你的ERP波形。
本章知识体系
下面这张图概括了本章的核心逻辑,从原始信号到可建模数据的完整流程:
好了,第一章的内容就到这里。数据预处理是深度学习模型的基础,别嫌麻烦。下一章咱们聊聊如何从预处理后的信号中提取有意义的特征——那才是真正让模型「听懂」大脑语言的关键一步。