信息速览
心电压缩的论文里,压缩比(Compression Ratio,CR)与百分均方根差(Percent Root-mean-square Difference,PRD)几乎总是成对出现:前者说省了多少字节,后者说为此弄丢了多少波形。两个数字都不难算,难的是它们算在什么数据上。绝大多数压缩研究把实验做在 MIT-BIH 心律失常库上——那套记录三十分钟长、两通道、三百六十赫兹,波形规整、节律典型、噪声不高。在一套本来就很好压的数据上拿到八倍压缩比,说明不了算法在真实监护场景里撞上肌电干扰、基线漂移、频发室早与电极接触不良时会变成什么样。
MIT-BIH 心电压缩测试库就是冲着这个空档来的。它不追求覆盖,只追求难:一百六十八段、每段二十点四八秒的双通道心电,是专挑出来"不好压"的那一批。QRS 波群的陡峭跳变、缓慢爬行的基线漂移、噪声段、节律不齐的片段,都被有意收进来。官方的数据描述只有一句话,却把定位交代得毫不含糊:这些记录是为给压缩器、尤其是给有损压缩方法出难题而选的。
它的代价同样清楚,而且一眼就能看见。每段只有二十点四八秒,全库未压缩合计二点六兆字节;一条标注都没有,连患者年龄、性别、诊断都不给;头文件里两个通道的描述栏一律只写一个笼统的 ECG,不告诉你第一路是 II 还是 V1。它不是拿来训练模型的数据,是一把用来量压缩算法下限的尺子——而且是一把被有意磨薄的尺子。一屏速览见下表。
| 维度 | 内容 |
|---|---|
| 全名 | MIT-BIH ECG Compression Test Database |
| 发布方 | PhysioNet(由 MIT 计算生理实验室托管;署名作者 George Moody、Roger Mark) |
| 首次发布 | 1999 年 8 月 3 日,版本 1.0.0,至今仍是唯一版本 |
| 最近改动 | 版本历史仅 1.0.0 一条;目录内 .dat 与 .hea- 的日期为 1992-07-30,.xws 为 1999-12-12,.hea 为 2008-04-05 |
| 记录数 | 168 段 |
| 患者数 | 官方未公开;记录名中出现 38 个不同的五位源记录号 |
| 每段时长 | 20.48 秒,即 5120 个采样点 |
| 通道数 | 2,两路共用同一个 .dat |
| 采样率与分辨率 | 250 Hz;12 位模数转换;增益 400 ADC 单位/毫伏;ADC 零值 0 |
| 原始来源 | 官方未说明;记录名形如 08730_01,为源记录号加两位段号 |
| 标注规模 | 零。ANNOTATORS 文件内容为 [not annotated],目录内没有 .atr 文件 |
| 标注方式 | 不适用 |
| 文件格式 | WFDB:.dat(格式 212)/ .hea(现行头文件)/ .hea-(旧格式头文件)/ .xws(WAVEScript) |
| 体积 | 未压缩 2.6 MB,ZIP 包 2.7 MB;每段 .dat 固定 15360 字节 |
| 许可 | Open Data Commons Attribution License v1.0(ODC-BY 1.0),开放访问,无需注册 |
| 典型用途 | 压缩比—失真对照评测、有损压缩的失真定位、编解码器横向基准、可穿戴设备码率规划 |
§1 它要解决的具体麻烦
1.1 一份由被考人自己出的考卷
心电压缩这个方向有个结构性的尴尬:评价压缩算法好坏的指标(CR、PRD、PRDN、质量评分)都是纯数学量,谁都能算;但分母上那份"被压的数据"却从来没有被统一过。于是每篇论文都可以自己挑一套好压的记录,报出好看的压缩比,而审稿人很难判断这个数字里有多少来自算法、多少来自选数据。
这个尴尬在 1990 年代尤其明显。当时做压缩的人手边最方便的心电数据就是 MIT-BIH 心律失常库——它是为心律失常分析建的,选样时偏重"节律有料",波形本身反而相当规整。规整的波形对变换编码、对预测编码、对任何依赖相邻样本相关性的方法都格外友好。用这样一套数据当基准,等于让被考人自己出卷。
MIT-BIH 心电压缩测试库的出发点就是换一套卷子。官方那句话里最关键的两个词是"selected"(挑选)与"challenges"(难题):不是把一批记录原样搬上来,而是按"能不能把压缩器考住"这个标准反过来挑。这决定了它的全部性格——规模小、时长短、标注无,但因为每一段都是冲着难来的,它在"压不住"这件事上的信息密度远高于同等体量的随机采样。
1.2 三条常见的错误期待
第一条,期待它是"另一个 MIT-BIH 心律失常库"。 名字里都有 MIT-BIH,都是两通道,都是一段一段的,很容易被当成同一系列的又一份心律失常数据。但它的记录编号是五位数字加下划线(08730_01),与心律失常库的三位数(100–234)完全不同;目录里没有任何注释文件;头文件里没有年龄、性别、诊断。把它当心律失常库下载下来的人,第一件事通常是找标注,然后发现找不到。
第二条,期待它够长、够大、能训练模型。 一百六十八段、每段二十点四八秒,两段接起来还不到一分钟。全库未压缩二点六兆字节,一张低分辨率图片的大小。任何需要靠数据量换性能的用法在这里都会立刻撞墙。
第三条,期待它能代表"一般的心电"。 恰恰相反,它是按"难"这个标准挑出来的偏态样本。用它的平均压缩比去推算真实设备上的存储收益,会系统性低估——真实数据里既有难压段也有大片好压的平稳段,而这里只有前者。这一点在 §7.3 会展开。
1.3 它能给到的与给不到的
| 你的需求 | 它给的 | 够不够 |
|---|---|---|
| 压缩算法的压缩比—失真对照 | 168 段短记录,参数完全统一,可直接全量跑 | 够,这是它唯一的主场 |
| 有损压缩的失真空间定位 | 每段 5120 点,可逐点看误差落在哪个波群上 | 够,短段反而便于逐点检视 |
| 编解码器的横向对照 | 全库参数一致,不同算法在同一批数据上可直接比 | 够,且是同类比较里最省事的一组 |
| 无失真压缩的极限测试 | 12 位整数量化、格式 212,可算无损压缩比 | 够,但注意它本身已是打包格式 |
| 心搏分类训练 | — | 不够,一条标注都没有 |
| 房颤检测 | — | 不够,无节律标签、无患者信息 |
| 心率变异性分析 | 20.48 秒一段,可算极短程变异 | 不够,段长不足以支撑常规指标 |
| 患者级建模 | — | 不够,无患者标识 |
| 长时程场景的存储仿真 | 每段独立 20.48 秒 | 不够,无法还原连续记录的跨段冗余 |
| 诊断级形态学研究 | 只有两路、无导联名、无临床上下文 | 不够 |
1.4 你的处境与它能帮到的事
| 你的处境 | 它能帮你什么 | 下一步该做什么 |
|---|---|---|
| 你写了一个压缩算法,只在一套数据上报过数 | 提供一份公认的、参数统一的第二考场 | 全量 168 段跑一遍,按段报告 CR 与 PRD 而不是只报均值 |
| 你在做可穿戴设备的码率规划,担心算错底线 | 提供一份"难压样本"的下界参考 | 拿它当压力上限,再用自己的真实数据校准 |
| 你要复现某篇压缩论文的数字 | 体量只有 2.6 MB,几分钟就能下全 | 核对对方用的段集合与 PRD 定义,再跑 |
| 你想说明"我的方法的失真集中在 QRS 上" | 段短、采样点少,可以整段画误差曲线 | 逐段画原始与重建的叠加图,挑最差几段当例子 |
| 你要评测一个嵌入式编解码器 | 全库只有 2.6 MB,适合在板子上全量跑 | 记录每段耗时,注意第一段常有初始化开销 |
| 你在找带标注的心电训练集 | 什么都帮不上 | 换库,见 §6.3 的分工表 |
1.5 一句话判断
它不是心电数据的又一个来源,而是一份"难度考试卷":一百六十八道二十秒的短题,每一道都是为了让你现有的压缩器露出破绽才被挑进来的。
1.6 不适合谁:四条边界
不适合当训练集。 一百六十八段、约五十七分钟的总时长(168 × 20.48 秒),任何需要靠样本量换性能的深度模型都会立刻过拟合;而且没有标签,连监督信号都没有。
不适合做临床研究。 没有年龄、性别、诊断、用药、结局中的任何一项。它是一批波形,不是一批病人。
不适合做长时程信号特性研究。 每段二十点四八秒是刻意切短的,段与段之间的连续性、跨段的冗余结构都被切断了。想研究"连续记录里有多少冗余可压",它给不出答案。
不适合拿它的绝对数值当行业基准直接引用。 因为它是按难挑的,它的平均压缩比天然低于随机采样的数据。把它报出的数字当成"心电一般能压到几倍",是方向性的误读。
§2 数据构成与规格
2.1 一段记录由四个文件组成
数据库沿用 WFDB(Waveform Database)格式,这是 PhysioNet 自 1980 年代沿用至今的波形存储规范。每一段记录对应同名的四个文件,缺一个就会出现读取问题:
.dat 是信号本体,二进制,按采样点交错存放两个通道的整数值;.hea 是现行格式的纯文本头文件,声明采样率、信号条数、增益、初值与校验和;.hea- 是同一段记录的旧格式头文件,保留下来是为了兼容老版本工具;.xws 是 WAVEScript,一个几十字节的小脚本,供 PhysioNet 的波形查看器直接定位到这段记录。
三者之外的 .hea- 值得专门说一句。它在大多数 PhysioNet 库里并不常见,而这里一百六十八段全都带。原因是这批文件的时间戳停在 1992 年,头文件格式在这之后经历过规范化修订(字段顺序与描述栏写法都有调整),平台于是把旧版头文件原样留了一份,同时生成一份新版。两者声明的是同一份 .dat,读哪一份都能拿到同样的信号,但字段解释上略有差别(§2.3)。
根目录另有四个附属文件:RECORDS 列出全部一百六十八个记录名;ANNOTATORS 只有一行 [not annotated];HEADER.html 是早期 PhysioBank 时代的说明页归档;SHA256SUMS.txt 给出每个文件的校验值,共 675 行。整个目录在对象存储侧共 676 个对象,即 168 × 4 加四个附属文件。
2.2 字段/记录的完整字典
需要强调:这里的"字段"不是表格列,而是 WFDB 头文件的记录行与信号行上的位置。下表把能拿到的信息项逐条列出,并标出哪些是官方给了的、哪些是这里根本没有的。
| 信息项 | 所在位置 | 本库取值 | 含义 | 使用注意 |
|---|---|---|---|---|
| 记录名 | .hea 第 1 行首字段 |
08730_01–13687_04 |
源记录号加段号 | 字面量就是文件名前缀 |
| 信号条数 | .hea 第 1 行第 2 字段 |
2 |
通道数 | 一百六十八条记录全部为 2 |
| 采样率 | .hea 第 1 行第 3 字段 |
250 |
每秒采样点数 | 与心律失常库的 360 Hz 不同 |
| 采样点数 | .hea 第 1 行第 4 字段 |
5120 |
单通道总长度 | 5120 ÷ 250 = 20.48 秒 |
| 文件名 | .hea 信号行第 1 字段 |
如 08730_01.dat |
信号存放位置 | 两路共用同一个 .dat |
| 存储格式 | .hea 信号行第 2 字段 |
212 |
每两个采样点打包进 3 字节 | 不是 16 位也不是 8 位 |
| 增益 | .hea 信号行第 3 字段 |
400 |
ADC 单位/毫伏 | 全库恒定,2.5 微伏/单位 |
| 位分辨率 | .hea 信号行第 4 字段 |
12 |
模数转换位数 | 决定量化噪声底 |
| ADC 零值 | .hea 信号行第 5 字段 |
0 |
零电平对应的整数值 | 换算时先减零值 |
| 初始值 | .hea 信号行第 6 字段 |
如 -67 |
首采样点整数值 | 逐段不同,可被用于校验 |
| 校验和 | .hea 信号行第 7 字段 |
如 -16108 |
采样点累加和 | 用于检测 .dat 是否完整 |
| 块大小 | .hea 信号行第 8 字段 |
0 |
每帧字节数 | 0 表示由工具自动计算 |
| 通道描述 | .hea 信号行第 9 字段 |
ECG |
信号类型 | 两路都只写 ECG,无导联名 |
| 年龄 | — | 无 | — | 头文件没有注释行 |
| 性别 | — | 无 | — | 同上 |
| 诊断 | — | 无 | — | 同上 |
| 患者编号 | — | 无 | — | 只有源记录号可作分组依据 |
| 心搏位置与类型 | — | 无 | — | 目录内没有 .atr 文件 |
| 文件校验值 | SHA256SUMS.txt |
675 行 | 逐文件 SHA-256 | 下载后核对用 |
这张表最该被记住的是下半部分:那些"无"不是遗漏,而是它的设计。它是一份纯波形基准,一切与临床语义有关的信息都被有意剥掉了。
2.3 一条记录的完整解剖
以 13005_01 为例。现行格式头文件只有三行,没有任何以 # 开头的注释行:
13005_01 2 250 5120
13005_01.dat 212 400 12 0 -26 5997 0 ECG
13005_01.dat 212 400 12 0 81 -29113 0 ECG
第一行读作:记录名 13005_01,2 个通道,250 Hz,每通道 5120 个采样点。第二、三行分别是两路信号的声明,字段从左到右是文件名、存储格式、增益、位分辨率、ADC 零值、初始值、校验和、块大小、通道描述。
对照同名旧格式头文件 13005_01.hea-,两行的数值完全一致,差别只在末尾的描述栏:旧格式写 ECG signal 0、ECG signal 1,现行格式写 ECG。也就是说,通道身份从一开始就只有"第 0 路""第 1 路"这种位置序号,从来没有给过解剖学导联名。
读取信号的标准写法:
import wfdb
import numpy as np
#-- 远程直读,无需先下载;pn_dir 指向 PhysioNet 上的库名
rec = wfdb.rdrecord('13005_01', pn_dir='cdb')
print(rec.fs, rec.sig_len, rec.sig_name) #-- 250 5120 ['ECG', 'ECG']
print(rec.p_signal.shape) #-- (5120, 2) 已换算成毫伏
print(rec.units, rec.adc_gain) #-- ['mV','mV'] [400. 400.]
print(rec.comments) #-- [] 空列表,无患者信息
#-- 原始整数量与物理量的换算关系
raw = rec.d_signal
print(raw.min(), raw.max()) #-- 12 位量化后的整数范围
print(np.allclose(raw / 400.0, rec.p_signal))
p_signal 是已按增益与零值换算过的浮点毫伏值;d_signal 是原始整数。因为本库增益恒定为 400、零值恒定为 0,两者之间就是一个除以 400 的关系——这跟增益逐记录变化的库(如 INCART)不一样,是它少数几个"省心"的地方。
顺带一提,想确认自己下载的 .dat 有没有损坏,不需要额外工具:头文件里第 7 字段那个校验和就是该通道全部采样点的累加和,逐点相加比对即可。
2.4 记录命名的两段式结构
一百六十八个记录名全部形如 NNNNN_SS:前五位是源记录号,后两位是段号。RECORDS 里一共出现 38 个不同的源记录号(从 08730 到 13687),段号从 _01 一直排到 _12。
这个结构透露了两件事。第一,这些短段是从更长的心电记录里切出来的——同一源记录号下的若干段,来自同一次采集、同一套电极、同一个放大器,彼此之间的相关性极强。第二,每段被精确切到 5120 个采样点,也就是 20.48 秒,没有例外。
由此产生一个容易被忽略的方法论问题:如果你按段随机划分训练集与测试集,同一源记录号下的兄弟段会同时出现在两侧。对压缩算法评测来说,这件事的影响比分类任务小——压缩评测通常不做训练—测试划分,而是全量跑一遍报指标。但一旦有人想在这里做"学习的压缩"(比如用神经网络做端到端编解码并在本库上训练),这个泄漏就不可回避,正确做法是按源记录号分组(§7.3)。
官方页面没有说明这 38 个源记录来自哪里。它们与 MIT-BIH 心律失常库的 100–234 号、房颤库的 00735–08455 号、长时程库的 14046–15814 号都对不上,也不是任何一套公开 MIT-BIH 库的记录号。这一点已列入 §10 的未核验内容。
2.5 与同类资源的字段差异
| 对比项 | 本库(cdb) | MIT-BIH 心律失常库 | MIT-BIH 房颤库 | MIT-BIH 噪声压力测试库 |
|---|---|---|---|---|
| 通道数 | 2 | 2 | 2 | 2(含 3 段纯噪声) |
| 单段时长 | 20.48 秒 | 约 30 分钟 | 约 10 小时 | 30 分钟 |
| 采样率 | 250 Hz | 360 Hz | 250 Hz | 360 Hz |
| 记录数 | 168 段 | 48 段 | 25 段 | 12 段加 3 段噪声 |
| 位分辨率 | 12 | 11 | 12 | 12 |
| 逐搏标注 | 无 | 有 | 有(部分) | 无 |
| 患者信息 | 无 | 部分 | 部分 | 无 |
| 导联/通道名 | ECG |
如 MLII、V1 |
未明确 | 未明确 |
| 设计目的 | 考验压缩算法 | 心律失常分析 | 房颤研究 | 噪声鲁棒性研究 |
| 许可 | ODC-BY 1.0 | ODC-BY 1.0 | ODC-BY 1.0 | ODC-BY 1.0 |
这张表要说清的只有一件事:本库在"时长"这一列上极端地短,在"记录数"这一列上意外地多。它用一百六十八个短样本换取了一百六十八种不同的难法——这正是压缩评测需要的形状,也正是一切其他任务避之不及的形状。
2.6 格式 212 的字节账与"ECG"这个描述
格式 212 是 WFDB 里为双通道 12 位信号设计的一种紧凑打包:同一时刻的两路采样值不各自占满两字节,而是把连续的采样点按 12 位切分,每两个采样点共占 3 字节。按本库的参数算一遍:
| 项 | 数值 | 推算 |
|---|---|---|
| 每通道采样点 | 5120 | 头文件第 1 行第 4 字段 |
| 双通道采样点合计 | 10240 | 5120 × 2 |
| 每两个采样点占字节 | 3 | 格式 212 的定义 |
单段 .dat 字节数 |
15360 | 10240 ÷ 2 × 3 |
| 目录所列大小 | 15360 | 一百六十八个 .dat 大小全部相同 |
| 全库未压缩体积 | 2.6 MB | 官方页面声明 |
这张字节账的意义在于:它同时锁死了"能压多少"的参照系。无损压缩面对的输入已经是 12 位打包后的 15360 字节,而不是 5120 × 2 × 2 = 20480 字节的朴素 16 位表示。报无损压缩比时不说清这一点,数字会比别人好看一截,而那一截与算法无关。
至于通道描述栏里那个孤零零的 ECG:它意味着官方没有承诺这两路是肢体导联还是胸前导联,也没有承诺第一路是什么位置。做压缩评测不需要知道这个;但只要研究问题稍稍离开"压得好不好"——比如想比较不同解剖位置的可压性——这条信息就从"可有可无"变成了"根本缺口"。
§3 它是谁的数据
3.1 贡献者与机构
官方页面把这份数据归于 George Moody 与 Roger Mark 两人,二人署名机构均为 MIT 计算生理实验室(MIT Laboratory for Computational Physiology)。平台侧由 PhysioNet 托管,页脚注明维护方为该实验室,资助来自美国国立生物医学影像与生物工程研究所(NIBIB)、国立心肺血液研究所(NHLBI)与 NIH 主任办公室。
值得注意的是官方指定的引用文献有三位作者——Moody、Mark 与 Goldberger,而数据资源的署名只有前两位。这种"论文作者多于资源作者"的情况在 PhysioNet 早期库里很常见,它说明的是:这批数据最初是为一个具体算法(TRIM 压缩器)的评测而整理的,后来才被平台收编为公共资源。这也解释了它为什么没有患者信息、没有标注——原始用途根本不需要这些。
3.2 时间线
| 时间 | 事件 | 意义 |
|---|---|---|
| 1988 | Moody、Mark、Goldberger 在 Computers in Cardiology 发表 TRIM 压缩器评测论文 | 数据的原始用途确立 |
| 1992-07-30 | 目录内 .dat 与 .hea- 的时间戳 |
信号文件与旧格式头文件的落盘时间 |
| 1999-08-03 | 在 PhysioNet 上线,版本 1.0.0 | 首次公开发布 |
| 1999-12-12 | .xws 脚本的时间戳 |
波形查看器接入 |
| 2008-04-05 | .hea 头文件的时间戳 |
头文件格式规范化后的版本 |
| 2023-12-22 | 对象存储侧全部文件的统一时间戳 | 托管基础设施迁移,非内容修订 |
官方版本历史里只有 1.0.0 一条,日期 1999 年 8 月 3 日。也就是说,发布二十多年以来它既没有扩版,也没有修订记录。这在 PhysioNet 里不算特例,但对使用者是个信号:这里的任何数字都对应唯一的版本号,不存在"用新版还是旧版"的选择困难,只有"用新格式头文件还是旧格式头文件"这个小差别(§2.1)。
3.3 采集场景:一份被剥干净的波形
关于采集场景,官方给的信息少得惊人,一共只有一句"168 段短心电记录,为给压缩器出难题而选"。没有采集机构、没有设备型号、没有人群描述、没有筛选流程。
可以从文件里读出来的只有三层结构信息。其一,全部记录为双通道、250 Hz、12 位,这与 MIT 早期动态心电采集的常规配置一致;其二,记录名的五位源记录号与段号说明它们来自若干次更长的采集,被切成等长片段;其三,ANNOTATORS 明确写着 [not annotated],说明平台侧从未为它组织过标注工作。
至于"难"具体难在哪、按什么标准挑的、有没有排除某些类型的段,官方页面与目录内说明页都没有交代,也没有对应的技术文档。这是理解这份数据时必须接受的一个空白:你知道它难,但不知道它为什么难,只能自己把一百六十八段跑一遍去看。§4.3 给的正是这样一段可复现的观察代码。
3.4 数据来源的三个层次
理解这份数据要先分清三层:原始采集(不可得)、等长片段(公开的基本单位)、压缩评测指标(由使用者自己算)。第一层不可得,意味着你无法验证某一段是不是被有意截取了最难的一段;第二层决定了每段都是 5120 个采样点,边界整齐;第三层说明这个库本身不提供任何"分数",分数是你自己跑出来的。
这三层叠加出一个结论:它是一个考场,不是一本答案册。考场提供题目与统一的规格,分数由考生自己算,而算分口径是否统一,取决于使用者之间有没有约定(§5.5)。
§4 它最独特的东西
4.1 为"难"而挑,不为"全"而挑
公开数据集的选样逻辑大致分三种:随机抽样(代表总体)、按病种富集(代表某类病人)、按难度富集(代表某类困难)。前两种占绝大多数,第三种极其少见,因为它刻意放弃"代表性"——而代表性恰恰是多数研究的立身之本。
本库是第三种。它的全部统计特性因此都与"自然人群的心电"不同:难压的段被放大,好压的段被剔除。这不是缺陷,而是它的定义。一旦把它当成代表性样本去推算真实场景的存储收益,结论就会偏;一旦把它当成一个"把算法逼到极限"的考场,它就是目前公开数据里最直接的那一个。
这个定位还有一层副作用值得说:因为它是按难挑的,不同算法在它上面的排序比绝对值更可信。绝对值受选样偏斜影响,排序相对不受影响。所以用它写论文时,最有说服力的句式是"在相同口径下,方法 A 的 PRD 低于方法 B",而不是"方法 A 在心电压缩上达到了 X 倍的压缩比"。
4.2 20.48 秒这个数字为什么是它
20.48 秒不是一个随手的长度。它等于 5120 个采样点除以 250 Hz,而 5120 是 2 的十次幂乘五(5120 = 1024 × 5)。这个长度对做变换的人格外友好:1024 点的整倍数可以干净地切进 FFT 与小波分解的层级结构,不需要补零,也不会出现跨段边界的伪影。
同时它足够短,短到可以让一百六十八段全部被完整检视。全库未压缩只有 2.6 MB,意味着任何一台机器都能在几分钟内把每一段的重建误差曲线画出来。这是"按难度富集"能落地的前提——挑难样本需要人工或半人工逐段看,段太长就看不完。
代价是它无法回答连续长记录里的问题。真实的可穿戴设备面对的是连续几小时甚至几天的波形,跨段的字典复用、基线的长程漂移、活动状态的缓慢切换,在 20.48 秒的片段里全都不存在。想研究这些,它给不了(§1.6 第四条边界)。
4.3 案例:把一段记录压一遍,看误差落在哪
下面的例子不依赖任何具体压缩器,只用两个最朴素的基线:抽点降采样与均匀量化。它的用途是给出一套可复现的口径,让你能把"这段好不好压"变成一个具体数字。
import wfdb
import numpy as np
#-- 取一段记录,两路都读成毫伏
rec = wfdb.rdrecord('13005_01', pn_dir='cdb')
x = rec.p_signal #-- (5120, 2)
fs = rec.fs #-- 250
def prd(a, b):
#-- 百分均方根差:重建误差能量占原信号能量的比例
return float(np.sqrt(np.sum((a - b) ** 2) / np.sum(a ** 2)) * 100)
def cr(n_in, n_out):
#-- 压缩比:原始比特数 / 压缩后比特数
return n_in / n_out
#-- 基线一:每隔 k 个点抽一个,再用线性插值补回
k = 4
idx = np.arange(0, x.shape[0], k)
up = np.stack([np.interp(np.arange(x.shape[0]), idx, x[idx, c])
for c in range(x.shape[1])], axis=1)
print('降采样 4 倍 -> CR', cr(x.size, idx.size * x.shape[1]), ' PRD', round(prd(x, up), 3))
#-- 基线二:把 12 位量化的步长再粗化一倍
step = 2 / 400.0 #-- 增益 400,故 1 个整数单位 = 1/400 mV
q = np.round(x / step) * step
print('量化步长加倍 -> CR', cr(12, 11), ' PRD', round(prd(x, q), 3))
跑出来的两组数字会告诉你一件很具体的事:抽点降采样对平缓的基线几乎无损,但会直接抹掉 QRS 波群的峰尖;粗化量化则相反,它伤的是低幅段与 P 波。两类误差形状完全不同,而这在一份只报单个 PRD 的论文里是看不出来的。
把这段实验推广到全库,就得到一个更有用的做法:逐段计算 PRD,然后去看最差的那些段长什么样。本库的价值恰恰集中在尾部——那些让所有算法都难看的段,是"按难度富集"真正买回来的东西。
4.4 标注的粒度:这里没有标注
这一节的标题就是结论。目录里没有 .atr 文件,ANNOTATORS 写着 [not annotated],头文件没有注释行。没有心搏位置,没有心搏类型,没有节律标签,没有信号质量标记,也没有噪声段的边界。
由此推出三条直接影响。第一,任何需要监督信号的用法都不能用它,包括心搏分类、房颤检测、信号质量评分的监督式训练。第二,连"这一段是不是噪声段"这样的基本判断都没有官方答案,只能自己算信噪比或看波形。第三,它也反过来成为一种自由:因为没有标准答案,你可以把自己的检测结果当作真值在上面做一致性分析,而不必担心与官方标注冲突。
对压缩评测而言,没有标注几乎不构成障碍——压缩是无监督的,输入波形、输出波形、比一比就够了。这正是它能以"零标注"姿态存在二十多年而无人抱怨的原因。
4.5 边界条件:什么情况下它不再适用
| 条件 | 是否适用 | 判断依据 |
|---|---|---|
| 压缩算法的横向对照 | 适用,且是最合适的公开场地 | 参数统一、段长统一、全库可全量跑 |
| 有损压缩的失真定位 | 适用 | 5120 点一段,可逐点画误差 |
| 无失真压缩测试 | 适用,但要先说清输入是 212 打包 | 见 §2.6 的字节账 |
| 端到端学习式编解码的训练 | 谨慎 | 168 段太少,且需按源记录号分组 |
| 真实设备的存储收益推算 | 不适用 | 按难度富集,绝对值偏保守 |
| 心搏/节律类监督任务 | 不适用 | 零标注 |
| 长时程冗余结构研究 | 不适用 | 每段独立 20.48 秒 |
| 人群差异或临床结论 | 不适用 | 无患者信息、无采集背景 |
| 多中心泛化评测 | 不适用 | 单一来源、来源机构未公开 |
这张表里最容易被忽略的是第四行。随着端到端学习式压缩兴起,开始有人在小库上训练编解码网络,而本库一百六十八段、三十八个源记录号的结构正好踩在两个坑上:样本量远小于常规需求,而按段随机划分会导致同一源记录的兄弟段跨在训练与测试两侧。要做,就必须按源记录号分组(§7.3)。
§5 怎么拿到
5.1 目录结构与下载面
PhysioNet 的文件目录是扁平的:根目录下直接放一百六十八组文件,每组四个,加上四个附属文件,没有子目录。
| 路径 | 内容 | 大小 |
|---|---|---|
/files/cdb/1.0.0/RECORDS |
168 行记录名 | 约 1.4 KB |
/files/cdb/1.0.0/ANNOTATORS |
一行 [not annotated] |
16 B |
/files/cdb/1.0.0/HEADER.html |
早期 PhysioBank 说明页归档 | 数 KB |
/files/cdb/1.0.0/SHA256SUMS.txt |
逐文件校验值,675 行 | 数十 KB |
/files/cdb/1.0.0/NNNNN_SS.dat |
双通道信号,格式 212 | 15360 B,全部相同 |
/files/cdb/1.0.0/NNNNN_SS.hea |
现行格式头文件 | 102–110 B |
/files/cdb/1.0.0/NNNNN_SS.hea- |
旧格式头文件 | 120–128 B |
/files/cdb/1.0.0/NNNNN_SS.xws |
WAVEScript 查看脚本 | 86 B,全部相同 |
官方同时提供整库 ZIP(2.7 MB)与对象存储同步两种方式。全库只有 2.6 MB,是全站最容易一次下完的心电库之一——这一点在使用体验上很实在:不需要挑子集,不需要断点续传,一条命令就能拿到全部。
5.2 批量下载
#-- 方式一:整库 ZIP(2.7 MB),走官方的 get-zip 端点
curl -sL -o cdb.zip https://physionet.org/content/cdb/get-zip/1.0.0/
#-- 方式二:按目录递归拉取,只取需要的后缀
wget -r -N -c -np --accept=hea,dat \
--directory-prefix=cdb \
https://physionet.org/files/cdb/1.0.0/
#-- 方式三:对象存储同步(无需凭证)
aws s3 sync --no-sign-request s3://physionet-open/cdb/1.0.0/ ./cdb
#-- 核对记录数:按空白切分,别用 wc -l
python3.11 - <<'PY'
import urllib.request
t = urllib.request.urlopen('https://physionet.org/files/cdb/1.0.0/RECORDS').read().decode()
names = t.split()
print('段数', len(names), '源记录数', len({n.split('_')[0] for n in names}))
print('首', names[0], '末', names[-1])
PY
最后一段特意写出来,是因为 RECORDS 这类文件在部分库里末行没有换行符,用 wc -l 统计会少一条。按空白切分计数不受影响。本库的一百六十八这个数字应当以这种方式核对。
5.3 用 wfdb 直接流式读取
import wfdb
names = wfdb.get_record_list('cdb')
print(len(names)) #-- 168
#-- 逐段读取,不落盘;这里只累计每段的峰峰值
import numpy as np
rng = []
for n in names[:8]:
rec = wfdb.rdrecord(n, pn_dir='cdb')
rng.append((n, float(np.ptp(rec.p_signal[:, 0])), float(np.ptp(rec.p_signal[:, 1]))))
for n, a, b in rng:
print(n, '通道0 峰峰值 %6.2f mV 通道1 峰峰值 %6.2f mV' % (a, b))
get_record_list 从 RECORDS 解析,能正确处理末行无换行的情况。这个接口的好处是不需要本地磁盘;代价是每段都要走一次网络,全库过一遍需要一些时间。全库只有 2.6 MB,一次性拉下来再本地读通常更划算。
5.4 转成通用的表结构
如果下游是常规机器学习管线,通常需要把波形落成定长数组。本库每段长度恒定,这一步格外省事:
import wfdb
import numpy as np
names = wfdb.get_record_list('cdb')
X = np.empty((len(names), 5120, 2), dtype='float32')
src = [] #-- 源记录号,用作分组键
for i, n in enumerate(names):
rec = wfdb.rdrecord(n, pn_dir='cdb')
X[i] = rec.p_signal.astype('float32')
src.append(n.split('_')[0])
np.save('cdb_wave.npy', X)
print(X.shape, len(set(src))) #-- (168, 5120, 2) 38
注意 src 这一列。它是全库唯一可用的分组依据——没有患者编号,没有采集日期,只有它。任何需要划分训练集与测试集的用法都必须靠它(§7.3)。
5.5 压缩评测的口径
这个库不提供分数,分数口径要自己定,并且必须写进方法节。最低限度要说清四件事:
| 口径项 | 常见做法 | 为什么必须写 |
|---|---|---|
| 压缩比的分母 | 原始比特数按 12 位×采样点算,还是按 212 打包后的字节算 | 两种算法差 4/3,直接决定数字好不好看 |
| PRD 的定义 | 是否先去均值、是否按通道分别算再平均 | 逐段差异可达数倍 |
| 是否含头文件 | 通常不含 | 含了会稀释真实压缩比 |
| 段集合 | 全量 168 段,还是某个子集 | 挑子集等于重选出卷人 |
把这四项固定下来之后,最简单的全量评测代码大致是这样:
import wfdb
import numpy as np
def prd(a, b):
#-- 先去均值,避免基线电平影响;逐通道算再取平均
a0, b0 = a - a.mean(), b - b.mean()
return float(np.sqrt(np.sum((a0 - b0) ** 2) / np.sum(a0 ** 2)) * 100)
names = wfdb.get_record_list('cdb')
rows = []
for n in names:
x = wfdb.rdrecord(n, pn_dir='cdb').p_signal
xhat = my_codec(x) #-- 换成你自己的编解码器
rows.append((n, prd(x[:, 0], xhat[:, 0]), prd(x[:, 1], xhat[:, 1])))
arr = np.array([(r[1], r[2]) for r in rows])
print('逐段 PRD 最大 %.3f 最小 %.3f' % (arr.max(), arr.min()))
print('最难压的五段:', sorted(rows, key=lambda r: -(r[1] + r[2]))[:5])
最后一行是本库最该被利用的一句:它把"哪几段最难"直接列出来。拿到这五段去看波形,往往比盯着平均数字更有信息量。
5.6 五个最容易出错的地方
| 位置 | 错误写法 | 后果 | 正确做法 |
|---|---|---|---|
| 统计段数 | wc -l RECORDS |
末行无换行时会少算 | 按空白切分计数 |
| 压缩比分母 | 按 16 位算原始大小 | 比真实值虚高 4/3 | 按 12 位或 212 打包字节算,并写明 |
| 数据划分 | 按段随机切分 | 同一源记录的兄弟段跨在两侧 | 按源记录号分组 |
| 采样率 | 当成 360 Hz | 时间轴与频域分析全错 | 头文件明确是 250 Hz |
| 导联假设 | 当成 II 加 V1 之类的标准配对 | 任何依赖导联的解释都不成立 | 官方只写 ECG,不要推断导联 |
§6 与库内条目的关系
6.1 关系图谱
把它摆进心电类数据的坐标系里,最合适的两个轴是"单段时长"与"是否有标注":
| 条目 | 单段时长 | 标注 | 与它的关系 |
|---|---|---|---|
| 本库(cdb) | 20.48 秒 | 无 | 坐标系里最短且无标注的一角 |
| MIT-BIH 心律失常库 | 约 30 分钟 | 逐搏 | 常被拿来当压缩基准的那个库,本库是它的"换卷"方案 |
| LTDB 长时程心电库 | 14–22 小时 | 逐搏 | 另一个极端:长而有标注 |
| AFDB 房颤库 | 约 10 小时 | 部分 | 长时程节律研究的主力,与压缩评测无关 |
| BUT QDB 信号质量库 | 短片段 | 质量标签 | 同属"小体量专用评测集",主题是质量而非压缩 |
| MACECGDB 运动伪迹库 | 分钟级 | 部分 | 同属"专挑极端条件",主题是运动伪迹 |
这条关系线要说的是:本库在"时长"轴上的位置是孤立的。它比所有常规心电库都短一个数量级以上,这不是偷懒,而是"按难度富集"这个选样逻辑的必然结果——短才能多,多才能覆盖足够多的难法。
6.2 检索路径
| 你要找的东西 | 检索词 | 会落到哪里 |
|---|---|---|
| 同一套读写工具的其他库 | WFDB、wfdb、PhysioBank |
§2.1 的格式说明在多个库通用 |
| 想换一套有标注的心电 | mitdb、ltstdb、ltdb |
§6.1 表 |
| 想找长时程压缩评测数据 | ltdb、ltstdb |
§1.6 第四条边界 |
| 想找同样"专挑极端"的小库 | nstdb、macecgdb、butqdb |
§6.1 表与 §8.3 |
| 想核对 WFDB 字段含义 | wfdb header、format 212 |
§2.2 的字段字典 |
| 压缩评测指标的通行定义 | CR、PRD、PRDN |
§5.5 的口径表 |
6.3 与其他库的分工判据
选数据集时最实际的问题不是"哪个最好",而是"哪个负责哪一段"。下面把常见任务逐项拆开:
| 任务 | 主粮仓该用什么 | 本库的位置 | 原因 |
|---|---|---|---|
| 压缩算法的横向对照 | 无替代 | 主力 | 唯一按难度富集的公开心电压缩集 |
| 有损压缩的失真分析 | 无替代 | 主力 | 段短、参数统一 |
| 心搏分类训练 | 大体量逐搏标注库 | 不适用 | 零标注 |
| 房颤检测 | 长时程节律库 | 不适用 | 无节律标签、段太短 |
| 心率变异性 | 长时程 RR 序列库 | 不适用 | 20.48 秒不足以算常规指标 |
| 信号质量评分 | 带质量标签的库 | 不适用 | 无质量标签 |
| 噪声鲁棒性 | 噪声压力测试库 | 不参与 | 它不含噪声段标注 |
| 教学演示压缩原理 | 任意 | 可用 | 2.6 MB 全库,便于课堂演示 |
这张表最该被记住的是前两行与其余各行之间的落差:它的适用面窄得异常,但在那两格里几乎没有对手。这不是"用途广泛的小库",是"用途单一的专用件"。
§7 质量评估与局限性
7.0 阅读本节前的一个前置校正
先纠正一个常见误判:把"没有标注、没有患者信息"当成"数据质量差"。这两件事在数据质量的通常含义上是两回事。标注与人口学信息属于语义层,而压缩评测只需要波形层——波形层的完整性,它给得很足:一百六十八段、参数完全统一、逐文件有 SHA-256 校验值、头文件带校验和、新旧两套头文件都在。
语义层的空白是设计选择,不是疏漏。1988 年那篇论文要回答的问题是"TRIM 压缩器在有损模式下丢了多少信息",这个问题不需要知道患者几岁。把这份数据后来用于别的任务时,缺失才成为问题;而在它自己的任务上,缺失不构成扣分。
这个校正会改变下面五维评分的读法:语义层相关的维度分数低,是定位使然,不是质量事故。
7.1 五维评分
按 DAIMS 的常用维度打分,每项 1–5 分,分数针对"作为心电压缩算法评测数据"这个用途:
| 维度 | 分数 | 理由 |
|---|---|---|
| 数据规模 | 2 | 168 段、约 57 分钟总时长,横向对照够用,训练不足 |
| 标注质量 | 1 | 零标注;本维度对它不适用,按缺失计 |
| 任务适配 | 5 | 按难度富集的短段集,压缩评测几乎没有替代品 |
| 文档完整 | 2 | 官方描述仅一句话,采集背景、选样标准、来源均未交代 |
| 可获取性 | 5 | 免注册、ODC-BY、2.6 MB,一条命令下完 |
五项合计 15 分(满分 25)。这个分数结构说明了它的性格:可获取性与任务适配拉满,规模与文档拖后腿,标注一栏按缺失计。它不是"全能型"数据,是一把只在特定场合用的工具。
7.2 信号层与语义层的两层
把它拆成两层看,很多争议就消失了:
| 层 | 内容 | 状态 | 说明 |
|---|---|---|---|
| 信号层 | 波形、采样率、增益、位分辨率、校验和 | 完备且统一 | 一百六十八段参数完全一致,有 SHA-256 与头文件校验和 |
| 语义层 | 患者信息、诊断、心搏标注、节律标签、采集背景 | 全部缺失 | 原始用途不需要,平台未补充 |
| 组织层 | 记录名、源记录号、段号、RECORDS、SHA256SUMS | 完备 | 命名规律清晰,可作分组依据 |
上表第二行是它的全部争议来源。有人因为它缺这些而弃用,有人因为不需要这些而长期用它——两方的分歧不在数据质量,而在任务是否落在信号层。判断标准很简单:你的研究问题如果只需要波形,它的完整性是满分;如果需要波形之外的任何东西,它就是空白。
7.3 已知的、可枚举的偏差
| 偏差 | 表现 | 影响 |
|---|---|---|
| 难度富集偏斜 | 按"难压"而非随机选取 | 平均压缩比与 PRD 不能外推到一般心电 |
| 段长截断 | 每段独立 20.48 秒 | 无法还原连续记录的跨段冗余 |
| 段间相关性 | 同一源记录号下有多段,最多到 _12 |
按段随机划分会泄漏,须按源记录号分组 |
| 来源不明 | 采集机构、设备、人群、筛选标准均未公开 | 无法判断人群代表性,也无法做来源层面的对照 |
| 通道语义缺失 | 两路都只写 ECG |
无法做导联相关的分析 |
| 无噪声段标记 | 目录内没有质量标签 | 无法把噪声段单独拎出来分析 |
| 单一版本 | 只有 1.0.0,无修订记录 | 无版本选择问题,但也意味着从未有人回头补元数据 |
第三行值得单独说。三十八个源记录号对应一百六十八段,段号最少的排到 _01,最多的排到 _12,各源记录下的段数并不相同。如果不做分组,同一源记录的兄弟段会分别落在训练与测试两侧,而它们共享同一次采集的电极、放大器与基线特征——这与 INCART 那类"同一份 Holter 切多段"的泄漏是同一个机制,只是本库连患者编号都没有,唯一能用的分组键就是源记录号。
7.4 外部验证矩阵
它最常见的出场方式不是"训练",而是"对照"。这类用法可以从公开文献里看出轮廓:
| 验证角色 | 做法 | 讲得通的地方 | 风险 |
|---|---|---|---|
| 压缩算法的第二考场 | 在自己的主库之外,把本库全量跑一遍 | 参数统一,别人可复现 | 只报均值会掩盖尾部差异 |
| 失真定位 | 挑 PRD 最差的几段画误差曲线 | 段短,便于逐点检视 | 挑段的口径要写清楚 |
| 编解码器横评 | 多个算法在同一份 168 段上比较 | 全库只有 2.6 MB,任何人都能复现 | 压缩比的分母定义必须统一 |
| 无失真压缩的极限测试 | 在本库上算无损压缩比 | 输入是 12 位打包,有明确的比特底 | 分母口径不写清会虚高 |
| 嵌入式部署评测 | 在板子上跑全库并记录耗时 | 体量小,适合裸机环境 | 第一段常有初始化开销,应单独说明 |
这里有个值得警惕的现象:不少压缩论文在摘要里写"在 MIT-BIH 数据库上评测",而实际用的记录集合彼此不同,有的用心律失常库的一部分,有的用本库,有的两者混用。读者看到的都是"MIT-BIH",分母却完全不同,数字自然不可比。写论文时把库名、版本、段集合、PRD 定义四件事写全,是解决这个问题的最低要求(§5.5)。
7.5 它为何能持续:稀缺的不是数据,是那个"出卷人"身份
它从 1999 年上线到现在没有扩版、没有修订、没有补充元数据,却一直被引用。原因是它占着一个别人不愿意再占的坑:公开可得的、按难度富集的、参数统一的心电压缩考题。
再建一个这样的库需要有人愿意做一件吃力不讨好的事——从大量记录里人工或半人工地挑出难压的片段,然后捐赠出来,还不指望它被用于热门任务。相比之下,采集一批新病人、挂上诊断标签、发一篇论文,对机构的回报明显更高。这个激励结构决定了它不会有人接班。
只要心电压缩这个方向还在(可穿戴设备、远程监护、植入式记录仪全都依赖它),这份二十多年前的老考卷就仍然是最省事的那一份。它不会被更大的库取代,因为它提供的不是规模,是一个立场:站在被考人之外出的题。
§8 生态位与历史
8.1 历史沿革
它的源头是 1988 年那篇关于 TRIM 压缩器的论文。TRIM 是 MIT 当时开发的一套心电数据压缩方案,论文要回答的问题是:有损压缩到底丢了什么。为了回答这个问题,作者需要一批能把压缩器的弱点逼出来的波形,于是有了这 168 段。
1999 年 8 月 3 日,PhysioNet 在同一天上线了一批 MIT-BIH 系列库——心律失常库、多导联睡眠库、长时程心电库、ST 改变库、正常窦性心律库、噪声压力测试库,其中就有它。这一批集中上线标志着 PhysioBank 从零散数据集走向成体系的资源目录:格式统一(WFDB)、工具统一(PhysioToolkit)、注释码统一。它在这批里是体量最小、目的最专的一个。
此后二十多年,它在平台侧几乎没有变化:版本历史只有一条,文件时间戳停在 1992 与 2008 年,2023 年随托管基础设施迁移到对象存储(目录内全部文件的统一时间戳即为此)。没有扩版计划被公开过。
8.2 与挑战赛的关系
这一点需要说清楚,因为很容易想当然:它没有被用作 PhysioNet/CinC 挑战赛的数据来源。挑战赛系列(2011、2017、2020、2021 等)用的心电数据来自短片段库、十二导联库与单导联可穿戴记录,与压缩评测无关。
它在挑战赛体系之外的位置反而更清晰:它服务的是一个没有竞赛、没有排行榜、也没有统一评测脚本的小领域。这个领域的所有"基准"都是惯例性的——某几篇论文用了它,后面的人跟着用,于是成为事实标准。这种惯例性基准的好处是灵活、门槛低,坏处是口径容易被稀释(§7.4)。
8.3 生态位:专用件而不是粮仓
| 角色 | 主力粮仓 | 专用件 |
|---|---|---|
| 典型用法 | 大规模训练、预训练 | 单一用途的对照与压力测试 |
| 需要的前提 | 数据量大、标签细 | 目的单一、条件极端、体量可控 |
| 代表 | 心律失常库、十二导联大库、长时程库 | 本库、噪声压力测试库、信号质量库 |
| 失败模式 | 过拟合、标签噪声累积 | 误被当成代表性样本使用 |
它坐在右边这一列,而且是最靠边的那个:体量比同类专用件还小一个量级,目的比同类还窄。把专用件当粮仓用,会立刻撞上规模与标注两堵墙;把粮仓当专用件用,则会遇到"数据太好说话、压不出差别"的问题。两者不能互换。
8.4 谁在用它,用来做什么
从公开文献的摘要看,使用者集中在三类。第一类是做心电压缩与编解码的信号处理组,把它当标准考场,这是主体。第二类是可穿戴与远程监护方向的工程组,用它估算码率下界与失真上界。第三类是教学,因为全库只有 2.6 MB、一百六十八段长度一致,很适合在课堂上演示"有损压缩到底丢了什么"。
三类里只有第一类真正依赖它的独特性。对另外两类来说,它是"顺手可得的公开波形",换成别的双通道心电也能完成任务——区别只在于,用它能让结论更容易被同行接受,因为同行认这个名字。
§9 使用指引
9.1 三种角色的路径
| 角色 | 第一天 | 第一周 | 注意事项 |
|---|---|---|---|
| 压缩算法研究者 | 下全库,跑通 §4.3 的两个基线 | 全量 168 段跑自己的编解码器,逐段报告 PRD | 分母口径与 PRD 定义必须写进方法节 |
| 设备固件工程师 | 取几段跑通板子上的编解码 | 记录逐段耗时与失真,挑最差段做压力测试 | 第一段常有初始化开销,应单独说明 |
| 教学与演示 | 下载 ZIP(2.7 MB),画一段原始波形 | 对比抽点降采样与粗化量化的失真形状 | 段长一致,做课堂演示很省事 |
9.2 一条最短的可用流程
#-- 1. 取记录清单
curl -sL https://physionet.org/files/cdb/1.0.0/RECORDS -o RECORDS
#-- 2. 抽一条记录,看头文件长什么样
head -3 <(curl -sL https://physionet.org/files/cdb/1.0.0/13005_01.hea)
#-- 3. 建立分组表:源记录号 -> 段名,任何划分都按它来
awk -F_ '{print $1, $0}' RECORDS > record2source.txt
cut -d' ' -f1 record2source.txt | sort -u | wc -l
import wfdb
import numpy as np
#-- 读一段并核对参数,确认与官方声明一致
rec = wfdb.rdrecord('13005_01', pn_dir='cdb')
assert rec.fs == 250 and rec.sig_len == 5120 and rec.p_signal.shape[1] == 2
print('参数核对通过:250 Hz / 5120 点 / 2 通道')
#-- 全库落成数组,并把源记录号留作分组键
names = wfdb.get_record_list('cdb')
src = [n.split('_')[0] for n in names]
print(len(names), '段,', len(set(src)), '个源记录')
上面 bash 段的第三步是全流程里唯一不能省的一步。因为这个库没有任何患者标识,源记录号是唯一可用于分组的键——一旦跳过,后面做的任何训练—测试划分都不可靠。
9.3 引用方式
PhysioNet 的惯例是同时引用数据集、原始论文与平台:
| 引用对象 | 内容 |
|---|---|
| 数据集 | MIT-BIH ECG Compression Test Database, version 1.0.0. PhysioNet. 1999. DOI: 10.13026/C2QP41 |
| 原始论文 | Moody GB, Mark RG, Goldberger AL. Evaluation of the “TRIM” ECG data compressor. Computers in Cardiology. 15:167-170 (1988). |
| 平台 | PhysioNet 平台综述论文(2026 年发表于 Nature Health),按官方页面的 Cite 按钮生成 |
写论文时建议同时注明:使用的版本号(1.0.0)、是否纳入全部 168 段、压缩比的分母口径、以及 PRD 的定义是否先去均值。这四项决定别人能不能复现你的数字(§5.5)。
§10 AI 使用声明
| 项目 | 声明 |
|---|---|
| 文本生成 | 本文由 AI 辅助撰写,编辑把关结构与事实 |
| 数字来源 | 规模、结构、边界三类数字来自 PhysioNet 官方页面与文件目录 |
| 文件核验 | RECORDS、ANNOTATORS、HEADER.html、SHA256SUMS.txt 与全部 168 份 .hea 头文件经直接访问确认 |
| 不一致说明 | 官方页面未公开患者数、采集来源与选样标准,正文一律标注为官方未给出 |
| 论文信息 | 仅核对官方页面指定的引用文献题录,未读全文 |
| 未核验内容 | 38 个源记录号的采集来源与人群背景;"难压"的具体遴选标准;各段的噪声水平与节律构成;通道对应的解剖导联;是否存在未被目录列出的附加文件 |
| 图像 | 无配图;封面为生成式提示词,不含真实心电图 |
| 免责 | 本文不构成临床建议;数据使用须遵守 ODC-BY 1.0 署名要求 |
常见误读(避坑清单)
| 说法 | 实际 | 证据 |
|---|---|---|
| 「它是 MIT-BIH 心律失常库的一部分」 | 记录号体系完全不同,且无标注 | 心律失常库为 100–234,本库为五位源记录号加段号 |
| 「它有 48 段记录」 | 168 段 | RECORDS 按空白切分计得 168 |
| 「它能用来训练心搏分类器」 | 一条标注都没有 | ANNOTATORS 内容为 [not annotated],目录无 .atr |
| 「它是 360 Hz」 | 250 Hz | 全部 168 份头文件第 1 行第 3 字段均为 250 |
| 「它的两个通道是 II 和 V1」 | 官方只写 ECG,未给导联 |
头文件信号行第 9 字段 |
| 「按段随机划分训练集测试集没问题」 | 同一源记录号下的兄弟段会跨在两侧 | 记录名的两段式结构,段号排到 _12 |
| 「压缩比按 16 位算就行」 | 输入是 12 位打包的 212 格式,按 16 位算会虚高 | 单段 .dat 固定 15360 字节 |
| 「它的平均压缩比代表一般心电」 | 按难度富集,绝对值偏保守 | 官方描述:selected to pose challenges |
| 「它很长,能研究长时程冗余」 | 每段独立 20.48 秒 | 头文件第 1 行第 4 字段为 5120 |
| 「它被用作 CinC 挑战赛的数据」 | 未被挑战赛采用,服务的是压缩评测这个小领域 | 官方页面未列任何挑战赛关联 |
FAQ
Q1:它到底有多少段?每段多长?
一百六十八段,每段二十点四八秒,也就是每通道五千一百二十个采样点。段数应当按空白切分 RECORDS 来核对,不要用 wc -l——这类清单文件末行常常没有换行符,行计数会少算。一百六十八段来自三十八个不同的源记录号,段号从 _01 排到 _12,各源记录下的段数并不相同。
Q2:它有没有标注?能不能拿来做心搏分类?
没有任何标注。目录里没有 .atr 文件,ANNOTATORS 的内容就是一行 [not annotated],头文件里也没有以 # 开头的注释行,因此连年龄、性别、诊断都没有。心搏分类、房颤检测、信号质量评分这些需要监督信号的用法都不适用它。它的设计用途是压缩评测,而压缩评测不需要标签——输入波形、输出重建波形、比一比就够了。
Q3:为什么它叫 MIT-BIH,却和心律失常库对不上号?
"MIT-BIH"是机构与医院来源的标识,不是某个具体数据库的编号。心律失常库的记录号是一百到二百三十四,本库是五位源记录号加两位段号(08730_01 这种),两者没有任何重叠。而且心律失常库有逐搏标注、每段约三十分钟、采样率三百六十赫兹,本库则是零标注、二十点四八秒、二百五十赫兹。名字相似是因为出自同一个实验室,内容与用途完全不同。
Q4:压缩比该怎么算才不会偏高?
关键是分母。本库的信号以 WFDB 的 212 格式存储,每两个采样点占三字节,等价于每点十二位;单段 .dat 固定一万五千三百六十字节。如果按"五千一百二十点乘两通道乘十六位"来算原始大小,得到的分母比真实存储大一倍多,压缩比会凭空虚高一截。正确做法是写明分母用的是十二位还是 212 打包字节,两种口径相差一又三分之一倍,必须说清。
Q5:它和 MIT-BIH 噪声压力测试库有什么不一样?
两者都是"专挑极端条件"的小体量专用集,但挑的东西不同。噪声压力测试库给的是三段的典型动态心电噪声记录,用来给干净信号叠加噪声做鲁棒性测试;本库给的是一百六十八段真实波形,按"难压"这个标准挑出来,用来考压缩算法。前者是噪声源,后者是难样本。此外噪声压力测试库是三百六十赫兹、三十分钟的长记录,与本库的参数也不通用。
术语表
| 术语 | 解释 |
|---|---|
| 压缩比 CR | 原始比特数与压缩后比特数之比,衡量省了多少空间 |
| 百分均方根差 PRD | 重建误差能量占原信号能量的百分比,衡量丢了多少波形 |
| 有损压缩 | 允许重建信号与原始信号不完全相同的压缩方式 |
| 无失真压缩 | 重建信号必须与原始信号逐点相同的压缩方式 |
| WFDB | PhysioNet 使用的波形数据库格式,含头文件、信号与注释 |
| 格式 212 | WFDB 的一种打包方式,每两个采样点占 3 字节,对应 12 位分辨率 |
.hea |
纯文本头文件,声明采样率、通道数、增益与校验和 |
.hea- |
旧格式头文件,为兼容老版本工具而保留 |
.dat |
二进制信号文件 |
.xws |
WAVEScript,供波形查看器定位记录的小脚本 |
| 增益 | 每毫伏对应的 ADC 整数值,本库为 400,即 2.5 微伏一个单位 |
| ADC 零值 | 零电平对应的整数值,换算物理量时先减掉 |
| 位分辨率 | 模数转换的位数,本库为 12 位 |
| 校验和 | 头文件里的采样点累加和,用于检测信号文件是否完整 |
| 难度富集 | 按"能否考住算法"而非随机或按病种来选样的策略 |
| 源记录号 | 记录名前五位,标识原始采集,是唯一可用的分组键 |
| 按段分组划分 | 同一源记录号下的所有段只进训练或只进测试 |
| 源记录分组 | 与按段随机切分相对,避免兄弟段跨在两侧造成泄漏 |
| ODC-BY | 开放数据共享署名许可,允许商业使用,条件是署名 |
| PhysioNet | 由 MIT 计算生理实验室维护的生物医学信号数据平台 |
| TRIM | MIT 开发的心电数据压缩器,本库最初为评测它而整理 |
附录 A:核心数字核对表
| 数字 | 出处 |
|---|---|
| 168 段 | 官方页面数据描述;RECORDS 按空白切分计数 |
| 38 个源记录号 | RECORDS 中不同前五位取值 |
| 20.48 秒 | 官方页面数据描述;5120 ÷ 250 复核 |
| 5120 采样点 | 全部 168 份 .hea 第 1 行第 4 字段 |
| 2 通道 | 全部 168 份 .hea 第 1 行第 2 字段 |
| 250 Hz | 全部 168 份 .hea 第 1 行第 3 字段 |
| 增益 400、12 位、零值 0 | 全部 168 份 .hea 信号行第 3 至 5 字段 |
通道描述 ECG |
全部 168 份 .hea 信号行第 9 字段 |
单段 .dat 15360 字节 |
目录所列大小,168 份全部相同 |
| 未压缩 2.6 MB / ZIP 2.7 MB | 官方页面 Files 区 |
| 零标注 | ANNOTATORS 内容为 [not annotated];目录无 .atr |
| 版本 1.0.0,1999-08-03 | 官方页面版本历史,仅一条 |
| 目录对象 676 个 | 对象存储侧完整列举:168 × 4 加 4 个附属文件 |
| SHA256SUMS.txt 675 行 | 附属文件直接访问 |
.xws 86 字节、.hea 102–110 字节、.hea- 120–128 字节 |
目录所列大小 |
| 许可 ODC-BY 1.0 | 官方页面 Access 区 |
| DOI 10.13026/C2QP41 | 官方页面 Discovery 区 |
| 发布者 George Moody、Roger Mark(MIT 计算生理实验室) | 官方页面作者栏 |
附录 B:数字的来源分层
第一层:官方页面与官方文件。 段数、段长、采样率、通道数、增益、位分辨率、文件体积、许可、版本与日期,来自项目主页的摘要与数据描述段,以及 RECORDS、ANNOTATORS、HEADER.html、SHA256SUMS.txt 与全部 168 份 .hea 头文件。这一层的数字可以逐一复核,也是正文里所有规模、结构、边界数字的来源。
第二层:目录结构可推出的结构量。 三十八个源记录号、段号的取值范围、单段 .dat 字节数与打包格式的对应关系、目录对象总数。这些不是官方页面写出的句子,但可以直接从文件目录读出来,正文里都标注了推导方式。
第三层:未核实。 采集机构、设备型号、人群构成、遴选"难压"段的具体标准、各段的节律与噪声构成、两个通道的解剖位置。这些官方页面与目录都没有给出,正文涉及处只做定性表述,并已列入 §10 的未核验内容。
附录 C:完整参考文献
| 编号 | 文献 | 链接 | 用途 |
|---|---|---|---|
| 1 | MIT-BIH ECG Compression Test Database v1.0.0 | https://physionet.org/content/cdb/1.0.0/ | 全部规模、结构、边界数字的来源 |
| 2 | 该库的 PhysioNet 文件目录 | https://physionet.org/files/cdb/1.0.0/ | RECORDS、头文件、附属文件 |
| 3 | 早期 PhysioBank 说明页归档 | https://physionet.org/files/cdb/1.0.0/HEADER.html | 数据描述与引用规范 |
| 4 | Moody GB, Mark RG, Goldberger AL. Evaluation of the “TRIM” ECG data compressor. Computers in Cardiology. 15:167-170 (1988). | http://ecg.mit.edu/george/publications/trim-cinc-1988.pdf | 官方指定引用文献,数据的原始用途 |
| 5 | PhysioNet 平台综述(2026,Nature Health) | https://doi.org/10.1038/s44360-026-00096-z | 托管模式与引用规范 |
| 6 | MIT-BIH 心律失常库(对照) | https://physionet.org/content/mitdb/ | §2.5 与 §6.1 的对比 |
| 7 | MIT-BIH 噪声压力测试库(对照) | https://physionet.org/content/nstdb/ | §2.5、§8.3 与 FAQ 第 5 问 |
| 8 | MIT-BIH 长时程心电库(对照) | https://physionet.org/content/ltdb/ | §6.1 与 §1.6 的边界说明 |
附录 D:任务代码
任务一:全库跑一遍基线压缩,逐段报 PRD
import wfdb
import numpy as np
def prd(a, b):
#-- 先去均值,避免基线电平进入分母
a0, b0 = a - a.mean(), b - b.mean()
return float(np.sqrt(np.sum((a0 - b0) ** 2) / np.sum(a0 ** 2)) * 100)
def decimate_then_interp(x, k):
#-- 最朴素的基线:每隔 k 点抽一个,再线性插值补回
idx = np.arange(0, x.shape[0], k)
return np.stack([np.interp(np.arange(x.shape[0]), idx, x[idx, c])
for c in range(x.shape[1])], axis=1)
rows = []
for n in wfdb.get_record_list('cdb'):
x = wfdb.rdrecord(n, pn_dir='cdb').p_signal
xhat = decimate_then_interp(x, k=4)
rows.append((n, prd(x[:, 0], xhat[:, 0]), prd(x[:, 1], xhat[:, 1])))
rows.sort(key=lambda r: -(r[1] + r[2]))
print('最难压的五段:', rows[:5])
print('最容易的五段:', rows[-5:])
任务二:按源记录号分组的划分骨架
import wfdb
from collections import defaultdict
names = wfdb.get_record_list('cdb')
groups = defaultdict(list)
for n in names:
groups[n.split('_')[0]].append(n)
keys = sorted(groups)
cut = int(len(keys) * 0.8) #-- 八成源记录进训练
train = [n for k in keys[:cut] for n in groups[k]]
test = [n for k in keys[cut:] for n in groups[k]]
print('源记录', len(keys), '训练段', len(train), '测试段', len(test))
#-- 自检:两侧不得出现同一个源记录号
assert not (set(n.split('_')[0] for n in train)
& set(n.split('_')[0] for n in test))
第二段代码给的是全库唯一可靠的划分方式。因为没有任何患者标识,源记录号是唯一的分组键;一旦按段随机切分,同一源记录下的兄弟段会同时落在两侧,而它们共享同一次采集的电极、放大器与基线特征,评测结果会偏乐观。
编辑说明
本文的核验时间为 2026 年 10 月 28 日。核对方式为直接访问 PhysioNet 项目页与文件目录,逐一确认 RECORDS、ANNOTATORS、HEADER.html、SHA256SUMS.txt 的可访问性与内容,并读取全部 168 份 .hea 头文件的记录行与信号行,确认通道数、采样率、采样点数、存储格式、增益、位分辨率、ADC 零值与通道描述在全部记录上完全一致;文件体积与对象总数通过对象存储的完整列举核对。论文侧只核对了官方页面指定的引用文献题录,未读全文。
需要特别说明的是官方信息的稀薄程度:项目页的数据描述只有一句话,没有采集机构、设备、人群、遴选标准的任何说明。因此凡涉及这些的内容,正文一律标注为官方未给出,并集中列入 §10 的未核验内容一栏,不作推测。同理,"患者数"这一项在官方页面上不存在,正文只能给出结构层面的替代口径——RECORDS 中出现的 38 个不同源记录号,并明确说明它不是患者数。
数字方面,正文只收规模、结构、边界三类:段数、源记录号数、段长、采样点、通道数、采样率、增益、位分辨率、文件体积、版本与日期属于规模与结构;段号取值范围、单段字节数、目录对象总数属于可由目录直接读出的结构量;许可限制与适用边界属于边界。凡是需要下载全量后统计才能得到的分布性内容——各段的节律构成、噪声水平、压缩难度的排列——本文一概未写。
相关数据集导航
以下为站内 AI-Ready 数据集百科中与本词条共享多个主题标签的相关数据集,按相关度降序排列:
- ieeepg — 共享标签:生理信号 / 心电信号 / 时序数据 / 评测基准
- fetalheartsounddata — 共享标签:生理信号 / 时序数据 / 评测基准
- bidsleep-dataset — 共享标签:生理信号 / 心电信号 / 时序数据 / 评测基准
- ptb-xl-plus — 共享标签:生理信号 / 心电信号 / 时序数据 / 评测基准
- charisdb — 共享标签:生理信号 / 心电信号 / 时序数据
- ob1db — 共享标签:生理信号 / 心电信号 / 时序数据
- adfecgdb — 共享标签:生理信号 / 心电信号 / 评测基准
- fecgsyndb — 共享标签:生理信号 / 心电信号 / 评测基准
- cpsc — 共享标签:生理信号 / 心电信号 / 时序数据
- mit-bih-arrhythmia — 共享标签:生理信号 / 心电信号 / 时序数据 / 评测基准
导航说明:本章节由全站统一标签体系自动计算生成(标签重合度算法),双向可达;点击链接可跳转至对应数据集词条。

