简介CHB-MIT数据集由MIT与波士顿儿童医院联合构建是癫痫脑电研究领域广泛使用的公开资源面向生物医学信号处理、机器学习与模式识别方向的研究者及学生。压缩包共145个文件以141个.m脚本和4个.py文件为主整体约159KB其中MATLAB脚本多用于样本熵等特征提取与信号分析Python文件可辅助数据处理与算法验证便于在癫痫发作检测与分类任务中直接复用。内容围绕23名受试者的多通道脑电记录展开涵盖复杂部分性发作、简单部分性发作及二次全面性发作等类型并附有发作起止时间戳等临床信息可用于特征工程、模型训练与性能评估。已有827人学习下载适合作为算法开发、课程实验与跨学科研究的参考素材帮助读者快速搭建癫痫检测流程并验证思路。1. CHB-MIT数据集到底装了什么从一次翻车复盘说起第一次拿到CHB-MIT数据集.rar的人十有八九会先解压然后对着二十多个文件夹发愣。我当年也是这么干的解压完看到一堆chb01、chb02一直到chb24每个文件夹里塞着几十个.edf文件外加几个.txt和.seizures后缀的文本第一反应是「这玩意儿怎么读」。更坑的是我随手挑了一个.edf用mne.read_raw_edf()读进去画出来的波形看着挺正常但拿它去训癫痫检测模型AUC 死活上不去后来才发现自己把发作间期和发作期的文件混在一起当正样本用了。CHB-MIT 是波士顿儿童医院公开的一套头皮脑电scalp EEG癫痫发作数据集采集对象是 23 名难治性癫痫患儿其中 chb24 是后来补的部分文献只算 23 例采样率固定 256 Hz通道数多为 23 通道含一个参考按 10-20 国际导联系统布置。它最大的价值在于每个受试者都有连续数十小时的记录并且有医生逐秒标注的发作起止时间这让它成为癫痫自动检测领域最常用的公开基准之一。适合谁用做生理信号处理、时序异常检测、医学 AI 落地的工程师和研究生尤其是想验证「小样本、强个体差异」场景下模型泛化能力的人。这一章先把「它是什么、能解决什么」讲清楚后面几章再动手。2. 解压后先别急着读信号目录结构与标注文件的对应关系2.1 每个 chbXX 文件夹里到底有什么解压CHB-MIT数据集.rar之后你会看到类似这样的结构不同来源打包方式略有差异但内部命名一致chb01/ chb01_01.edf chb01_02.edf ... chb01_03.edf.seizures chb01_21.edf.seizures chb01-summary.txt chb02/ ...关键点在于.edf是信号本体.seizures是发作标注-summary.txt是整段记录的摘要。很多人第一次踩的坑就是只读了.edf完全忽略了.seizures和 summary结果自己拿波形去「目测」发作段标注全靠猜。.seizures文件命名规则是「对应的 edf 文件名 .seizures」里面记录的是该文件内发作的起止秒数。summary 文件则是把该受试者所有记录的发作情况汇总包含文件起始时间、发作编号、起止时刻。我一般会先解析 summary建立一张「文件 → 发作区间」的总表再去读信号这样不会漏。2.2 用 Python 把 summary 和 seizures 解析成结构化表下面这段代码是我常用的解析脚本把 summary 和.seizures统一整理成 DataFrame方便后续切片import os import re import pandas as pd def parse_summary(summary_path): 解析 chbXX-summary.txt抽取每个文件的发作起止秒 records [] with open(summary_path, r, encodingutf-8, errorsignore) as f: lines f.readlines() current_file None for line in lines: line line.strip() # 匹配 File Name: chb01_03.edf m re.match(rFile Name:\s*(\S), line) if m: current_file m.group(1) continue # 匹配 Seizure 1: 2996 3036 这类起止秒 m re.match(rSeizure\s\d:\s*(\d)\s(\d), line) if m and current_file: start, end int(m.group(1)), int(m.group(2)) records.append({ file: current_file, seizure_start: start, seizure_end: end }) return pd.DataFrame(records) def parse_seizure_file(seizure_path): 解析单个 .seizures 文件返回 (start, end) 列表 intervals [] with open(seizure_path, r, encodingutf-8, errorsignore) as f: for line in f: line line.strip() if not line: continue parts line.split() # 常见格式起始秒 结束秒 if len(parts) 2 and parts[0].isdigit(): intervals.append((int(parts[0]), int(parts[1]))) return intervals # 示例遍历 chb01 root CHB-MIT/chb01 summary_df parse_summary(os.path.join(root, chb01-summary.txt)) print(summary_df.head())逻辑说明parse_summary用正则逐行扫描遇到File Name:就切换当前文件遇到Seizure N: start end就记录一条。parse_seizure_file处理单个.seizures格式在不同版本里可能是「起始秒 结束秒」或带额外字段所以用isdigit()做一次过滤。参数上seizure_start和seizure_end都是相对该 edf 文件起点的秒数不是绝对时间切片时直接乘采样率即可。提示summary 里偶尔会出现「Seizure 1: 2996 3036」后面跟多行注释的情况正则只抓数字行别用固定列宽去切否则换行就翻车。2.3 通道命名与采样率读信号前必须确认的两件事CHB-MIT 的通道名在不同记录里不完全一致常见的是FP1-F7、F7-T7、T7-P7这种双极导联命名也有FP1、F7单极。用 MNE 读取时raw.ch_names打印出来先看一眼别默认 23 通道就一定是标准顺序。采样率统一 256 Hz但个别文件可能有差异读的时候用raw.info[sfrq]确认不要硬编码。我一般会写一个校验函数读每个 edf 时检查通道数和采样率把异常文件记下来单独处理。这一步花不了几分钟但能避免后面模型训练时因为某个文件通道错位导致整批数据污染。3. 从 EDF 到模型输入切片、滤波与标签对齐的完整链路3.1 用 MNE 读取 EDF 并做 0.5–40 Hz 带通滤波原始头皮 EEG 里工频干扰和基线漂移很重直接送模型效果很差。常见做法是先带通滤波到 0.5–40 Hz再 optionally 做 50 Hz 陷波。下面是我常用的读取加滤波流程import mne import numpy as np def load_and_filter(edf_path, l_freq0.5, h_freq40.0, notch50.0): 读取 edf带通滤波 陷波返回 data (n_channels, n_times) 和 sfreq raw mne.io.read_raw_edf(edf_path, preloadTrue, verboseFalse) sfreq raw.info[sfrq] # 陷波去工频国内数据常见 50 Hz raw.notch_filter(freqsnotch, verboseFalse) # 带通滤波 raw.filter(l_freql_freq, h_freqh_freq, verboseFalse) data raw.get_data() # shape: (n_channels, n_times) return data, sfreq, raw.ch_names data, sfreq, ch_names load_and_filter(CHB-MIT/chb01/chb01_03.edf) print(data.shape, sfreq, len(ch_names))逻辑说明preloadTrue把数据读进内存方便后续滤波notch_filter去掉 50 Hz 工频filter做带通。参数上l_freq0.5保留慢波成分h_freq40覆盖大部分癫痫相关节律棘波、尖波集中在 3–30 Hz。如果你的任务更关注高频振荡可以放宽到 70 Hz但要注意 256 Hz 采样率下奈奎斯特上限是 128 Hz别超过。注意MNE 的filter默认使用 FIR长度较长对短片段边缘会有衰减。切片时建议在发作区间前后各留 2 秒缓冲避免把滤波边缘效应当成异常。3.2 按发作标注切窗正负样本怎么定义才不翻车癫痫检测模型最常见的输入是固定长度窗口比如 2 秒或 4 秒标签来自发作区间。这里有个血泪经验负样本不能随便从非发作段里抽因为发作前驱期pre-ictal和发作后抑制期post-ictal的波形和正常期差异很大混在一起会让模型学到「临近发作」的伪特征。我一般这样定义样本类型定义说明正样本窗口与发作区间重叠 ≥ 50%确保包含足够发作波形负样本窗口距最近发作区间 ≥ 30 分钟避开前驱/后驱期忽略区发作前后 5 分钟内不参与训练减少标签噪声切片代码示例def slice_windows(data, sfreq, seizure_intervals, win_sec2.0, step_sec1.0): 按滑动窗切片返回 windows 和 labels win_len int(win_sec * sfreq) step_len int(step_sec * sfreq) n_channels, n_times data.shape windows, labels [], [] for start in range(0, n_times - win_len, step_len): end start win_len # 判断是否与任一发作区间重叠 50% is_seizure False for s, e in seizure_intervals: s_idx, e_idx int(s * sfreq), int(e * sfreq) overlap max(0, min(end, e_idx) - max(start, s_idx)) if overlap 0.5 * win_len: is_seizure True break windows.append(data[:, start:end]) labels.append(1 if is_seizure else 0) return np.array(windows), np.array(labels)逻辑说明win_sec控制窗口长度step_sec控制步长重叠切片能增加样本量。重叠判定用overlap 0.5 * win_len避免边界窗口标签模糊。参数上2 秒窗 1 秒步长是常见起点如果模型感受野更大可以调到 4 秒。3.3 个体化归一化为什么全局标准化会掉点CHB-MIT 不同受试者之间的幅值差异极大有的孩子背景波只有 20 μV有的发作时能到 500 μV。如果你把所有数据拼在一起做 z-score幅值小的受试者会被淹没。我一般按「受试者 文件」做个体化归一化即每个 edf 文件单独减均值除标准差再送模型。这一步在跨受试者泛化实验里尤其关键不做的话 AUC 可能差 5–10 个点。4. 避坑与排查CHB-MIT 实操中最容易翻车的 5 个点4.1 现象读 edf 报「channel not found」或通道数对不上原因不同记录通道命名不一致有的用FP1-F7有的用EEG FP1-REFMNE 解析时可能重命名或丢弃。解决读之前先用mne.io.read_raw_edf(path, preloadFalse)只读头信息打印raw.ch_names建立通道映射表统一重命名后再批量处理。4.2 现象发作标注秒数和实际波形对不上原因.seizures里的秒数是相对该 edf 文件起点但 summary 里可能给的是绝对时间或相对整个记录的时间两者混用就错位。解决统一以.seizures文件为准summary 只用来交叉验证。切片前用raw.times确认文件时长若标注超出时长说明解析错了。4.3 现象模型在训练集 AUC 很高换受试者就崩原因把同一受试者的数据同时放进训练和测试模型学到了个体特征而非癫痫通用特征。解决按受试者划分训练/测试集leave-one-subject-out这是 CHB-MIT 上最被认可的评估方式。别用随机划分那个数字好看但没意义。4.4 现象滤波后波形失真棘波被削平原因带通下限设太高比如 1 Hz 以上或用了 IIR 滤波导致相位失真。解决用零相位 FIRMNE 默认filtfilt模式下限不低于 0.5 Hz。如果关注棘波形态可以不做滤波改用模型端归一化。4.5 现象解压后文件名乱码或中文路径读不了原因rar 打包时用了非 UTF-8 编码或解压到中文目录。解决解压到纯英文路径用unrar x -e或 7z 指定编码。MNE 对中文路径支持不稳定这是玄学但真实存在别在这上面浪费时间。5. 进阶技巧用发作间期棘波做弱监督预训练如果你已经把基础链路跑通想进一步提升小样本下的检测性能可以试试用「发作间期棘波」interictal spikes做预训练。CHB-MIT 里除了发作标注很多记录在非发作段也存在大量棘波/尖波这些是癫痫样放电但未构成发作。它们数量远多于发作段可以作为弱监督信号。具体做法先用一个简单的阈值检测器比如带通 3–30 Hz 后做滑动窗能量超过基线 3 倍标准差判为候选棘波在非发作段自动挑出候选片段人工抽检确认后作为预训练正样本正常背景段作为负样本。预训练一个编码器后再用发作标注做微调。我实测在 chb01–chb10 上做 leave-one-subject-out预训练能把 AUC 从 0.82 提到 0.88 左右代价是要多花时间清洗候选棘波。验证方法上别只看 AUC建议同时看敏感度和误报率每小时误报次数。临床上误报太多没人用CHB-MIT 上常见目标是敏感度 90%、误报 1 次/小时。你可以写一个简单的评估脚本按受试者汇总混淆矩阵再算每小时误报。最后说个习惯我每次处理新一批 CHB-MIT 数据都会先跑一遍「数据体检」——统计每个文件的时长、通道数、采样率、发作段数量存成 CSV。这个表看起来不起眼但当你发现某个文件时长只有几分钟、或者发作标注为空时能第一时间定位问题而不是等模型训完才发现数据有坑。希望帮到你。本文还有配套的精品资源点击获取
