简介CHB-MIT数据集是由MIT与波士顿儿童医院联合发布的癫痫脑电研究资源面向生物医学信号处理、机器学习与模式识别方向的研究者及学生用于癫痫发作检测、分类与长期监测等课题。本压缩包共145个文件以141个.m脚本和4个.py脚本为主涵盖样本熵等特征提取与信号处理代码整体约159KB体积轻便便于快速部署到MATLAB或Python环境复现实验。数据集本身包含23名受试者的多通道脑电记录采用10-20国际系统标准采集19通道信号并附有发作类型与起止时间戳等临床标注可支撑复杂部分性、简单部分性及二次全面性发作的算法验证。目前已有837人学习下载适合作为深度学习模型训练、特征工程对比与跨学科脑电研究的入门与基准素材。1. 拆开 CHB-MIT 数据集癫痫脑电研究绕不开的公开资源如果你正在做癫痫自动检测、发作预测或者脑电信号分类大概率会在论文里反复看到一个名字CHB-MIT。它来自某儿童医院的长期脑电监测记录是当前少有的、带专家逐秒标注的公开头皮脑电数据集。很多新手第一次拿到CHB-MIT数据集.rar时会以为解压完就是一堆 CSV结果打开发现是几十个文件夹、上百个 EDF 文件每个文件里塞着 23 或 24 通道的连续信号采样率 256 Hz时间跨度动辄几小时。真正让人头疼的不是数据量而是怎么把「发作区间」从标注文件里对齐到信号上怎么切窗、怎么去伪迹、怎么避免训练集和测试集混入同一患者的记录。这份资源能解决的核心问题就一个给你一套真实、可复现、带金标准的癫痫脑电基准让你把检测算法跑起来、比出来。适合做信号处理、深度学习时序建模、医学 AI 落地的从业者也适合刚进实验室、需要快速搭起实验管线的同学。2. 先搞懂数据组织EDF、标注与患者划分2.1 文件结构与命名规律解压后你会看到chb01到chb24这样的患者目录编号不连续因为部分记录未公开。每个目录下是若干.edf文件命名类似chb01_03.edf其中01是患者号03是该患者的第几次记录。同时存在一个chb01-summary.txt里面按记录逐条列出发作起止时间、发作类型和通道信息。EDF 是欧洲数据格式头文件里存了通道名、采样率、物理量纲信号本身是 16 位整数。常见做法是用pyedflib或mne读取不要自己写二进制解析头文件里的增益和偏移量很容易算错。import mne import numpy as np # 读取单个 EDFpreloadTrue 把信号载入内存长记录慎用 raw mne.io.read_raw_edf(chb01/chb01_03.edf, preloadTrue) # 查看通道名和采样率 print(raw.ch_names) print(raw.info[sfreq]) # 取前 10 秒数据单位是伏特 data, times raw[:23, :2560]这段代码做了三件事加载 EDF、打印通道与采样率、截取前 10 秒。参数preloadTrue对几小时的记录会吃内存建议先读头信息再按需分段。raw[:23, :2560]里23是通道数2560是 10 秒乘 256 Hz。注意 CHB-MIT 部分记录是 23 通道部分是 24 通道多出的一个通常是 ECG 或参考建模前要统一通道集否则拼接会报维度错误。2.2 标注文件怎么读、怎么对齐summary.txt不是标准表格格式类似File Name: chb01_03.edf Number of Seizures in File: 1 Seizure Start Time: 2996 seconds Seizure End Time: 3036 seconds解析时按行扫描遇到File Name开新记录遇到Start和End就存区间。关键点时间是相对该文件起点的秒数不是绝对时间。对齐时用start * sfreq得到样本索引。发作前通常还要留一段「发作前状态」常见做法是取发作前 30 到 60 分钟作为 preictal发作间期随机采样作为 interictal。别把发作前几分钟当成正常样本那是典型的标签泄漏模型离线指标会虚高。def parse_summary(path): records {} cur None with open(path, r) as f: for line in f: line line.strip() if line.startswith(File Name:): cur line.split(:)[1].strip() records[cur] [] elif line.startswith(Seizure Start Time:): start int(line.split(:)[1].split()[0]) elif line.startswith(Seizure End Time:): end int(line.split(:)[1].split()[0]) records[cur].append((start, end)) return records逻辑说明用cur跟踪当前文件遇到起止时间就追加元组。参数上split()[0]是为了去掉seconds单位。如果某文件没有发作records[cur]保持空列表后续做二分类时要单独处理不能直接丢否则正负样本比例会失真。2.3 患者独立划分为什么是底线CHB-MIT 有 20 多名患者每人发作模式差异极大。如果随机切分所有窗口同一患者的相似片段会同时出现在训练和测试里准确率能到 99%但换一个人就崩。常见做法是留一患者交叉验证每次取一个患者做测试其余做训练。这样得到的指标才接近临床场景。代价是训练轮次多、耗时长但这是这个数据集上唯一能说服审稿人的划分方式。3. 从原始信号到模型输入预处理与切窗实战3.1 滤波、重参考与伪迹处理原始 EDF 里混着工频干扰、眼动和肌电。标准流程是先带通滤波通常保留 0.5 到 45 Hz再陷波去掉 50 Hz 或 60 Hz。重参考常用平均参考或 Cz 参考CHB-MIT 通道里没有严格 Cz可以用通道均值代替。伪迹方面幅度超过 200 微伏的片段建议标记而非直接删因为发作时棘波幅度本身就高一刀切会误伤。raw.filter(0.5, 45.0, fir_designfirwin) raw.notch_filter(50.0) # 平均重参考 raw.set_eeg_reference(average, projectionFalse) # 标记高幅度片段 data raw.get_data() bad np.where(np.abs(data) 200e-6) print(高幅度样本数:, len(bad[0]))filter的firwin设计比 IIR 稳定适合离线。notch_filter频率按采集地电网选国内数据常见 50 Hz。set_eeg_reference的projectionFalse表示直接改数据不再保留原参考。最后一步只是统计真正处理时可以按通道算方差把异常通道整段剔除。3.2 滑窗切分与标签生成发作检测通常切成 1 到 4 秒的窗发作预测用更长窗比如 30 秒。步长可以等于窗长做不重叠也可以半重叠增广。标签规则窗口与发作区间有交集就算正否则为负。注意边界窗口如果只重叠 0.1 秒也标正会引入噪声常见做法是要求重叠超过窗长一半。def make_windows(signal, sfreq, win_sec2, step_sec1): win int(win_sec * sfreq) step int(step_sec * sfreq) windows [] for start in range(0, signal.shape[1] - win, step): windows.append(signal[:, start:start win]) return np.stack(windows) def label_windows(n_win, sfreq, win_sec, seizures, step_sec1): labels np.zeros(n_win) for i in range(n_win): s i * step_sec e s win_sec for zs, ze in seizures: if min(e, ze) - max(s, zs) win_sec / 2: labels[i] 1 return labelsmake_windows返回形状(n_win, n_ch, win)直接喂给 CNN 或 LSTM。label_windows里min(e, ze) - max(s, zs)算重叠长度大于半窗才标正。参数step_sec控制增广倍数步长越小样本越多但相邻窗高度相关划分时要按患者整体分不能按窗随机分。3.3 归一化与通道统一不同患者、不同记录的幅度差异大逐窗 z-score 是稳妥选择。通道数不一致时取 23 个共同通道或者把多出的 ECG 丢掉。别用全局均值方差那会把发作时的幅度信息抹掉。逐窗归一化保留相对变化对检测任务更友好。def zscore_per_window(windows): mean windows.mean(axis(1, 2), keepdimsTrue) std windows.std(axis(1, 2), keepdimsTrue) 1e-8 return (windows - mean) / stdaxis(1,2)表示对每个窗的所有通道和时间点一起算keepdims保持维度便于广播。1e-8防止除零。这一步之后数据范围在零附近训练时学习率可以设大一点。4. 避坑与排查那些让实验翻车的细节4.1 现象模型准确率 99%换患者就 50%原因随机切分窗口导致同一患者片段泄漏。解决按患者划分用留一法或固定患者列表做测试集训练前打印两边患者编号确认无交集。4.2 现象读取 EDF 报通道数不匹配原因部分记录 23 通道部分 24 通道拼接时维度冲突。解决先扫描所有文件头确定共同通道集读取时用pick_channels只保留交集或在read_raw_edf后用raw.pick()统一。4.3 现象发作区间对不上标签全零原因summary.txt里的时间单位是秒但有人误当成毫秒或者把相对时间当绝对时间。解决解析后打印前几条起止时间和 EDF 时长对比确认end不超过文件总秒数。4.4 现象训练损失震荡不收敛原因未做逐窗归一化或滤波后仍有大漂移。解决加 z-score检查滤波截止频率是否过低导致信号失真必要时先做 1 Hz 高通去基线。4.5 现象内存爆掉几小时记录读不进来原因preloadTrue一次性载入全部信号。解决用mne.io.read_raw_edf不预载按时间段raw.crop后再load_data或者直接用pyedflib分块读。5. 进阶技巧把 CHB-MIT 用出论文级复现度想让结果可复现第一件事是固定随机种子并记录患者划分。第二报告指标别只给准确率发作检测正负极度不平衡敏感度和误报率每小时FA/h才是审稿人看的。第三做消融时控制变量通道数、窗长、滤波频段一次只动一个。我一般会先跑一个逻辑回归加手工特征作为基线比如方差、峰度、频带能量再上深度模型这样能判断增益到底来自网络还是特征。from sklearn.linear_model import LogisticRegression from sklearn.metrics import recall_score # 假设 X_train, y_train, X_test, y_test 已按患者划分 clf LogisticRegression(max_iter1000, class_weightbalanced) clf.fit(X_train, y_train) pred clf.predict(X_test) print(敏感度:, recall_score(y_test, pred))class_weightbalanced自动按类别频率加权缓解不平衡。max_iter调大避免不收敛。这个基线跑通后再换 CNN 或 Transformer对比才有意义。还有一个容易忽略的点EDF 头里的物理最大值和最小值决定了数字量到伏特的换算mne会自动处理但如果你用pyedflib手动读务必用physical_max / digital_max做缩放否则幅度全错滤波和归一化都会跟着废。从那以后我每次拿到新的 EDF 记录都强制先打印头信息、通道名、采样率和物理量纲再开始任何处理。希望帮到你。本文还有配套的精品资源点击获取