UPenn-Mayo — 颅内脑电癫痫检测挑战赛

十二个受试者、四只狗、八个人、一秒一段。它用一场 Kaggle 竞赛把癫痫检测的算法门槛一次性抬到 AUC 0.96,也留下了一个更难堪的后续:前三名里有两个换一份数据就跌回随机水平

来源 美国宾夕法尼亚大学(UPenn)神经工程与治疗中心、梅奥诊所(Mayo Clinic)癫痫与神经生理实验室;International Epilepsy Electrophysiology Portal(ieeg.org);NINDS 与美国癫痫学会(AES)资助;Kaggle 承办发布时间: 2026-10-21最后更新: 2026-10-21 阅读 23
UPenn-Mayo — 颅内脑电癫痫检测挑战赛

信息速览

数据集名称UPenn-Mayo — 颅内脑电癫痫检测挑战赛
数据类型颅内脑电,1 秒片段,犬与人类双物种,发作检测,早期检出标签,11.62 GB,竞赛受限许可
规模12
接入方式美国宾夕法尼亚大学(UPenn)神经工程与治疗中心、梅奥诊所(Mayo Clinic)癫痫与神经生理实验室;International Epilepsy Electrophysiology Portal(ieeg.org);NINDS 与美国癫痫学会(AES)资助;Kaggle 承办
AI 就绪度

这是一份很小的数据集,只有十二个受试者。它却在 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)。切片规则有三处细节值得单独说:

  1. 训练集按时间顺序排列,测试集随机打乱。这条看起来只是文件排序,实际影响很大——按顺序排列意味着相邻文件在时间上连续,做特征时如果不小心用滑动窗口,会把同一秒的数据同时放进训练和验证,造成泄漏。
  2. **发作片段覆盖整次发作。**从起始到终止全部切出来,不抽样、不截断。
  3. **发作间期片段的总时长约等于该受试者的平均发作时长。**这是一个刻意做平衡的设计——让正负类在时间总量上大致相当,而不是按自然发生率(极低)采样。

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 这个指标教给模型什么

把它拆开看,模型学到的其实是三件事:

  1. 分清发作与非发作(AUC_seizure)
  2. 在发作内部,分清「刚开始」与「已经开始一会儿」(AUC_early)
  3. 由于早段被双计,把确定性更多地分配给早段是有利的

第 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),验证数据包括两类:

  1. UPenn 与 Mayo 的标准临床颅内记录(未参与原竞赛)
  2. 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 第六步:报数时写清四件事

任何基于这份数据的结果,至少要交代:

  1. 用了犬、人,还是两者
  2. 受试者数(分母是 4、8 还是 12)
  3. 切分是按片段还是按发作
  4. 报的是竞赛口径(双 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 为什么这个差价很难消除

三个结构性原因:

  1. **受试者太少。**12 个人 + 4 只狗,任何「留一受试者交叉验证」的方差都大到难以解读。
  2. **片段太短。**1 秒切断了发作的演化过程,而临床判断发作靠的恰恰是演化。
  3. 过渡态被剔除。「距发作 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 可立即验证的疑问

  1. **第 2、3 名算法具体掉到多少?**AES 2015 只说「接近随机」,未给数字。
  2. **两份验证集分别包含多少受试者?**两篇摘要都未交代,这直接决定了 0.8297 与 0.962 的可比性。
  3. **NeuroVista 记录的具体时长与受试者数?**未公开。
  4. **12 个受试者各自贡献了多少次发作?**数据页未给明细。
  5. **latency 的专家标记是否双人复核?**未见说明。
  6. **犬类的 4 只狗与预测赛的 5 只狗为何不一致?**两场竞赛的受试者构成不同,原因未说明。
  7. **竞赛数据的长期归档地址在哪?**ieeg.org 的对应集合编号未在论文中给出。
  8. **第 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 — 共享标签:生理信号 / 脑电信号 / 评测基准

导航说明:本章节由全站统一标签体系自动计算生成(标签重合度算法),双向可达;点击链接可跳转至对应数据集词条。

返回 AI-Ready 数据集