在临床与工程的交界处,一张12导联心电图通常在一分钟内采集完成,但判读环节远没有采集那么快。医生需要逐导联观察P波、QRS波群、ST段和T波,还要结合患者年龄、症状和既往史综合判断。AI心电图分析系统要解决的,就是把这套依赖经验的过程变成端到端的自动初筛:输入一张原始心电图,模型在不到2秒内输出心律失常、心肌缺血、心肌梗死等异常类别和置信度。对于急诊胸痛、院前筛查、体检中心大规模心电判读这类场景,快速分类意味着能够大幅压缩常规阴性样本的处理时间,把有限的人力集中到可疑病例上。
这篇文章写给已经接触过Python和深度学习、希望进入医疗AI方向的开发者。文中不会只讲“AI很厉害”的概念,而是从数据结构、信号预处理、模型设计、推理加速、指标验证和实际部署六个层面,梳理一套可复现的AI心电图识别系统是怎么从零跑起来的。读完之后,你会得到一张完整的技术地图,也会知道哪些坑需要在一开始就避开,而不是等模型上线后再返工。
1. 先理解心脏信号里藏着什么,才能设计AI要去提取的特征
1.1 心电图的本质是心脏电活动的时间序列
心电图记录的是心脏在每个心动周期中的电活动变化。一个完整的心动周期曲线,主要包含P波、QRS波群、T波和它们之间的间期。P波对应心房去极化,QRS波群对应心室去极化,T波对应心室复极化。这些波形之间还有PR间期、QT间期、ST段等测量区间,临床医生通过观察这些形态和间期,寻找超出正常范围的异常信号。
从数据工程的角度看,一条心电图本质上是一条多维时间序列。12导联心电图在同一个时间窗口内记录12个通道的电压变化,每个通道对应心脏不同位置的探测方向。常规采集参数通常是10秒时长,采样率在250Hz到1000Hz之间。以500Hz采样率、10秒时长为例,一条记录就是一个 [12, 5000] 的数值矩阵。
这就是AI心电图系统最底层的输入形态。很多刚入门的开发者以为模型输入是“一张图片”,这是常见的误解。心电图信号是连续波形,不是图像;即使保留二维形状,第一个维度也是导联通道,第二个维度是时间采样点,而不是像素的行和列。后续所有卷积、池化、注意力机制的设计,都要围绕时间序列这个本质展开。
1.2 规则式判读为什么漏掉“隐藏信号”
传统心电图机已经内置了大量自动测量规则,比如计算心率、检测PR间期、判断QTc是否延长。规则式判读能覆盖常见、明确、阈值清晰的异常,但真正的临床难题恰恰是阈值不清晰的形态组合。
举几个例子:早期复极综合征在某些导联上的ST段形态与正常变异性非常接近,仅靠单个导联的ST段抬高幅度很难界定;阵发性房颤可能在一张10秒心电图上只表现为短串的不规则心律,漏看一个片段就会判为正常;心肌缺血早期的T波改变往往不是全导联同时出现,而是某个导联的T波形态发生细微倒置或切迹。这些信号对医生来说是“隐藏”的,因为需要使用多个导联的组合信息和连续时间上下文。
规则式算法的问题在于,每条规则都是人工定义的分界值。分界值只能描述统计规律,无法覆盖个体差异。同一个人的不同年龄段、不同心率状态、不同的电极贴放位置,都会让波形产生合法范围内的变化。
1.3 AI的模式识别方式更接近医生的综合判断
深度学习模型并不依赖某一条固定阈值,而是从大量标注样本中学习“哪些形态组合更可能对应哪种疾病”。以1D-CNN为例,底层卷积核会自动提取短时波形片段,比如R波尖峰、ST段平缓段、T波形态;高层网络再把多个导联、多个时间段的特征组合起来,形成全局判断。
可以把“隐藏信号”理解为需要跨导联、跨时间组合才能发现的模式。模型在训练中见过成千上万条带标签的波形,它知道某种T波形态在V1导联出现的同时,V5导联ST段也发生轻微改变时,大概率对应哪种心肌缺血类型。这种组合能力正是规则式方法最薄弱的地方。
需要明确边界:AI心电图通常作为辅助筛查工具存在,最终诊断仍然需要临床医生确认。模型输出的不是“确诊”,而是“可疑程度”和“初步分类建议”。这也是医疗AI产品和普通算法项目之间最大的差别。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据工程决定AI心电图的上限:从原始波形到可训练样本
2.1 原始数据结构要先对齐,否则后续代码全部失效
开始写模型前,必须先确认数据集的三种基本属性:导联数量、采样率和记录时长。不同品牌的心电图机输出格式并不统一,有的包含12导联,有的只有单导联;有的采样率是500Hz,有的只有250Hz;有的时长是10秒,有的可能更长。
在常见项目中,可以按这个思路处理:先读一条记录的原始数据,打印数组形状和采样率,确认导联顺序是固定不变的。导联顺序尤其重要,模型训练时会把第一个通道当成I导联,第二个通道当成II导联,一旦设备输出顺序发生变化,模型接收到的特征分布就会完全错位。
| 数据属性 | 常见取值 | 对模型的影响 |
|---|---|---|
| 导联数量 | 单导联、3导联、12导联 | 决定输入通道数,12导联信息量最大 |
| 采样率 | 250Hz、500Hz、1000Hz | 决定时域分辨率,影响滤波参数设计 |
| 记录时长 | 10秒、24小时动态 | 短记录适合分类,长记录需要切片处理 |
| 导联顺序 | 固定顺序 | 不一致时会导致训练和推理完全失效 |
如果原始材料没有给出明确版本或设备型号,落地前要先确认设备的输出规范。不要默认所有设备都输出标准12导联顺序。
2.2 基线漂移、肌电干扰和工频干扰需要先处理
原始心电信号中除了心脏电活动,还有大量干扰成分。呼吸和肢体动作会造成基线漂移,肌肉颤抖会造成高频肌电干扰,市电环境会引入50Hz或60Hz工频干扰。这些噪声会直接影响模型学习。
常见的预处理方式是使用带通滤波。心电信号的临床有效频带大致在0.5Hz到45Hz之间,低于0.5Hz的漂移和高于45Hz的高频噪声都可以被过滤掉。下面是一个典型的滤波和标准化处理函数:
python复制import numpy as np
from scipy import signal as scipy_signal
def preprocess_ecg(raw: np.ndarray, fs: int = 500) -> np.ndarray:
# 1. 带通滤波,去除基线漂移和高频噪声
b, a = scipy_signal.butter(4, [0.5, 45], btype='bandpass', fs=fs)
filtered = scipy_signal.filtfilt(b, a, raw, axis=-1)
# 2. 对明显异常的幅值段做掩码处理
filtered[np.abs(filtered) > 20] = 0
# 3. 对每个导联独立做标准化
mean = filtered.mean(axis=-1, keepdims=True)
std = filtered.std(axis=-1, keepdims=True) + 1e-6
normalized = (filtered - mean) / std
return normalized
这里使用 filtfilt 而不是 lfilter,是因为 filtfilt 做双向滤波,不会引入相位偏移,波形形态在时域上不会失真。滤波后做标准化,是为了让不同设备、不同幅值增益的波形进入模型时保持量纲一致。
注意:预处理方式一旦确定,训练、验证、推理三个环节必须使用完全相同的参数。真实部署中常见的错误是训练时做了滤波和标准化,上线时只做了标准化,导致模型接收的输入分布完全不同。
2.3 数据增强要围绕心电信号本身的物理含义
图像领域常用的随机裁剪、旋转、翻转让模型学到了更多不变性,但这些操作不能直接套到心电信号上。翻转心电信号会让R波变成倒置,旋转更加没有物理意义。
相对可行的增强方式包括:小幅幅值缩放、时间轴轻微伸缩、叠加低幅噪声、对单个导联做微小偏移。这些操作模拟的是电极接触变化、皮肤阻抗差异、采集设备增益不同等真实情况。增强的目的不是无中生有造数据,而是让模型对采集环境差异不敏感。
这里要控制增强幅度。STM段偏移、QRS波振幅、QT间期长度在临床上都有明确含义,把波形改动到跨越疾病分界线,就会制造标签噪声。建议增强后随机抽几条样本,打印波形和标签,用人眼确认语义没有翻转。
2.4 标签设计:单标签多分类还是多标签分类
心电图异常经常同时存在。比如一位患者可能既有心房颤动,又存在左心室肥厚;急诊心电图上可能同时出现ST段抬高和前间壁心肌梗死的表现。如果使用单标签多分类,强制所有样本只能属于一个类别,会丢失非常关键的临床信息。
推荐使用多标签分类。模型输出层使用sigmoid激活,每个类别独立计算概率,最终设置一个阈值判断是否命中。损失函数使用binary cross-entropy,而不是softmax交叉熵。
| 标签方式 | 输出层 | 损失函数 | 适用场景 |
|---|---|---|---|
| 单分类 | softmax | cross entropy | 只能选一个疾病类别 |
| 多标签 | sigmoid | binary cross entropy | 允许同时出现多种异常 |
| 多任务 | 每个任务独立head | 加权损失之和 | 同时预测类别和位置/间期 |
在多标签设计下,类别数取决于标注体系。常见分类包括正常、心房颤动、室性早搏、ST段抬高、ST段压低、T波异常、传导阻滞等。标签体系直接决定了模型的业务边界,不要随意自创类别,建议先和心内科医生确认一套临床常用分类清单。
3. 模型结构选型:先用1D-CNN跑通基线,再决定加不加时序模块
3.1 1D-CNN为什么是心电图分类的合理起点
心电图是时间序列,但1D-CNN仍然是目前最可靠的起点。卷积核沿着时间维度滑动,天然适合提取局部波形形态:短卷积核可以捕获QRS尖峰,长卷积核可以覆盖ST段和T波这样的平缓形态。多个卷积层堆叠后,感受野逐渐扩大,模型能够从“局部波形”过渡到“全局节律”。
相比LSTM,1D-CNN训练更快、显存占用更低、对序列长度不敏感。相比Transformer,1D-CNN输入不需要位置编码,也不需要处理复杂的注意力矩阵,更适合一开始就快速搭建可验证的闭环。
在实际项目中,可以用一个带残差结构的1D-CNN作为基线,先看指标是否达到业务阈值,再逐步替换更强模型。不要一上来就上大模型,医疗数据的标注成本远高于模型结构带来的收益。
3.2 一个可运行的PyTorch心电图分类基线模型
下面这个模型假设输入形状是 [batch_size, 12, 5000],对应12导联、10秒、500Hz采样。输出层使用sigmoid处理多标签分类:
python复制import torch
import torch.nn as nn
class BaseECGNet(nn.Module):
def __init__(self, num_leads=12, num_classes=5, base_channels=32):
super().__init__()
self.stem = nn.Sequential(
nn.Conv1d(num_leads, base_channels, kernel_size=15, stride=2, padding=7),
nn.BatchNorm1d(base_channels),
nn.ReLU(inplace=True),
)
self.backbone = nn.Sequential(
nn.Conv1d(base_channels, base_channels * 2, kernel_size=7, stride=2, padding=3),
nn.BatchNorm1d(base_channels * 2),
nn.ReLU(inplace=True),
nn.Conv1d(base_channels * 2, base_channels * 4, kernel_size=5, stride=2, padding=2),
nn.BatchNorm1d(base_channels * 4),
nn.ReLU(inplace=True),
)
self.gap = nn.AdaptiveAvgPool1d(1)
self.head = nn.Linear(base_channels * 4, num_classes)
def forward(self, x):
x = self.stem(x)
x = self.backbone(x)
x = self.gap(x).squeeze(-1)
return self.head(x)
stem 中的第一个卷积核大小为15,目的是用稍大的窗口覆盖一个完整的局部波形成分,比如一个较宽的P波。backbone 逐层降低时间维度,抽取更高层特征。gap 全局平均池化把最后一层特征图压缩成一维向量,再输入到分类头。
这个模型只有数十万参数,在CPU上都能较快完成推理。它的价值不是拿到最高精度,而是帮助团队快速跑通“数据加载-模型训练-指标评估-推理部署”整条链路。确认链路稳定后,再进入结构优化。
3.3 LSTM和注意力机制加在哪里更合适
1D-CNN的短板在于感受野受限,对长时间依赖关系的建模较弱。心电图中的某些异常需要跨多个心动周期判断,比如房颤的不规则RR间期、阵发性心动过速的起止点。这时可以在CNN后端追加LSTM或Transformer encoder。
一个比较稳定的结构是先CNN后LSTM:CNN负责提取局部形态特征,LSTM负责建模时间依赖,最后用全连接层输出分类结果。这种方式既能保留卷积的局部特征提取能力,又能补上长序列建模能力,训练成本也比纯Transformer低。
| 结构方案 | 能力优势 | 训练成本 | 适合的异常类型 |
|---|---|---|---|
| 纯1D-CNN | 局部形态强、速度快 | 低 | ST段、T波、QRS形态 |
| CNN + LSTM | 增加时间依赖建模 | 中 | 房颤、早搏、心动过速 |
| CNN + Transformer | 全局注意力强 | 高 | 需要跨导联长程依赖 |
| 时频域双分支 | 同时看时域和频域 | 中高 | 心率变异性、节律类异常 |
如果原始项目只要求识别ST段改变和心肌梗死这类形态学异常,纯1D-CNN已经足够。如果业务场景偏向房颤筛查和动态心电分析,建议在CNN后面接一层单向LSTM。注意不要盲目堆叠深度,可解释性和训练稳定性比模型参数量更重要。
4. 不到2秒的推理时间从哪里来:延迟预算拆解与部署优化
4.1 先把两秒拆解成预处理、模型和前处理的预算
“2秒”不是模型的单次前向传播时间,而是从原始数据输入到结果返回的端到端时间。多数团队只盯着模型推理时间,忽略预处理和后处理,导致真实接口延迟远超预期。
合理的延迟预算可以这样分配:
| 阶段 | 耗时占比 | 主要内容 |
|---|---|---|
| 数据读取与格式解析 | 30% | 读文件、解析字段、提取波形矩阵 |
| 信号预处理 | 15% | 滤波、标准化、切片、重组 |
| 模型推理 | 45% | 神经网络前向传播 |
| 后处理与结果组装 | 10% | sigmoid阈值、类别格式化、业务输出 |
如果使用Python,建议对预处理做以下优化:避免在请求处理线程里重复创建滤波器系数,滤波参数启动时算一次;避免把整条记录多次拷贝,使用内存视图或切片;后处理逻辑保持轻量,尽量用向量运算而不是Python循环。
4.2 把PyTorch模型导出为ONNX,再接入推理服务
PyTorch模型直接用于线上服务虽然可行,但会伴随大量运行时开销,锁机制和动态图逻辑都会拉低单次推理性能。更稳妥的做法是把模型导出为ONNX,再使用ONNX Runtime执行推理。
下面是导出示例:
python复制import torch
model = BaseECGNet(num_leads=12, num_classes=5)
model.load_state_dict(torch.load("ecg_weights.pth", map_location="cpu"))
model.eval()
dummy_input = torch.randn(1, 12, 5000)
torch.onnx.export(
model,
dummy_input,
"ecg_model.onnx",
input_names=["ecg_signal"],
output_names=["logits"],
dynamic_axes={"ecg_signal": {0: "batch"}},
opset_version=14,
)
print("导出完成: ecg_model.onnx")
导出时让batch维度保持动态,这样同一个模型既可以处理单条请求,也可以处理小批量请求。推理端代码如下:
python复制import onnxruntime as ort
import numpy as np
import time
sess = ort.InferenceSession("ecg_model.onnx", providers=["CPUExecutionProvider"])
x = np.random.randn(1, 12, 5000).astype(np.float32)
# 预热
for _ in range(5):
sess.run(None, {"ecg_signal": x})
# 计时
start = time.perf_counter()
for _ in range(50):
sess.run(None, {"ecg_signal": x})
avg_ms = (time.perf_counter() - start) / 50 * 1000
print(f"平均推理耗时: {avg_ms:.1f} ms")
上面这个 avg_ms 只代表模型前向传播时间。要评估接口总耗时,还需要把数据读取、滤波、后处理的耗时都统计进去,形成一份多阶段的耗时报告。
4.3 进一步压缩延迟:量化、固定batch和会话复用
在CPU部署时,FP32模型可能无法满足2秒预算,尤其是当心电图记录堆叠了滤波、标准化和多导联组装之后。下面三种优化方式最有效。
量化可以让模型权重从32位浮点数变为8位整数,模型体积缩小到四分之一,推理速度通常能提升一倍以上。缺点是可能出现精度损失,尤其是涉及特别细致的T波形态时。建议先用量化后的模型跑一遍验证集,确认AUC没有明显下降再上线。
固定batch大小能减少动态shape带来的额外开销。如果业务场景是单条请求处理,导出时可以把batch维度固定为1,虽然牺牲了批处理能力,但换取更稳定的延迟。
会话复用最重要。ONNX Runtime的InferenceSession对象创建成本很高,不要在每次请求时都重新初始化。正确做法是启动服务时创建一次session,之后所有请求复用同一个session对象。
注意:如果目标设备是手机或嵌入式设备,还需要考虑更底层的加速方案,包括内存分配策略、多线程数、指令集支持和编译优化。这些优化通常需要在真实设备上逐一验证,不能只靠台式机上的基准数据。
4.4 延迟达标不只靠模型,还要看接口设计
即使模型推理只有300ms,如果每次请求都重新读取文件、重新建立数据库连接、重新初始化ONNX session,总时长依然会超过2秒。建议把整个处理链做成流水线:
- 服务启动时加载模型、滤波系数和标签映射表。
- 请求到达后,只做文件读取、波形提取和轻量预处理。
- 推理完成后立刻返回,异步记录日志和波形信息用于后续分析。
生产环境还需要考虑并发。如果同时有多个请求进入,CPU资源被争抢,单请求延迟会明显上升。这时需要做压测,确认系统在目标并发数下仍能满足2秒延迟。
5. 验证AI心电图模型时,不能只看准确率
5.1 敏感性、特异性和AUC才是临床级指标
在医疗AI场景中,准确率往往具有欺骗性。如果数据集中95%是正常样本,模型把所有样本都判为正常,准确率就是95%,但这个模型毫无临床价值。需要同时看敏感性、特异性和AUC。
| 指标 | 含义 | 在AI心电图中的作用 |
|---|---|---|
| 敏感性 | 实际异常样本中被正确召回的比例 | 越高越能减少漏诊 |
| 特异性 | 实际正常样本中被正确排除的比例 | 越高越能减少误报 |
| AUC | ROC曲线下面积 | 反映模型排序能力,不依赖阈值 |
| F1 | 精确率和召回率的调和平均 | 用于类别不均衡时综合衡量 |
在急诊场景,漏掉心肌梗死是重大医疗事故,因此更看重敏感性;在体检筛查场景,大量健康人群会被误报为异常,造成不必要的复检,因此更看重特异性。团队应该根据业务场景,明确是优先提升敏感性还是特异性,而不是笼统要求“准确率越高越好”。
5.2 验证集必须按患者划分,不能按记录划分
心电图数据往往来自大量患者,每个患者可能有多条记录。如果同一个患者的记录同时出现在训练集和验证集中,模型可能没有学到泛化规律,而是记住了这个人特有的波形特征,验证指标会异常偏高。
正确的划分方式是按患者列表切分。也就是说,一个患者的所有记录要么全部进入训练集,要么全部进入验证集。划分后还要确认训练集和验证集的患者年龄分布、性别分布、疾病分布基本一致,避免出现训练集全是中老年患者、验证集全是青年患者的偏差。
5.3 错误样本需要回到原始波形上复核
指标只是数字,真正发现问题的是错误样本复核。建议在每轮模型评估后,从验证集中抽出判断错误的样本,打印波形图、真实标签和模型预测概率。
错误复审通常能发现三类问题:标注错误、难以辨别样本、数据质量问题。标注错误说明需要重新清洗数据;难以辨别样本说明模型能力接近临床争议边界;数据质量问题说明预处理管线需要修正。重复这个“训练-评估-错误复审-修正”的循环,比单纯更换模型结构有效得多。
6. 排错实战:AI心电图落地最容易遇到的四类问题
6.1 训练准确率很高,线上准确率断崖下降
现象:本地验证AUC达到0.95,进入真实数据后识别结果明显变差。
常见原因包括训练集和线上数据的分布不一致,比如采样率不同、导联顺序不同、预处理参数不同、设备厂家不同。检查方式是对比训练样本和线上样本的数组形状、幅值分布和波形形态。解决办法是使用目标设备采集的数据重新校准预处理参数,并加入少量目标域样本微调模型。
6.2 标注噪声导致模型学到错误模式
现象:模型对某些导联的特定波段特别敏感,但判别逻辑明显和临床知识不符。
常见原因是训练标签本身不准确。心电图标注非常依赖医生经验,不同医生对同一张心电图的判断可能存在分歧。解决办法是建立双人标注加仲裁机制;至少对训练集中影响最大的类别进行抽查复核,剔除明显错误标签。
6.3 类别不平衡导致少数类被多数类压掉
现象:房颤类别的召回率很低,模型把大量房颤样本判成了正常。
常见原因是训练集中房颤样本占比太小,模型没有足够的正向样本学习特征。解决方式有多种:对多数类欠采样,对少数类过采样,使用类别加权损失,或者用focal loss缓解样本不平衡。在本章指标部分,要特别关注少数类的敏感性和AUC,而不是只看整体准确率。
6.4 预处理和导联顺序不一致,换设备后模型立刻失灵
现象:同一套模型在A设备数据上表现正常,切到B设备后输出混乱。
这种情况大概率是导联顺序问题。不同设备可能按不同的顺序输出12导联,比如A设备先输出V1导联,B设备先输出I导联。模型第二层已经把导联顺序信息编码进了卷积核,输入顺序变化后特征组合完全错位。排查时先打印一个样本的所有导联波形,确认标准导联顺序;再检查滤波频率和采样率是否一致。
| 问题现象 | 常见原因 | 检查方式 | 处理建议 |
|---|---|---|---|
| 在线效果差于离线指标 | 数据分布或预处理不一致 | 对比波形、采样率、幅值分布 | 统一预处理并微调 |
| 模型学到错误模式 | 标注噪声严重 | 抽查错误样本标签 | 双人标注加仲裁 |
| 少数类召回率低 | 类别不平衡 | 查看各类别样本数 | 加权损失或重采样 |
| 换设备后性能下降 | 导联顺序或采样率不同 | 打印原始波形与矩阵形状 | 建立设备参数映射表 |
7. 从实验室原型到临床辅助工具,还差的不只是模型
7.1 使用环境差异是原型转产品的主要门槛
实验室里跑通模型和临床部署是完全不同的两件事。学习环境可以容忍数据不规范、日志不完整、接口不稳定;生产环境则必须有异常监控、结果审计和回滚机制。
| 维度 | 学习/开发环境 | 生产/预生产环境 |
|---|---|---|
| 数据来源 | 固定数据集 | 多型号设备,实时采集 |
| 标签标准 | 一次性标注 | 动态更新和复核 |
| 异常处理 | 记录日志即可 | 监控、告警、人工复核 |
| 模型更新 | 随时替换 | 灰度发布、回滚 |
| 审计要求 | 较低 | 需要记录输入、输出、版本 |
| 性能目标 | 跑通即可 | 端到端延迟和并发达标 |
建议团队在原型阶段就按生产思维设计接口,至少把模型版本、预处理参数版本、输入数据hash写入日志。这样线上发现问题时,能够准确追溯到是哪个版本的模型、哪版数据预处理导致的偏差。
7.2 可解释性不是附加选项,而是排查工具
深度学习模型被认为是黑盒,但在医疗场景中完全黑盒是难以接受的。实际项目中,可解释性工具的主要价值并不是向医生解释模型,而是帮助开发团队定位问题。
常用的方法有Grad-CAM、输入扰动和注意力可视化。Grad-CAM可以标出模型决策时关注的波形区间;输入扰动可以通过修改某个导联的某段信号观察输出变化。如果模型在诊断心肌梗死时完全忽略ST段所在区间,而把注意力放在基线噪声上,说明模型学习方向有偏差,需要重新检查数据。
7.3 明确系统是“辅助筛查”还是“自动诊断”
辅助筛查和自动诊断在医疗产品中的定位完全不同。自动诊断意味着系统给出结论后直接用于医疗决策,需要满足更高的安全标准和验证要求。辅助筛查则是在系统输出疑似异常后,由临床医生复核再做最终判断。
单纯从技术上看,两类系统使用的模型可能相同,但产品流程、质量体系、验证深度和监管要求差异非常大。团队在项目启动时就应该明确产品的用途边界,并把边界写进系统设计文档。医疗AI项目里的很多风险,都源于没有在早期界定清楚系统是“帮人判断”还是“替人判断”。
8. 启动AI心电图项目前,可以反复使用的检查清单
以下是一份可直接复用的清单,适合在数据清洗、模型训练、部署上线三个阶段分别执行。
数据阶段:
- 确认导联数量、采样率、记录时长,并统一到同一标准。
- 确认所有数据源设备输出的导联顺序一致。
- 记录滤波、标准化等预处理参数,确保训练与推理完全一致。
- 按患者级别划分训练集、验证集和测试集,避免数据泄漏。
- 对标注质量进行抽查,至少覆盖主要疾病类别。
- 记录每个标签的样本数量,确认少数类没有失衡到无法训练。
模型阶段:
- 先用1D-CNN跑通最小闭环,再引入LSTM或Transformer。
- 使用多标签分类时,确认输出层和损失函数匹配。
- 在验证集上同时记录敏感性、特异性、AUC和混淆矩阵。
- 对错误样本回归原始波形,区分标注错误、数据噪声和难以辨别样本。
- 不改变预处理参数的前提下,对量化后的模型重复一遍验证。
部署阶段:
- 导出ONNX并确认dynamic axis按需设置。
- 固定推理服务的session复用方式,避免每次请求重建会话。
- 对端到端链路做延迟压测,覆盖读取、预处理、推理、后处理四个阶段。
- 启用模型版本、预处理版本、输入数据hash的日志记录。
- 明确输出结果的业务边界,是辅助筛查还是自动诊断。
- 建立回滚机制,新模型性能异常时能快速切回旧版本。
AI心电图系统的核心从来不是单点模型精度,而是数据质量、时间优化、指标验证和部署边界几个环节的配合。对刚开始尝试的团队来说,不如先放弃“我要做一个超强智能诊断系统”的念头,从一张真实的心电图、一条干净的预处理管线、一个能在CPU上稳定运行的1D-CNN开始,跑通完整闭环后再逐步扩大范围。一个能在2秒内稳定输出、可解释、可回滚、可审计的初筛系统,远比一个只能在论文指标上表现很好的复杂模型更有工程价值。
