信息速览
这是一份很小的数据集,只有十二个受试者。它却在 2014 年做了一件影响很久的事:把「癫痫检测算法能到什么水平」这个问题,从一个内部讨论变成了一个公开可比的数字。
结果是 AUC 0.96。四个月里,几百支来自工业界、学术界和业余爱好者的队伍,在这批一秒一段的颅内脑电上反复刷分,前三名最后卡在 0.96287、0.95655、0.95643——差距只有千分之六。
故事如果到这里结束,它只是又一个 Kaggle 竞赛。真正的下半场在三年后:同一批获奖算法被拿去做样本外验证,第 2 名和第 3 名跌得比医院里现成的基础检测算法还差,接近随机水平。只有第 1 名撑住了。
**这就是为什么今天仍值得读这一页。**它的价值不在那 11.62 GB 数据,在于它用最干净的方式演示了一个反复出现的失效:在竞赛切分上 0.96,换个数据集就掉到 0.5。
§1 它是什么:一场竞赛、两份物种、三种采样率
1.1 一句话定义与四条边界
这是一份为算法竞赛而切片的颅内脑电检测数据集:12 个受试者(4 只自然发生癫痫的犬、8 名药物难治性癫痫患者),记录被统一切成 1 秒片段,训练片段标注为发作(Ictal)或发作间期(Interictal),评测指标是把「是否发作」和「是否在发作头 15 秒内」两个 AUC 取平均。
四条边界先划清楚:
- 不是头皮脑电。全部是颅内记录——电极在颅骨以内,直接贴着或插进皮层。信噪比比头皮脑电高一个量级,也意味着这份数据教出来的模型不能直接搬到消费级脑电设备上。
- **不是单一物种。**4 只犬 + 8 个人。这两组数据的电极数量、位置、采样率、参考方式全都不一样,无法拼成一张表。
- **不是连续记录。**你拿到的是切好的 1 秒片段,不是原始长程波形。片段之外的上下文(睡眠期、用药时点、发作前几小时)都不在里面。
- **不是开放许可。**Kaggle 页面写明
Subject to Competition Rules,要登录并接受规则才能下载。这不是 CC-BY,也不是 CC0。
把这四条合起来看,它对应的其实是一个非常具体的工程场景:**植入设备在人体内实时跑,只有十几导电极,需要在发作刚起时做出判断,而且绝不能靠观察波形演化来辅助。**竞赛数据集就是为了逼近这个场景而切的。理解这一点,后面所有看起来奇怪的设计——1 秒切片、距发作 1 小时、双 AUC——都能解释得通。
反过来说,如果你的任务是「给长程监测录像打标签」「做发作预测」「研究发作传播路径」,这份数据就不是为你的任务切的,硬用会处处别扭。
1.2 为什么会有这场竞赛:一个具体的工程需求
竞赛页面把动机写得很直白:美国有超过两百万人患反复发作的癫痫,其中五十万人试过多种药物仍控制不住。对这部分人,响应式神经刺激是可能的出路——植入设备实时读脑电,一旦识别到发作起始就立刻电刺激,把发作掐断在扩散之前。
问题在于「识别到发作起始」这一步做不好。页面原话是:现有商用响应式神经刺激设备里的检测算法为了不漏掉发作,被调得极度敏感,高假阳性率导致不必要的刺激。另一头,医生和研究者还得人工翻看大量连续脑电去找发作,有些患者的发作在波形上非常隐蔽。
所以竞赛设了两个并列目标,对应两种真实用途:
| 用途 | 关键指标 | 容忍什么 |
|---|---|---|
| 自动发作日记 | 准确率 | 延迟不重要,漏报和误报都不可接受 |
| 响应式刺激 | 检出延迟 | 可以容忍较高假阳性,因为刺激强度低于患者感知阈值 |
这两个目标在数学上冲突,竞赛的评测指标就是为调和它们设计的。
再往下拆一层,会看到更细的取舍。自动发作日记是给医生和患者看的,它关心的是「这次到底有没有发作、什么时候开始的」,一天错报两三次就会让人放弃使用,所以假阳性必须压住,延迟几十秒无所谓。响应式刺激关心的是「能不能在发作扩散前打断」,晚 5 秒可能就来不及了,所以宁可误刺激也不能漏,而刺激强度可以设到患者感知阈值以下来对冲。
这两个需求对同一段脑电会给出完全相反的最优决策。一段振幅刚开始抬升、还没有典型节律的波形:日记系统应该判「不是发作」(证据不足),刺激系统应该判「是发作」(宁错勿漏)。同一份数据,两个正确答案。
竞赛用双 AUC 把这个矛盾写进了目标函数,而不是让它藏在讨论里。这是这场竞赛设计上最讲究的一处。
1.3 时间线:四个月,两场
| 节点 | 日期 | 说明 |
|---|---|---|
| 开赛 | 2014-05-19 | Kaggle 开放报名与数据下载 |
| 首次提交截止 | 2014-08-12 | 未提交者失去排名资格 |
| 检测赛关闭 | 2014-08-20 | 检测阶段结束 |
| 预测赛开赛 | 2014-08-25 | 第二阶段,独立竞赛 |
| 预测赛关闭 | 2014-11-17 | 预测阶段结束 |
| 结果公布 | 2014-12-06 | AES 第 68 届年会(西雅图)主席研讨会 |
第二场是 American Epilepsy Society Seizure Prediction Challenge,任务从「这一段是不是发作」换成「这一段是不是发作前状态」。两场共用同一批机构与同一批原始记录,但切片方式不同。
1.4 规模数字:三个来源,三套口径
这是使用这份数据集时第一个必须处理的问题——「多少人参加」这个最基础的数字,官方来源之间并不一致。
| 来源 | 表述 | 口径 |
|---|---|---|
| Kaggle 竞赛页统计栏 | 386 报名者 / 241 参赛者 / 200 队伍 / 4,503 次提交 | 仅检测赛 |
| IEEE 信号处理学会通报 | 检测赛「吸引了 200 支队伍、241 名选手、4,503 条提交」 | 仅检测赛,与 Kaggle 一致 |
| Baldassano 等 2017(Brain)摘要 | 「两百支来自世界各地的数据科学家队伍」 | 仅检测赛 |
| Epilepsy Foundation 新闻稿 | 「504 支队伍参加了两场挑战赛」 | 两场之和 |
新闻稿的 504 常被二手文献误引为单场数字,甚至有研究生论文写成「预测赛有 504 支队伍」。实际检测赛是 200 队。
一个佐证:Kaggle 私有榜最终只显示 201 行(前 49 名单独列出,另 152 行折叠)。这与 200 队完全吻合,与 504 对不上。
引用建议:写检测赛就写「200 支队伍、241 名参赛者、386 人报名、4,503 次提交」,并注明来源是 Kaggle 统计栏。不要用 504。
1.5 奖金:8,000 美元和一个更大的池子
检测赛奖金总额 8,000 美元,由美国癫痫学会提供:第 1 名 5,000、第 2 名 2,000、第 3 名 1,000。
预测赛的奖金池单独为 25,000 美元。两场加起来也不到四万美元——与其说这是商业竞赛,不如说是一次由学术机构出题、用少量奖金换大量免费算法尝试的科研众包实验。
算一笔粗糙的账:检测赛 4,503 次提交,全部奖金 8,000 美元,平均每次提交的「成本」不到 1.8 美元。同等工作量如果走外包或课题合作,价格会高两个数量级。
这笔账也解释了为什么这个模式后来被大量复制:数据放在公共平台上,用排行榜换注意力,用奖金买一个体面的收尾。真正付出去的是数据,真正收回来的是数十种独立实现。
1.6 谁在办这件事
四个角色的分工很清楚:
| 角色 | 承担方 | 具体职责 |
|---|---|---|
| 出题与数据 | Mayo Clinic 癫痫与神经生理实验室(Benjamin Brinkmann) | 竞赛设计与原始数据 |
| 数据平台 | UPenn / ieeg.org(Zack Ives、Joost Wagenaar) | 数据托管与分发 |
| 承办 | Kaggle | 竞赛系统、排行榜、代码托管 |
| 出资 | NINDS、美国癫痫学会、Epilepsy Foundation | 奖金与项目资助 |
两位共同主席是 Brian Litt(UPenn 神经病学与生物工程教授、神经工程与治疗中心主任)和 Gregory Worrell(Mayo Clinic 神经病学、生物医学工程与生理学教授)。这个组合本身就是这场竞赛的性质说明:一个做神经工程、一个做临床电生理,中间隔着的那段距离,正是竞赛想填的。
Kaggle 侧的官方引用格式为:bbrinkm, sbaldassano, and Will Cukierski. UPenn and Mayo Clinic's Seizure Detection Challenge. 2014. Kaggle.
§2 数据构成:四只狗和八个人
2.1 受试者构成
| 维度 | 人类 | 犬类 |
|---|---|---|
| 数量 | 8 名 | 4 只 |
| 病因 | 药物难治性癫痫,颞叶与颞外叶 | 自然发生的癫痫 |
| 场景 | 癫痫手术评估期间的颅内监测 | 长期植入设备连续记录 |
| 电极 | 沿海马前后轴的深部电极 + 各部位硬膜下栅格 | 16 导硬膜下电极,每半球两条 4 触点条状电极,前后向 |
| 采样率 | 500 Hz 至 5,000 Hz(各受试者不同) | 400 Hz 固定 |
| 参考 | 颅外电极 | 组均值 |
| 通道数 | 随受试者不同 | 固定 16 |
这张表里最关键的一行是采样率。人类受试者内部就跨越了 10 倍——有人 500 Hz,有人 5,000 Hz。写成「采样率 500–5,000 Hz」看着像一句说明,实际意味着同一批数据里不同受试者的奈奎斯特频率差了 10 倍。500 Hz 采样最多看到 250 Hz,高频振荡(HFO,通常 80–500 Hz)在这部分记录里基本看不到;5,000 Hz 采样才够。
如果你要研究高频成分,先按采样率筛受试者,再谈其他。
2.2 犬类数据为什么值得认真对待
很多论文把犬类部分当作「补量」,这是低估了它。
犬类数据来自植入式设备,16 导电极位置固定、采样率固定、参考方式固定——这意味着四只狗的数据在结构上是可以直接比较的。人类那八个受试者反而做不到,因为电极方案是临床决定的,每人一套。
更实际的差别是记录时长。犬类设备是连续采集的,跨度以月计;人类是术前监测,通常几天到一两周。同样一次发作,犬类数据周围的上下文要长得多。
代价是跨物种迁移几乎不成立:狗的 16 导前后向条状电极,对应不到人的海马深部电极。
2.3 切片方式:1 秒,以及三个刻意的设计
每个片段固定 1 秒(data_length_sec 恒为 1)。切片规则有三处细节值得单独说:
- 训练集按时间顺序排列,测试集随机打乱。这条看起来只是文件排序,实际影响很大——按顺序排列意味着相邻文件在时间上连续,做特征时如果不小心用滑动窗口,会把同一秒的数据同时放进训练和验证,造成泄漏。
- **发作片段覆盖整次发作。**从起始到终止全部切出来,不抽样、不截断。
- **发作间期片段的总时长约等于该受试者的平均发作时长。**这是一个刻意做平衡的设计——让正负类在时间总量上大致相当,而不是按自然发生率(极低)采样。
2.4 那条「距发作一小时」的硬约束
发作间期片段的起点从全程随机抽取,但有一条限制:不得落在任何一次发作前后 1 小时以内。
这条规则同时是好消息和坏消息。
好消息是它保证了 interictal 段真的是「干净的非发作期」,不会混进发作后抑制或发作前预兆,标签噪声低。
坏消息更重要:**训练集里因此完全不存在「发作前后过渡态」这一整块数据。**模型学到的是「明确的发作」对「明确的非发作」,中间那段最难分辨的地带被系统性剔除了。于是模型在新数据上遇到发作前几十秒时,行为是未定义过的。
如果你想做连续片段上的检测,这条约束会直接变成误报来源。
2.5 文件结构:五个字段
每个 .mat 文件是一个五字段结构体:
| 字段 | 含义 | 容易踩的地方 |
|---|---|---|
data |
电极×时间矩阵 | 行是电极、列是采样点,不是转置的 |
data_length_sec |
本段时长 | 恒为 1 |
latency |
专家标记的发作起点到本段首点的秒数 | 仅 ictal 训练段有,interictal 与测试段没有 |
sampling_frequency |
每秒采样点数 | 可能是非整数,表示全记录均值,含缺失样本 |
channels |
电极名列表 | 各受试者命名体系不同,不可直接对齐 |
sampling_frequency 非整数这一点很隐蔽。它意味着原始记录里有丢包——设备不是完美地每 400 个点传一包。你要是按整数去算时间轴,几百秒后就会累积出可观的偏移。
文件命名规则很朴素:ictal_segment_N.mat、interictal_segment_N.mat、test_segment_N.mat。
2.6 体量与格式
数据包 clips.tar.gz,11.62 GB,另有 sampleSubmission.csv 与 sample_clip.mat.gz 两个辅助文件。
11.62 GB 在 2014 年不算小——竞赛讨论区里不止一个人提到「这份数据比 Kaggle 中位数竞赛大不少」。这也解释了为什么几乎所有前列方案都做了同一件事:下采样到 400 Hz 左右再算特征。
11.62 GB 听起来不大,但解压后是几万个独立 .mat 文件。逐个读文件的开销(磁盘寻道 + MATLAB 结构体解析)往往比计算本身还大。竞赛讨论区里不止一个人抱怨「光是读数据就花了四小时」。
一个实用做法:先一次性转成一个大的内存映射数组(.npy 或 HDF5),后续所有实验都从它读。这一步纯工程,但能把迭代速度提高一个数量级。
2.7 一个必须提前想清楚的问题:片段不能拼回记录
这一点在原始说明里没有强调,但影响很大。
训练集「按时间顺序排列」,听起来像是连续的——但它不是连续记录。文件编号连续只代表它们在时间上依次出现,中间可能跳过了任意长的间隔。测试集更是被打乱过。
所以下面这些操作都是错的:
- 把
ictal_segment_1..N首尾相接当作一次完整发作的连续波形 - 用相邻文件的差分做特征
- 假设
sample_segment_N与sample_segment_N+1在时间上相差 1 秒
想要连续波形,只有一条路:回到 ieeg.org 拿原始长程记录。这份竞赛包给不了你。
2.8 为什么只有 12 个受试者
这不是疏忽,是当时的现实约束。
颅内脑电的成本极高:需要开颅植入电极、住院监测数天到数周、专业团队判读。全球能提供这类数据的中心本就有限,能公开的更少。2014 年时,Mayo Clinic 的犬类植入记录和 UPenn 的术前监测记录加起来,能凑出 12 个受试者已经是相当可观的开放数据量。
对比一下:今天 TUSZ 有 675 个受试者,那也是近十年持续积累的结果。用今天的标准批评这份数据「太小」是不公平的,但用它训模型时心里必须有这个数。
§3 评测口径:一个被双重计分的指标
3.1 公式
竞赛指标叫 Mean Columnwise AUC:
score = 1/2 × (AUC_seizure + AUC_early)
两个 AUC 分别来自两列预测:
seizure:这个片段是发作的概率early:这个片段落在其所属发作前 15 秒内的概率
关键设计是:发作头 15 秒的片段被重复计分——它们既算进 AUC_seizure,又算进 AUC_early。竞赛页面的解释是「早检应对响应式刺激至关重要」,所以早段应该有额外权重。
3.2 这个指标教给模型什么
把它拆开看,模型学到的其实是三件事:
- 分清发作与非发作(
AUC_seizure) - 在发作内部,分清「刚开始」与「已经开始一会儿」(
AUC_early) - 由于早段被双计,把确定性更多地分配给早段是有利的
第 2 条常被忽略。它不是检测能力,是分期能力——判断一个已经确认的发作处在哪一阶段。这需要 latency 字段,而 latency 只在 ictal 训练段里有。
于是出现一个结构性问题:**你能学分期,但测试时没有 latency 可参照。**模型必须从波形本身推断「这是第几秒」,而训练时这个信息是以标签形式白送的。
3.3 私有榜的 85%
最终排名用约 85% 的测试数据计算(私有榜),公开榜只占 15%。这是 Kaggle 的常规做法,用来防止对公开榜过拟合。
但它也意味着:你看到的分数不是对全部测试集的表现,而是对其中一个约 85% 子集的表现。对于只有 12 个受试者的数据集,这 15% 的差别可能对应的就是一到两个受试者——换一批人,分数会明显动。
3.4 提交格式
一个 CSV,三列:clip, seizure, early。行如 Dog_1_test_segment_1.mat, 0.03, 0.01。
朴素得让人意外。这也说明竞赛关注的是判别本身,不是工程管线。
注意两列都是概率而不是硬标签。这意味着评测只看排序能力(AUC 的天然属性),不看你在哪个阈值上切。
3.5 一个常被忽略的后果:AUC 好不代表阈值好
AUC 只衡量排序,不告诉你阈值该设在哪。这对医疗场景是个要命的缺口。
一个 AUC 0.96 的模型,如果所有概率都挤在 0.45–0.55 之间,你根本找不到一个能用的阈值——切低了误报爆炸,切高了漏报爆炸。而竞赛榜单一栏只显示 AUC,没有任何一列反映这件事。
AES 2015 摘要里「所有获奖算法性能均下降」这句话,如果换成「敏感度 / 误报率」的表述,落差会比 AUC 看着更刺眼。原因很简单:AUC 对排序的微小扰动不敏感,而误报率对阈值的移动极其敏感。同一个模型,阈值挪一点,误报可能翻三倍。
所以如果你的目的是部署,看到 AUC 就要追问一句:**在敏感度 90% 的操作点上,每小时误报多少次?**这个数在竞赛里没有被报道过。
§4 最终成绩:千分之六的差距
4.1 榜单
| 名次 | 队伍 | 私有榜得分 | 备注 |
|---|---|---|---|
| 1 | Michael Hills | 0.96287 | 悉尼,软件工程师 |
| 2 | Olson and Mingle | 0.95655 | Eben Olson(Woodside, NY)、Damian Mingle(Nashville) |
| 3 | cdipsters | 0.95643 | Ishan Talukdar、Nathan Moore、Alex S.(均在加州伯克利) |
| 4 | alap | 0.95599 | |
| 5 | Fusion | 0.95436 | |
| 6 | Maineiac | 0.95238 | |
| 27 | Jonathan Street | 0.93558 | 赛前 8 天才加入 |
前三名之间差 0.00644,第 3 与第 4 名差 0.00044。
这个分布本身就传递了信息:前 30 名的分数密集挤在 0.934–0.963 之间。当所有人都在 0.95 附近时,排名靠前的意义不在于方法更优,更多在于集成调得更细。
4.2 第一名做了什么
Michael Hills 的代码公开在 MichaelHills/seizure-detection(MIT 许可),技术栈相当朴素:
- Python 2.7 + scikit-learn
- 每个受试者单独训练一个分类器(12 个模型)
- 特征:FFT + 时频相关
- 数据下采样到 400 Hz
- 硬件:2013 款 15 英寸 MacBook Pro,2.7 GHz i7,16 GB 内存
没有深度学习,没有 GPU,没有集成学习的花活。一台笔记本。
值得单独记一笔的是交叉验证方式:按整次发作切分(split on entire seizures),而不是按片段随机切。这个选择避免了一个典型错误——如果按片段随机切,同一次发作的相邻秒会同时出现在训练和验证集里,验证分数会虚高。
这一条是整套方案里最经得起时间检验的部分。
4.3 前十名的普遍形态
从讨论区和技术帖可以还原出一个相当一致的画像:
| 做法 | 普及程度 | 代表 |
|---|---|---|
| 下采样到 400 Hz 左右 | 几乎全部 | 多位选手在讨论区提到 |
| 频段功率(δ/θ/α/β/low-γ/high-γ) | 几乎全部 | 第 6 名明确列出六段 |
| 频段功率两两比值 | 多数 | 第 6 名 |
| 通道间相关矩阵上三角 | 多数 | 第 6、27 名 |
| 集成(RF + GBM + glmnet 等) | 多数 | 第 6、29 名 |
| 时域统计量(方差、极差、分位数) | 多数 | 第 6、27、33 名 |
| 深度学习 | 几乎没有 | 前十未见 |
竞赛讨论区里有一句被反复认同的总结:这是一场特征工程比赛。Mike Kim 的原话是「把能想到的特征变换一股脑扔进去」,最终排到第 29 名。
第 27 名 Jonathan Street 的赛后记录更说明问题:他用 sklearn pipeline,只用了方差、中位数、FFT 六分箱三个极简特征,没有调任何超参,拿了 0.93558。在 2014 年的这份数据上,简单特征的边际收益已经很高,复杂模型的边际收益很低。
顺带一提他的时间线:赛前 8 天才决定参赛,最终第 27 名。**八天做到这个分数,说明这道题的「入场门槛」很低。**门槛低对吸引参与者是好事,对判别方法优劣是坏事——它意味着前排的分数差异很难归因到方法本身。
4.4 一个真实存在的隐忧:第 1 名用了 12 个独立模型
回到第 1 名的方案。它「每个受试者单独训练一个分类器」——12 个受试者就是 12 个模型,各用各的特征分布、各用各的阈值。
这个方法在竞赛里完全正确,因为它避开了跨受试者的分布差异。但它在两个方向上有代价:
**一是无法冷启动。**新患者来了,没有他的数据,这套模型一个都用不上。你需要先给他记足够长的时间,才能训一个专属模型。
**二是 12 个模型意味着极小的样本。**如果某个受试者只有几次发作,那么给这一个人训的模型实际上是在个位数样本上拟合。
这恰好对应了后来论文的走向:Baldassano 等把「个人化检测」当作结论来写,而不是当作局限——论文摘要最后一句话是「sets a new reproducible benchmark for personalized seizure detection」。个人化既是优势也是限制,取决于你问的是哪个问题。
4.5 为什么这种「简单就够」是危险的信号
把 4.3 和 4.1 放一起看,会发现一件不太舒服的事:如果一个三特征方案能到 0.935,而第一名 0.963,那么这 0.028 的差距大概率不是来自「更好的癫痫理解」,而是来自「更好的调参」。
这为后面的故事埋了伏笔。
§5 下半场:换个数据集,一半的赢家掉队
5.1 赛后验证做了什么
赛事结束后,主办方做了一个当时并不常见的动作:把前三名算法拿去做样本外验证。
Baldassano 等 2017 年发表在 Brain(140(10):2673–2680,doi:10.1093/brain/awx098),验证数据包括两类:
- UPenn 与 Mayo 的标准临床颅内记录(未参与原竞赛)
- NeuroVista 植入式癫痫预警系统的多年连续移动记录
第二类尤其狠。NeuroVista 是植入人体的设备,记录的是患者在真实生活中的连续脑电,不是病床上的术前监测。这是完全不同的域。
5.2 两套互相矛盾的结论
这里有本页最需要小心的地方:同一批验证,两个官方摘要给出的结论方向不同。
版本 A(AES 2016 摘要,Baldassano 等):
前三名在竞赛中 AUC 分别为 0.963 / 0.957 / 0.956;在同一验证研究中分别为 0.962 / 0.948 / 0.958,「similar performance」。
版本 B(AES 2015 摘要,Blevins 等):
所有获奖算法在新数据集上性能均下降。第 1 名最稳健,在新验证集上平均 AUC 0.8297,优于标准检测算法的 0.6258;第 2 名和第 3 名跌到标准算法之下,接近随机水平。
0.8297 与 0.962 相差近 0.13。这两个数字不可能来自同一份验证集。
合理解释:两篇摘要对应的是不同阶段的验证。2015 年那次用的是 ieeg.org 上跨机构的、与训练集分布差异更大的长程数据(更接近真实临床边界),2016 年那次用的是更接近竞赛分布的同机构数据。也就是说,验证集越接近真实使用场景,掉得越多。
这个解释本身比任何一个数字都更重要——它说明「性能下降多少」不是一个固定值,而取决于你把测试集设得多难。
5.3 第 2 名为什么掉得最狠
AES 2015 摘要给了一条很具体的线索:
第 2 名算法在扩展到更大数据集时,因计算负载过重而难以运行。
这句话把「泛化失败」拆成了两个不同的失败:
- 第 3 名是判别失败——算得出来,但算错了
- 第 2 名是工程失败——算不出来
后者常被忽略。一个在 12 个受试者、几百小时数据上跑得动的方案,放到几十个受试者、上千小时上可能直接跑不完。论文里报 AUC 的时候不会提这件事。
5.4 一个必须说的话:这不是「Kaggle 没用」
把这段故事讲成「竞赛算法都是刷分」,是过度简化。
事实是:第 1 名在两次验证中都站住了,而且在更难的验证集上仍有 0.8297,显著优于标准算法的 0.6258。第 1 名的方案是每受试者单独建模 + 按整次发作切分验证——这两个选择恰好都是抗过拟合的。
真正被证伪的不是「众包」,而是「竞赛榜单一律可信」。前十里有两支队伍的方法换数据就失效,另外八支没被测过,我们不知道。
2014 年 12 月 Epilepsy Foundation 新闻稿引用 Brian Litt 的话说:此前十五年研究才勉强做出优于随机的预测,而竞赛几个月内「毫无癫痫专业知识的队伍」就一致达到 84%。这句话里的 84% 是预测赛的准确率口径,与检测赛的 AUC 0.96 不是一回事,两者不可直接比较。
5.5 三个可以从这件事里直接拿走的判断
抛开具体数字,这段赛后验证给出了三条相当硬的结论:
**第一,榜单前三不等于方法前三。**第 2、3 名的方案在竞赛里只比第 1 名低千分之六,在泛化上却差了一整个档次(掉到标准算法之下)。排名的分辨率远高于能力的分辨率。
**第二,稳健性和分数不完全相关。**第 1 名之所以站得住,很可能不是因为它的特征更聪明,而是因为它的验证协议更严格(按整次发作切分)。方法上的纪律性比方法上的复杂度更能预测泛化。
**第三,验证集的选择决定了结论的方向。**同一批算法,在「同机构、近分布」的验证集上是 0.962,在「跨机构、跨设备、真实生活」的验证集上是 0.830。报哪个数字,取决于你想让读者相信什么。
§6 常见误读与引用陷阱
| 常见说法 | 实际情况 | 证据 |
|---|---|---|
| 「504 支队伍参加了这场竞赛」 | 504 是检测 + 预测两场之和;检测赛实为 200 队 / 241 人 / 386 报名 | Kaggle 统计栏;IEEE SPS 通报 |
| 「数据里有 4 只狗 8 个人,所以是 12 个受试者的统一数据集」 | 两组数据电极、采样率、参考方式全不同,不是同一张表,必须分轨处理 | Kaggle 数据页 |
| 「采样率 500–5,000 Hz」当成一句无关紧要的说明 | 同一批数据内奈奎斯特频率差 10 倍,高频研究必须先按采样率筛受试者 | Kaggle 数据页 |
| 「AUC 0.96,说明算法接近可用」 | 赛后样本外验证中第 2、3 名跌至标准算法之下;第 1 名从 0.963 掉到 0.8297 | AES 2015 摘要 |
| 「竞赛数据和真实临床数据分布一致」 | interictal 段被硬性限制在距发作 1 小时以外,训练集不含发作前后过渡态 | Kaggle 数据页 |
| 「这是个开放数据集」 | 许可为 Subject to Competition Rules,须登录接受规则;非 CC-BY / CC0 |
Kaggle 数据页 |
| 「1 秒片段可以拼回连续记录」 | 训练集按时间顺序、测试集随机打乱;片段之间不保证时间连续,拼接会引入断点 | Kaggle 数据页 |
| 「latency 字段可以用来做实时检测」 | latency 只在 ictal 训练段存在,测试段没有;它是标签,不是特征 | 文件结构说明 |
| 「私有榜分数可以直接和别的划分比较」 | 私有榜用约 85% 测试数据;12 个受试者时,15% 的差异可能就是一两个人 | Kaggle 榜单说明 |
| 「获奖代码可以直接用在今天的任务上」 | 第 1 名仓库用 Python 2.7 + 2018 年版本依赖;作者本人已无原始数据,仅用 sample_clip.mat 复测 |
GitHub 仓库 README |
§7 怎么用:六步
7.0 八个高频坑点
在进入步骤之前,先把踩过的坑列清楚。这八个都来自这份数据特有的结构,不是通用工程建议。
坑点 1:把 12 个受试者当成一个整体做随机切分
- 问题:直接用
train_test_split按片段随机切,验证集 AUC 会虚高到 0.99 以上。 - 症状:验证分数极高,换一个新受试者就崩到 0.6。
- 解决:用
GroupKFold,group 取「受试者 + 发作编号」;interictal 段各自成群。Kaggle 上第 1 名正是这么做的。 - 参考:第 1 名仓库
cross_validation.py明确写「Cross-validation set is obtained by splitting on entire seizures」。
坑点 2:把犬与人合并训练
- 问题:16 导 400 Hz 组均值参考 vs 可变导联 500–5,000 Hz 颅外参考,特征空间不重叠。
- 症状:模型在犬上 0.95,在人上 0.55;或反之。
- 解决:分物种建两套管线,输出分开报。若一定要迁移,只迁移「方法」不迁移「权重」。
- 参考:Kaggle 数据页对两组电极方案的描述完全并行,未提供任何对齐说明。
坑点 3:忽略 sampling_frequency 可能是非整数
- 问题:按整数 400 算时间轴,几十秒后累积偏移。
- 症状:频谱峰值位置随片段序号漂移。
- 解决:一律用
float(m.sampling_frequency)做分母,不要int()。 - 参考:数据页原文「Non-integer values represent an average across the full data record and may reflect missing EEG samples」。
坑点 4:把 latency 当特征喂进模型
- 问题:
latency是标签(发作起点到本段首点的秒数),只在 ictal 训练段存在。 - 症状:训练 AUC 0.99,测试时该特征全缺失,模型直接失效。
- 解决:只用它派生
early = latency < 15这一列,绝不入特征矩阵。 - 参考:文件结构说明中明确括注「in ictal training segments only」。
坑点 5:试图把 1 秒片段拼成连续波形
- 问题:训练集按时间排序但不连续,测试集被打乱。
- 症状:拼接后出现周期性阶跃,做差分特征时爆炸。
- 解决:放弃拼接。需要连续波形就回 ieeg.org 拿原始长程记录。
- 参考:数据页「Training data are arranged sequentially while testing data are in random order」——「sequentially」不等于「contiguously」。
坑点 6:以为 data 是时间×电极
- 问题:MATLAB 里矩阵转置很容易搞反,而
data是电极×时间。 - 症状:特征维度变成采样点数,或者通道相关算成了时间相关。
- 解决:读进来先
assert data.shape[0] == len(channels)。 - 参考:数据页「a matrix of EEG sample values arranged row x column as electrode x time」。
坑点 7:以为数据是开放许可,直接再分发
- 问题:Kaggle 许可是
Subject to Competition Rules,不是 CC-BY。 - 症状:把处理后的数据上传到公开仓库,构成违约。
- 解决:只分发代码与元数据;数据让用户自己去 Kaggle / ieeg.org 取。
- 参考:Kaggle 数据页 License 栏。
坑点 8:用私有榜分数与他人论文对比
- 问题:私有榜是约 85% 测试子集;别人的论文可能用全测试集或自己的划分。
- 症状:两篇论文都写「在该数据集上 AUC 0.96」,实际不可比。
- 解决:报自己的划分协议(受试者级 / 发作级 / 片段级),并说明是否用官方测试集。
- 参考:Kaggle 排行榜页面「The private leaderboard is calculated with approximately 85% of the test data」。
7.1 第一步:先决定你要不要用这份数据
适合:方法学复现、泛化性研究、小样本域适应、竞赛基线对比。
不适合:训练生产级检测模型(12 个受试者不够)、跨物种迁移研究(结构不兼容)、需要连续上下文的预测任务。
如果只是想要「癫痫检测数据」,2020 年之后的 TUSZ、CHB-MIT 扩展版在规模和标注规范上都更合适。这份数据的独特价值是它的失败方式被公开记录过——这种材料很少见。
7.2 第二步:按物种分轨,不要合并
把犬与人当成两个独立子任务:
# 目录结构决定了必须分轨
species = {
'canine': ['Dog_1', 'Dog_2', 'Dog_3', 'Dog_4'],
'human': ['Patient_1', ..., 'Patient_8'],
}
不要试图做一个跨物种的统一特征空间。犬是 16 导固定位置 400 Hz 组均值参考;人是可变通道数、500–5,000 Hz、颅外参考。通道既对不上名字,也对不上解剖位置。
7.3 第三步:把采样率当成一等筛选条件
import scipy.io as sio
import glob, collections
rates = collections.Counter()
for f in glob.glob('seizure-data/*/*_segment_1.mat'):
m = sio.loadmat(f, squeeze_me=True, struct_as_record=False)['segment']
rates[round(float(m.sampling_frequency))] += 1
print(rates) # 先看清每个受试者到底是什么采样率
只要涉及高频成分(>100 Hz),就把 500 Hz 的受试者单独拿出去。不要用插值把它们拉到 5,000 Hz——上采样不产生信息,只会让频谱图看起来更漂亮。
7.4 第四步:切分必须按发作,不能按片段
这是第 1 名方案里最值得抄的一条。
# 错误做法:按片段随机切
# train, val = train_test_split(clips, test_size=0.2) # 相邻秒会串味
# 正确做法:按整次发作分组切
from sklearn.model_selection import GroupKFold
# group 用「受试者 + 发作编号」;interictal 段各自成组
cv = GroupKFold(n_splits=5)
for tr, va in cv.split(X, y, groups=seizure_groups):
...
1 秒片段之间的自相关极强。随机切分会让验证集分数虚高好几个百分点,而且虚高的幅度恰好掩盖了真正的泛化问题。
7.5 第五步:latency 怎么处理
latency 只在 ictal 训练段有,含义是「专家标记的发作起点到本段首点的秒数」。
两个用法,别混:
- 当标签用(正确):派生出
early = (latency < 15)这一列,对应竞赛的AUC_early。 - 当特征用(错误):测试时没有 latency,训练时喂进去等于给模型开了上帝视角。
7.6 第六步:报数时写清四件事
任何基于这份数据的结果,至少要交代:
- 用了犬、人,还是两者
- 受试者数(分母是 4、8 还是 12)
- 切分是按片段还是按发作
- 报的是竞赛口径(双 AUC 均值)还是单一 AUC
这四条差任何一条,两个数字就没有可比性。
7.7 一个可以照抄的最小可运行骨架
下面这段不含任何建模技巧,只是把「读数据 → 建组 → 按发作切分 → 记录」这条最小链路跑通。它不追求分数,追求的是不犯结构性错误。
import glob, os, numpy as np, scipy.io as sio
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GroupKFold
from sklearn.metrics import roc_auc_score
def load_clip(path):
m = sio.loadmat(path, squeeze_me=True, struct_as_record=False)['segment']
return m.data, float(m.sampling_frequency), list(np.atleast_1d(m.channels))
def simple_feats(x, fs):
# x: 电极 x 采样点
f = [x.var(axis=1), x.max(axis=1) - x.min(axis=1),
np.percentile(x, 95, axis=1) - np.percentile(x, 5, axis=1)]
# 极简频段功率
spec = np.abs(np.fft.rfft(x, axis=1)) ** 2
freqs = np.fft.rfftfreq(x.shape[1], 1.0 / fs)
for lo, hi in [(0.5, 4), (4, 8), (8, 14), (14, 30)]:
band = (freqs >= lo) & (freqs < hi)
f.append(spec[:, band].mean(axis=1))
return np.concatenate(f)
X, y, groups = [], [], []
for subj_dir in sorted(glob.glob('seizure-data/*')):
subj = os.path.basename(subj_dir)
# ictal 段:同一受试者的不同发作,group 用文件名里的编号
for i, p in enumerate(sorted(glob.glob(f'{subj_dir}/ictal_segment_*.mat'))):
x, fs, _ = load_clip(p)
X.append(simple_feats(x, fs)); y.append(1); groups.append(f'{subj}-ictal-{i}')
for i, p in enumerate(sorted(glob.glob(f'{subj_dir}/interictal_segment_*.mat'))):
x, fs, _ = load_clip(p)
X.append(simple_feats(x, fs)); y.append(0); groups.append(f'{subj}-inter-{i}')
X, y, groups = np.array(X), np.array(y), np.array(groups)
for tr, va in GroupKFold(n_splits=5).split(X, y, groups):
clf = RandomForestClassifier(n_estimators=300, n_jobs=-1, random_state=0)
clf.fit(X[tr], y[tr])
print('val AUC =', roc_auc_score(y[va], clf.predict_proba(X[va])[:, 1]))
这份骨架能跑出什么分数不重要(大概率 0.90 上下)。重要的是它把三件事摆对了:**按受试者兼按发作分组的切分、按受试者自己的采样率算频率、不碰测试集。**在这份数据上,摆对这三件事比换模型有用得多。
§8 它在生态里的位置
8.1 与 TUSZ 的分工
| 维度 | UPenn-Mayo(2014) | TUSZ(2016 起,现行 v2.0.6) |
|---|---|---|
| 受试者 | 12 | 675 |
| 记录时长 | 数百小时 | 1,476 小时 |
| 片段 | 1 秒,预切 | 长程连续,附标注 |
| 任务 | 二分类 | 事件级检测(SzCORE 口径) |
| 评测 | 双 AUC 均值 | 事件级敏感度 / 误报率 / F1 |
| 许可 | 竞赛规则 | CC-BY-4.0 |
TUSZ 是这份数据的直系后继,但它换了评测哲学:从「片段分类」转向「事件检测」。SzCORE 2025 挑战赛里冠军算法的事件级 F1 只有 43%——这个数字和 UPenn-Mayo 的 AUC 0.96 放在一起看,才能理解「片段级指标好看」和「事件级能用于临床」之间隔着多远。
8.2 与 CHB-MIT 的对比
CHB-MIT 是头皮脑电,23 名儿科患者,长程连续。它的价值是可及性(PhysioNet 开放下载)和长期被用作基线。
UPenn-Mayo 的优势是颅内信号质量,劣势是许可封闭、受试者极少。做基线对比时用 CHB-MIT,做信号质量研究时用 UPenn-Mayo。
8.3 与 EPILEPSIAE 的对比
EPILEPSIAE 是欧洲的商业分档数据库,275 名患者、四万五千小时、四段时刻标注。它回答的是「发作预测需要多精细的时间标注」,UPenn-Mayo 回答的是「检测算法在公开可比的口径下能到多少」。
两者都不免费,但 EPILEPSIAE 是明码标价,UPenn-Mayo 是竞赛规则——后者更麻烦,因为它连一个价目表都没有,你得先找到 ieeg.org 的申请入口。
8.4 一个容易被忽略的位置:数据共享基础设施的试验田
这场竞赛的另一个身份是 ieeg.org 的第一次大规模实战。
International Epilepsy Electrophysiology Portal 由 NINDS 资助、UPenn 的 Zack Ives 与 Joost Wagenaar 运营。竞赛用的数据从它那里出,验证用的额外数据也从它那里出。竞赛新闻稿里明确写:数据「正由 NINDS、宾夕法尼亚大学和梅奥诊所为全世界研究者免费提供」。
**换句话说,这场 Kaggle 竞赛是「把院内数据搬到公共平台」这条路线的一次公开展示。**今天 ieeg.org 上的长程数据、NEMAR 对 MEG/EEG/iEEG 的整合,某种程度上都是这条线的延伸。
值得对照的是同期的另一条路线。CHB-MIT 走的是 PhysioNet 式开放下载——不需要注册、不需要签协议、没有竞赛规则。UPenn-Mayo 走的是竞赛式封闭分发。两条路线在 2014 年并行存在,今天看,开放下载那条在「被引用次数」上明显赢了,竞赛那条在「推动算法基准」上更有效。
这不是二选一。**PhysioNet 提供的是原料,竞赛提供的是靶子。**原料可以长期被反复取用,靶子会过期。UPenn-Mayo 的问题是它作为靶子已经过期了(榜单冻结在 2014 年),但作为原料又没完全开放。
8.5 后续影响:它把「事件级评测」这件事推上了日程
竞赛用的是片段级 AUC。这个口径有个天然缺陷:临床上没人关心「这一秒是不是发作」,临床关心的是「这次发作有没有被检出、误报了几次」。
Baldassano 等人的后续工作把这个问题暴露出来之后,癫痫检测领域的评测重心逐步从片段级转向事件级。今天 SzCORE 挑战赛用的那套规则——最小重叠任意、前容差 30 秒、后容差 60 秒、间隔小于 90 秒合并、超过 5 分钟拆分、先按受试者算再平均——本质上都是在回答「片段级指标掩盖了什么」。
从这个角度看,UPenn-Mayo 竞赛的历史位置不是「一个成功的竞赛」,而是**「一次让整个领域意识到评测口径很重要的竞赛」**。它自己的分数已经不重要了,它引出的那个问题还很重要。
§9 一个更宽的问题:我们到底在测什么
9.1 三个数字,三种含义
把这份数据集上的三个数字摆在一起:
| 数字 | 含义 | 离临床有多远 |
|---|---|---|
| AUC 0.96287 | 竞赛私有榜,12 受试者,1 秒片段 | 最近,但最不真实 |
| AUC 0.8297 | 样本外,同机构 + NeuroVista 长程 | 中 |
| AUC 0.6258 | 医院现成的基础检测算法 | 最远,但最真实 |
第 1 名从 0.963 到 0.830 掉了 0.133。这 0.133 就是「竞赛切分」与「真实使用」之间的差价。
9.2 为什么这个差价很难消除
三个结构性原因:
- **受试者太少。**12 个人 + 4 只狗,任何「留一受试者交叉验证」的方差都大到难以解读。
- **片段太短。**1 秒切断了发作的演化过程,而临床判断发作靠的恰恰是演化。
- 过渡态被剔除。「距发作 1 小时」这条约束让训练集变干净,也让模型在最需要它的地方没有训练样本。
这三条都不是「换个更强的模型」能解决的。
9.3 一个可以推广的检查清单
把这份数据集的经验抽象出来,任何医疗时序数据集都可以用这五个问题自查:
| 问题 | UPenn-Mayo 的答案 | 你的数据集呢 |
|---|---|---|
| 片段之间的切分是否按「事件」而不是按「片段」? | 竞赛没做,第 1 名自己做了 | ? |
| 负样本是否被「清洗」到远离正样本? | 是,距发作 ≥1 小时 | ? |
| 评测指标是否只看排序、不看阈值? | 是,纯 AUC | ? |
| 测试集是否与训练集同机构、同设备? | 是 | ? |
| 上榜单的分数有没有样本外复核? | 有,且结论更难堪 | ? |
五个问题里,只要有两个答「是」,你就知道自己拿到的数字是「竞赛数字」而不是「临床数字」。两者的差距,在 UPenn-Mayo 上是 0.13 个 AUC。
9.4 对 AI-ready 的真实含义
如果要用一句话概括这份数据集对今天的意义:
它是一份关于「指标可信度」的反面教材,而不是一份关于「癫痫检测」的正面试卷。
在把它当作训练数据之前,先把它当作一次校准:你的评测设计里,有没有一条「距发作 1 小时」的隐性约束,正把最难的部分悄悄删掉?
§10 术语、核对与评估
10.1 关键数字一览
| 项 | 值 | 来源 |
|---|---|---|
| 竞赛期 | 2014-05-19 至 2014-08-20 | Kaggle |
| 报名 / 参赛 / 队伍 | 386 / 241 / 200 | Kaggle 统计栏 |
| 提交数 | 4,503 | Kaggle 统计栏 |
| 私有榜行数 | 201 | Kaggle 排行榜 |
| 奖金 | 8,000 美元(5,000 / 2,000 / 1,000) | Kaggle |
| 受试者 | 4 犬 + 8 人 | Kaggle 数据页 |
| 片段长度 | 1 秒 | Kaggle 数据页 |
| 人类采样率 | 500–5,000 Hz | Kaggle 数据页 |
| 犬类采样率 | 400 Hz | Kaggle 数据页 |
| 犬类电极 | 16 导硬膜下,每半球两条 4 触点 | Kaggle 数据页 |
| interictal 间隔约束 | 距任一次发作 ≥1 小时 | Kaggle 数据页 |
| 数据量 | 11.62 GB | Kaggle 数据页 |
| 第 1 名 | 0.96287(Michael Hills) | Kaggle 排行榜 |
| 第 2 名 | 0.95655(Olson and Mingle) | Kaggle 排行榜 |
| 第 3 名 | 0.95643(cdipsters) | Kaggle 排行榜 |
| 第 1 名样本外 | 0.8297 | AES 2015 摘要 |
| 标准算法 | 0.6258 | AES 2015 摘要 |
| 赛后论文 | Brain 2017, 140(10):2673–2680 | doi:10.1093/brain/awx098 |
10.2 可立即验证的疑问
- **第 2、3 名算法具体掉到多少?**AES 2015 只说「接近随机」,未给数字。
- **两份验证集分别包含多少受试者?**两篇摘要都未交代,这直接决定了 0.8297 与 0.962 的可比性。
- **NeuroVista 记录的具体时长与受试者数?**未公开。
- **12 个受试者各自贡献了多少次发作?**数据页未给明细。
- **
latency的专家标记是否双人复核?**未见说明。 - **犬类的 4 只狗与预测赛的 5 只狗为何不一致?**两场竞赛的受试者构成不同,原因未说明。
- **竞赛数据的长期归档地址在哪?**ieeg.org 的对应集合编号未在论文中给出。
- **第 1 名方案在 5,000 Hz 受试者上表现如何?**其方案统一降到 400 Hz,理论上放弃了高频信息。
10.3 DAIMS 评估
| DAIMS 维度 | 评分 | 说明 |
|---|---|---|
| 数据可发现性 | 高 | Kaggle 页面长期存活,论文与新闻稿齐全 |
| 数据可访问性 | 低 | 需接受竞赛规则;赛后入口依赖 ieeg.org 申请 |
| 数据可用性 | 中 | 五字段结构清晰,但预切片、无法重建长程上下文 |
| 数据互操作性 | 低 | MATLAB .mat 非标准格式,通道命名不统一,非 BIDS |
| 数据可重用性 | 中 | 代码全公开(前三名 + 大量讨论区方案),但受试者数与许可限制复现价值 |
| 数据质量 | 中高 | 专家标注,但仅有 12 受试者,且无标注一致性统计 |
| 数据治理 | 中 | 规则明确,但无统一价目与长期归档承诺 |
| 元数据完备性 | 低 | 仅有通道名与采样率,缺年龄、性别、用药、病理 |
| 文档质量 | 中高 | Kaggle 数据页说明清楚,赛后论文补充验证 |
| 基准完备性 | 高 | 这是它最强的一项:有完整排行榜 + 公开代码 + 独立赛后验证 |
10.4 这份数据为什么没有被后继竞赛取代
一个自然的问题是:既然 2014 年的竞赛暴露了这么多问题,为什么没有一场「更好的 UPenn-Mayo」出现?
答案是评测哲学换了赛道,而不是把同一道题重做一遍。
2014 年的问题是「能否在 1 秒片段上分开发作与非发作」。这个问题的答案已经被给出(能,AUC 0.96),而且给得太干脆,没有继续竞争的空间。后来的工作转向了两个更有临床意义的问题:
- 事件级检测:一段长程记录里,发作被完整检出了几次,误报了几次(SzCORE 路线)
- 发作预测:能不能在发作前若干分钟给出预警(EPILEPSIAE、NeuroVista 路线)
这两个问题都无法用「1 秒片段二分类」来回答,所以竞赛形式必须换,数据也必须换。UPenn-Mayo 不是被超越,是被绕过了。
这对使用者的直接含义是:如果你今天下载这份数据是为了「做一个更好的检测器」,那你优化的目标在 2020 年之后已经不再是主流指标了。先想清楚你要回答哪个问题,再决定用哪份数据。
10.5 一句话结论
**这不是一份拿来训模型的癫痫数据,而是一份拿来校准评测观的癫痫数据。**它的 0.96 和它的 0.83 之间那段落差,比它本身更有教学价值。
相关数据集导航
以下为站内 AI-Ready 数据集百科中与本词条共享多个主题标签的相关数据集,按相关度降序排列:
- tuab — 共享标签:生理信号 / 脑电信号 / 癫痫 / 评测基准
- tusz — 共享标签:生理信号 / 脑电信号 / 癫痫 / 评测基准
- tuev — 共享标签:生理信号 / 脑电信号 / 癫痫 / 评测基准
- capsleep — 共享标签:生理信号 / 脑电信号 / 癫痫 / 评测基准
- cinc-2018-sleep-arousal — 共享标签:生理信号 / 脑电信号 / 挑战赛数据集 / 评测基准
- epilepsiae — 共享标签:生理信号 / 脑电信号 / 癫痫
- siena-scalp-eeg — 共享标签:生理信号 / 脑电信号 / 癫痫
- bigp3bci — 共享标签:生理信号 / 脑电信号 / 评测基准
- neurocritical-pediatric — 共享标签:生理信号 / 脑电信号 / 评测基准
- eeg-power-anesthesia — 共享标签:生理信号 / 脑电信号 / 评测基准
导航说明:本章节由全站统一标签体系自动计算生成(标签重合度算法),双向可达;点击链接可跳转至对应数据集词条。

