房颤预测的元祖赛题 — PAF 预测挑战赛数据库

100 个受试者 × 双通道 ECG 128 Hz / 30 分钟窗口 / ODC-By 1.0

来源 https://physionet.org/content/afpdb/1.0.0/发布时间: 2026-10-27最后更新: 2026-10-27 阅读 18
房颤预测的元祖赛题 — PAF 预测挑战赛数据库

信息速览

数据集名称房颤预测的元祖赛题 — PAF 预测挑战赛数据库
数据类型时序信号,波形,人工标注
规模100 名受试者(学习集 48 名 + 测试集 50 名)
接入方式https://physionet.org/content/afpdb/1.0.0/
AI 就绪度

房颤的临床难点在一个常被混淆的地方:辨认「此刻是不是房颤」早就不难,难的是提前说出「这个人半小时后会进入房颤」。前者靠 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:完整参考文献

  1. 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).
  2. PhysioNet. PAF Prediction Challenge Database, v1.0.0. https://physionet.org/content/afpdb/1.0.0/ (DOI: 10.13026/C2H59W)
  3. 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
  4. 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
  5. 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
  6. 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:如果只记五件事

  1. afpdb 是 PAF Prediction Challenge Database,不是 MIT-BIH 房颤库 afdb;两者页面、DOI、采样率、标注全不同。
  2. 它的核心结构是「同一个人两段 30 分钟,一段止于发作前,一段远离发作」;配对设计是为了切断患者身份这条捷径。
  3. 全库没有节律标注,只有 QRS 位置;用它做检测是自建标签,用它做预测只有 30 分钟这一个尺度。
  4. 它上世纪的成绩(96% 量级)衡量的是配对可分性,不是临床预警效用;房颤预测至今没有公认的前瞻性效用证据。
  5. 体积极小(不到两百兆)、许可宽松、免注册,是最省事的起点,也正因如此被长期当作「房颤预测基准」,这个定位是惯性的,不是质量的。

编辑说明

本篇的候选名与实体的对应关系:任务派发的 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 — 共享标签:生理信号 / 心电信号 / 挑战赛数据集 / 心血管疾病

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

返回 AI-Ready 数据集