1. 数据集来龙去脉:为什么CHB-MIT这么多年来始终绕不开
如果你是做癫痫EEG相关研究的人,有一个数据集你迟早会碰到,那就是CHB-MIT。这个来自波士顿儿童医院(Children's Hospital Boston)的公开脑电数据集,几乎是癫痫检测、发作预测、EEG分类方向入门必跑的benchmark。它由MIT与波士顿儿童医院联合发布,多年来一直是国内外论文里出现频率最高的公共数据集之一,很多经典算法、深度学习模型的验证都跑在它上面。
这套数据采集自22位难治性癫痫患儿(其中包含5例男性和17例女性,年龄从1.5岁到22岁不等),共记录了24例病例的连续头皮EEG信号(部分患者因随访时间跨度大被当作多个独立病例记录)。所有信号均采用国际标准10-20导联系统放置电极,采样率统一为256Hz,16位分辨率,22到23个通道不等(部分记录额外包含心电等生理信号通道)。简单来说,这就是一份质量相当高、标注相对规范、规模适中的长时程癫痫脑电记录,特别适合用来做发作检测和发作起始区分析。
这一系列文章,我计划从数据集的底层格式解析开始,一步步带你走通数据读取、标注转换、预处理、坏道处理、发作片段提取,再到特征工程和深度学习建模的完整链路。今天这一篇,先解决最基础也最容易让人卡住的问题:EDF文件到底是什么结构、怎么用Python正确读出来、标注文件怎么解析,以及拿到手后第一步该做什么。
适合什么人来读?两类人。第一类是刚接触癫痫EEG分析、打算用CHB-MIT跑通第一个实验的研究生或工程师,你可能之前只会调用现成工具,但没搞懂底层数据长什么样。第二类是已经跑过一些代码,但对数据集细节知其然不知其所以然的人,比如为什么有的文件是23通道有的24通道,为什么标注时间不能直接用,这些问题你需要一个明确答案。
2. 标准数据集背后的采集细节与通道布局
2.1 病例组成与文件命名规则
先花点时间把数据集的物理结构讲清楚,因为这个结构直接影响后续所有代码逻辑。
下载解压后,你会看到以chb01、chb02这类命名的子文件夹,每个文件夹对应一名患者。每个患者文件夹里包含若干EDF格式的脑电记录文件,文件命名通常是这样的:
chb01_01.edf chb01_02.edf chb01_03.edf ... chb01_29.edf每个EDF文件代表一段独立的连续记录时间,时长从几分钟到数小时不等。以chb01为例,全部记录的累积时长约40小时,其中的发作事件分布在6个文件里。除EDF文件本身外,每个患者文件夹里还有一个名为chb01-summary.txt的文本文件(也可能是.txt格式),这个summary文件记录了该患者全部EDF文件中癫痫发作起止时间,是整个数据集唯一可靠的标注来源。
22位患者中,注意一个特殊情况:chb12和chb13来自同一位患者(该患者在不同时间点接受了两轮记录),chb14到chb24也是按不同时间段的随访检查划分的,所以论文里常说“22个受试者,24个病例记录”。这个细节在写论文时如果表述不严谨,容易被审稿人挑毛病。
2.2 通道配置与10-20系统
这套数据绝大部分采用的是双极参考导联,导联名称在EDF文件头里直接标注,常见的有FP1-F7、F7-T7、T7-P7、P7-O1这类经典双极链,每个文件的通道数量差异主要取决于是否额外录制了心电或其它生理信号。
以chb01为例,它的标准通道如下:
FP1-F7, F7-T7, T7-P7, P7-O1, FP1-F3, F3-C3, C3-P3, P3-O1, FP2-F4, F4-C4, C4-P4, P4-O2, FP2-F8, F8-T8, T8-P8, P8-O2, FZ-CZ, CZ-PZ, P7-T7, T7-FT9, FT9-FT10, FT10-T8, T8-P8最后两个导联FT9-FT10和FT10-T8是颞叶下方的电极组合,对颞叶癫痫的棘波检测非常重要。实际处理时你会发现部分文件会多出一个心电图通道,也就是ECG,或者因为记录设备的问题出现通道缺失、坏道。这些都是预处理阶段必须处理到的,不能拿到手就直接丢进模型。
2.3 为什么选择EDF格式存储
EDF(European Data Format)是医学信号领域最常见的标准格式之一,由欧洲多个睡眠实验室在上世纪90年代推动建立,后来在脑电、心电、多导睡眠监测等领域广泛使用。EDF的精髓在于:一个二进制文件,前半部分用ASCII码写了固定的文件头,后半部分是纯数字的信号数据块,通过文件头里的元信息解释每一段数据对应哪个通道、多少个样本点。
这种设计的优势非常明显,文件自包含,不依赖外部数据库就能完整还原记录现场,而且EDF格式是公开标准,任何语言都能实现解析。代价是文件头结构略显老旧,有些字段比较“死板”,比如患者名、记录编号等字段长度固定为80字节,信号个数、采样率等字段长度固定为8或10字节。旧格式带来的这些问题,实际读取时经常引发小麻烦,后面我会逐一提到。
3. EDF文件结构与Python读取实战
3.1 文件头字段逐项拆解
EDF文件头主要分成三大部分,第一部分是固定长度的全局头信息,第二部分是每个信号通道的局部头信息,第三部分是各通道的数据正文。全局头信息的关键字段如下表所示:
| 偏移量 | 长度(字节) | 字段含义 |
|---|---|---|
| 0 | 8 | 版本号(通常为"0") |
| 8 | 80 | 患者ID(可包含姓名、性别等) |
| 88 | 80 | 记录ID |
| 168 | 8 | 记录开始日期 |
| 176 | 8 | 记录开始时间 |
| 184 | 8 | 文件头总字节数 |
| 192 | 44 | 保留字段 |
| 236 | 8 | 数据记录段数量 |
| 244 | 8 | 每个数据记录段的时长(秒) |
| 252 | 4 | 通道数(信号个数) |
每个通道的局部头信息有256字节,包括通道名称(16字节)、转换因子(8字节)、物理量纲(8字节)、幅值范围、采样点数等。数据正文则按“数据记录段”为单位存放,每段包含所有通道在特定时长内(通常是1秒)的样本,通道顺序和局部头信息的顺序一一对应。
3.2 pyedflib和mne的选择
Python生态里读取EDF文件主要有两个选择:pyedflib和MNE-Python。pyedflib是底层的EDF读写库,功能直接,适合对数据细节有清晰要求的开发者;MNE-Python则是一套完整神经影像分析框架,内置了EDF读取器,并且会顺手做很多数据处理,比如自动提取通道类型、生成Raw对象,更适合直接用现成流程做预处理。
我的建议是,第一个实验用MNE就够了,它对EDF的兼容性好,几行代码就能把数据读进内存。但如果你打算在服务器上做大规模离线处理,或者不想引入过重的依赖,直接基于pyedflib写一套轻量级读取函数也很方便。下面给出两种方式的示例。
3.3 Python读取代码与细节说明
用MNE读取一个EDF文件并查看基本信息,只需这几行:
import mne import numpy as np # 读取EDF文件 raw = mne.io.read_raw_edf('chb01_03.edf', preload=True, verbose='ERROR') # 查看基本属性 print(raw.info) print(raw.ch_names) print('采样率:', raw.info['sfreq']) print('数据矩阵形状:', raw.get_data().shape)preload=True会把数据全部加载进内存,适合文件不大的情况(单个EDF几十分钟的256Hz数据,大概几十到两百兆不等)。如果只是先做元数据检查,建议preload=False,快速扫描文件头,避免内存开销。
用pyedflib读取的写法是:
import pyedflib import numpy as np with pyedflib.EdfReader('chb01_03.edf') as f: n_channels = f.signals_in_file ch_names = f.getSignalLabels() sfreq = f.getSampleFrequency(0) sample_count = f.getNSamples()[0] duration = sample_count / sfreq print('通道列表:', ch_names) print('采样率:', sfreq) print('记录时长(秒):', duration) # 读取第0个通道的全部数据 signal = f.readSignal(0).astype(np.float64)实际使用中我的建议是:如果不用MNE后面那套滤波、ICA、epoch等高级功能,pyedflib足够轻量顺手;如果你本来就要做完整EEG预处理,那就直接用MNE,少写很多胶水代码。
3.4 EDF文件头解析的踩坑记录
EDF格式有一些经典坑,我当年第一次用的时候踩了几次,列出来供你排雷。
第一,患者ID字段、记录时间字段都是定长文本,内容是带空格的,直接用字符串切片没问题,如果试图解析成日期时间对象,注意去掉首尾空格。第二,全局头中的通道数和后面的nSignals字段可能不一致,少数文件里有“幽灵信号”,就是头信息里声明了通道,但数据段里根本没有对应采样点,遇到这种情况pyedflib读出来的通道数和文件头声明对不上,需要根据实际采样点数做修正。第三,转换因子的问题,EDF里每个通道的单位不一定是µV,可能是mV甚至直接是ADC码值,读数据时一定要用文件头里的物理量纲和转换因子换算,否则后续幅值全部出错。最简单的方法还是用pyedflib的readSignal函数,它会自动完成换算。
4. 标注体系与发作事件提取
4.1 summary文件结构与发作时间解析
拿到CHB-MIT数据后,真正花时间的不是读EDF本身,而是把summary文件里的标注对到具体的EDF文件上。以chb01-summary.txt为例,其内容片段如下:
File Name: chb01_03.edf File Start Time: 15:56:29 File End Time: 15:59:57 Number of Seizures in File: 1 Seizure Start Time: 2967 seconds Seizure End Time: 3036 seconds这里的美妙之处在于,一个summary里出现多个File Name块,每个块对应一个EDF文件的全面信息,块内部有“Number of Seizures in File”字段,如果大于0,就继续读下面每一个“Seizure Start Time”和“Seizure End Time”。时间单位是秒,以该EDF文件的起始时刻为0点。
比如上面这个文件,从EDF文件开始记录起第2967秒起,第3036秒止,一共持续69秒的发作。注意有些文件同时包含多次发作,summary里会依次列出每一段的起止时间。
4.2 一个可以复用的标注解析脚本
我写过一个精简的解析函数,每次处理一个患者文件夹时直接调用,返回一个字典,键是文件名,值是该文件中所有发作事件的起止时间列表:
import re from collections import defaultdict def parse_chb_summary(summary_path): with open(summary_path, 'r') as f: content = f.read() # 按File Name分块处理 file_blocks = re.split(r'File Name:', content)[1:] out = defaultdict(list) for block in file_blocks: lines = block.strip().split('\n') fname = lines[0].strip() for line in lines[1:]: line = line.strip() if line.startswith('Seizure Start Time:'): start = float(line.split(':')[1].strip()) # 下一个Seizure End Time才对应 end_match = re.search(r'Seizure End Time:\s*([0-9.]+)', lines[lines.index(' '+line)+1] if lines.index(' '+line)+1 < len(lines) else '') if end_match: end = float(end_match.group(1)) out[fname].append((start, end)) return dict(out)需要注意,原始summary文件里有的行首带空格,有的不带,直接用split经历不同版本数据时可能出现index错误,所以我这个版本里严格按行匹配更稳妥。另外还有一个需要注意的地方,就是某患者(比如chb10)的summary里,开头几行有一个“Number of Seizures: ”的全局总数字,那个只管整个文件的总和,不影响我们按文件块解析。
实际上更稳妥的做法是直接用状态机按行解析,遇到“File Name”切文件,遇到“Seizure Start”记录start,遇到“Seizure End”补上end,这样不依赖行数和换行格式。我测试过不同患者文件夹里的summary格式,有少量细节不一致,比如chb24的文件名后缀是chb24_07.edf.chb24_07.edf这种重复命名,不影响解析,正确处理的时候按路径找文件即可。
4.3 发作段与正常段的划分策略
有了起止时间,下一步就是把这些时间点转成样本索引。假设EDF的采样率是256Hz,那么第2967秒对应第2967*256=759552个样本点,第3036秒对应第777216个样本点。虽然CHB-MIT里发作标注的时间粒度是秒级,但这个秒其实并非整数,有些是带小数的,直接用整数乘采样率会丢掉部分精度,建议保留小数乘完再取整。
这里通常有两种策略。一种是把发作段完整截下来作为正样本,再从非发作区域随机截取等长片段作为负样本;另一种是固定窗口法,比如每个窗口2秒,滑窗时保持发作起止时间的边界对齐,窗口与发作段交集超过某个阈值就算正类。前者实现简单,后者更细致,适合做逐窗口分类任务。实操中我倾向于后者,因为发作的起止边界往往是模型最容易错分的位置,把边界附近窗口也纳入训练,有助于模型学习过渡态。
5. 预处理链条:坏道检测、滤波与降采样
5.1 EEG坏道检测到底怎么做
EEG坏道检测是预处理中非常关键的一环。CHB-MIT虽然整体数据质量尚可,但毕竟记录对象是儿童,儿童在长时间监测中头部活动多,有些通道的电极松动或脱落很常见,采集出来的信号要么是一段幅度极高的平直或饱和波形,要么是持续的无规则高频毛刺。如果不处理,这些通道会把后面所有频段分析全带偏。
坏道检测的方法核心思路就是找“和其他通道显著不同的时间序列”。我常用的检测流程分为三步:
第一步,逐通道计算时域统计量,比如方差、峰峰值、绝对幅度中位数。如果一个通道的方差比其它通道高出20倍以上,基本可以判定是坏道或异常饱和;如果一个通道的峰峰值长期接近采集器满量程,说明电极可能短路或脱落。
第二步,做频域检测,计算每个通道在全频段、高频段(比如35-100Hz)和低频段(0.5-4Hz)的功率占比。坏道通常在中高频段表现出异常的高功率,因为系统里混入设备干扰或接触不良的噪声。
第三步,检查与相邻通道的相关系数。正常EEG信号在相邻电极之间往往有较高的空间相关性,坏道与周围通道的相关系数一般会显著下降。把相关系数低于某个阈值的通道标记出来,再结合前两步结果做投票判定。
下面是一段基于MNE的坏道自动识别参考代码:
import numpy as np def detect_bad_channels(raw, threshold_variance=20.0, threshold_corr=0.4): data = raw.get_data() n_ch = data.shape[0] bad_ch = set() # 1. 方差检测 variances = np.var(data, axis=1) median_var = np.median(variances) for i, v in enumerate(variances): if v > threshold_variance * median_var: bad_ch.add(raw.ch_names[i]) # 2. 高频功率占比检测 from scipy.signal import butter, filtfilt high_freq_power = {} fs = raw.info['sfreq'] b_high, a_high = butter(5, 35/(fs/2), btype='highpass') b_low, a_low = butter(5, 4/(fs/2), btype='lowpass') for i in range(n_ch): sig = data[i] - np.mean(data[i]) high_f = filtfilt(b_high, a_high, sig) low_f = filtfilt(b_low, a_low, sig) high_power = np.sum(high_f**2) low_power = np.sum(low_f**2) high_freq_power[raw.ch_names[i]] = high_power / (low_power + 1e-8) median_ratio = np.median(list(high_freq_power.values())) for ch_name, ratio in high_freq_power.items(): if ratio > 5 * median_ratio: bad_ch.add(ch_name) # 3. 相关检测(与相邻通道) for i in range(1, n_ch): corr = np.corrcoef(data[i-1], data[i])[0, 1] if np.abs(corr) < threshold_corr: bad_ch.add(raw.ch_names[i]) return list(bad_ch)这个版本偏向工程化,未必完美,但检测结果可以用MNE的raw.info['bads']属性记录下来,后续无论做ICA、伪迹剔除还是画图都会自动跳过这些通道。手动审查时,我会把自动检测出的坏道叠加在脑电波形图上逐段确认,避免误杀有效通道。
5.2 滤波参数的确定和分频段分析基础
脑电信号的有效频率范围大约在0.5到100Hz之间,超过100Hz的成分基本都是肌电或电磁干扰。CHB-MIT的采样率是256Hz,按奈奎斯特定理,其可分析的最高频率是128Hz,实际工程中使用低通滤波器上限一般设为70或100Hz。
对癫痫发作检测而言,最关注的频段通常是:
- 0.5-4Hz:delta波,慢波活动,部分局灶性发作后期会出现明显的delta节律;
- 4-8Hz:theta波,某些颞叶癫痫发作期会出现theta节律演化;
- 8-13Hz:alpha波,静息态背景节律;
- 13-30Hz:beta波,发作放电的快波活动;
- 30-70Hz:gamma波,高频振荡,与癫痫发生区有较强关联。
我的推荐流程是先做0.5Hz高通和70Hz低通滤波,再看看50Hz工频干扰严重与否。如果频谱图上50Hz处有明显的尖峰,需要加50Hz陷波滤波器;如果50Hz处没有明显峰值,则尽量不做陷波,因为陷波器会连带损失一部分邻频信息,这对后面高频振荡分析不利。
下面是一个典型预处理管线,把滤波、重参考和坏道标记都做了:
import mne raw = mne.io.read_raw_edf('chb01_03.edf', preload=True, verbose='ERROR') # 重采样(可选,如果后续要降采样到128/200Hz) # raw.resample(200) # 带通滤波,注意先做高通再做低通 raw.filter(0.5, 70, fir_design='firwin', verbose='ERROR') # 50Hz陷波(观察频谱后决定是否启用) # raw.notch_filter(50, verbose='ERROR') # 设置参考(常用平均参考) raw.set_eeg_reference('average', projection=False, verbose='ERROR') # 统一通道类型,保证后面MNE的处理能识别 raw.set_channel_types({'ECG': 'ecg'} if 'ECG' in raw.ch_names else {})有一点要特别说明:CHB-MIT多数记录本身就是双极导联,双极导联下相邻通道的差值已经把共模干扰抑制掉了一部分,这时候再做一次平均参考效果有限,但也不是完全没意义。平均参考在数据质量波动大的文件里能进一步压平基线漂移。具体做不做,建议针对每个文件目检后再决定。
5.3 降采样和内存优化的工程考量
256Hz的采样率在很多任务场景下其实是偏高的。比如做癫痫发作检测时,脑电的主要特征集中在0.5-40Hz,过高的采样率只会增加计算负担和内存占用。如果目标是快速跑通分类实验,往往降到128Hz甚至100Hz就够用,计算量能减少一半以上,信息损失在可接受范围内。
MNE的resample函数很方便,但要注意重采样前必须做好低通滤波,防止混叠。MNE的resample内部会自动选择合适的方法,如果不放心可以先用lowpass滤波到40Hz再resample到128Hz。
内存方面,一个256Hz、23通道、1小时的数据,float64存储大约是256360023*8约170MB,多个文件同时载入会很吃内存。如果做全数据集实验,强烈建议按窗口切分后使用数据管道,不要一次性把所有EDF全部读进来。用一个生成器逐文件读取、逐窗口产生样本,是工程实践里更稳的方式。
6. 训练集验证集划分与数据泄漏预防
6.1 为什么要按患者划分而不是按文件划分
做癫痫检测模型时,最常见的错误之一是把同一患者的多个文件随机分进训练集和测试集。理论上同一患者的EEG极具个体特异性,模型记住了这个人特征,测试时看到同一个人的另一段数据就能轻松做对,导致在测试集上效果虚高,而换到新患者身上效果暴跌。
正确的做法是患者划分,比如22位患者的完整记录中,选17位作为训练集,5位作为验证集或测试集,保证训练和测试时见不到同一个人的任何数据。这样评测出来的跨患者泛化能力才是临床上有意义的指标。
如果想做半泛化实验,也可以采用患者中一部分文件训练、一部分文件测试,但这需要明确标注并谨慎质疑模型学到了多少患者特异性特征。更严格的做法是采用双交叉验证或leave-one-subject-out,只是计算量会成倍增加,不能作为日常快速实验的首选。
6.2 序列切分时防止相邻窗口信息混叠
另一个容易被忽略的泄漏源是数据增广或滑窗切分时,相邻窗口之间高度重叠。如果训练集和测试集来自同一个患者的连续记录,且窗口的起始点只差几十毫秒,那么训练集和测试集的实际内容几乎重合,评测指标会严重失真。
打断信息混叠的方法很简单:切窗口时给训练和测试各留一段独立时间区域,或者让窗口间隔大于窗口长度,保证两个集合之间没有重叠覆盖。另外,在做归一化、z-score标准化时,必须用训练集统计量,并把同样的均值和标准差应用到测试集上。这一点做特征工程时很容易翻车,如果直接从整个数据集上统计均值和方差,相当于把测试集的信息提前泄漏进了训练阶段,得到的效果同样不值得信任。
我在组织CHB-MIT实验时通常会先按患者划分数据,再在训练集内部切窗、计算统计量,全部处理完再对测试集做同样的变换处理。虽然代码上稍微复杂,但结果要可靠得多,用来发论文也不会被审稿人抓数据泄漏的把柄。
7. 关于数据增强和波形可视化的一点实操心得
7.1 发作样本过少怎么办
CHB-MIT虽然总计包含上百次发作事件,但是分散到20多个患者后,每个患者的发作次数并不多,多的几十次,少的只有几次。直接做有监督分类时正负样本数量往往严重不平衡,哪怕正负样本按1:1采样,训练出来的模型还是容易过拟合发作形态。
常用的增强手段有三种:加噪声、幅值扰动和时域缩放。加噪声用高斯白噪声叠加,模拟采集设备和周围环境干扰;幅值扰动对每个样本随机乘以0.8到1.2的系数,模拟电极接触阻抗变化导致的增益漂移;时域缩放稍微麻烦一点,需要保持时间轴和标签对齐,不能随意拉伸窗口。实际操作中我发现组合使用噪声和幅值扰动收益最大,时域缩放则慎用,因为过度的缩放会破坏棘波形态,导致模型学到失真特征。
如果想要更稳定的样本均衡方法,可以优先尝试随机下采样正常段,让正常段和发作段数量比维持在5:1到10:1之间。发作段本来就是一个比例极低的事件,盲目追求1:1反而会让模型看到太多重复的发作形态,对背景脑电特征的泛化能力变差。
7.2 用Topomap和波形图辅助判断预处理是否达标
很多人做完滤波、参考和坏道处理后,直接就把数据丢进模型,这其实不够负责。建议每个患者至少保留一张预处理前后的对比图:一个通道挑选FP1-F7或其它前头部导联,用matplotlib画一段包含发作起止的波形,查看滤波后棘波形态是否清晰,是否出现大幅脉冲伪迹。
MNE里画Topomap能直观看到每个通道的空间分布和异常电压,对判断预处理质量很有帮助。比如预处理后某通道的Topomap看起来颜色极度饱和,说明这个通道可能仍有坏的残余信号,需要回到坏道检测阶段重新标记。我每次处理完一批文件后,都会随机抽查3到5个文件做目检,这个习惯帮我发现过很多自动流程无法捕捉的问题。
8. 常见错误与排查清单
根据我自己跑CHB-MIT一年的经验,把新手最容易踩的坑整理成一个速查表:
| 容易出问题的地方 | 具体表现 | 排查思路 |
|---|---|---|
| EDF文件头解析 | 读取通道数比实际多/少 | 先打印所有通道名,逐个和标准10-20通道对照 |
| 采样频率设置错误 | 时频图横轴单位不对 | 用raw.info['sfreq']确认,勿手动假设256 |
| 发作时间解析 | 起止时间完全对不上波形 | 检查summary中单位是否为秒,注意文件起始时间不是0点 |
| 坏道未处理 | 模型训练不收敛或性能极差 | 画出通道方差和高频功率,自动加人工确认 |
| 滤波器相位失真 | 发作起始边界偏移 | 使用filtfilt做零相位滤波,勿用causal filter |
| 训练测试数据重叠 | 模型看似性能极佳但泛化差 | 按患者严格划分,杜绝同源文件出现在两个集合 |
| 幅值范围不一致 | 上下限差异大导致特征失效 | 用训练集统计做z-score,勿用测试集统计 |
| 文件解压损坏 | read失败或通道数异常 | 通过文件哈希校验是否完整下载 |
这套清单是通用的,排查时从最底层的数据文件格式开始一层层往上走,往往能找到根本原因。EEG数据预处理没有一步到位的神器,多花时间做质量审查,比多调几个模型参数重要得多。
9. CHB-MIT处理完成后的下一步规划
这篇作为系列第一篇,主要解决的是“拿到数据后怎么正确读出来并看懂”的问题。到了这一步,你已经能完成从EDF文件到带标注发作区间样本矩阵的转换,这是整个CHB-MIT分析链路中最基础也最关键的一步。
后续内容我会陆续展开发作检测模型的基线搭建、短时傅里叶变换与小波变换的特征对比、深度学习模型(如EEGNet、TSception)在CHB-MIT上的表现复现,以及如何利用该数据集做患者特异性迁移学习。CHB-MIT的好处在于它的标注规范性和采样一致性,便于研究者专注于算法本身,而不必花大量时间清洗数据。但它也存在一些局限,比如受试者都是儿童、发作类型和脑区覆盖面有限、记录时间并非全天候,这些局限会在你后续做通用模型评估时暴露出来,需要结合其它数据集(比如TUH EEG Seizure Corpus)做交叉验证。TUH数据集规模更大、来源更杂、包含更多不同的记录环境,是CHB-MIT之外非常值得关注的补充资源,我后面也会专门讲它的下载格式和标注体系。
不过在深入研究模型之前,先把数据基础打牢,保证每一步处理都经得起复查。信号质量决定了特征上限,标注对齐决定了标签可靠性,这两点做到位之后,模型实验才有真正的意义。下一篇我会继续沿着数据管道往下走,写清楚从原始信号到训练样本的具体转化细节。