信息速览
心脏骤停发生的那一刻,心电图上的变化只有几秒钟:原本还能分辨的宽大 QRS 波群忽然失去节律,振幅时大时小,最后瘫成一条细碎的颤动。临床要在这几秒里做出判断,除颤器要在几秒里决定是否充电。而这几秒之前,通常还有一段更长、更规整、也更容易被算法抓住的室性心动过速——它是室颤的前奏,也是干预窗口。
CUDB(CU Ventricular Tachyarrhythmia Database)就是围绕这段前奏建起来的库。它只有 35 段记录、每段约八分半、每条只有一个导联,规模小到在今天的大模型语境里几乎不值一提。但它的稀缺性不在体量,而在内容:库里的每一段都取自真正发生过持续性室速、室扑或室颤的人,记录里保留着从相对规整的快速心律跌入无序颤动的完整过渡。这类高质量记录在临床上极难获得,因为事件不可预告,而能完整录下它并保存三十年的机构屈指可数。
它的定位也因此非常明确:这不是一个用来训练心搏分类器的库,官方甚至把全部心搏都标成了正常。它是一件用来校准"室颤检测器什么时候该响"的量具——量的是报警提前量,而不是波形形态。理解这一点,后面所有的取舍都能推出来。一屏速览见下表。
| 维度 | 内容 |
|---|---|
| 全名 | CU Ventricular Tachyarrhythmia Database |
| 中文常用名 | 克雷顿大学室性快速心律失常数据库 |
| 发布方 | PhysioNet(原始数据由克雷顿大学心脏中心采集) |
| 首次发布 | 2007 年 5 月 2 日,版本 1.0.0 |
| 记录数 | 35 段(cu01–cu35) |
| 受试者数 | 官方未公布 |
| 每段时长 | 约 8.5 分钟(127,232 采样点) |
| 导联数 | 1(单通道心电) |
| 采样率与精度 | 250 Hz,12 位 |
| 模拟带宽 | 二阶有源 Bessel 低通,截止 70 Hz |
| 信号来源 | cu01 为 Holter 回放,其余为床旁监护仪模拟输出 |
| 标注内容 | 心搏位置(全部标为正常)、室颤段起止、零星的节律与质量标记 |
| 标注性质 | 官方称仅供定位事件,「绝不是定论」 |
| 文件格式 | WFDB:.hea / .dat / .atr,另有 .hea-、.atr-、.xws |
| 全库体积 | 未压缩约 6.4 MB |
| 许可 | ODC-BY 1.0,免注册 |
| DOI | 10.13026/C2X59M |
| 典型用途 | 室颤检测器的外部测试、报警提前量标定、室速到室颤的过渡分析 |
§1 它要解决的具体麻烦
1.1 除颤器要回答的不是"是不是室颤",而是"还来得及吗"
把室颤检测写成一个二分类问题,是算法视角的简化。临床上真正决定生死的,是另一条时间线:从可救治的快速心律,到不可逆的颤动,中间隔着几十秒到几分钟。除颤每延迟一分钟,存活率就往下掉一截。如果检测器等到波形完全变成室颤才报警,它报得再准,也已经吃掉了最宝贵的干预窗口。
这就产生了一个反直觉的评价标准:好的检测器应该在室颤之前就响。而在以"室颤起点"为时间零点的标注体系里,这种提前报警会表现为负的延迟。多数评测框架默认延迟是非负的,一遇到负数就截断成零,于是最该被奖励的行为反而被抹平了。
CUDB 的设计正是冲着这个矛盾去的。官方在数据说明里写得很直白:心室颤动的起点在很多情况下极难精确定位,而任何有临床价值的检测器都应当对室颤之前的室速段作出反应,因此检测器相对于参考标注可能出现负的"报警时间"。也正因为如此,这个库把自己命名为快速心律失常库(tachyarrhythmia database),而不是室颤库(fibrillation database)。这不是文字游戏,是在提醒使用者:你要预测的是一个过程,不是一个瞬间。
1.2 三条常见的错误期待
第一条,期待它是一套逐搏标注齐全的心律失常库。事实相反:官方明确说明参考注释里的所有心搏都标为正常,哪怕其中很多实际是异位的。这意味着它无法支撑任何心搏类型分类任务,也无法用来评估早搏检出率。想找逐搏标签的人应该去看 MIT-BIH 心律失常库或 AHA 数据库,而不是这里。
第二条,期待它的标注是金标准。官方用了很重的措辞——这些标注"绝不是定论"(in no sense definitive),室颤起点只标出大致位置。它是一份帮助使用者快速找到事件位置的索引,不是一份可以据此计算毫秒级误差的判读结果。
第三条,期待它有正常人的对照记录。库里没有一条是健康人的安静心电。全部记录都围绕恶性室性心律失常事件截取,阴性样本的缺失是结构性的,不是抽样偶然。
1.3 它能给到的与给不到的
| 你的需求 | 它给的 | 够不够 |
|---|---|---|
| 室颤检测器的外部测试 | 35 段真实发作记录,含室颤起止 | 够做小规模外测,不足以单独支撑结论 |
| 报警提前量的标定 | 明确的室颤起点,允许负延迟的评价口径 | 够,这是它最核心的用途 |
| 室速向室颤过渡的形态分析 | 连续单导联信号,过渡段完整保留 | 够,且是少数能拿到完整过渡的公开数据 |
| 逐搏心律失常分类 | 全部心搏标为正常 | 不够,完全不可用 |
| 正常心律与室颤的二分类 | 无正常对照记录 | 不够,阴性样本需另找来源 |
| 除颤伪迹的识别与剔除 | 多条记录含反复除颤 | 够做定性观察,官方未标出电击时刻 |
| 多导联或十二导联研究 | 单通道 | 不够 |
| 人群层面的流行病学分析 | 无人口学、无病史、无结局 | 不够 |
| 可穿戴单导联设备的算法验证 | 单导联、250 Hz、真实发作 | 够做可行性验证,但设备带宽需对齐 |
1.4 你的处境与它能帮到的事
| 你的处境 | 它能帮你什么 | 下一步该做什么 |
|---|---|---|
| 你训练了一个室颤检测网络,在自建数据上指标很好,但不知道换一批人还灵不灵 | 提供另一家机构、另一批患者、另一套采集链路的真实发作记录 | 先做零样本外测,记录灵敏度与报警延迟两个数,不要只看 AUC |
| 你在做可穿戴设备的室颤报警,苦于拿不到真实发作片段 | 35 段真实过渡过程,单导联,正好对应腕式或贴片设备的通道数 | 先把信号重采样到设备采样率,再确认 70 Hz 低通是否已低于设备带宽 |
| 你要给除颤算法定一个"提前多少秒报警"的阈值 | 提供从记录起点到室颤起点的真实时间分布区间 | 用官方给出的均值与标准差做先验,再用 §4.3 的案例核对单条时间轴 |
| 你在研究室速蜕变为室颤的动力学 | 连续的过渡段,没有被人为切断 | 逐段看周期长度与振幅包络的变化,注意除颤段的干扰 |
| 你在复现一篇早期室颤检测论文,需要同一份数据 | 这是那批早期工作最常引用的公开库之一 | 先确认论文用的是原始 250 Hz 还是重采样版本 |
| 你只需要一个小到能一次下完的教学数据集 | 全库压缩包只有几兆,几分钟就能跑通 | 直接按 §9.2 的流程读一条记录画图 |
1.5 一句话判断
它不是一个能教会模型认识室颤的库,而是一把能告诉模型"你响得太晚"的尺子;把它当训练集是浪费,把它当外部时钟才是正用。
1.6 不适合谁:四条边界
不适合拿来做心搏分类。 全部心搏标为正常,这个决定是官方写明的,不是标注遗漏。任何以心搏类型为标签的训练都会得到一组全同标签,模型什么也学不到。
不适合拿来做人群建模。 没有年龄、性别、病史、用药、结局。你无法知道这 35 段来自什么样的心脏,只知道它们都发生过恶性室性心律失常。
不适合拿来做严格的时刻回归。 室颤起止是人工粗标,官方承认只是大致位置,且三条记录在 2006 年才补上缺失的起止。用它做毫秒级的时间误差分析,测到的主要是标注噪声。
不适合拿来做规模化训练。 35 段、每段八分半,总时长不到五小时。深度模型在这么小的集合上几乎必然过拟合,除非你做严格的交叉验证并把结论限定在方法层面。
§2 数据构成与规格
2.1 一段记录由三个文件组成
数据库沿用 PhysioNet 的 WFDB 格式。每段记录对应三个同名文件:.hea 是纯文本头文件,声明采样率、采样点数、信号条数、增益、基线、初始值与校验和;.dat 是二进制信号本体,按 212 格式把两个 12 位采样点打包进三个字节;.atr 是二进制注释文件,存放事件位置与符号。
除此之外还有三类附属文件。.hea- 与 .atr- 是历史版本,文件名末尾多一个连字符,每条记录都各附一份,用于对照修订前后的差异。.xws 是给旧版波形显示工具用的脚本,内容只有记录名与一个地址模板。ANNOTATORS 是一个几十字节的清单,写明 atr 这一路注释的角色是"初步的心搏与节律标注"。
这套结构的直接后果是:只拿 .dat 无法还原毫伏值,因为增益在 .hea 里;只拿 .atr 无法知道这些时刻对应什么波形,因为采样率也在 .hea 里。把库转成 CSV 或 NumPy 是有损操作,除非你把头文件里的增益、基线、ADC 零值一起搬过去。
2.2 字段与注释码的完整字典
先看头文件。CUDB 的头文件短到只有两行,第一行描述记录,第二行描述信号:
| 行 | 字段 | 示例值 | 含义 |
|---|---|---|---|
| 1 | 记录名 | cu01 |
与文件名一致 |
| 1 | 信号条数 | 1 |
单通道 |
| 1 | 采样率 | 250 |
每秒 250 个采样点 |
| 1 | 采样点数 | 127232 |
每条记录固定 |
| 2 | 文件名 | cu01.dat |
信号文件 |
| 2 | 存储格式 | 212 |
12 位、两样本打包为三字节 |
| 2 | 增益 | 400 |
每毫伏 400 个整数单位 |
| 2 | 位宽 | 12 |
ADC 分辨率 |
| 2 | ADC 零值 | 0 |
零电平对应的整数 |
| 2 | 初始值 | -109 |
第一个采样点的整数值 |
| 2 | 校验和 | -28468 |
全段采样点累加和,用于完整性校验 |
| 2 | 块大小 | 0 |
不分块 |
| 2 | 信号描述 | ECG |
通道名称 |
换算关系是:物理毫伏 = (整数值 − ADC 零值)÷ 增益。增益在抽查的记录里都是 400,ADC 零值都是 0,但使用前仍应逐条读头文件确认,不要把这个值硬编码进管线。
再看注释。注释文件里的符号遵循 PhysioBank 的标准定义,但在本库里实际只会遇到很少几种:
| 符号 | 名称 | 在本库中的含义 | 是否可靠 |
|---|---|---|---|
N |
Normal beat | 心搏位置,官方统一标为正常 | 位置可用,类型不可用 |
[ |
Start of VF/VFL | 室颤或室扑段的起点 | 官方称仅近似 |
] |
End of VF/VFL | 室颤或室扑段的终点 | 官方称仅近似 |
+ |
Rhythm change | 节律发生改变 | 未逐条说明 |
~ |
Signal quality change | 信号质量改变 | 未逐条说明 |
ANNOTATORS 文件只有一行:atr preliminary beat and rhythm annotations。其中 preliminary 这个词是关键——官方把这一路注释定性为初步结果,而不是复核过的判读。
2.3 一条记录的完整解剖
以 cu01 为例。头文件只有两行,读出来是 cu01 1 250 127232 与 cu01.dat 212 400 12 0 -109 -28468 0 ECG。
第一行说:记录名 cu01,1 条信号,250 Hz,127232 个采样点。第二行说:信号存在 cu01.dat,212 打包格式,增益 400,12 位,ADC 零值 0,首样本 −109,校验和 −28468,不分块,信号名 ECG。
127232 除以 250 得 508.9 秒,也就是 8 分 29 秒。官方说"略少于 8.5 分钟",与此吻合。每条 .dat 文件约 186 KB,全库未压缩约 6.4 MB,压缩包约 6.5 MB——压缩后略大是因为打包开销。
用 wfdb 读取时,物理值会自动按增益换算:
import wfdb
rec = wfdb.rdrecord('cu01', pn_dir='cudb')
print(rec.fs, rec.sig_len, rec.sig_name)
#-- 250 127232 ['ECG']
x = rec.p_signal[:, 0] #-- 单位毫伏
print(x.min(), x.max(), x[:5])
ann = wfdb.rdann('cu01', 'atr', pn_dir='cudb')
print(len(ann.sample), sorted(set(ann.symbol)))
#-- 206 ['+', 'N', '[', ']']
抽查 cu01 的注释可以看到:N 标注覆盖绝大多数心搏,[ 与 ] 各出现一次,围出唯一一个室颤段,另有一个 + 标记节律改变。这与官方"所有心搏标正常、室颤起点只标大致位置"的描述一致。cu12 与 cu30 的结构相同,只是 ~ 与 [、] 的出现次数不同——它们同样只承担定位作用,不构成节律分类。
2.4 官方点名的特殊记录
官方没有逐条给出 35 段记录的临床明细,只在数据说明里点名了几条特殊情况。把这些点名集中起来,就得到一张很小的结构表:
| 记录 | 官方点名的属性 | 对使用的影响 |
|---|---|---|
| cu01 | 唯一来自长时程 Holter 的记录,回放后数字化 | 采集链路与其他 34 条不同,噪声谱可能不一致 |
| cu03 | 注释在 2006 年 1 月补全缺失的室颤起止 | 用旧版 .atr- 会漏掉事件 |
| cu12 | 起搏患者,注释同期补全 | 起搏节律可能与室速混淆 |
| cu15 | 起搏患者 | 起搏伪迹可能不可见 |
| cu24 | 起搏患者 | 同上 |
| cu25 | 起搏患者 | 同上 |
| cu32 | 起搏患者 | 同上 |
| 其余 28 条 | 官方未逐条说明 | 只能从波形自行判断 |
这张表的价值不在于完整,而在于提醒:这个库的内部异质性比它的体量看起来更大。35 条记录里至少有两种采集链路(Holter 回放与监护仪实时数字化)、两类节律背景(起搏与非起搏),而官方只对其中七条作了说明。
2.5 与同类资源的字段差异
把 CUDB 放进 PhysioNet 的心电库序列里,它的字段配置是极简的一档:
| 维度 | CUDB | MIT-BIH 心律失常库 | INCART | AHA 室性心律失常库 | VTaC 报警库 |
|---|---|---|---|---|---|
| 记录数 | 35 段 | 48 段 | 75 段 | 开发 80 + 测试 75 | 五千余条报警 |
| 每段时长 | 约 8.5 分钟 | 30 分钟 | 30 分钟 | 3 小时 | 数秒到数十秒 |
| 导联数 | 1 | 2 | 12 | 2 | 多模态 |
| 采样率 | 250 Hz | 360 Hz | 257 Hz | 250 Hz | 250 Hz |
| 心搏类型标注 | 全部标正常 | 逐搏多类 | 逐搏多类 | 逐搏多类 | 无 |
| 室颤起止标注 | 有,粗略 | 部分有 | 无 | 部分有 | 无 |
| 正常对照 | 无 | 有 | 有 | 有 | 有 |
| 主要用途 | 室颤检测与报警提前量 | 心搏分类基准 | 多导联跨库测试 | 室性心律失常判读 | 监护仪假报警 |
差异最刺眼的是标注粒度:别人给的是逐搏类型,它给的是"全都是正常"加一对括号。这不是偷懒,而是任务定义不同——它关心的不是每个心搏叫什么,而是整段节律什么时候跨过临界点。
2.6 为什么单导联在这里不是缺陷
现代心电算法几乎默认多导联,单导联听起来像倒退。但在室颤检测这个具体任务上,单导联有它的道理。
第一,除颤器与可穿戴设备本身就是单导联或双导联。院前急救的 AED 通过两块电极板采集,腕式设备只有一个通道。用十二导联数据训练出来的模型,部署时根本拿不到同样的输入。
第二,室颤是全心电活动的失序,不是某个壁的局部异常。它不像心梗定位那样依赖导联间的空间关系,单导联足以看到周期长度与振幅包络的崩溃。
第三,采集年代决定了配置。这批记录来自 1980 年代的床旁监护,当时的数字化设备就是单通道。要求它有多导联,等于要求它不存在。
§3 它是谁的数据:一位工程师与一家心脏中心
3.1 贡献者与机构
官方页面把原始采集归给 Floyd M. Nolle,机构是克雷顿大学心脏中心(Creighton University Cardiac Center)。同一段说明里还感谢了 Richard W. Bowser,称他保存了这些记录、使它们能以这种格式分发,并整理了数据说明的前身文本。
这两位名字指向同一条技术脉络。1986 年,Nolle 与 Badura、Catlett、Bowser、Sketch 在《Computers in Cardiology》第 13 卷发表了一篇题为 CREI-GARD 的文章,副标题是"计算机化心律失常监护系统的一个新概念"。PhysioNet 把这篇会议论文列为该库的原始出版物,要求使用者在引用数据时一并引用。CREI-GARD 的名字由缩写拼接而成,官方页面没有展开说明各段的含义。
这段历史解释了一个容易被忽略的事实:这批数据不是为机器学习准备的,而是为一个实时监护系统的开发与验证准备的。它的评价目标从第一天起就是"系统什么时候该响",而不是"这个心搏属于哪一类"。标注之所以如此简略,是因为系统本身不需要更细的标签。
3.2 时间线
| 时间 | 事件 | 依据 |
|---|---|---|
| 1980 年代 | 记录在克雷顿大学心脏中心采集 | 官方数据说明 |
| 1986 年 | CREI-GARD 会议论文发表 | 官方引用项 |
| 1992 年 7 月 | 多数 .dat、.hea-、.atr- 文件的时间戳 |
官方文件目录 |
| 1999 年 12 月 | .xws 显示脚本的时间戳 |
官方文件目录 |
| 2003 年 | cu05、cu19、cu21 的注释文件更新 | 官方文件目录 |
| 2006 年 1 月 | cu01、cu03、cu12 补全缺失的室颤起止 | 官方数据说明 |
| 2007 年 5 月 2 日 | 作为版本 1.0.0 上线 PhysioNet | 官方页面 |
| 2019 年 2 月 | 生成 SHA256 校验清单 | 官方文件目录 |
这条时间线里有个细节值得留意:文件时间戳集中在 1992 年,比 PhysioNet 上线早了十五年。这说明数据在进入平台之前,已经在本地以 WFDB 格式存放了很久;是谁在那一轮完成了格式转换,官方页面没有交代。
3.3 采集场景:床旁监护与一次 Holter 回放
官方对采集方式的描述很具体,分为两类。cu01 来自一段长时程心电(Holter)记录,为了数字化而按实时速度回放。其余记录则在患者监护现场,从监护仪的高电平模拟输出直接实时数字化,标称幅度是每毫伏一伏。
两类链路的差别在信号上留得下痕迹。Holter 回放经过磁带或固态存储的录制与重放,低频漂移和磁带噪声的谱形与床旁监护不同;床旁监护的模拟输出经过监护仪自身的滤波与放大,高频成分可能已被设备前端削掉一部分。官方在两类信号之后又统一加了一道二阶有源 Bessel 低通,截止 70 Hz。Bessel 滤波器的特点是通带内群延迟平坦,能保住波形的形状,代价是过渡带较缓。对室颤检测来说,这个选择是合适的:它要保的是振幅包络与周期结构,不是高频切迹。
3.4 数据来源的三个层次
理解这批数据的来源,需要分清三层。第一层是患者:发生过持续性室速、室扑或室颤的人,具体人数官方未公布。第二层是记录:从这些人的监护过程中截出的 35 段,每段约八分半,围绕事件选取。第三层是文件:每段被写成一组 WFDB 文件,加注、编号、打包。
三层之间的对应关系并不完整。官方没有说明一段记录对应一名患者,还是有人被录了多段。这个空白直接影响训练集划分——如果同一患者贡献了多段,按记录随机切分就会泄漏。稳妥的做法是把这一点写进限制,并在实验中采用保守的划分策略。
§4 它最独特的东西:从室速跌进室颤的那几十秒
4.1 为什么这段过渡难以获得
室颤不是随机降临的。在多数心源性猝死的过程中,它会由一段室性心动过速逐步蜕化而来:周期长度先变短、再变得不规则,QRS 形态从均一走向多形,振幅包络开始起伏,最后完全失去可辨认的波群。这个过程可能持续几十秒,也可能只有几个心搏。
要录到它,需要三个条件同时成立:患者恰好在监护下、监护设备恰好在记录、记录恰好在事件后被保存下来。第三个条件最难。绝大多数床旁监护的波形只留在环形缓冲里,几小时后就覆盖了;能留下来的,往往是工程师为了调试算法而特意导出的片段。CUDB 的 35 段正是这样一批"被特意留下"的记录。
这也是它无法被新数据轻易替代的原因。今天的医院有更多监护床位、更强的存储,但把恶性心律失常的完整过渡段公开出来,仍然要面对伦理审查、脱敏、归属权等一系列问题。数量不会因为技术进步而自然增长。
4.2 官方给出的两个时间常数
官方在数据说明里给了两个可以直接引用的量。第一,室颤段之前非室颤心搏的最少数量是 61 个。第二,从记录起点到室颤起点的平均时间是 5 分 47 秒,标准差 2 分 1 秒。
这两个数字决定了这个库的时间结构。八分半的窗口里,前六分钟左右是前奏,最后两分多钟是事件本身。换句话说,每段记录都把大部分时长留给了"还没室颤"的那一侧。这是有意的设计:如果一段记录里室颤占了八成,它就无法用来评估提前报警,因为留给检测器的反应时间太短。
61 个心搏这个下限也有意义。按每分钟一百次左右的室速心率估算,61 个心搏大约是三十多秒。这给出了一个保守的报警窗口:任何检测器只要能在室颤前三十秒内响应,在这批记录上就至少有一次机会。
4.3 案例:cu01 里那场被回放的室颤
cu01 是这个库里唯一来自 Holter 的记录,也是官方在 2006 年补全过注释的三条之一。把它当作 §4.3 的案例,可以看清这个库的时间结构到底长什么样。
读它的注释,会看到一串 N 标注沿着时间轴排开,中间夹着一个 +,然后是一对方括号。方括号之间的区间就是室颤段。用 wfdb 把这三个位置打出来:
import wfdb
ann = wfdb.rdann('cu01', 'atr', pn_dir='cudb')
fs = 250
for s, sym in zip(ann.sample, ann.symbol):
if sym in '[]+':
print(f'{sym} 第 {s} 点 = {s/fs:7.1f} 秒 = {s/fs/60:5.2f} 分')
输出的三个时刻把整段记录切成两半:前半是室速前奏,后半是颤动与随后的终止。注意 ] 之后记录并没有立刻结束,还有一段恢复或后续节律——这部分常被忽略,但它对评估"报警后是否该继续报警"很有用。
§4.3 的案例还暴露出一个常被跳过的问题:方括号标的是"室颤段",不是"室颤起点"。在 cu01 里,[ 之前的最后几十个心搏已经不是正常窦性心律,而是宽大畸形的室速波群。如果严格按"室颤起点"评估,检测器在这段室速上报警就会被算成假阳性;但按临床逻辑,它报得对。这正是官方选择"快速心律失常库"这个名字的原因。
把 §4.3 的案例与 INCART 那类逐搏库对照,差别一目了然。INCART 会告诉你每个心搏是 N、V 还是 S,但不会告诉你哪一段是室颤;CUDB 只告诉你室颤从哪到哪,却不告诉你中间那些宽大波群叫什么。两者不是精细度的差别,是问题定义的差别。
4.4 标注的粒度:一段清楚,一搏模糊
| 标注对象 | 官方说法 | 可用性 | 典型误用 |
|---|---|---|---|
| 室颤段起止 | 只标大致起点,绝非定论 | 可做段级定位,误差数十秒内 | 当作毫秒级真值算延迟 |
| 心搏位置 | 全部标为正常 | 可定位 QRS,不可分类 | 训练心搏分类器 |
| 异位搏动 | 明确说很多是异位的但未标 | 不可用 | 假定标注里没有异位 |
| 节律改变 | 符号存在但未逐条说明 | 仅作线索 | 当作完整节律标签 |
| 除颤时刻 | 未标注 | 需自行识别 | 把电击伪迹当室颤波形 |
| 起搏状态 | 仅点名五条记录 | 可排除,不可逐搏定位 | 忽略起搏导致的形态改变 |
这张表的用法是反向的:先看你要的标签在不在,再决定要不要用这个库。绝大多数失败的用法,都是先选了库再找标签。
4.5 边界条件:什么情况下它不再适用
当你的任务是心搏级分类时。 全部标签同质,训练信号为零。
当你的模型需要阴性对照时。 库里没有正常记录,也没有非室颤的长时段。你可以从别的库借阴性样本,但那就变成了跨库拼接,域差异会混进结论。
当你要评估除颤效果时。 官方没有标电击时刻,也没有标电击后的节律转归。反复除颤在波形上可见,但"第几次电击成功"这类问题它答不了。
当你要做患者级的泛化声明时。 患者数未知,记录与患者的对应未知。任何"在多少名患者上验证"的表述都缺乏依据。
当你要做实时部署的吞吐测试时。 总时长不足五小时,且全是事件段。它能测延迟,不能测长时运行稳定性。
§5 怎么拿到:从六兆压缩包到可用的张量
5.1 目录结构与下载面
官方文件目录是扁平的,没有子目录。打开 https://physionet.org/files/cudb/1.0.0/ 会看到五类文件排在一起:
| 文件 | 数量 | 作用 | 是否必需 |
|---|---|---|---|
cuNN.hea |
35 | 头文件 | 必需 |
cuNN.dat |
35 | 信号本体 | 必需 |
cuNN.atr |
35 | 参考注释 | 强烈建议 |
cuNN.hea- |
35 | 头文件历史版本 | 仅对照用 |
cuNN.atr- |
35 | 注释历史版本 | 仅对照用 |
cuNN.xws |
35 | 旧版显示脚本 | 可忽略 |
RECORDS |
1 | 记录名清单,35 行 | 建议 |
ANNOTATORS |
1 | 注释角色说明 | 可选 |
SHA256SUMS.txt |
1 | 全文件校验和 | 建议校验 |
全库未压缩约 6.4 MB,压缩包约 6.5 MB。这个体量意味着你可以把它整个下载到内存里,也可以在 CI 里每次都重新拉一遍。它是少数不需要做下载管理的公开心电库。
有一点需要注意:.hea- 与 .atr- 里的连字符是文件名的一部分,不是笔误。在某些 shell 里直接 cu01.hea- 会被当成参数,需要加 ./ 前缀或引号。
5.2 三种下载方式
最短的是整包下载,解压即用:
#-- 整包下载(约 6.5 MB)
curl -L -o cudb.zip https://physionet.org/content/cudb/get-zip/1.0.0/
unzip -q cudb.zip -d cudb
ls cudb | head
#-- 只取记录清单
curl -sL https://physionet.org/files/cudb/1.0.0/RECORDS -o RECORDS
wc -l RECORDS
如果要做版本固定与增量更新,用 wget 递归更稳:
#-- 递归镜像,已存在则跳过,不追上级目录
wget -r -N -c -np -q \
https://physionet.org/files/cudb/1.0.0/ \
-P ./cudb_mirror
#-- 校验完整性
cd cudb_mirror/physionet.org/files/cudb/1.0.0
sha256sum -c SHA256SUMS.txt | grep -v ': OK$' || echo 'all files verified'
如果你的环境在云上,用开放桶同步会快很多:
#-- 无需凭证的公共桶
aws s3 sync --no-sign-request \
s3://physionet-open/cudb/1.0.0/ ./cudb_s3/
#-- 只同步头文件与注释,先做结构检查
aws s3 sync --no-sign-request --exclude '*.dat' \
s3://physionet-open/cudb/1.0.0/ ./cudb_meta/
5.3 用 wfdb 直接流式读取
不必先下载。wfdb 的 pn_dir 参数会让它按需向 PhysioNet 取文件:
import wfdb
names = wfdb.get_record_list('cudb')
print(len(names), names[:5], names[-1])
#-- 35 ['cu01', 'cu02', 'cu03', 'cu04', 'cu05'] cu35
rec = wfdb.rdrecord(names[0], pn_dir='cudb')
print(rec.fs, rec.sig_len, rec.sig_name, rec.units)
#-- 250 127232 ['ECG'] ['mV']
print(rec.adc_gain, rec.adc_zero, rec.baseline)
#-- [400.0] [0] [0]
get_record_list 读的就是 RECORDS,返回 35 个记录名。注意它按行解析而不是按换行计数,所以不受末行有无换行的影响。
流式读取的代价是每次都要走网络。批量处理时应该先落盘再读:
import wfdb, os
os.makedirs('cudb_local', exist_ok=True)
for n in wfdb.get_record_list('cudb'):
wfdb.dl_database('cudb', dl_dir='cudb_local',
records=[n], keep_subdirs=False)
5.4 把室颤段抽成事件表
这个库最实用的中间产物不是张量,是一张事件表:每条记录有几个室颤段、起止在哪。用注释符号 [ 与 ] 配对即可:
import wfdb
def vf_segments(name):
ann = wfdb.rdann(name, 'atr', pn_dir='cudb')
segs, start = [], None
for s, sym in zip(ann.sample, ann.symbol):
if sym == '[':
start = s
elif sym == ']' and start is not None:
segs.append((start, s))
start = None
#-- 若记录在室颤中结束,末尾补一个开区间
if start is not None:
segs.append((start, ann.sample[-1] if len(ann.sample) else start))
return segs
for n in wfdb.get_record_list('cudb'):
segs = vf_segments(n)
print(n, [(round(a/250, 1), round(b/250, 1)) for a, b in segs])
这段代码有一个必须处理的边界:如果记录在室颤尚未终止时结束,[ 后面不会出现 ]。上面的写法把这种情况补成开区间,但更严谨的做法是显式标注 end=None,让下游知道这是一段被截断的事件。
5.5 重采样与滤波的两个陷阱
第一个陷阱是重复滤波。官方信号已经过 70 Hz 二阶 Bessel 低通。很多管线的默认动作是先做 0.5–40 Hz 带通,再做归一化。对这批数据,40 Hz 低通会进一步削掉室颤的高频成分,而室颤的振幅包络恰恰部分依赖这些成分。如果你的模型在别的库上训练,需要统一预处理,那应该在论文里写清"为对齐训练集带宽而额外滤波",而不是假装这是无损操作。
第二个陷阱是重采样到 250 Hz 以下。可穿戴设备常在 125 Hz 或 100 Hz 工作。降采样本身没错,但必须加重采样前的抗混叠滤波,且要意识到室颤的主频成分分布在 3–10 Hz 一带,降到 100 Hz 仍够用,降到 50 Hz 就开始丢信息。
import wfdb, numpy as np
from scipy.signal import resample_poly
rec = wfdb.rdrecord('cu12', pn_dir='cudb')
x = rec.p_signal[:, 0]
fs = rec.fs
#-- 250 -> 125 Hz,先做抗混叠再抽取
y = resample_poly(x, up=1, down=2)
print(len(x), len(y), fs / 2)
#-- 只做去均值,不再额外低通(官方已滤至 70 Hz)
z = y - np.mean(y)
print(z.std())
5.6 五个最容易出错的地方
| 出错点 | 现象 | 正确做法 |
|---|---|---|
把 N 当正常心搏的真值 |
训练出的分类器只会输出一类 | 只把 N 当位置标记,不当类型标签 |
忽略 [ 后没有 ] 的记录 |
事件表里出现空段或负长度 | 显式处理开区间 |
用 wc -l 数记录 |
可能少一条(取决于末行换行) | 用 get_record_list 或逐行读到文件尾 |
把 .hea- 当主文件 |
读到旧版头,校验和对不上 | 只用 .hea,历史版本另存 |
| 假定记录采样点一致就时长一致 | 重采样后长度对不齐 | 统一按秒切窗,不按点数切窗 |
5.7 接进管线前的三次对齐
第一次对齐是单位。WFDB 的 p_signal 已经按增益换算成毫伏,但 d_signal 仍是原始整数。很多开源管线默认吃整数再自己归一化,如果直接把 p_signal 喂进去又做一次除以增益,幅值会小三个数量级。判断方法很简单:正常心电的 R 波峰峰值在一毫伏上下,室颤段常在零点几毫伏,若你的输入全在百分之一量级,就是多除了一次。
第二次对齐是时间轴。ann.sample 是采样点序号,不是秒,也不是毫秒。转秒要除以 250,转毫秒要乘 4。有些工具把注释时间导出成以 1/250 秒为单位的整数刻度,读进来若当成秒会差二百五十倍,当成毫秒会差四倍。
第三次对齐是极性。官方没有说明这批记录的电极极性约定,头文件里也没有极性字段,因此不能假定全部记录的主波方向一致。若你的模型对极性敏感,最稳的做法是在训练侧做随机翻转增强,而不是在推理侧猜。
| 对齐项 | 常见错法 | 后果 | 自检 |
|---|---|---|---|
| 单位 | 对 p_signal 再除增益 |
幅值缩小约四百倍 | 看 R 波峰峰值是否接近一毫伏 |
| 时间 | 把采样点当秒 | 事件时刻偏早二百五十倍 | 事件时刻应落在零到五百一十秒之间 |
| 时间 | 把采样点当毫秒 | 事件时刻偏早四倍 | 同上 |
| 极性 | 假定全部主波向上 | 部分记录形态反转 | 逐条看前五秒波形 |
| 通道 | 按二维数组取列 | 单通道取列后形状仍为二维 | 打印 shape 确认维度 |
§6 与库内条目的关系
6.1 关系图谱
在千方医数集已收录的心电类资源里,CUDB 的位置可以由两条轴定出来:一条是"标注精细度",一条是"事件覆盖"。
| 库 | 记录规模 | 导联 | 标注粒度 | 与 CUDB 的关系 |
|---|---|---|---|---|
| CUDB | 35 段 × 8.5 分钟 | 1 | 仅室颤段起止 | 本库,极端偏"事件覆盖"一端 |
| MIT-BIH 心律失常库 | 48 段 × 30 分钟 | 2 | 逐搏多类 | 心搏分类的基准,与 CUDB 互补而非替代 |
| INCART | 75 段 × 30 分钟 | 12 | 逐搏多类 | 跨导联与跨库测试,标注精细但无室颤段标签 |
| AHA 室性心律失常库 | 开发 80 + 测试 75 段 | 2 | 逐搏多类,仅末段 | 同属室性心律失常主题,但开放部分极少 |
| VTaC 报警库 | 五千余条报警 | 多模态 | 专家双标真假 | 任务最接近(都是报警),但它是假报警基准 |
| BIDMC 心衰库 | 15 例 × 20 小时 | 2 | 无逐搏 | 长时程心衰,无恶性事件标注 |
| AAMI EC13 测试波形 | 10 条合成记录 | 1 | 无 | 标准符合性测试,非临床数据 |
6.2 检索路径
按需求找库,比按名称找库更有效。下面几条路径都通向 CUDB:
| 你要做的事 | 检索词 | 为什么指向本库 |
|---|---|---|
| 找室颤检测的公开测试集 | ventricular fibrillation, VF detection | 官方主题词就是 ventricular fibrillation 与 ecg |
| 找报警提前量评估数据 | time to alarm, precursor tachycardia | 官方明确用"前驱室速"定义评价口径 |
| 找室速到室颤的过渡波形 | VT to VF transition | 记录围绕过渡段截取 |
| 找单导联恶性心律失常数据 | single lead, sudden cardiac arrest | 单通道、真实发作 |
| 找极小体量的教学数据 | tiny ECG dataset, WFDB example | 全库六兆,一条记录几行代码读完 |
6.3 与其他库的分工判据
选库时最好先问三个问题,答案会直接把人推向不同的库。
第一个问题:你需要标签来训练,还是需要事件来测试?需要标签,去 MIT-BIH 或 INCART;需要事件,留在这里。
第二个问题:你的输入是几个导联?一个导联,这里最匹配;十二个导联,去 INCART;多模态,去 VTaC。
第三个问题:你要评估的是"识别得对不对",还是"报得早不早"?前者用逐搏库算灵敏度特异度;后者只能用带事件起止的库,而这正是 CUDB 的独占位置。
§4.3 的案例在这里可以再被引用一次:cu01 的方括号把一段连续信号切成"前奏"与"事件",这种切法在逐搏库里找不到对应物,因为逐搏库的标签是点,不是区间。
§7 质量评估与局限性
7.0 阅读本节前的一个前置校正
看这一节之前,需要先放下一个默认假设:数据集的质量等于标注的精细度。按这个假设,CUDB 会被判为劣质——它的心搏标签全是同一个值,室颤起止只标大致位置,连患者人数都没公布。
但如果换一个评价轴,结论会反过来。它的目标不是"把每个心搏说清楚",而是"把事件的时间边界给出来,让检测器的报警时刻有地方可比"。在这个轴上,它的标注恰好够用:段级定位不需要毫秒精度,心搏类型对它毫无意义。用错轴去评分,就会把一个专用工具误判成残缺数据。下面五维评分按"室颤检测研究数据"这个用途给分,不按通用心电库给分。
7.1 五维评分
按 DAIMS 的常用维度打分,每项 1–5 分,分数针对"作为室颤检测研究数据"这个用途:
| 维度 | 分数 | 理由 |
|---|---|---|
| 数据规模 | 1 | 35 段、不足五小时,任何统计结论都站不稳 |
| 标注质量 | 2 | 段级可用、搏级不可用,官方自认"绝非定论" |
| 任务适配 | 5 | 室颤前奏与报警提前量,公开数据里几乎没有替代品 |
| 文档完整 | 3 | 采集链路、滤波、时间常数写得清楚,人群与患者数完全空白 |
| 可获取性 | 5 | 免注册、六兆、ODC-BY,下载与校验都极简 |
合计 16 分(满分 25)。分数的形状很有说明性:适配性满分,规模垫底。这意味着它天生是"评测件"而不是"训练件",任何试图用它补数据量的做法都会立刻撞上第一项。
7.2 标注质量的两层
第一层是事件边界。官方说室颤起点只标大致位置,且 cu01、cu03、cu12 在 2006 年才补上缺失的起止。这说明连"这一段有没有室颤"都曾经存在遗漏,后来才被使用者报告并修正。把这份标注当作绝对真值,会重复前人踩过的坑。
第二层是心搏位置。位置本身应该来自 QRS 检测,但官方没有说明用的是哪种检测器,也没有说明是否人工校正。考虑到全部心搏被统一标为正常,可以推断这一路标注的主要目的是给波形浏览工具提供定位锚点,而不是支撑节律分析。
| 用途 | 受哪一层影响 | 建议 |
|---|---|---|
| 段级室颤检测 | 事件边界 | 可用,但容差要给到数十秒 |
| 报警延迟统计 | 事件边界 | 可用,报告时说明标注不确定性 |
| 心率变异性 | 心搏位置 | 不建议,需自行检 R 峰 |
| 心搏分类 | 心搏类型 | 不可用,标签同质 |
| 室速负荷统计 | 两层都受影响 | 需自行定义室速判据 |
7.3 已知的、可枚举的偏差
| 偏差 | 表现 | 影响 |
|---|---|---|
| 选择偏倚 | 只保留发生恶性事件的人与段 | 阴性样本为零,无法估计特异度 |
| 单中心 | 全部来自克雷顿大学心脏中心 | 设备、贴片方式、判读习惯单一 |
| 年代偏斜 | 1980 年代采集 | 监护仪前端特性与现代设备不同 |
| 链路不一 | 一条 Holter 回放,其余床旁实时 | 噪声谱与带宽存在批间差 |
| 起搏混杂 | 五条记录来自起搏患者 | 起搏节律可能被误判为室速 |
| 除颤污染 | 多条记录含反复电击 | 电击瞬间波形不可用于形态分析 |
| 事件截断 | 部分记录在室颤中结束 | 事件表出现无终点段 |
| 人口学空白 | 无年龄、性别、病史 | 无法做亚组分析或公平性评估 |
| 患者数空白 | 不知 35 段对应多少人 | 无法做严格的患者级划分 |
7.4 外部验证矩阵
这个库在文献里的角色,几乎恒定是"外部测试集"或"阈值标定集",很少是训练集。原因很直接:它的阳性样本真实、阴性样本缺失,适合检验灵敏度与提前量,不适合训练判别边界。
| 验证角色 | 做法 | 讲得通的地方 | 风险 |
|---|---|---|---|
| 跨库室颤检测 | 在合成或其他库上训练,本库外测 | 真实发作,阳性样本可信 | 无阴性对照,特异度无法估 |
| 报警提前量标定 | 以 [ 为时间零点算延迟 |
官方就是为此设计的 | 标注误差直接进入延迟 |
| 过渡动力学研究 | 分析室速到室颤的周期演化 | 连续段完整 | 除颤段需先剔除 |
| 轻量模型可行性 | 在单导联上跑嵌入式推理 | 通道数与设备一致 | 样本太少,结论只能定性 |
| 数据增强的有效性 | 用本库做小样本增广 | 事件稀缺,增广动机强 | 增广后仍需独立测试集 |
外部研究里常见的表述是"在 CUDB 上评估",但很少给出患者级置信区间,因为患者数本身未知。看到这类结果时,应把它当作方向性证据,而不是可复现的绝对指标。
7.5 它为何能持续:稀缺的是那几十秒的过渡,不是数据量
这个库从 2007 年上线至今没有扩版,也没有被更大的库取代。原因不在平台,而在供给:能同时满足"患者在监护下"“设备在记录”“记录被保存”"机构愿意公开"四个条件的恶性心律失常过渡段,全世界也不多。新增数据要过伦理、要脱敏、要有人愿意整理,成本远高于再采一批健康人的静息心电。只要室颤检测还需要真实的前奏段,这 35 段就仍有位置。
§8 生态位与历史
8.1 历史沿革
CUDB 的源头不是学术数据集项目,而是一个产品化的监护系统。1980 年代,克雷顿大学心脏中心的团队开发了 CREI-GARD,按论文标题,它是一套计算机化心律失常监护系统的新构想。要做这样的系统,必须先有一批真实发作记录来调阈值、测误报。这批记录就是后来的 CUDB。
1990 年代初,这批记录被转成 WFDB 标准格式存放,文件时间戳大多停在 1992 年 7 月。此后的十几年里,它作为一个内部资源存在,偶尔被引用。2007 年 5 月,它正式以版本 1.0.0 上线,获得独立 DOI。2006 年的那次注释补全,发生在上线之前,说明整理者在发布前做过一轮质量核查。
8.2 与 PhysioNet 其他库的关系
PhysioNet 的心电库可以粗分为三代。第一代是 1980 年代为算法开发采集的库,MIT-BIH 心律失常库、AHA 数据库、CUDB 都属于这一代,特点是样本小、事件真、标注围绕特定任务。第二代是 2000 年代为挑战赛整理的库,规模变大、格式统一、标注规范化。第三代是 2010 年代之后的超大库与可穿戴库,强调规模与多样性。
CUDB 站在第一代的末端。它没有经历第二代的标注规范化,所以心搏标签停留在"全标正常"的粗糙水平;也没有进入第三代的规模竞赛,所以至今只有 35 段。这不是停滞,是定位:它的任务从来不需要更细的标签。
8.3 生态位:事件时钟而不是波形教材
| 角色 | 波形教材型 | 事件时钟型 |
|---|---|---|
| 代表库 | MIT-BIH、INCART、AHA | CUDB |
| 提供的核心信息 | 每个心搏是什么 | 事件从哪到哪 |
| 典型任务 | 分类、检测、分割 | 报警时刻评估、过渡分析 |
| 失败模式 | 标签噪声、类别不平衡 | 样本太少、无阴性对照 |
| 不可替代性 | 中等,同类库多 | 高,同类库极少 |
把它放错列,就会得到两种典型错误:拿它训练分类器,模型学不到东西;拿逐搏库评估提前量,找不到事件边界。两种错误都很常见,因为库名里的"database"让人以为它们可以互换。
8.4 谁在用它,用来做什么
从公开文献的使用方式看,主要有三类。第一类是做室颤检测算法的人,把它当外部验证集,检验模型换一批人之后还能不能报出来。第二类是做除颤与 AED 的人,用它标定"充电决策"的时间阈值。第三类是做心脏骤停机制研究的人,用它观察室速蜕变为室颤的周期演化。
第三类最少,但最贴近这个库的本意。前两类关心的是"报得准不准、早不早",第三类关心的是"为什么会崩"。后者的答案藏在振幅包络与周期长度的演化里,而这恰恰需要完整的过渡段——也就是 §4.3 的案例所展示的那种连续记录。
§9 使用指引
9.1 三种角色的路径
| 角色 | 第一天 | 第一周 | 注意事项 |
|---|---|---|---|
| 算法研究者 | 跑通 §5.3 的读取,画出 cu01 全段波形 | 建立事件表,算基线模型的灵敏度与延迟 | 先定事件容差,再谈指标 |
| 设备工程师 | 把信号降到设备采样率,确认带宽 | 在单导联上做阈值法与轻量模型对比 | 不要再叠加低通,官方已滤至 70 Hz |
| 机制研究 | 挑三条记录看过渡段 | 量化周期长度与振幅包络的变化率 | 先剔除除颤段,否则包络会被电击拉爆 |
9.2 一条最短的可用流程
#-- 1. 取一条记录(含头、信号、注释)
python3 - <<'PY'
import wfdb
wfdb.dl_database('cudb', dl_dir='cudb_one', records=['cu01'])
PY
#-- 2. 确认文件齐全
ls cudb_one
#-- 3. 打印事件时刻
python3 - <<'PY'
import wfdb
ann = wfdb.rdann('cudb_one/cu01', 'atr')
for s, sym in zip(ann.sample, ann.symbol):
if sym in '[]+':
print(sym, round(s / 250, 1))
PY
import wfdb
import numpy as np
rec = wfdb.rdrecord('cudb_one/cu01')
x, fs = rec.p_signal[:, 0], rec.fs
ann = wfdb.rdann('cudb_one/cu01', 'atr')
onset = ann.sample[list(ann.symbol).index('[')] / fs
print(f'时长 {len(x)/fs:.1f} 秒,室颤起点 {onset:.1f} 秒')
#-- 取室颤前 60 秒做前奏分析
seg = x[int((onset - 60) * fs):int(onset * fs)]
print(seg.shape, float(np.std(seg)))
这段流程的价值是让你在几分钟内看到真实波形与真实事件时刻。看到之后,多数关于"这个库能不能用"的疑问会自己消失:能用在哪,一眼就清楚。
9.3 引用方式
官方要求引用原始会议论文,而不是只引用平台:
| 引用对象 | 内容 |
|---|---|
| 数据集 | CU Ventricular Tachyarrhythmia Database, version 1.0.0. PhysioNet. 2007. DOI: 10.13026/C2X59M |
| 原始论文 | Nolle FM, Badura FK, Catlett JM, Bowser RW, Sketch MH. CREI-GARD, a new concept in computerized arrhythmia monitoring systems. Computers in Cardiology 13:515–518 (1986). |
| 平台 | PhysioNet 平台综述(2026 年发表于 Nature Health),按官方页面的 Cite 按钮生成 |
写论文时应注明版本号与是否使用 2006 年修订后的注释。如果只引平台不引 CREI-GARD,会漏掉数据的真正来源。
9.4 接进来之后最先撞上的三个卡点
卡点一:找不到负样本。 你打算做二分类,打开目录才发现每条记录都是阳性。可行的补救是两条路:一是从同平台的其他库借正常段,但要接受域差异;二是把同一段记录里事件之前很远的部分当阴性,但这在临床上不成立,因为那些人离室颤只有几分钟。前者诚实,后者方便,选哪个要在论文里说清。
卡点二:指标算不出来。 只有 35 段,漏报一条灵敏度就掉近三个百分点。若再按患者划分,而患者数未知,置信区间根本无法定义。合理的做法是报告逐条结果与总命中数,不报告带小数点的平均灵敏度。
卡点三:复现别人对不上。 同一个库、同一篇论文,你算出的延迟与原文差几百毫秒,原因往往是标注版本不同。2006 年之前的注释缺三条记录的室颤起止,用旧版会得到完全不同的事件时刻。下载后先确认 .atr 的修改时间是 2006 年之后,再开始算。
| 卡点 | 表面症状 | 根因 | 处理 |
|---|---|---|---|
| 无负样本 | 验证集全是阳性 | 库的选择偏倚 | 借外部阴性段并声明域差异 |
| 指标不稳 | 换个种子结论翻转 | 样本量只有 35 段 | 报逐条结果,不报小数点 |
| 复现不一致 | 延迟差数百毫秒 | 注释版本或时间单位不同 | 锁版本、锁采样率、锁单位 |
| 事件表有空洞 | 某些记录没有方括号 | 事件被截断或旧注释缺失 | 显式处理开区间并核对版本 |
§10 AI 使用声明
| 项目 | 声明 |
|---|---|
| 文本生成 | 本文由 AI 辅助撰写,编辑把关结构与事实 |
| 数字来源 | 规模、结构、边界三类数字来自 PhysioNet 官方页面与官方文件 |
| 文件核验 | RECORDS、ANNOTATORS、cu01/cu02/cu12/cu35 头文件、cu01/cu12/cu30 注释经直接访问确认 |
| 记录数 | 官方页面与 RECORDS 均为 35 条,一致 |
| 患者数 | 官方未公布,本文未做推断 |
| 论文信息 | 原始论文信息取自官方页面的引用项,未读会议全文 |
| 未核验内容 | 逐条节律构成、除颤次数、各记录的确切事件数 |
| 图像 | 无配图;封面为生成式提示词,不含真实心电图 |
| 免责 | 本文不构成临床建议;使用须遵守 ODC-BY 1.0 署名要求 |
常见误读(避坑清单)
| 说法 | 实际 | 证据 |
|---|---|---|
| 「它是室颤库,所以每条都是室颤」 | 每条都含事件,但室颤只占段尾一小部分,前面是前奏 | 官方称记录围绕室速、室扑、室颤发作截取;平均到事件起点约 5 分 47 秒 |
| 「它有逐搏心律失常标签」 | 所有心搏统一标为正常,官方明说其中很多是异位的 | 官方数据说明原文 |
| 「35 段就是 35 名患者」 | 患者数官方未公布,不能假定一一对应 | 官方只说人类受试者,未给人数 |
| 「标注是真值,可以直接算误差」 | 官方称标注「绝不是定论」,室颤起点只标大致位置 | 官方数据说明原文 |
| 「它是十二导联或双导联」 | 单通道,头文件信号条数为 1 | cu01、cu02、cu35 头文件均为 1 |
| 「它是 360 Hz,和 MIT-BIH 一样」 | 250 Hz,且已做 70 Hz 低通 | 头文件第 3 字段为 250 |
| 「它可以直接训练深度模型」 | 总时长不足五小时且无阴性样本,只能做验证 | 35 段 × 约 8.5 分钟 |
| 「2006 年改过波形」 | 只改注释,补上缺失的室颤起止,信号未动 | 官方说明与 .atr- 历史文件 |
| 「反复除颤不影响分析」 | 电击瞬间波形不具生理意义,官方未标电击时刻 | 官方称许多记录可见反复除颤 |
| 「起搏只有一条记录」 | 官方点名五条:cu12、cu15、cu24、cu25、cu32 | 官方数据说明原文 |
FAQ
Q1:CUDB 到底有多少条记录?
35 条,编号 cu01 到 cu35。官方页面摘要写 35 段,RECORDS 文件也是 35 行,两处一致。这个数字从 2007 年上线至今没有变过。
Q2:它有多少名患者?
官方没有公布。数据说明只写记录取自"发生过持续性室速、室扑与室颤的人类受试者",没有给出人数,也没有说明一段记录是否对应一个人。做实验划分时应把这一点写进限制,采用保守策略。
Q3:为什么心搏全标成正常?这是标注错误吗?
不是错误,是设计。官方原文说所有心搏都标为正常,尽管很多是异位的。这一路标注的作用是给波形浏览与事件定位提供锚点,不承担心搏分类功能。想要逐搏类型,得换库。
Q4:它能用来训练室颤检测模型吗?
可以训练,但结论会很不稳。35 段、不足五小时、没有正常对照,模型几轮就能记住全部波形。更合适的用法是把它当外部测试集或阈值标定集,训练放在别的库上做。
Q5:为什么评价里会出现负的报警时间?
因为官方鼓励检测器对室颤之前的室速就报警。以室颤标注起点为时间零点,提前报警自然得到负值。如果你的评测脚本把负延迟截断为零,就会把最该奖励的行为抹掉。
Q6:室颤起止标注有多准?
官方说只标大致起点,而且明确讲这些注释"绝不是定论"。cu01、cu03、cu12 三条还在 2006 年补标过缺失的起止,说明连事件是否存在都曾需要修正。做延迟统计时要把这份不确定性算进容差。
Q7:为什么 cu01 和其他记录不一样?
cu01 来自长时程 Holter 记录,为了数字化按实时速度回放;其余记录是在患者监护现场从监护仪的高电平模拟输出直接实时数字化的。两条链路的噪声谱与前端特性不同,跨记录比较时要留意。
Q8:五条起搏记录是哪几条?影响是什么?
cu12、cu15、cu24、cu25、cu32。官方特别提醒,有些记录的起搏伪迹并不可见,只能从节律的规整性推断起搏存在。这类记录的宽 QRS 可能被算法误判为室速。
Q9:它能做多导联或十二导联的研究吗?
不能。它是单通道,头文件里信号条数为 1。要做导联间对比,去看 INCART;要做双导联的长时程,去看 MIT-BIH 心律失常库或 AHA 数据库。
Q10:全库有多大?下载要多久?
未压缩约 6.4 MB,压缩包约 6.5 MB。任何能上网的环境都能在几秒内下完,适合放进 CI 做每次构建的回归测试。
Q11:许可允许商业使用吗?
ODC-BY 1.0 允许商业使用,条件是按许可要求署名。署名时应同时引用数据集与 CREI-GARD 原始论文,具体条款以许可全文为准。
Q12:它和 VTaC 报警库有什么区别?
VTaC 是监护仪室速报警的真假判定基准,规模五千余条、多模态、专家双标;CUDB 是真实发作的连续波形,规模 35 段、单导联、只标室颤段起止。前者回答"这条报警该不该响",后者回答"室颤什么时候开始"。
术语表
| 术语 | 解释 |
|---|---|
| 室性心动过速 | 起源于心室的快速心律,QRS 宽大,可持续数秒到数分钟 |
| 心室扑动 | 极快的室性心律,波形呈连续正弦状,常是室颤前兆 |
| 心室颤动 | 心室电活动完全失序,无有效收缩,数分钟内致死 |
| 快速心律失常 | 心率异常增快的统称,本库用它涵盖室速、室扑与室颤 |
| 前驱室速 | 室颤发作之前出现的那段室性心动过速 |
| 报警提前量 | 检测器报警时刻相对事件起点的提前程度,可为负 |
| 除颤 | 用电击终止室颤或室速,波形上表现为瞬时大幅尖峰 |
| 起搏 | 用起搏器发放电脉冲带动心室,可产生规整的宽 QRS |
| WFDB | PhysioNet 使用的波形数据库格式,含头、信号、注释三类文件 |
| 212 格式 | 每两个 12 位采样点打包进三个字节的存储方式 |
| 增益 | 每毫伏对应的整数单位数,本库为 400 |
| ADC 零值 | 零电平对应的整数,换算物理量时先减 |
| 校验和 | 全段采样点累加和,用于检测文件完整性 |
| 注释符号 | 标注文件里代表事件类型的单字符编码 |
| 段级标注 | 标出一段时间区间,如室颤起止 |
| 搏级标注 | 标出单个心搏的位置与类型 |
| 单通道 | 只有一路信号,无导联间空间关系 |
| Bessel 低通 | 群延迟平坦的低通滤波器,保波形形状 |
| 截止频率 | 滤波器开始显著衰减的频率,本库为 70 Hz |
| 阴性样本 | 未发生目标事件的记录,本库缺失 |
| 选择偏倚 | 因入选标准导致样本不代表性 |
| 患者级划分 | 同一患者的所有记录只进训练或只进测试 |
| ODC-BY | 开放数据共享署名许可 |
| DOI | 数字对象标识符,用于长期定位数据集 |
附录 A:核心数字核对表
| 项 | 值 | 来源 | 来源层级 |
|---|---|---|---|
| 记录数 | 35 | 官方页面;RECORDS 逐行 | 官方页面 |
| 记录编号 | cu01–cu35 | RECORDS | 官方文件 |
| 每段时长 | 约 8.5 分钟 | 官方页面 | 官方页面 |
| 每段采样点 | 127232 | cu01/cu02/cu35 头文件 | 官方文件 |
| 采样率 | 250 Hz | 官方页面;头文件 | 官方页面 |
| 位宽 | 12 位 | 官方页面;头文件 | 官方页面 |
| 导联数 | 1 | 头文件信号条数 | 官方文件 |
| 增益 | 400 | 抽查四条头文件 | 官方文件 |
| 低通截止 | 70 Hz | 官方页面 | 官方页面 |
| 模拟标称幅度 | 1 V/mV | 官方页面 | 官方页面 |
| 事件前最少非室颤心搏 | 61 | 官方页面 | 官方页面 |
| 到事件起点平均时间 | 5 分 47 秒 | 官方页面 | 官方页面 |
| 到事件起点标准差 | 2 分 1 秒 | 官方页面 | 官方页面 |
| 起搏记录 | 5 条 | 官方页面 | 官方页面 |
| 注释修订 | 3 条(2006 年 1 月) | 官方页面 | 官方页面 |
| 全库未压缩体积 | 6.4 MB | 官方页面 | 官方页面 |
| 压缩包体积 | 6.5 MB | 官方页面 | 官方页面 |
| 许可 | ODC-BY 1.0 | 官方页面 | 官方页面 |
| DOI | 10.13026/C2X59M | 官方页面 | 官方页面 |
| 首次发布 | 2007-05-02 | 官方页面 | 官方页面 |
附录 B:数字的来源分层
第一层:官方页面与官方文件。 记录数、时长、采样率、位宽、滤波、事件时间常数、起搏记录编号、注释修订记录、体积、许可、DOI、发布日期,全部来自项目主页的数据说明段,以及 RECORDS、ANNOTATORS 与抽查的头文件、注释文件。这一层的数字可以直接引用。
第二层:论文摘要与引用条目。 原始论文的出处、作者、卷期页码来自官方页面的引用项;CREI-GARD 的系统定位来自该文献的标题与副标题。没有读到会议全文,因此不引用论文里的任何具体数字。
第三层:未核实。 受试者人数、年龄、性别构成、每条记录的节律构成、除颤次数、室颤段的确切数量与长度、起搏伪迹在五条记录中的可见程度。这些需要逐条判读波形才能得到,正文中凡涉及处均写为"官方未公布"或只做定性表述。
附录 C:完整参考文献
| 编号 | 文献 | 链接 | 用途 |
|---|---|---|---|
| 1 | CU Ventricular Tachyarrhythmia Database v1.0.0 | https://physionet.org/content/cudb/1.0.0/ | 全部规模、结构、边界数据 |
| 2 | 该库文件目录 | https://physionet.org/files/cudb/1.0.0/ | RECORDS、头文件、注释 |
| 3 | Nolle FM 等,CREI-GARD,1986 | Computers in Cardiology 13:515–518 | 原始采集与系统背景 |
| 4 | PhysioNet 平台综述(2026) | https://doi.org/10.1038/s44360-026-00096-z | 托管模式与引用规范 |
| 5 | PhysioBank 注释码定义 | https://physionet.org/physiobank/annotations.shtml | [、]、N 等符号含义 |
| 6 | MIT-BIH 心律失常库(对照) | https://physionet.org/content/mitdb/ | §2.5 与 §6.1 对比 |
| 7 | INCART 数据库(对照) | https://physionet.org/content/[incartdb](https://www.qianfanghub.com/ai-ready-dataset/incartdb/1440)/ | 多导联逐搏库对比 |
| 8 | AHA 室性心律失常库(对照) | https://physionet.org/content/ahadb/ | 同主题库对比 |
| 9 | VTaC 报警基准(对照) | https://doi.org/10.13026/4jqj-j428 | 报警任务对比 |
附录 D:任务代码
任务一:按事件切窗并生成带标签的片段表
import wfdb
import numpy as np
def build_windows(name, pre=60, post=30, fs=250):
rec = wfdb.rdrecord(name, pn_dir='cudb')
ann = wfdb.rdann(name, 'atr', pn_dir='cudb')
x = rec.p_signal[:, 0]
starts = [s for s, y in zip(ann.sample, ann.symbol) if y == '[']
ends = [s for s, y in zip(ann.sample, ann.symbol) if y == ']']
rows = []
for i, s in enumerate(starts):
a = max(0, s - pre * fs)
b = min(len(x), s + post * fs)
seg = x[a:b]
if len(seg) < (pre + post) * fs:
continue
rows.append({
'record': name,
'onset_sec': round(s / fs, 2),
'end_sec': round(ends[i] / fs, 2) if i < len(ends) else None,
'label': 1,
'std': float(np.std(seg)),
})
return rows
for n in wfdb.get_record_list('cudb')[:5]:
for r in build_windows(n):
print(r)
任务二:报警延迟评估骨架
import wfdb
import numpy as np
def first_alarm(sig, fs, win=2.0, thresh=0.35):
#-- 以 2 秒窗计算归一化差分能量,超过阈值即视为报警
step = int(win * fs)
energy = np.array([np.std(np.diff(sig[i:i + step])) for i in range(0, len(sig) - step, step)])
if energy.max() == 0:
return None, energy
norm = energy / energy.max()
idx = np.where(norm > thresh)[0]
return (idx[0] * step / fs if len(idx) else None), norm
def evaluate(name):
rec = wfdb.rdrecord(name, pn_dir='cudb')
ann = wfdb.rdann(name, 'atr', pn_dir='cudb')
onset = [s for s, y in zip(ann.sample, ann.symbol) if y == '['][0] / rec.fs
t_alarm, _ = first_alarm(rec.p_signal[:, 0], rec.fs)
if t_alarm is None:
return {'record': name, 'onset': onset, 'alarm': None, 'delay': None}
return {'record': name, 'onset': onset, 'alarm': t_alarm,
'delay': round(onset - t_alarm, 2)}
for n in wfdb.get_record_list('cudb'):
print(evaluate(n))
第二段代码里的阈值是随手设的,只用来演示接口形状,不构成任何性能结论。真实评估需要固定阈值策略、说明容差、并报告有多少条记录根本没有触发报警——在只有 35 段的情况下,漏报一条就会让灵敏度掉好几个百分点。
任务三:室速到室颤的周期演化
import wfdb
import numpy as np
rec = wfdb.rdrecord('cu12', pn_dir='cudb')
ann = wfdb.rdann('cu12', 'atr', pn_dir='cudb')
x, fs = rec.p_signal[:, 0], rec.fs
onset = [s for s, y in zip(ann.sample, ann.symbol) if y == '['][0]
#-- 取事件前 120 秒,按 5 秒一段看振幅包络的变异系数
seg = x[max(0, onset - 120 * fs):onset]
win = int(5 * fs)
for i in range(0, len(seg) - win, win):
part = seg[i:i + win]
env = np.abs(part)
print(f'{i/fs:6.1f}s std={part.std():.4f} cv_env={env.std()/env.mean():.3f}')
这段输出会给出一个粗糙的包络变异序列。它的用途不是下结论,而是让你看到过渡段的形态确实在变——越接近事件,包络的起伏通常越剧烈,周期也越不规整。把这条曲线与 §4.3 的案例放在一起看,能对"过渡"这个词有具体的感受。
附录 E:如果只记五件事
| 序号 | 记住什么 | 为什么 |
|---|---|---|
| 1 | 35 段、每段约 8.5 分钟、单导联、250 Hz | 这四个数决定了它的全部能力边界 |
| 2 | 心搏全标正常,只有室颤段起止可用 | 想训心搏分类器的人可以立刻离开 |
| 3 | 它的任务是报警提前量,不是事件识别 | 负延迟是特性,不是评测错误 |
| 4 | 它没有阴性对照,也没有患者人数 | 任何灵敏度和泛化声明都要加限定 |
| 5 | 它是外部时钟,不是训练粮仓 | 用它验证别人的模型,比用它训练自己的模型有价值 |
编辑说明
本文的核验时间为 2026 年 10 月 28 日。核对方式为直接访问 PhysioNet 项目页与文件目录,逐一确认 RECORDS、ANNOTATORS、SHA256SUMS 与 cu01、cu02、cu12、cu35 头文件的可访问性与内容,并用 wfdb 读取 cu01、cu12、cu30 的注释符号集合,确认其与官方"全标正常、只标室颤起止"的描述一致。论文侧只依据官方页面给出的引用项,未读 CREI-GARD 会议全文,因此不引用其中的任何数字。未覆盖的内容包括:受试者人数与人口学构成、逐条记录的节律构成、除颤次数、室颤段的确切数量与长度、起搏伪迹的可见程度。这些需要逐条判读波形,超出了本篇只写规模、结构、边界三类数字的范围。库内检索确认此前没有 CUDB 词条;与 AHA 室性心律失常库、VTaC 报警库、MIT-BIH 心律失常库、INCART 的区隔已在 §2.5 与 §6.1 写明。
相关数据集导航
以下为站内 AI-Ready 数据集百科中与本词条共享多个主题标签的相关数据集,按相关度降序排列:
- wctecgdb — 共享标签:生理信号 / 心电信号 / 心血管疾病
- ahadb — 共享标签:生理信号 / 心电信号 / 心血管疾病
- chfdb — 共享标签:生理信号 / 心电信号 / 心血管疾病
- crisdb — 共享标签:生理信号 / 心电信号 / 心血管疾病
- ltdb — 共享标签:生理信号 / 心电信号 / 心血管疾病
- ltstdb — 共享标签:生理信号 / 心电信号 / 心血管疾病
- iafdb — 共享标签:生理信号 / 心电信号 / 心血管疾病
- mvtdb — 共享标签:生理信号 / 心电信号 / 心血管疾病
- nsr2db — 共享标签:生理信号 / 心电信号 / 心血管疾病
- stdb — 共享标签:生理信号 / 心电信号 / 心血管疾病
导航说明:本章节由全站统一标签体系自动计算生成(标签重合度算法),双向可达;点击链接可跳转至对应数据集词条。

