信息速览
房颤的临床难点在一个常被混淆的地方:辨认「此刻是不是房颤」早就不难,难的是提前说出「这个人半小时后会进入房颤」。前者靠 RR 间期的变异度就能做到九成以上的准确率,后者至今没有公认的效用证据。PAF Prediction Challenge Database(PhysioNet 编号 afpdb)正是为后者而生的第一份公开数据——2001 年 Computers in Cardiology 挑战赛把「预测阵发性房颤发作」做成了一道正式的开放赛题,并为此专门录制了这批双通道心电。
它的设计相当克制:100 名受试者,每人两段连续 30 分钟,一端接着发作,另一端距离发作足够远。这个设计只回答一个问题——两段波形在统计学上能否区分。它不回答发作发生的时刻、不回答提前量多长才有用、也不回答预测对了之后临床该做什么。
本篇的立场因此是清楚的:它是一份有历史价值的挑战赛数据,适合做「发作前 vs 远离发作」的二分类基线,不适合做现代意义上的房颤预测研究,更不适合拿来宣称筛查能力。一屏速览见下表。
| 维度 | 内容 |
|---|---|
| 官方名称 | PAF Prediction Challenge Database |
| 通行简称 | AFPDB(论文中最常见);PhysioNet 编号 afpdb |
| 发布机构 | PhysioNet / MIT Laboratory for Computational Physiology |
| 首次发布 | 2001 年 3 月 1 日,v1.0.0;同年 3 月 16 日替换过四条学习集记录 |
| DOI | 10.13026/C2H59W |
| 受试者数 | 100 名(学习集 48 名、测试集 50 名) |
| 记录数 | 300 条:n 组与 p 组各 100 条(含延续),t 组 100 条 |
| 每组构成 | 两段连续 30 分钟;p 组另加两段 5 分钟延续记录 |
| 导联数 | 2(未标注电极位置) |
| 采样率 | 128 Hz,16 位整数 |
| 增益 | 名义 200 A/D 单位每毫伏 |
| 标注内容 | 仅 QRS 峰值样本位置(自动检测,未经审计) |
| 节律标注 | 无。全库没有逐秒 AFIB / 正常标签 |
| 单条 30 分钟记录 | 1800 秒 |
| 单条 5 分钟延续记录 | 38,400 个样本 |
| 全库体积 | 解压 191.4 MB,ZIP 191.6 MB |
| 许可 | Open Data Commons Attribution License v1.0 |
| 访问门槛 | 无。免注册、免签署使用协议 |
| 官方入口 | https://physionet.org/content/afpdb/1.0.0/ |
§1 它要解决的具体麻烦
1.1 一句话结论:它把「预测」固化成了「两段波形能否分开」
2001 年的挑战赛出题人做了一个非常聪明也留下长期麻烦的决定:不要参赛者在连续几小时的心电里找发作,而是把每名受试者的两段 30 分钟波形剪出来,一段止于发作前,一段远离发作,让算法判断哪一段是「前者」。这个形式把开放式的时序预测变成了封闭式的成对分类,评分规则因此可以写得很干净:两段配对,猜对哪一段紧接着发作算一次命中。
聪明的部分是它让比赛可复现。麻烦的部分是,此后二十多年里,几乎每一篇引用 afpdb 的论文都继承了这套「成对分类」的框架,而真正的临床问题——提前多久、提前量多少才算数、假阳性带来的代价——被这层框架挡在了门外。你在论文里读到的 96.5% 灵敏度、100% 特异度,衡量的都是「能不能分出哪一段接着发作」,不是「能不能在发作前 N 分钟给出预警」。
本篇后面会反复回到这个区分。把 afpdb 当作一份「发作前 30 分钟 vs 远离发作 30 分钟」的对照材料,它的价值是明确的;把它当作「房颤预测已经被证明可行」的证据,那是一次误读。
1.2 三条常见的错误期待
期待一:把它当成房颤检测数据集。 这是最普遍的错位。全库没有任何逐秒节律标注,你拿到的是 QRS 位置,不是 AFIB 或正常窦律的状态标签。想做检测,要么自建标注(按 RR 间期不规则度自己打),要么换 afdb、ltafdb 那类带节律注释的长记录。用 afpdb 做检测,本质上是在自造的标签上做自造的评测。
期待二:把它当成一份「预测成功」的证据。 挑战赛的排名确实存在,但排名靠前的方法都是在 100 名受试者、50 组配对上的封闭测试;测试集答案不公开,之后的复现全部基于学习集。二十多年下来,「预测房颤」这件事在临床端按血压测量那种标准来看,仍然没有公认的效用证据。数据集能证明的是「两段波形在特征上可分」,不是「预测能救人」。
期待三:把它当成可穿戴筛查的现成素材。 筛查人群是无症状、低先验的,afpdb 里每个人都已经知道自己是阵发性房颤患者或者明确没有房颤。先验被人为抬到了接近一半,任何分类器在这里都能拿到好看的数字。把它当筛查基准,等于在一个被调过先验的样本上做验证。
这三条期待之所以普遍,有一个共同的诱因:数据集的名称里同时出现了「预测」与「房颤」两个词,而这两个词在中文语境里经常被当成同一件事。实际上「房颤预测」至少有三种不同的读法——预测某人会不会得房颤(危险因素层面)、预测某人何时会从窦律转入房颤(本节讨论的层面)、预测房颤会不会自行终止(另一条独立的研究线,ltafdb 里有专门的终止事件标注)。afpdb 只服务第二种读法,而且是其中的一个很窄的子问题。
1.3 它能给到的与给不到的
| 你的需求 | 它给的 | 够不够 |
|---|---|---|
| 一个「发作前 vs 远离发作」的成对分类任务 | 50 组学习集配对 + 50 组测试集配对 | 够,这是它的原始设计 |
| 逐秒的房颤节律标签 | 没有,只有 QRS 位置 | 不够,须自建 |
| 房颤发作的确切起始秒 | 只能通过延续记录间接看到 | 勉强,且只覆盖学习集 |
| 数小时的正常窦律基线 | 最长约 30 分钟一段 | 不够,做不了小时级预测 |
| 同一个人多次发作的序列 | 每人只有一次,且分属不同片段 | 不够 |
| RR 间期序列(HRV 分析) | 可直接从 QRS 注释差分 | 够,但注释有错 |
| 可穿戴单导联场景的验证 | 双导联同步记录 | 不够,只能取一路 |
| 多中心、多设备、跨人群的泛化证据 | 单一来源的 1990 年代记录 | 不够 |
| 免注册直接下载 | ODC-By 1.0,无门槛 | 够 |
| 与近五年深度学习工作直接比数 | 测试集答案不公开 | 不够,只能比学习集 |
1.4 你的处境与它能帮到的事
| 你的处境 | 它能帮你什么 | 下一步该做什么 |
|---|---|---|
| 要做房颤检测/节律分类,正在挑公开数据 | 告诉你「检测」不该拿它当主基准 | 改用带节律注释的 afdb 或 ltafdb |
| 已经用了它,审稿人问「这是检测还是预测」 | 让你能准确改写方法段 | 把任务表述改为「发作前窗口分类」,并如实交代标签定义 |
| 想复现 2001 年挑战赛的经典方法 | 提供与当年完全一致的数据与划分 | 用学习集做开发,把测试集单独留出,不要调参 |
| 想做现代意义的发作预测,需要小时级前置 | 只能提供一个 30 分钟的锚点 | 换长记录数据集,或自建可穿戴队列 |
| 想在可穿戴设备上做筛查 | 提供一组已诊断患者的 RR 片段 | 另找真实筛查人群,afpdb 只做辅助对照 |
| 教学:讲清楚检测/预测/筛查三件事的分野 | 它本身就是最好的反例教材 | 直接把 §1.1 的区分与数据设计对照着讲 |
| 做 HRV 特征工程的对照实验 | 干净的 30 分钟双导联片段,体积小到能全量下载 | 注意 QRS 注释未审计,先做一次人工抽查 |
1.5 一句话判断
这是「房颤预测」被当成可比赛任务的第一份公开数据,它的边界比它的成绩更值得记住:它区分的是两段波形,不是两次心跳之间会发生什么。
1.6 不适合谁:四条边界
不适合做真正的前置预测。 预测的临床价值取决于提前量。afpdb 只给一个 30 分钟的锚点,你无法用它回答「提前 3 小时还是提前 30 分钟报警更有用」,因为整库只有这一个尺度。
不适合做筛查。 受试者的房颤状态事先已知,先验被抬到接近对半。筛查要处理的是 1% 量级的先验与大量混杂伪迹,二者不可互换。
不适合做需要节律真值的任务。 没有 AFIB/FLA 之类的状态标注,你无法直接测量房颤负荷,也无法直接评估节律分段的准确性。
不适合作为方法有效性的唯一证据。 100 名受试者、单一来源、1990 年代设备、128 Hz 采样,这几个条件叠加起来,任何「在 afpdb 上达到 97%」的结论都必须配上外部验证才成立。
§2 数据构成与规格
2.1 三层结构:组、记录、文件
整个库的组织方式是三层的。最上层是「组」(record set),一组对应一名受试者;中间层是「记录」,每组 2 或 4 条;最下层是每条记录的三个文件——.dat 存采样,.hea 存头信息,.qrs 存 QRS 位置。名字前缀把受试者的身份直接写进了文件名,这一点对划分训练集极其重要。
| 前缀 | 含义 | 组数 | 每组记录数 | 时长构成 | 有无延续记录 |
|---|---|---|---|---|---|
n |
无已知房颤受试者 | 50 | 4 | 30 分钟 × 2 + 5 分钟 × 2 | 有 |
p |
阵发性房颤受试者 | 50 | 4 | 30 分钟 × 2 + 5 分钟 × 2 | 有 |
t |
测试集(状态未公开) | 50 | 2 | 30 分钟 × 2 | 无 |
前缀之外还有后缀。每组两段 30 分钟记录用相邻编号,例如 p15 与 p16;对应的延续记录加 c,即 p15c 与 p16c。编号是连续的偶数-奇数对,偶数号在 p 组里代表「止于发作前」,奇数号代表「远离发作」。n 组的编号规则相同,只是没有发作作为锚点,两段都是离发作远。
2.2 字段字典:你能从每个文件里读出什么
.hea 是纯文本的头文件,只有两行有效内容;.dat 是纯二进制;.qrs 是 WFDB 的标准注释格式。三者合起来只有三个可用变量:样本值、采样率、QRS 位置。下表把所有能从文件里直接读出的信息列全。
| 字段 | 所在文件 | 类型 | 示例 | 说明 | 能否直接当特征 |
|---|---|---|---|---|---|
| record_name | 文件名 | 字符串 | p16 | 受试者编号 + 片段号 + 可选 c | 否,但决定划分 |
| n_sig | .hea 第 1 行 |
整数 | 2 | 导联数,全库恒为 2 | 否 |
| fs | .hea 第 1 行 |
整数 | 128 | 采样率,Hz | 是,换算时基 |
| n_samples | .hea 第 1 行 |
整数 | 230400 | 230400/128 = 1800 秒 | 是,判时长 |
| format | .hea 第 2 行 |
整数 | 16 | 采样格式,16 位补码 | 否 |
| gain | .hea 第 2 行 |
字符串 | 200/mV | 200 个 A/D 单位 = 1 mV | 是,还原毫伏 |
| baseline | .hea 第 2 行 |
整数 | 0 | 零点偏移 | 是 |
| adc_res | .hea 第 2 行 |
整数 | 12 | 名义 12 位有效 | 否 |
| adc_zero | .hea 第 2 行 |
整数 | 0 | ADC 零点值 | 否 |
| initial_value | .hea 第 2 行 |
整数 | -19 | 首样本值,校验用 | 否 |
| sig_name | .hea 第 2 行 |
字符串 | ECG0 / ECG1 | 通道名,未标电极位置 | 否 |
| samples | .dat |
16 位整数 | -19, -26249 | 逐样本 ADC 值,双通道交错 | 是,模型输入 |
| qrs_sample | .qrs |
整数 | 14726 | QRS 峰所在样本索引 | 是,差分得 RR |
| qrs_symbol | .qrs |
字符 | N | 固定为「正常心搏」,不携带类型信息 | 否 |
表里最值得注意的是最后一行。afpdb 的 .qrs 里每一个心搏的符号都是同一种,没有早搏、没有房颤标记、没有伪迹标记。它只告诉你「这里有一个 QRS 峰」,不告诉你这个峰属于什么节律。这一点与 afdb 的 .atr 有本质区别,也是后面所有误用的根源。
2.3 一条记录的完整解剖
以 p16 为例。它的头文件只有三行,打开就能读完。
p16 2 128 230400
p16.dat 16 200/mV 12 0 -19 -26249 0 ECG0
p16.dat 16 200/mV 12 0 -84 -30689 0 ECG1
三行分别交代:记录名 p16、2 个通道、128 Hz、共 230400 个样本。第二三行是两个通道的元信息:16 位格式、增益 200、名义 12 位、零点 0、首样本 -19 与 -84、通道名为 ECG0 与 ECG1。230400 除以 128 正好 1800 秒,也就是 30 分钟整。相邻的 p16c 头文件写作 p16c 2 128 38400,38400 除以 128 得 300 秒,正好 5 分钟。
把这些读成 Python 对象,一行就够。
#-- 读一条记录的头信息并核对时长
import wfdb
rec = wfdb.rdheader("p16", pn_dir="afpdb/1.0.0")
print(rec.record_name, rec.n_sig, rec.fs, rec.sig_len)
print(rec.sig_name, rec.adc_gain, rec.units)
print("时长(秒) =", rec.sig_len / rec.fs)
第一条 30 分钟记录与第二条 30 分钟记录的边界在哪,官方说法非常明确:同一组的四条记录都是同一段连续长记录的摘录,编号相邻的两条 30 分钟记录彼此直接相接。也就是说 p15 的最后一秒与 p16 的第一秒之间没有间隙。延续记录再往后接 5 分钟。四段拼起来是一个 65 分钟的时间轴,但它是被切成四块交付的。
时间轴拼好之后,p16 段结束的位置就是房颤开始的位置,你可以在 p16c 的开头看到它。注意起点并不精确到秒:官方的说法是偶数号记录「立刻先于」房颤发作,房颤出现在延续记录里,但具体是延续记录的第几秒,官方没有给逐秒标注。想确定起点,只能用 RR 间期不规则度去推断,这是有误差的。
2.4 规模分布表
| 统计项 | 值 | 来源口径 |
|---|---|---|
| 受试者 | 100 名 | 官方数据说明 |
| 学习集受试者 | 48 名(50 组) | 官方数据说明,两组来自同一人 |
| 测试集受试者 | 50 名(50 组) | 官方数据说明 |
| 记录总数 | 300 条 | RECORDS 清单逐行核对 |
| 30 分钟记录 | 200 条 | RECORDS 清单 |
| 5 分钟延续记录 | 100 条 | RECORDS 清单 |
| 30 分钟记录样本数 | 230400 | 头文件逐条一致 |
| 延续记录样本数 | 38,400 | 头文件逐条一致 |
| 单条 30 分钟记录体积 | 约 900 KB | PhysioNet 文件列表 |
| 单条延续记录体积 | 约 150 KB | PhysioNet 文件列表 |
| 总时长(按样本累加) | 约 108 小时 | 200 × 0.5 h + 100 × 5/60 h |
| 有效总时长(去掉延续记录的重复覆盖) | 约 100 小时 | 延续记录是后续时间的补充,不与主记录重叠 |
| 全库解压体积 | 191.4 MB | PhysioNet 文件列表 |
| ZIP 体积 | 191.6 MB | PhysioNet 页面 |
按 128 Hz、两通道、16 位计算,一条 30 分钟记录是 230400 × 2 × 2 = 921,600 字节,与文件列表里的 900 KB 一致。全库 300 条记录加起来不到两百兆,这个体量意味着任何一台笔记本都能全量加载、全量跑特征、全量交叉验证,这也是它至今仍被大量用作教学与快速实验的原因。
2.5 与同类资源的字段差异
把它和库里另外几份心电数据放在一起看,差异集中在三处:时长、采样率、标注层级。
| 资源 | 记录数 | 单条时长 | 采样率 | 节律标注 | 主要用途 |
|---|---|---|---|---|---|
afpdb |
300 | 30 分钟 / 5 分钟 | 128 Hz | 无 | 发作前 vs 远离发作的二分类 |
afdb |
25 | 约 10 小时 | 250 Hz | AFIB / AFL / J / N | 长程节律分割与检测 |
ltafdb |
84 | 24–25 小时 | 128 Hz | 节律 + 终止事件 | 长程检测与终止预测 |
ecg-arrhythmia |
大于十万条 10 秒 | 10 秒 | 500 Hz | 逐拍诊断标签 | 短片段多类分类 |
chb-mit |
二十余例 | 数小时至数十小时 | 256 Hz | 癫痫发作(非房颤) | 对照参考,说明长记录的价值 |
最刺眼的一列是节律标注。afpdb 是上表唯一没有节律标注的资源。这份「缺失」不是发布疏漏,而是任务定义决定的:当年的赛题只要求判断哪一段「即将发作」,不需要逐秒知道每一秒是什么节律。但二十多年后,绝大多数使用者拿到手的工具链都期待输入带标签的分段,于是每个人都在自建标签,每个人的标签定义都不一样。这是它被误用的结构性原因。
§3 核心层 A:它的身份是「挑战赛数据」,不是「临床队列」
3.1 出题人、出题机构与题面
这套数据由 MIT 计算生理实验室的 George Moody 组织,与 Goldberger、McClennen、Swiryn 共同署名,发表在 2001 年 Computers in Cardiology 会议论文集。它不是为某个临床研究准备的观察性队列,而是为一次公开比赛准备的赛题数据。这个身份的差别非常实际:临床队列的采样逻辑是「按人群纳入」,赛题的采样逻辑是「按题目需要剪裁」。
3.2 赛题被拆成两问
挑战被明确分成两部分。第一部分是组级判断:给定一组受试者的两段记录,判断这名受试者是否有阵发性房颤。第二部分是记录级判断:在已知有房颤的组里,判断两段记录中哪一段紧接着发作。测试集里约一半的组来自房颤受试者,第一问要认出这些组,第二问要在其中指出方向。
| 赛题部分 | 判断对象 | 输入 | 输出 | 难度来源 |
|---|---|---|---|---|
| 第一部分 | 组 | 两段 30 分钟 ECG | 有 / 无阵发性房颤 | 需要跨记录聚合特征 |
| 第二部分 | 记录 | 一段 30 分钟 ECG(或成对) | 是否紧接着发作 | 两组记录高度相似,区别细微 |
两个部分的分野值得说清楚:第一部分是「身份识别」,第二部分才是官方定义的「预测」。很多后来引用 afpdb 的论文只做了第一部分,却在摘要里写「预测房颤」,这是论文写作层面的错位。第二部分才是这套数据真正的靶心。
3.3 为什么必须是「成对」而不是「单条」
如果只给一条 30 分钟记录让算法判断「这是不是发作前片段」,那么任何分类器都可以靠「这个人是不是房颤患者」这条捷径拿到高分,而不是靠「这段时间离发作多近」。成对设计切断了这条捷径:同一名受试者的两段记录来自同一段连续长记录、同一台设备、同一次佩戴,患者身份、设备特性、日间时段全部相同,唯一不同的是与发作的距离。
这个设计在实验方法上很干净,代价是把「预测」这件事的难度削掉了一半——真实的预警系统没有配对的参照段可以对比。你用 afpdb 训练的模型,部署时得到的只有一段未来心电,没有「这人的另一段」可以放在旁边比较。从成对分类迁移到单条推断,中间隔着一整个建模问题。
3.4 身份对使用的三个约束
约束一:划分必须按组,不能按记录。 同一组的两段记录来自同一人、同一段连续记录,按记录随机划分会把这两段分到训练与测试两侧,指标会虚高。更严重的是学习集里有 50 组但只有 48 人,两组来自同一人,按组划分时这两组也要捆在一起。
约束二:不能假设测试集有答案。 官方从未公开测试集的组级与记录级真值。任何声称在 t 组上得到具体指标的论文,要么是自己人工标注的(那就不叫官方测试集),要么是把学习集的结果错记成了测试集。
约束三:不能把「延续记录」当独立样本。 延续记录是从主记录结束处直接接续的 5 分钟,它与主记录在时间上相连,当成独立样本会同时造成时间泄漏与重复计数。
§4 核心层 B:质量与特色
4.1 设计上的三处巧思
巧思一:奇偶配对让「患者身份」被控制住。 这是全库最重要的设计。同一受试者的两段波形在 QRS 形态、基线漂移、噪声水平上都高度相关,模型如果想靠患者身份蒙对,配对设计直接堵死这条路。这一点在 2001 年是相当超前的实验设计。
巧思二:45 分钟的干净间隔。 官方对奇数号记录的规定很具体:在这段 30 分钟记录开始前 45 分钟内、结束后 45 分钟内都不含房颤。这个规定保证了「远离段」是真的远离,不是「离得比较远」。它也顺带给出了一个可用的负样本定义——距离任何发作至少 45 分钟。
巧思三:延续记录提供了边界校验。 p16 结束之后发生了什么,p16c 给出了答案。这 5 分钟是整库唯一能让你直接看到「发作起点」的材料,也是验证你自己推断的房颤起点是否靠谱的唯一手段。
4.2 三处已知瑕疵
瑕疵一:短爆发可能漏标。 官方在数据说明里主动提醒:学习集这组 30 分钟记录中,少数可能包含极短的房颤爆发,整理学习集时没有被注意到。这句话翻译成工程语言就是:负样本里可能有未被发现的阳性。任何在负样本上测到的特异度,都带这个方向的乐观偏差。
瑕疵二:QRS 注释未经审计。 官方明说 .qrs 是机器生成、未经校正、含错误。它的用途被限定在「给不想自己写 QRS 检测器的人行个方便」。用 HRV 特征做分类时,QRS 检测错误会直接污染 RR 序列,而且这种污染在房颤发作前恰好更容易发生——因为房颤前期 RR 变异增大,检测器更容易误判 T 波。
瑕疵三:测试集没有答案。 这不是瑕疵,但对复现工作是一道硬墙。所有后来者只能在 50 组学习集上做交叉验证,学习集只有 48 人,任何百分之几的差异都落在抽样误差范围内。
4.3 案例:p15 与 p16 这一对
把一组具体的记录摊开看,能最快理解这套数据的结构与边界。p15 与 p16 是相邻编号的一对,来自同一名阵发性房颤受试者的同一段连续长记录。
p15 是奇数号,属于「远离发作」的那一段。官方保证它的前后各 45 分钟内没有房颤。它的延续记录 p15c 接着它往后 5 分钟,把这段记录证实为确实不含房颤——这也是整库唯一能直接验证「远离段真的干净」的手段。
p16 是偶数号,属于「紧接着发作」的那一段。它本身不含房颤,房颤出现在它的延续记录 p16c 里。也就是说,p16 的最后一秒与房颤开始之间只隔着一个采样点级别的边界(在原始波形里这个边界是连续的),而房颤的位置由 p16c 的头两分钟决定。
这一对的时间轴可以这样读:
| 片段 | 时长 | 起点 | 终点 | 房颤状态 | 用途 |
|---|---|---|---|---|---|
p15 |
30 分钟 | T−75 分 | T−45 分 | 无(前后各 45 分钟无发作) | 负样本 |
p15c |
5 分钟 | T−45 分 | T−40 分 | 无 | 负样本的边界校验 |
p16 |
30 分钟 | T−30 分 | T | 无(止于发作前) | 正样本 |
p16c |
5 分钟 | T | T+5 分 | 有,从 T 附近开始 | 正样本的边界校验 |
表中 T 代表房颤发作的时刻。这张表解释了两件事。第一,所谓「发作前 30 分钟」不包括任何房颤时刻,它是纯粹的窦律或至少非房颤心律;分类器要区分的是「一段干净的窦律」与「一段即将被房颤打断的窦律」,而不是「有房颤」与「没房颤」。第二,p15 与 p16 之间隔着 p15c 那 5 分钟,所以这一对的完整覆盖是从 T−75 到 T+5,中间没有断点。
正是这个结构决定了后面的两条判断。第一条:§4.3 的案例说明它做不了「提前一小时」的预测,因为正样本只覆盖最后半小时。第二条:§4.3 的案例也说明它的正负样本间隔最多 45 分钟,这在 HRV 的时间尺度上属于「相邻」,远超一小时的预测需要跨越的距离。
4.4 特色:一份「负样本比正样本更稀缺」的数据
多数医学数据集的麻烦是正样本太少。afpdb 反过来:正样本(发作前 30 分钟)有 50 段(学习集),负样本(远离发作 30 分钟)也只有 50 段,看起来平衡,但真正稀缺的是「长时间、连续、可授权为完全干净的负样本」。奇数号记录虽然有 45 分钟的保护区,那 45 分钟并没有作为数据交付——它被留在了长记录里,只截了 30 分钟。
想构造小时级的负样本,你只能自己去找别的数据源,或者接受「负样本只有半小时」这个事实。这是这套数据在今天的最大结构性短板,也是下面所有局限的起点。
把这一点说得更具体一些。所谓「负样本稀缺」不是数量稀缺,而是「时长」稀缺。假设你要做一个提前 2 小时的预警模型,你需要的负样本是「两小时内不会发作」的完整片段,而 afpdb 能提供的负样本只保证「前后各 45 分钟无发作」——它连「两小时内」这个前提都不覆盖。你只能在 30 分钟的尺度上宣称「这一段是干净的」,超出这个尺度的一切陈述都没有数据支撑。
正样本侧的问题相反。正样本不缺时长,缺的是多样性。50 段正样本全部止于发作前的那一秒,形态上高度雷同:都是「最后半小时越来越不规则」的窦律。模型很容易学到「最后几分钟 Irregularity 抬升」这个单一模式,而在真实部署中,发作前的形态可能多种多样,有的前置房性早搏很多,有的几乎看不出异常。用 50 段高度同质的正样本训练,得到的分类器对「不典型的发作前状态」几乎没有覆盖。
这两侧的问题叠在一起,构成了一个很典型的漏斗:负样本只有半小时、正样本只有一种形态。任何在这个漏斗里刷出来的分数,都只在漏斗内部成立。
4.5 与「检测」这条线的质量对比
把 afpdb 与 afdb 放在质量维度上并排看,会更清楚地看出它为什么难以承担预测任务。检测这条线有节律标注,标注是真值,评测是逐秒对答案;预测这条线没有节律标注,正负样本靠「时间位置」定义,评测是成对二选一。前者的每一个错误都能定位到具体的一秒,后者的错误只能表示为「这一对选错了」,至于错在哪个特征、哪个时段,完全不可见。
| 质量维度 | afpdb(预测线) |
afdb(检测线) |
差距的性质 |
|---|---|---|---|
| 真值来源 | 设计(时间位置) | 标注(人工节律) | 前者无法逐点定位错误 |
| 错误可定位性 | 只到「组」粒度 | 到「秒」粒度 | 差错分析无法开展 |
| 负样本可定义性 | 靠 45 分钟保护区 | 靠 AFIB 段之外的时段 | 前者只有 30 分钟可交付 |
| 评测协议稳定性 | 50 组配对,答案不公开 | 逐段对答案,可复现 | 前者社区无法独立复核 |
| 可扩展性 | 不能补标注(原设计如此) | 可重标、可细分 | 前者只能等新数据 |
这张表最有信息量的是最后一行。检测线的数据可以随着标注规范演进而重标、细化、扩类;预测线的数据不能,因为它的标签不是从波形里读出来的,而是从采集设计里规定出来的。设计一旦定下,标签就定死了。这也是为什么 afpdb 二十多年没有再出过实质更新——不是没人想更新,是它在设计上就没有更新的空间。
§5 核心层 C:取数、读取与最小流程
5.1 三条下载路径
| 路径 | 命令或地址 | 适用场景 | 注意 |
|---|---|---|---|
| 网页 ZIP | 项目页的 Download 按钮 | 一次性拿全库 | 约 191.6 MB |
| wget 递归 | wget -r -N -c -np https://physionet.org/files/afpdb/1.0.0/ |
断点续传、只补缺文件 | 会保留目录层级 |
| AWS 同步 | aws s3 sync --no-sign-request s3://physionet-open/afpdb/1.0.0/ ./afpdb |
在云上跑批 | 免认证,公开桶 |
| wfdb 直读 | wfdb.rdrecord("p16", pn_dir="afpdb/1.0.0") |
只取几条记录做探索 | 需要网络,不落盘 |
最后一条路径对写论文复现最友好:不用先下全库,把用到的那几条列在代码里即可,审稿人照着跑就能复现。缺点是每次都要联网,离线环境不可用。
5.2 读取一条记录
#-- 一次性读入信号与 QRS 注释
import wfdb
import numpy as np
rec = wfdb.rdrecord("p16", pn_dir="afpdb/1.0.0")
ann = wfdb.rdann("p16", "qrs", pn_dir="afpdb/1.0.0")
print(rec.p_signal.shape) # (230400, 2)
print(rec.fs) # 128
print(len(ann.sample)) # QRS 个数
print(ann.sample[:5]) # 前五个 QRS 峰位置(样本索引)
print(ann.symbol[:5]) # 全库固定为 N
rec.p_signal 已经按增益还原成毫伏浮点数。如果想跳过浮点转换、直接拿原始 ADC 整数(做某些滤波更省内存),用 physical=False。
5.3 把 QRS 变成 RR 间期
#-- QRS 位置 -> RR 间期(秒)
import numpy as np
import wfdb
ann = wfdb.rdann("p16", "qrs", pn_dir="afpdb/1.0.0")
fs = 128.0
rr = np.diff(ann.sample) / fs
print("心搏数", len(ann.sample))
print("平均心率 bpm", 60.0 / rr.mean())
print("RR 标准差 s", rr.std())
print("RR 变异系数", rr.std() / rr.mean())
在房颤前期,RR 变异系数往往已经出现抬升,这正是 2001 年那批参赛方法的主要抓手。但它不是房颤本身,窦性心律不齐、房性早搏、检测器误判都会让变异系数上去,这也是为什么后来的工作都在特征工程上加码而不是只用一个数。
5.4 拼接一组记录、复原 65 分钟时间轴
#-- 拼接 p15 / p15c / p16 / p16c,得到连续 65 分钟
import numpy as np
import wfdb
parts = ["p15", "p15c", "p16", "p16c"]
sigs, marks = [], []
offset = 0
for name in parts:
rec = wfdb.rdrecord(name, pn_dir="afpdb/1.0.0")
ann = wfdb.rdann(name, "qrs", pn_dir="afpdb/1.0.0")
sigs.append(rec.p_signal)
marks.append(ann.sample + offset)
offset += rec.sig_len
sig = np.concatenate(sigs, axis=0)
qrs = np.concatenate(marks)
print(sig.shape, qrs.shape, offset / 128.0)
拼接时要留意一个细节:p15 与 p15c 之间、p16 与 p16c 之间的边界是否真的零间隙,官方只对「同一组内的记录来自同一段连续记录」作了说明,并没有给逐样本的对齐证明。做精细的相位分析时,建议在边界两侧各留一两个心搏不用。
5.5 按受试者划分,杜绝配对泄漏
#-- 从 RECORDS 清单构造受试者级别划分
import re
import urllib.request
url = "https://physionet.org/files/afpdb/1.0.0/RECORDS"
names = urllib.request.urlopen(url).read().decode().split()
def subject_key(name):
#-- p15c -> p15 ; p16 -> p16 ; 两者同属一个 30 分钟对
base = name.rstrip("c")
num = int(re.sub(r"\D", "", base))
return f"{base[0]}{(num - 1) // 2:03d}"
subjects = sorted({subject_key(n) for n in names})
print(len(subjects)) # 学习集 100 个「对」,实际 48 人
上面这段只按编号配对,得到的是「记录对」而不是「受试者」。学习集 50 个对来自 48 人,其中两组是同一人,官方没有说明是哪两组,所以真正严格的受试者级划分在 afpdb 上是做不到的——这一点必须在论文的划分声明里写清楚。
5.6 一个完整的基线:只用 RR 统计量做配对分类
#-- 仅用 RR 间期统计量做 50 组配对分类(按组做留一交叉验证)
import numpy as np
import wfdb
from sklearn.linear_model import LogisticRegression
def feats(name):
ann = wfdb.rdann(name, "qrs", pn_dir="afpdb/1.0.0")
rr = np.diff(ann.sample) / 128.0
rr = rr[(rr > 0.3) & (rr < 2.0)] #-- 去掉明显漏检与误检
d = np.diff(rr)
return np.array([
rr.std(), rr.std() / rr.mean(),
np.mean(np.abs(d) > 0.1), np.percentile(np.abs(d), 90),
np.mean(rr), (rr > 0.6).mean(),
])
pairs = []
for i in range(1, 51):
pairs.append((f"p{2 * i - 1:02d}", f"p{2 * i:02d}"))
acc, y_true, y_score = [], [], []
for k, (odd, even) in enumerate(pairs):
X_train, y_train = [], []
for j, (a, b) in enumerate(pairs):
if j == k:
continue
X_train += [feats(a), feats(b)]
y_train += [0, 1]
clf = LogisticRegression(max_iter=1000).fit(X_train, y_train)
xo, xe = feats(odd), feats(even)
so = clf.predict_proba(xo.reshape(1, -1))[0, 1]
se = clf.predict_proba(xe.reshape(1, -1))[0, 1]
ok = se > so
acc.append(ok)
y_score.append(se)
print("配对准确率", np.mean(acc), "n =", len(acc))
这段代码的意义不在分数,而在于把实验流程讲清楚:特征只用 RR 统计量、按组留一、每折重新训练。把这个框架换成更复杂的模型,分数会上去几个点,但结论很难改变——因为负样本里混着未被发现的短爆发(§4.2 的瑕疵一),任何超过某个水平的分数都无法排除是标签噪声在帮忙。
§6 与库内条目的关系
6.1 关系图谱
afpdb 在库内处于一个特殊位置:它是「预测」这条线的起点,但它服务的下游条目并不多。真正能接上它的是长记录库与筛查类数据,中间的桥是「节律标注」的有无。
| 库内条目 | 关系 | 差异 | 什么时候换它 | 什么时候留 afpdb |
|---|---|---|---|---|
afdb |
同源姊妹库 | 长记录带节律注释,afpdb 是短片段无注释 |
需要节律分割、负荷测量 | 需要配对设计的对照实验 |
ltafdb |
长记录延伸 | 24 小时、含终止事件 | 需要跨昼夜的长基线 | 只做 30 分钟级对比 |
ecg-arrhythmia |
短片段大库 | 十万级 10 秒片段、逐拍标签 | 需要规模与多类标签 | 需要同一患者的配对对照 |
chb-mit |
非房颤长记录 | 癫痫发作、非心电疾病 | 借它的长记录结构做方法迁移 | 不适用 |
apnea-ecg |
同年代 PhysioNet 挑战赛 | 呼吸暂停事件标注 | 对照 2000 年前后挑战赛的数据风格 | 不适用 |
mimic-iv-ecg |
现代大规模临床心电 | 十万级 10 秒 12 导联,带报告 | 需要现代临床工作流与文本配对 | 需要 1990 年代设备的历史对照 |
表中第一行最需要强调。很多人把 afpdb 与 afdb 混为一谈,因为两者都是「房颤 + PhysioNet + MIT」,连缩写都只差一个字母。§8.4 会专门讲这个缩写之争。
6.2 检索路径
想从库内找到它,有四个入口。按 PhysioNet 编号 afpdb 检索最准,按论文里的 AFPDB 检索会同时命中讨论 afdb 的中文材料(因为不少人把 afdb 写成 AFDB、把 afpdb 写成 AFPDB,两个缩写视觉上极易混淆)。按「阵发性房颤预测」检索会命中大量综述,按「CinC Challenge 2001」检索能直接定位到出题的那篇会议论文。
| 检索词 | 命中率 | 需要注意 |
|---|---|---|
afpdb |
高,精确命中 PhysioNet 页面 | 最可靠 |
AFPDB |
高,命中多数论文 | 少数中文材料把它当 afdb 的别名 |
paroxysmal atrial fibrillation challenge |
中 | 会混入 2017 年挑战赛材料 |
CinC Challenge 2001 |
中 | 会议文献检索更准 |
PAF prediction database |
高 | 全称,命中率高但篇幅长 |
MIT-BIH atrial fibrillation |
低 | 命中的是 afdb,不是本篇 |
§7 质量评估与局限性
7.0 阅读本节前的一个前置校正
评价这套数据的质量之前,必须先纠正一处常见的框架误判:把「预测性能」与「检测性能」当成同一把尺子。afpdb 上报道的 96% 灵敏度、100% 特异度,是在「成对分类」的封闭任务上测的,任务里已经给了配对参照,也已经把先验拉到对半;这些数字与传统意义上的检测灵敏度和特异度不是同一个量。
更具体的误判是把它与 afdb 上的检测成绩并列在同一张表里比较。两者的评价对象完全不同:afdb 评的是「逐秒节律分类对不对」,afpdb 评的是「两段 30 分钟谁先发作」。把 96.5% 与 97.4% 放在一行里,读者会以为这是同一个任务的进步,实际上这是两个不同的任务。下表的评分标准据此重新校准,只评它自己声称要做的事。
7.1 五维评分(DAIMS 框架)
DAIMS 是千方医数集用于评估数据集 AI 就绪度的五维框架:数据可用性、标注质量、代表性、时效性、可维护性。评分只针对它声明的用途——发作前 vs 远离发作的二分类。
| 维度 | 分数 | 判据 | 主要扣分点 |
|---|---|---|---|
| Data Availability(可用性) | 5 / 5 | ODC-By 1.0、免注册、191 MB、有 AWS 直链 | 无 |
| Annotation Quality(标注质量) | 2 / 5 | 只有 QRS 位置,无节律标签;QRS 未审计;正负样本的标签靠设计而非标注 | 节律真值缺失、QRS 检测误差 |
| Interpretability / Independence(可解释与独立) | 3 / 5 | 配对设计控制了患者身份,但测试集无答案 | 无法独立复核测试结果 |
| Representativeness(代表性) | 2 / 5 | 100 名受试者、单一来源、1990 年代设备、以西方人群为主 | 人群窄、设备旧、无多中心 |
| Sustainability(可持续性) | 5 / 5 | PhysioNet 长期托管,有 DOI,25 年稳定在线 | 版本冻结在 2001 年,不会再补标注 |
五维里两项满分、两项两分,这个分布本身就说明了它的性质:数据本身极其容易拿到,但拿到手之后能做的事被标注层卡住了一大半。
7.2 三个层次的问题
数据层的问题是可修的。 QRS 检测误差、短爆发漏标、48 人只有 50 组,这些都可以通过重新标注、补录、扩充解决。PhysioNet 在 2001 年 3 月 16 日就替换过四条记录(p02、n24、n47、n48),说明维护方对数据质量是敏感的。但从那以后二十多年没有新版本,说明社区没有把这个库当作需要持续投入的对象。
标签层的问题不可修。 官方从头到尾没有承诺过给节律标注,也没打算给。这不是遗漏,是任务定义。所以「用它做节律检测」这条路上的困难永远不会被上游解决,只能由每个使用者自己承担。
方法层的问题最难。 预测窗口的定义没有统一标准。在 afpdb 上是 30 分钟,在其他论文里常见 5 分钟、10 分钟、30 分钟、1 小时、几小时乃至 24 小时。窗口一变,任务难度完全改变,跨论文比较立刻失效。这才是「房颤预测至今没有公认效用证据」的核心原因之一——不是没有方法,而是方法之间不可比。
7.3 已知的、可枚举的偏差
| 偏差 | 方向 | 影响 | 缓解办法 |
|---|---|---|---|
| 短爆发漏标混入负样本 | 特异性被高估 | 模型在「干净的远离段」上表现好,在真实负样本上变差 | 用延续记录与长记录交叉核对 |
| QRS 注释未审计 | 方向不定,房颤前期偏向于更差 | RR 特征被噪声污染 | 自己跑一个 QRS 检测器并做一致性对比 |
| 先验被人为拉到接近对半 | 阳性预测值被高估 | 部署到低先验人群时 PPV 崩塌 | 报告时同时给出按真实先验重算的 PPV |
| 人群来自已诊断患者与已知无房颤者 | 泛化性被高估 | 筛查场景下的表现不可预期 | 另找真实筛查队列做外部验证 |
| 测试集答案不公开 | 无法察觉过拟合 | 学习集上的分数被反复刷高 | 只报告学习集留一或嵌套交叉验证 |
| 设备与采样率偏旧 | 与现代可穿戴的距离变大 | 128 Hz 与今天 200–500 Hz 之间要重采样 | 显式报告重采样与滤波参数 |
| 单次发作、无时间序列 | 无法做跨发作的建模 | 个体化预测研究无法开展 | 换用长记录或可穿戴纵向数据 |
表中的第一行与第四行叠加起来,会制造出一种很典型的幻觉:在学习集上做到极高特异度,论文里写「可用于临床预警」,而实际上这两个偏差恰好都在同一个方向上抬高了那个数字。
7.4 外部验证矩阵
| 验证方向 | 现有证据 | 强度 | 缺什么 |
|---|---|---|---|
| 学习集内交叉验证 | 大量论文报告 90%–100% 区间 | 中 | 一致的划分口径 |
| 学习集 → 测试集 | 只有 2001 年挑战赛的官方结果 | 弱,且不公开 | 测试集答案 |
afpdb → afdb |
少见,两类任务不可直接换算 | 弱 | 统一的时间窗定义 |
afpdb → 可穿戴单导联队列 |
极少,且多为小样本自采 | 很弱 | 公开的多导联/单导联配对队列 |
afpdb → 临床预警结局 |
没有公开的 RCT 或前瞻研究 | 无 | 前瞻性研究 |
第五行是整张表最要紧的一格。房颤预测在临床端至今没有公认的效用证据,指的正是这一格空白:没有任何一项公开的前瞻研究证明「按预测结果干预」比「按发作后处理」带来更好的结局。数据集解决不了这个问题,任何声称用 afpdb 证明预测有用的论文,都是把数据集层面的可分性当成了临床层面的有效性。
7.5 它为何能持续:因为它是最省事的那个起点
afpdb 能活到今天,不是因为它的标注质量,而是因为它的进入成本几乎为零:不到两百兆、一条命令下完、清晰的组结构、教科书级的配对设计。任何刚进入心电领域的人,第一份想做「预测」的数据都会撞上它。它是被惯性维持的,不是被质量维持的。
这也解释了它的更新停滞。一份已经在教学与基线里被反复使用的数据,改动会破坏可比性;而补标注的成本又要由某个机构承担,收益却是全社区的。于是它停在 2001 年 3 月 16 日那一版,成为一份「稳定的旧物」。
§8 生态位与历史
8.1 2001 年:挑战赛的诞生
2001 年的 Computers in Cardiology 挑战赛把题目定在阵发性房颤的发作预测上,这在当时是相当前沿的方向。那几年 PhysioNet 已经积累了几份经典心电库,出题人有现成的长记录可以剪裁。挑战赛的组织者把「预测」这件事拆解成可评分的形式,本身就是一次方法学上的贡献:它让一个模糊的临床问题第一次有了明确的评价协议。
8.2 2001–2010:特征工程时代
挑战赛之后,这条线上的主流方法是 HRV 特征工程。研究者从 RR 间期里提取时域与频域特征,喂给分类器。这一代工作留下的最有价值的东西不是分数,而是「房颤前期 RR 变异已经改变」这个观察——它在今天仍然是几乎所有预测方法的共同前提。同时期也留下了大量不可比的分数,因为每个人用的窗口、特征、划分都不同。
8.3 2010 至今:深度学习的进入与瓶颈
深度学习进入之后,做法变成了把 30 分钟的波形直接喂给卷积网络或循环网络,端到端出分数。分数确实更高了,但瓶颈没有移动:训练样本还是 50 组,负样本还是那半小时,测试集还是没有答案。数据规模的上限比模型能力更早到来,这是这一代工作的共同处境。要突破,只能在数据侧找新的长记录与新的前瞻性队列,而不是在模型侧。
8.4 一个必须说清的对应关系:afpdb 与 afdb
两个库的缩写只差一个字母,连 PhysioNet 的两个页面也确实同时存在。本篇在开写前核对了这两个页面,确认它们都返回 200 并各自有独立的版本页。区别是全方位的:
| 对照项 | afpdb(本篇) |
afdb |
|---|---|---|
| 全称 | PAF Prediction Challenge Database | MIT-BIH Atrial Fibrillation Database |
| 主要用途 | 发作前 vs 远离发作的二分类 | 房颤检测、节律分割 |
| 受试者/记录 | 100 人 / 300 条短记录 | 25 条长记录(23 条含信号) |
| 单条时长 | 30 分钟或 5 分钟 | 约 10 小时 |
| 采样率 | 128 Hz | 250 Hz |
| 导联数 | 2 | 2 |
| 节律标注 | 无 | 有,AFIB / AFL / J / N |
| 版本日期 | 2001-03 | 2014-03 |
| DOI | 10.13026/C2H59W | 10.13026/C2MW2D |
| 体积 | 191.4 MB | 约 606 MB |
| 一句话 | 让「预测」变成可打分的比赛 | 让「检测」有标答 |
在中文材料里,AFPDB 这个写法被两个库共用,造成了不少张冠李戴。判断方法是看采样率与记录时长:122 Hz 那种说法不存在,128 Hz、30 分钟就是本篇;250 Hz、10 小时就是 afdb。这一条已经写进后面的避坑清单。
8.5 它在今天的生态位
用一句话定位:它是「预测」这条线的时间原点,是每个新手都会经过的路标,但它不是这条线今天的前沿。今天做房颤预测的人,数据侧更依赖长记录库与可穿戴纵向队列,方法侧更依赖个体化的时序模型,评价侧更依赖前瞻性研究。afpdb 仍然会出现在论文的数据表里,但更多是作为「与经典基准对比」的一行,而不是主战场。
§9 使用指引
9.1 三种角色的路径
| 角色 | 目标 | 最短路径 | 会踩的坑 |
|---|---|---|---|
| 学生 / 入门者 | 理解「检测」与「预测」的区别 | 读 §1.1,跑 §5.2 与 §5.3,看 RR 变异系数在正负样本上的差异 | 把分数当成预测有效性的证明 |
| 算法工程师 | 搭一个可复现的配对分类基线 | §5.5 按组划分 + §5.6 的基线,再换模型 | 按记录随机划分造成泄漏 |
| 临床研究者 | 评估「预测」这个方向是否值得投入 | 读 §7.4 的外部验证矩阵,重点看最后一格 | 把数据集层面的可分性当成临床结论 |
| 论文作者 | 在方法段准确表述任务 | 读 §3.2 的两问拆解,按实际做的部分写 | 做了第一问却写「预测」 |
9.2 一条最短的可用流程
从零到出一个可复现的数字,最短的路径是六步。第一步确认依赖,第二步拉清单,第三步取记录,第四步做特征,第五步按组划分,第六步报告时把划分与标签定义一起写出来。
#-- 最短流程:取数 -> 检查 -> 特征 -> 报告
python3.11 -m pip install wfdb numpy scikit-learn
mkdir -p afpdb_data && cd afpdb_data
wget -q https://physionet.org/files/afpdb/1.0.0/RECORDS
head -8 RECORDS
#-- 只取 p 组的学习集做对照实验
wget -q https://physionet.org/files/afpdb/1.0.0/p15.hea
wget -q https://physionet.org/files/afpdb/1.0.0/p15.dat
wget -q https://physionet.org/files/afpdb/1.0.0/p15.qrs
#-- 最小自检:样本数、QRS 数、时长三者是否自洽
import wfdb
for name in ["p15", "p16", "p15c", "p16c"]:
rec = wfdb.rdheader(name, pn_dir="afpdb/1.0.0")
ann = wfdb.rdann(name, "qrs", pn_dir="afpdb/1.0.0")
print(f"{name:6s} samples={rec.sig_len:6d} "
f"dur={rec.sig_len / rec.fs:6.1f}s qrs={len(ann.sample):4d}")
最后一步是写报告。报告里至少要有四句话:用了哪些记录、按什么粒度划分、正负样本各自的定义是什么、测试集是否被使用。缺任何一句,读者就无法判断你的分数意味着什么——§7.2 说的「窗口定义不统一」,就是从这种缺失开始的。
9.3 引用方式
官方页给出了两种引用义务:一是引用出题的那篇会议论文,二是引用 PhysioNet 平台本身(平台论文会更新,以官方页当前列出的为准)。
Moody GB, Goldberger AL, McClennen S, Swiryn SP.
Predicting the Onset of Paroxysmal Atrial Fibrillation:
The Computers in Cardiology Challenge 2001.
Computers in Cardiology 28:113-116 (2001).
数据集本身的引用要带版本与 DOI:
PAF Prediction Challenge Database (version 1.0.0).
PhysioNet. DOI: 10.13026/C2H59W.
https://physionet.org/content/afpdb/1.0.0/
许可要求署名,ODC-By 1.0 允许商业使用与再分发,但衍生作品必须保留署名与许可声明。
§10 AI 使用声明
| 项 | 内容 |
|---|---|
| 生成方式 | 由大语言模型辅助起草,编辑部核对后定稿 |
| 核对范围 | 记录数、样本数、采样率、体积、许可、DOI 均对照 PhysioNet 1.0.0 页面与文件清单 |
| 未核对项 | 逐条节律状态、测试集答案、学习集两组来自同一人的具体编号 |
| 外部文献 | 仅引用两篇 2011 年前后的方法论文摘要,用于说明分数口径 |
| 代码 | §5 与 §9 的脚本为可运行示例,未在其上跑出过正式基准 |
| 数字口径 | 时间长度按样本数除以采样率计算,与官方的「30 分钟」措辞一致 |
| 免责 | 本文不构成临床建议;预测类模型的临床使用须经监管审批 |
| 更新约定 | PhysioNet 若发布新版本,需重新核对记录数与体积 |
常见误读(避坑清单)
| 常见说法 | 实际 | 证据 |
|---|---|---|
「afpdb 就是 MIT-BIH 房颤库」 |
不是,两者是不同页面、不同 DOI、不同设计的两个库 | §8.4 的对照表;两个 PhysioNet 页面并存 |
| 「它有房颤的逐秒标注」 | 没有,只有 QRS 位置 | 官方数据说明;.qrs 符号全为同一种 |
| 「p 组偶数号记录里有房颤发作」 | 记录本身不含房颤,房颤在其延续记录里 | 官方对 p16 与 p16c 的举例说明 |
| 「它适合做房颤预测」 | 只适合做 30 分钟尺度的发作前对照分类 | §1.1 与 §7.2 的窗口问题 |
| 「灵敏度 96.5% 说明预测可行」 | 那是配对分类的分数,不是预警效用 | 论文摘要口径;§7.0 的前置校正 |
| 「可以随机按记录划分」 | 同组两段来自同一人,必须按组划分 | 官方说明四条记录来自同一段连续记录 |
| 「测试集可以本地打分」 | 官方答案从未公开 | §3.4 的约束二 |
| 「128 Hz 足够做形态学分析」 | 对 P 波形态偏粗,做房颤前房波分析吃力 | 128 Hz 下每样本约 7.8 毫秒 |
| 「n 组是健康人」 | 官方写明包含健康对照、动态心电门诊与 ICU 患者 | 官方数据说明 |
| 「它是可穿戴筛查的现成素材」 | 先验被抬到接近对半,不可外推到筛查 | §1.2 的期待三 |
FAQ
Q1:afpdb 和 afdb 是同一个数据集吗?
不是。afpdb 是 PAF Prediction Challenge Database,100 名受试者、128 Hz、30 分钟片段;afdb 是 MIT-BIH Atrial Fibrillation Database,25 条约 10 小时的长记录、250 Hz、带节律注释。两者在 PhysioNet 上各有独立页面与 DOI,§8.4 有逐项对照。
Q2:它适合做房颤检测模型吗?
只适合做「发作前 vs 远离发作」的配对分类。全库没有逐秒节律标注,做通用检测需要自建标签,严格说那已经不是这个数据集的原任务了。
Q3:为什么说它不适合做预测研究?
因为它只提供 30 分钟这一个时间尺度,没有跨小时的正常基线,也没有同一人的多段发作。预测研究要回答的是「提前多久、用什么特征、在什么人群里」,它只能回答「半小时内是否即将发作」这一小格。
Q4:p 组的偶数号记录里,房颤从第几秒开始?
不在 30 分钟记录里。房颤出现在其延续记录中,官方没有给逐秒起点,只能用 RR 不规则度去推断,或者直接看延续记录的前两分钟。
Q5:可以用它训练房颤筛查模型吗?
不建议。这里的受试者房颤状态事先已知,先验被抬到接近对半;筛查面对的是低先验的总体人群,两者不能互换。
Q6:n 组受试者都是健康人吗?
不是。官方写明包括健康对照、因长期动态心电监测就诊的患者,以及 ICU 患者。用它做「健康 vs 房颤」的分类,会在非健康对照上出错。
Q7:QRS 注释可以直接用吗?
可以用于快速起步,但官方明确说它未经审计、含错误。做 HRV 特征时,QRS 误差会直接进入 RR 序列,房颤前期尤其容易误判。稳妥做法是自己跑一个检测器并做一致性比对。
Q8:为什么测试集不能复现?
官方从未公开测试集的组级与记录级真值。所有声称在测试集上得到具体数字的论文,都需要核对它是否把学习集的结果写错了位置。
Q9:125 Hz 还是 128 Hz?
128 Hz。.hea 第二列固定为 128,全部记录一致。
Q10:下载和许可有什么限制?
ODC-By 1.0,免注册下载,允许商用与再分发,要求署名。全库 ZIP 约 191.6 MB。
Q11:学习集 50 组为什么只有 48 人?
官方说明 50 组来自 48 名不同受试者,但没说哪两组来自同一人。做严格受试者级划分时这一条无法落实,必须在论文里如实交代。
Q12:能不能把它和 afdb 拼起来做跨库实验?
可以,但要先统一采样率、滤波带宽、正负样本定义。afdb 的正样本是逐秒房颤,afpdb 的正样本是发作前 30 分钟,两者在任务层面不是同一件事。
术语表
| 术语 | 英文 | 含义 |
|---|---|---|
| 心房颤动 | atrial fibrillation, AF | 心房电活动高度无序、RR 间期绝对不齐的心律失常 |
| 阵发性房颤 | paroxysmal AF, PAF | 发作性出现、可自行终止的房颤 |
| 房颤负荷 | AF burden | 一段时间内处于房颤状态的比例 |
| 检测 | detection | 判断「此刻是否处于房颤」 |
| 预测 | prediction | 判断「未来一段时间是否会进入房颤」 |
| 筛查 | screening | 在无症状人群中找出未被诊断的房颤 |
| 预测窗口 | prediction horizon | 提前多久给出预警 |
| 提前量 | lead time | 预警发出到发作之间的实际时间 |
| RR 间期 | RR interval | 相邻两个 QRS 峰的间隔 |
| 心率变异性 | heart rate variability, HRV | RR 序列的统计波动 |
| 变异系数 | coefficient of variation, CV | 标准差除以均值 |
| 记录组 | record set | 同一受试者的两段 30 分钟记录 |
| 延续记录 | continuation record | 接在主记录之后的 5 分钟片段 |
| 远段 | AF-distant segment | 前后各 45 分钟无发作的对照段 |
| QRS 注释 | QRS annotation | 心搏位置的样本索引 |
| WFDB | Waveform Database | PhysioNet 的信号与注释格式 |
| 采样率 | sampling rate | 每秒采样点数,本文为 128 Hz |
| 增益 | gain | 每毫伏对应的 ADC 单位数 |
| ODC-By | Open Data Commons Attribution | 要求署名的开放数据许可 |
| 先验 | prior | 人群中被检出的基础概率 |
| 阳性预测值 | PPV | 判为阳性者中真阳性的比例 |
| 配对分类 | paired classification | 每对样本里选一个正类 |
| 受试者泄漏 | subject leakage | 同一人的数据同时进入训练与测试 |
| 外部验证 | external validation | 在另一数据源上复核模型 |
| 前瞻性研究 | prospective study | 先设方案再随访的研究设计 |
| 这属于本表未收录的人群类别。 |
附录 F:三类人群的决策树
如果你只是想知道「房颤检测做到什么水平了」:不要从 afpdb 开始。先用 afdb 或 ltafdb 建立逐秒分割的基线,理解 AFIB/AFL/J 的标注体系,再回来看 afpdb,你会立刻明白它为什么做不了检测。
如果你要做「预测」且需要一个公共基准做对照:可以用 afpdb,但必须把任务表述为「30 分钟尺度的发作前窗口分类」,并且在论文里明说「这不是预警系统,是窗口级可分性检验」。任何把它的分数写成「预测准确率」的做法都会在评审环节被质疑。
如果你要做可穿戴筛查:跳过 afpdb。它的人群先验是人为拉高的,设备是 1990 年代的双导联动态心电,采样率 128 Hz,与今天的单导联光电容积或单导联心电相差一整代。这类工作应该去找真实筛查队列或者大规模开放心电库,afpdb 最多只能作为一个「历史基线」出现在讨论段。
如果你要把它的结论写进综述:只写「在 50 组学习集的学习者分组交叉验证中报告了某某分数」,不要写「房颤预测达到某某水平」。前者是事实陈述,后者是把一个封闭任务的分数外推成了领域结论,而后者恰恰是「预测至今没有公认效用证据」这一说法被反复误引的源头。
附录 G:一句话交接卡
交给下一位维护者时,需要传递的信息只有五条:这个库的编号是 afpdb,不是 afdb;它的正样本是发作前 30 分钟而不是房颤本身;它的负样本只有半小时;它的测试集没有答案;它的许可要求署名。这五条之外的一切,都可以从 PhysioNet 项目页重新读出来。
附录 A:核心数字核对表
| 项 | 值 | 来源 | 来源层级 |
|---|---|---|---|
| 受试者数 | 100 | 官方数据说明 | 官方 |
| 学习集受试者 | 48 | 官方数据说明 | 官方 |
| 每组记录数 | 4(学习集)/ 2(测试集) | 官方数据说明 | 官方 |
| 30 分钟记录数 | 200 | RECORDS 清单逐行计数 | 官方文件 |
| 延续记录数 | 100 | RECORDS 清单逐行计数 | 官方文件 |
| 30 分钟记录样本数 | 230400 | .hea 逐条核对 |
官方文件 |
| 延续记录样本数 | 38,400 | .hea 逐条核对 |
官方文件 |
| 采样率 | 128 Hz | .hea 逐条核对 |
官方文件 |
| 增益 | 200 / mV | .hea 逐条核对 |
官方文件 |
| 有效位深 | 12 位(名义) | .hea 逐条核对 |
官方文件 |
| 全库体积 | 191.4 MB(解压) | 项目页文件列表 | 官方 |
| ZIP 体积 | 191.6 MB | 项目页下载按钮 | 官方 |
| 许可 | ODC-By 1.0 | 项目页 Access 区块 | 官方 |
| DOI | 10.13026/C2H59W | 项目页 Discovery 区块 | 官方 |
| 发布日期 | 2001-03-01 | 项目页版本标注 | 官方 |
| 修订日期 | 2001-03-16 | 项目页 Release Info | 官方 |
| 远离段最小间隔 | 45 分钟 | 官方数据说明 | 官方 |
| 测试集组数 | 50 | 官方数据说明 | 官方 |
| 测试集约一半为房颤者 | 约 25 组 | 官方「approximately half」措辞 | 官方(近似口径) |
附录 B:数字的来源分层
第一层:官方页面与官方文件。 全部结构性数字——受试者数、组数、记录数、样本数、采样率、增益、体积、许可、DOI、版本日期——都来自 PhysioNet 1.0.0 项目页与 RECORDS、.hea 文件。这一层可以直接引用。
第二层:论文摘要。 方法类数字(例如某方法报告 96.5% 灵敏度、100% 特异度、100% 阳性预测值)来自 2011 年前后两篇使用 afpdb 的论文摘要。这一层只说明「有人在这个数据上报告过这个数」,不代表这个数是这个数据的固有属性,也不代表它是当前最好成绩。
第三层:未核实。 学习集里哪两组来自同一名受试者,官方没有说明,本文不做推断。测试集的组级与记录级真值,本文没有拿到,也不做推测。房颤在延续记录中的逐秒起点,本文未逐条核对。
附录 C:完整参考文献
- Moody GB, Goldberger AL, McClennen S, Swiryn SP. Predicting the Onset of Paroxysmal Atrial Fibrillation: The Computers in Cardiology Challenge 2001. Computers in Cardiology 28:113-116 (2001).
- PhysioNet. PAF Prediction Challenge Database, v1.0.0. https://physionet.org/content/afpdb/1.0.0/ (DOI: 10.13026/C2H59W)
- Mohebbi M, Ghassemian H, Asl BM. Prediction of paroxysmal atrial fibrillation using recurrence plot-based features of the RR-interval signal. Physiological Measurement 32(8):1147-1162 (2011). DOI: 10.1088/0967-3334/32/8/010
- Mohebbi M, Ghassemian H, Asl BM. Structures of the recurrence plot of heart rate variability signal as a tool for predicting the onset of paroxysmal atrial fibrillation. 2011. DOI: 10.4103/2228-7477.95295
- Mohebbi M, Ghassemian H. Prediction of paroxysmal atrial fibrillation based on non-linear analysis and spectrum and bispectrum features of the heart rate variability signal. Computer Methods and Programs in Biomedicine 105(1):40-49 (2012). DOI: 10.1016/j.cmpb.2010.07.011
- MIT-BIH Atrial Fibrillation Database(
afdb), PhysioNet. https://physionet.org/content/afdb/1.0.0/ (DOI: 10.13026/C2MW2D)
附录 D:任务代码
按典型的三种用法,给出可直接抄的骨架。第一段做组级分类(赛题第一问),第二段做记录级配对(第二问),第三段做时间轴复原后的一致性检查。
#-- 任务一:组级「是否有阵发性房颤」分类(n 组 vs p 组)
import numpy as np
import wfdb
def group_features(prefix, idx):
out = []
for suffix in ["", "c"]:
a, b = f"{prefix}{2 * idx - 1:02d}{suffix}", f"{prefix}{2 * idx:02d}{suffix}"
for name in (a, b):
ann = wfdb.rdann(name, "qrs", pn_dir="afpdb/1.0.0")
rr = np.diff(ann.sample) / 128.0
rr = rr[(rr > 0.3) & (rr < 2.0)]
out += [rr.std(), rr.std() / rr.mean(), np.mean(np.abs(np.diff(rr)) > 0.1)]
return np.array(out)
print(group_features("n", 1).round(4))
print(group_features("p", 1).round(4))
#-- 任务二:记录级配对,判断哪一段紧接着发作
import numpy as np
import wfdb
from sklearn.ensemble import RandomForestClassifier
def rr_stats(name):
ann = wfdb.rdann(name, "qrs", pn_dir="afpdb/1.0.0")
rr = np.diff(ann.sample) / 128.0
rr = rr[(rr > 0.3) & (rr < 2.0)]
return [rr.std(), rr.std() / rr.mean(),
np.mean(np.abs(np.diff(rr)) > 0.1),
np.mean(np.abs(np.diff(rr)) > 0.2)]
X, y = [], []
for i in range(1, 51):
X += [rr_stats(f"p{2 * i - 1:02d}"), rr_stats(f"p{2 * i:02d}")]
y += [0, 1]
clf = RandomForestClassifier(n_estimators=300, random_state=0).fit(X, y)
print(clf.feature_importances_.round(3))
#-- 任务三:拼轴后检查 QRS 间距是否出现异常跳变(边界校验)
import numpy as np
import wfdb
parts = ["p15", "p15c", "p16", "p16c"]
qrs, offset = [], 0
for name in parts:
ann = wfdb.rdann(name, "qrs", pn_dir="afpdb/1.0.0")
qrs.append(ann.sample + offset)
offset += wfdb.rdheader(name, pn_dir="afpdb/1.0.0").sig_len
qrs = np.concatenate(qrs)
rr = np.diff(qrs) / 128.0
bad = np.where((rr < 0.25) | (rr > 2.0))[0]
print("异常 RR 个数", len(bad), "位置(秒)", (qrs[bad] / 128.0).round(1)[:10])
第三段的价值在于它能同时暴露两类问题:QRS 检测器的漏检与误检,以及拼接边界是否真有零间隙。如果异常点集中在两个拼接点附近,说明边界处有样本级错位,后续的相位分析要避开这几秒。
附录 E:如果只记五件事
afpdb是 PAF Prediction Challenge Database,不是 MIT-BIH 房颤库afdb;两者页面、DOI、采样率、标注全不同。- 它的核心结构是「同一个人两段 30 分钟,一段止于发作前,一段远离发作」;配对设计是为了切断患者身份这条捷径。
- 全库没有节律标注,只有 QRS 位置;用它做检测是自建标签,用它做预测只有 30 分钟这一个尺度。
- 它上世纪的成绩(96% 量级)衡量的是配对可分性,不是临床预警效用;房颤预测至今没有公认的前瞻性效用证据。
- 体积极小(不到两百兆)、许可宽松、免注册,是最省事的起点,也正因如此被长期当作「房颤预测基准」,这个定位是惯性的,不是质量的。
编辑说明
本篇的候选名与实体的对应关系:任务派发的 slug 为 afpdb,写作前分别访问了 PhysioNet 的 afpdb 与 afdb 两个项目页,两者都返回 302 并跳转到各自的 1.0.0 版本页,均正常打开。核实结论是 afpdb 存在且即 PAF Prediction Challenge Database,afdb 是另一份 MIT-BIH 房颤库,因此本篇按 afpdb 实体撰写,slug 保持 afpdb,并在 §8.4 用对照表把两个易混实体分开。
核验方式与范围:项目页正文与「Data Description」段落全文读过;RECORDS 清单与 p01、p16、n01、t01、p16c 等头文件逐个取过;challenge 目录下的 ANNOTATORS 与 RECORDS 返回 404,未采信其中任何内容。论文侧通过 Europe PMC 检索到三篇使用该库的方法论文摘要,仅引用其报告的分数口径,正文未使用其数据描述。
未覆盖内容:学习集的受试者身份映射、测试集真值、延续记录中房颤的逐秒起点、n 组三类人群的具体人数拆分。以上四项官方均未公开,本篇不做推断。
本文的评分(§7.1)只针对「发作前 vs 远离发作」这一原始任务,不构成对该库在其他任务上的判断。文中所有对临床价值的陈述均为方法学讨论,不构成诊疗建议。
相关数据集导航
以下为站内 AI-Ready 数据集百科中与本词条共享多个主题标签的相关数据集,按相关度降序排列:
- incartdb — 共享标签:生理信号 / 心电信号 / 挑战赛数据集 / 心血管疾病
- aftdb — 共享标签:生理信号 / 心电信号 / 挑战赛数据集 / 心血管疾病
- twadb — 共享标签:生理信号 / 心电信号 / 挑战赛数据集 / 心血管疾病
- cinc-2021 — 共享标签:生理信号 / 心电信号 / 挑战赛数据集 / 心血管疾病
- ltafdb — 共享标签:生理信号 / 心电信号 / 挑战赛数据集 / 心血管疾病
- cinc-2011 — 共享标签:生理信号 / 心电信号 / 挑战赛数据集 / 心血管疾病
- cinc-2020 — 共享标签:生理信号 / 心电信号 / 挑战赛数据集 / 心血管疾病
- butppg — 共享标签:生理信号 / 心电信号 / 挑战赛数据集 / 心血管疾病
- ecg-image-database — 共享标签:生理信号 / 心电信号 / 挑战赛数据集 / 心血管疾病
- georgia-12-lead-ecg-challenge — 共享标签:生理信号 / 心电信号 / 挑战赛数据集 / 心血管疾病
导航说明:本章节由全站统一标签体系自动计算生成(标签重合度算法),双向可达;点击链接可跳转至对应数据集词条。

