Skip to content

第 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()

png

每个子图都是一个成分的头皮地形图(它对哪些通道贡献最强)。典型的眨眼成分看起来是一个集中在最前方(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)]

png

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

if eog_indices:
    ica.plot_properties(raw, picks=eog_indices)
    plt.show()

png

排除成分并应用清理

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()

png

png

没有 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。

下一章:第 6 章 —— 事件与 Epoching