第 5 章:用 ICA 去除伪迹¶
滤波(第 4 章)去除的是不需要的频率。但眨眼和肌肉伪迹在频率上和真实的脑信号是重叠的 —— 不能简单地把它们滤掉,否则也会一并破坏真实数据。ICA(独立成分分析,Independent Component Analysis)换了一种思路:在空间上把信号分解为统计独立的若干个来源,这样我们就可以专门识别并去除属于伪迹的来源,同时保留其余的一切。
import mne
import matplotlib.pyplot as plt
from pathlib import Path
# 设置字体,确保图中的中文能正常显示
plt.rcParams["font.sans-serif"] = ["Microsoft YaHei", "SimHei", "DejaVu Sans"]
plt.rcParams["axes.unicode_minus"] = False
mne.set_log_level("WARNING")
sample_folder = Path(mne.datasets.sample.data_path()) / "MEG" / "sample"
raw = mne.io.read_raw_fif(sample_folder / "sample_audvis_raw.fif", preload=True)
raw.pick(["eeg", "eog"])
raw.filter(l_freq=1.0, h_freq=40.0) # ICA 在已经做过 >= 1 Hz 高通滤波的数据上效果最好
raw
| General | ||
|---|---|---|
| Filename(s) | sample_audvis_raw.fif | |
| MNE object type | Raw | |
| Measurement date | 2002-12-03 at 19:01:10 UTC | |
| Participant | Unknown | |
| Experimenter | MEG | |
| Acquisition | ||
| Duration | 00:04:38 (HH:MM:SS) | |
| Sampling frequency | 600.61 Hz | |
| Time points | 166,800 | |
| Channels | ||
| EEG | and | |
| EOG | ||
| Head & sensor digitization | 146 points | |
| Filters | ||
| Highpass | 1.00 Hz | |
| Lowpass | 40.00 Hz | |
ICA 的原理,直观理解¶
把你头皮上的电极想象成房间里的若干个麦克风,房间里同时有几个独立的"喇叭"在播放声音 —— 有些喇叭是脑源信号,可能有一个喇叭是"眨眼",另一个是"肌肉紧张"。每个电极接收到的,都是所有喇叭按不同比例混合出来的一种混音,具体比例取决于距离/几何位置。ICA 并不知道哪个喇叭是哪个,但它可以在数学上把这些记录"反混合"回(假设存在的)独立原始来源,因为它专门寻找彼此之间统计上尽可能独立的成分。
分解成若干成分之后,我们逐一检查每一个,根据它们的头皮地形图(topography)和时间走势,判断哪些看起来像是眼动/肌肉伪迹,只去除这些成分 —— 然后把剩下的成分重新混合回去。这比滤波要精细得多:它去除的是伪迹来源,而不是某个频率段,所以恰好和伪迹共享某个频率的真实脑信号,能够保留下来。
ica = mne.preprocessing.ICA(n_components=15, random_state=97, max_iter="auto")
ica.fit(raw)
ica.plot_components()
plt.show()

每个子图都是一个成分的头皮地形图(它对哪些通道贡献最强)。典型的眨眼成分看起来是一个集中在最前方(Fp1/Fp2 附近)的、强烈且对称的色块 —— 因为在大多数记录中,眨眼是额部区域最主要的信号来源。
用 EOG 通道自动检测¶
肉眼检查是可行的,但主观且费时。如果你的记录中包含专门的 EOG 通道(这个样例数据集就有 —— 它直接测量眼动),MNE 可以自动找出哪些 ICA 成分与它高度相关 —— 这比靠眼睛判断地形图要可靠得多。
eog_indices, eog_scores = ica.find_bads_eog(raw)
print("被标记为眨眼相关的成分:", eog_indices)
ica.plot_scores(eog_scores)
plt.show()
被标记为眨眼相关的成分: [np.int64(0)]

再仔细看看被标记出的成分:它的地形图、时间走势,以及它与真实 EOG 信号的相关程度 —— plot_properties 把这些信息都汇总进一张诊断图里。
if eog_indices:
ica.plot_properties(raw, picks=eog_indices)
plt.show()

排除成分并应用清理¶
把 ica.exclude 设为你想去除的成分索引(可以来自自动检测,也可以来自你自己的肉眼判断),然后 ica.apply() 会重建信号,减去这些成分。
ica.exclude = eog_indices # 也可以手动设置,例如 [0, 3],取决于你上面看到的情况
raw_clean = raw.copy()
ica.apply(raw_clean)
raw.plot(picks="eeg", n_channels=10, duration=10, scalings="auto", title="ICA 清理前")
raw_clean.plot(picks="eeg", n_channels=10, duration=10, scalings="auto", title="ICA 清理后")
plt.show()


没有 EOG 通道时怎么办¶
你自己的记录(32 通道,以 CPz 为参考)没有专门的 EOG 通道 —— 这种情况很常见。这时可以:
- 退回到肉眼检查:用
ica.plot_components()和ica.plot_sources(raw)(显示每个成分的时间走势,这样你就能直接观察是否有眨眼形状的尖峰)配合上面提到的"额部地形图"经验法则,用肉眼识别类似眨眼/肌肉的成分。 - 或者,一个更简单(也更粗糙)的备选方案,是在切分 epoch 之后基于幅度做拒绝 —— 丢弃那些任意通道峰峰值异常大的整个 epoch,我们会在第 6 章把这作为第二道防线来使用。
小结¶
- ICA 把信号分解为独立成分;我们只去除那些看起来像眼动/肌肉伪迹的成分。
- 如果存在 EOG 通道,
ica.find_bads_eog()可以自动完成这一步;否则,用肉眼检查ica.plot_components()/ica.plot_sources()。 - 务必在已经做过高通滤波(≥1 Hz)的数据上拟合 ICA。