AI+医疗:辅助诊断系统设计
AI 导读
AI+医疗:辅助诊断系统设计 灵阙学院 | 行业 AI 系列 引言:凌晨三点的 200 张未读 CT 2024 年某三甲医院急诊科,凌晨 3 点。值班放射科医师面前堆积了 200 多张未读的胸部 CT。其中一位 58 岁男性患者的肺结节在人工阅片流程中排在第 147 位——按正常流转速度,报告要到两天后才能签发。 而同一时刻,部署在 PACS 系统旁的 AI 辅助诊断模块已经在 4.7...
AI+医疗:辅助诊断系统设计
灵阙学院 | 行业 AI 系列
引言:凌晨三点的 200 张未读 CT
2024 年某三甲医院急诊科,凌晨 3 点。值班放射科医师面前堆积了 200 多张未读的胸部 CT。其中一位 58 岁男性患者的肺结节在人工阅片流程中排在第 147 位——按正常流转速度,报告要到两天后才能签发。
而同一时刻,部署在 PACS 系统旁的 AI 辅助诊断模块已经在 4.7 秒内完成了该序列的分析,标记出右下肺一枚 8mm 磨玻璃结节,以"高优先级"推送给值班医师。医师在 3 分钟内确认了发现,患者当天转入胸外科进一步评估。
这不是科幻。这是 2024-2025 年间数十家中国医院正在运行的真实场景。但从"Demo 跑通"到"拿到三类器械注册证、嵌入临床流程",中间隔着数据合规、监管审批、临床验证、人机协作模式设计等一系列硬仗。
本文从产品经理和技术负责人的双重视角,拆解 AI 辅助诊断系统的全链路设计。
一、系统全景:远不只是一个模型
很多团队犯的第一个错误,是把"AI 辅助诊断"等价于"训练一个分类模型"。实际上,一个可落地的系统至少包含以下子系统:
┌─────────────────────────────────────────────────────────────────┐
│ AI 辅助诊断系统全景 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 数据接入 │──>│ 预处理层 │──>│ 推理引擎 │──>│ 结果呈现 │ │
│ │ DICOM/HL7│ │ 标准化 │ │ 多模型 │ │ 报告生成 │ │
│ │ EMR/LIS │ │ 质控 │ │ 集成推理 │ │ 优先级队列│ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ │ │ │ │ │
│ └──────────────┴──────────────┴──────────────┘ │
│ │ │
│ ┌─────┴─────┐ │
│ │ 闭环反馈 │ │
│ │ 医师确认 │ │
│ │ 持续学习 │ │
│ └───────────┘ │
│ │
│ 横切关注点:审计日志 | 隐私脱敏 | 版本管理 | 性能监控 │
└─────────────────────────────────────────────────────────────────┘
1.1 数据接入层的真实复杂度
医院的数据生态远比互联网公司复杂。你对接的不是 REST API,而是:
| 数据源 | 协议/格式 | 典型挑战 |
|---|---|---|
| 影像(CT/MRI/X-ray) | DICOM | 厂商私有 Tag、压缩格式不统一、Gantry Tilt |
| 电子病历 EMR | HL7 FHIR / 私有接口 | 非结构化文本、方言缩写、版本碎片化 |
| 检验数据 LIS | HL7 v2.x / CSV | 单位不统一、参考范围各院各异 |
| 病理切片 | SVS/NDPI/MRXS | 单张 GB 级、多层金字塔结构 |
1.2 DICOM 处理:生产级管道
"""
DICOM 影像预处理管道
依赖: pip install pydicom numpy SimpleITK
"""
import pydicom
import numpy as np
import SimpleITK as sitk
from dataclasses import dataclass
@dataclass
class ProcessedSeries:
patient_id: str
study_uid: str
series_uid: str
volume: np.ndarray # (D, H, W) float32, HU values
spacing: tuple # (z, y, x) mm
origin: tuple
direction: tuple
metadata: dict
def load_dicom_series(directory: str) -> ProcessedSeries:
"""
加载 DICOM 序列并转换为标准化 3D 体积。
关键步骤:排序 -> 重采样 -> HU 转换 -> 窗位窗宽。
"""
reader = sitk.ImageSeriesReader()
dicom_files = reader.GetGDCMSeriesFileNames(directory)
if not dicom_files:
raise ValueError(f"No DICOM series found in {directory}")
reader.SetFileNames(dicom_files)
reader.MetaDataDictionaryArrayUpdateOn()
reader.LoadPrivateTagsOn()
image = reader.Execute()
ds = pydicom.dcmread(dicom_files[0])
volume = sitk.GetArrayFromImage(image).astype(np.float32)
return ProcessedSeries(
patient_id=str(getattr(ds, "PatientID", "UNKNOWN")),
study_uid=str(ds.StudyInstanceUID),
series_uid=str(ds.SeriesInstanceUID),
volume=volume,
spacing=image.GetSpacing()[::-1],
origin=image.GetOrigin(),
direction=image.GetDirection(),
metadata={
"modality": str(getattr(ds, "Modality", "")),
"manufacturer": str(getattr(ds, "Manufacturer", "")),
"slice_thickness": float(getattr(ds, "SliceThickness", 0)),
"kvp": float(getattr(ds, "KVP", 0)),
},
)
def resample_volume(
series: ProcessedSeries,
target_spacing: tuple = (1.0, 1.0, 1.0),
) -> np.ndarray:
"""
统一重采样到 1mm 各向同性。
各家 CT 层厚从 0.5mm 到 5mm 不等,这是模型输入的前提。
"""
image = sitk.GetImageFromArray(series.volume)
image.SetSpacing(series.spacing[::-1])
original_size = image.GetSize()
original_spacing = image.GetSpacing()
new_size = [
int(round(osz * ospc / tspc))
for osz, ospc, tspc in zip(
original_size, original_spacing, target_spacing
)
]
resampled = sitk.Resample(
image, new_size, sitk.Transform(), sitk.sitkBSpline,
image.GetOrigin(), target_spacing, image.GetDirection(),
0, image.GetPixelID(),
)
return sitk.GetArrayFromImage(resampled)
def apply_lung_window(volume: np.ndarray) -> np.ndarray:
"""肺窗:WL=-600, WW=1500。不同任务需要不同窗位窗宽。"""
wl, ww = -600, 1500
lower, upper = wl - ww // 2, wl + ww // 2
windowed = np.clip(volume, lower, upper)
return (windowed - lower) / (upper - lower)
生产环境中你会遇到大量边界情况:GE 的 JPEG2000 私有压缩变体、缺失层导致 Slice 间距不均匀、倾斜采集(Gantry Tilt)等。建议建立"数据质控看板",在推理前拦截不合格数据。
二、临床 NLP:从病历文本提取结构化信息
中文临床文本的挑战远超通用 NLP:
- 领域术语密集:"双下肢凹陷性水肿(+)"中的"(+)"是阳性标记
- 缩写与方言:"BP 130/80"(血压)、"T 37.2"(体温)、"WBC 12.5"
- 否定检测:"未见明显异常"中"异常"不应被提取为阳性发现
- 时序推理:"3 天前开始发热,昨日加重"需要相对时间锚定
2.1 临床命名实体识别管道
"""
临床 NLP 管道:命名实体识别 + 否定检测
生产中建议使用医学预训练 BERT (MC-BERT / PCL-MedBERT)
"""
from dataclasses import dataclass
from enum import Enum
from typing import List
import re
class EntityType(Enum):
DISEASE = "疾病"
SYMPTOM = "症状"
BODY_PART = "解剖部位"
MEDICATION = "药物"
PROCEDURE = "操作/手术"
LAB_TEST = "检验项目"
LAB_VALUE = "检验值"
class Polarity(Enum):
POSITIVE = "阳性"
NEGATIVE = "阴性"
UNCERTAIN = "不确定"
@dataclass
class ClinicalEntity:
text: str
entity_type: EntityType
polarity: Polarity
start: int
end: int
confidence: float
NEGATION_CUES = [
"未见", "无", "不伴", "排除", "否认", "未及", "无明显", "未发现",
]
UNCERTAINTY_CUES = [
"待排", "可能", "不排除", "考虑", "疑", "似",
]
def detect_polarity(text: str, entity_start: int) -> Polarity:
"""
在实体左侧 20 字符窗口内检测否定/不确定线索。
生产环境建议用依存句法分析做更精准的否定范围判定。
"""
window_start = max(0, entity_start - 20)
left_context = text[window_start:entity_start]
for cue in NEGATION_CUES:
if cue in left_context:
return Polarity.NEGATIVE
for cue in UNCERTAINTY_CUES:
if cue in left_context:
return Polarity.UNCERTAIN
return Polarity.POSITIVE
def extract_vital_signs(text: str) -> dict:
"""从入院记录提取生命体征:T 37.2, P 80, R 20, BP 130/80"""
patterns = {
"temperature": r"T[:\s]*(\d{2}\.?\d*)\s*[度C]?",
"pulse": r"P[:\s]*(\d{2,3})\s*次?/?分?",
"respiration": r"R[:\s]*(\d{1,2})\s*次?/?分?",
"bp_systolic": r"BP[:\s]*(\d{2,3})/\d{2,3}",
"bp_diastolic": r"BP[:\s]*\d{2,3}/(\d{2,3})",
}
results = {}
for key, pattern in patterns.items():
match = re.search(pattern, text)
if match:
results[key] = float(match.group(1))
return results
三、监管合规:三类医疗器械注册
这是 AI 医疗产品经理最容易低估的部分。在中国,AI 辅助诊断软件属于第三类医疗器械(最高风险等级),需经 NMPA 审批。
3.1 中美监管对比
| 维度 | NMPA(中国) | FDA(美国) |
|---|---|---|
| 分类 | 三类医疗器械 | Class II (510(k)) 或 De Novo |
| 临床试验 | 强制多中心前瞻性 | 可用回顾性数据 |
| 审批周期 | 12-24 个月 | 6-12 个月 |
| 数据要求 | 必须含中国人群数据 | 多样性数据集 |
| 算法变更 | 需补充申请 | PCCP(预定变更控制计划) |
| 网络安全 | 2024 年后强制 | 2023 年后强制 |
| 上市后监管 | 不良事件报告 + 年度评估 | TPLC + Real-World Performance |
3.2 注册申报材料核心清单
注册申报材料清单(简化版)
|- 软件描述文档
| |- 算法原理说明(可解释性要求)
| |- 训练数据集描述(来源、规模、标注规范、伦理审批)
| |- 软件架构设计
| |- 网络安全分析
|- 临床评价资料
| |- 多中心临床试验方案
| |- 统计分析报告(主要终点 + 亚组分析)
| |- 与临床金标准的对比(敏感性、特异性、AUC)
|- 性能验证报告
| |- 独立测试集评估(严禁使用训练集子集)
| |- 鲁棒性测试(不同设备、不同参数)
| |- 边界情况分析
|- 质量管理体系文件(ISO 13485)
3.3 关键指标底线
| 指标 | 最低要求 | 头部产品水平 |
|---|---|---|
| 敏感性 (Sensitivity) | >= 90% | >= 95% |
| 特异性 (Specificity) | >= 80% | >= 90% |
| AUC | >= 0.90 | >= 0.97 |
| 假阳性率 (FP/scan) | <= 3 | <= 1 |
| 推理延迟 | <= 120s | <= 10s |
| 系统可用性 (Uptime) | >= 99% | >= 99.9% |
四、人机协作模式设计
"辅助"二字是关键。系统的设计目标不是取代医生,而是重塑医生的工作流。
4.1 三种协作模式
模式 A:并行阅片(Concurrent Read)
AI 阅片 (4.7s) ───┐
├──> 差异对比 -> 仲裁(仅在不一致时弹出)
医师阅片 (3-5min) ──┘
模式 B:AI 预筛 + 医师确认(Triage + Confirm)
AI 全量分析 -> 优先级队列(高危优先) -> 医师有选择地确认
模式 C:医师主导 + AI 按需(Assist on Demand)
医师常规阅片 -> 触发 AI 辅助 -> AI 按需响应
选择建议:模式 B 最常见,适合影像科"筛查-确诊"流程;模式 A 对信任度要求高,适合成熟科室;模式 C 侵入性最低,适合初期推广。
4.2 信任校准:不是越准越好
一个反直觉的发现:如果 AI 准确率过高(比如 99.5%),医生反而会产生"自动化偏见"(Automation Bias),不再仔细复核 AI 输出。正确策略:
- 显示置信度:给置信区间,不只给结论
- 可解释性标注:在影像上标出 AI 关注区域(热力图/边界框)
- 暴露不确定性:边界案例标注"低置信度,请仔细复核"
- 闭环反馈:让医生标注"AI 判对/判错",持续改进
五、数据隐私与联邦学习
5.1 脱敏策略
| 数据类型 | 脱敏方法 | 备注 |
|---|---|---|
| 患者姓名 | 删除 / K-匿名 | DICOM Tag (0010,0010) |
| 身份证号 | 删除 | 绝对不保留 |
| 出生日期 | 保留年份或年龄段 | 年龄对诊断有临床意义 |
| 医院/科室 | 编码替换 | 多中心研究需保留可关联性 |
| 影像角标文字 | OCR + 涂黑 | CT 角落常有患者信息 |
| 自由文本 | NER + 替换 | 病历中的人名、地名、联系方式 |
5.2 联邦学习:数据不出院墙
当多家医院联合训练但数据不能出院时:
医院 A 医院 B 医院 C
本地训练 本地训练 本地训练
上传梯度 上传梯度 上传梯度
\ | /
\ | /
+-----> 聚合服务器 <------+
加权平均
差分隐私
|
+---------+---------+
| | |
下发模型 下发模型 下发模型
关键点:梯度上传前必须加差分隐私噪声(Differential Privacy),否则梯度本身可被逆向攻击还原训练数据。
六、可解释性设计
NMPA 对医疗 AI 的可解释性有明确要求:系统必须能说明"为什么给出这个诊断建议"。
| 可解释性层级 | 实现方式 | 监管价值 |
|---|---|---|
| 特征重要性 | SHAP / GradCAM 热力图 | 识别关键决策因素 |
| 决策路径 | 规则链 / 知识图谱路径展示 | 医生可理解的推理链 |
| 反事实解释 | "若血常规正常,概率降低 X%" | 支持敏感性分析 |
| 不确定性量化 | 置信区间 / 模型分歧度 | 识别需人工复核的案例 |
实战经验:GradCAM 热力图在影像科接受度最高,因为医生直觉上理解"AI 在看哪里"。但热力图只展示"注意力区域",不等于"决策原因"——这个区别在注册审评时经常被追问。
七、常见错误与避坑指南
7.1 技术陷阱
| 错误 | 后果 | 正确做法 |
|---|---|---|
| 训测来自同一家医院 | 泛化性极差 | 多中心数据,按医院做 Hold-out |
| 只看 AUC | AUC 0.95 但假阳太多 | 结合 DCA(临床决策曲线) |
| 忽略类别不平衡 | 罕见病全漏 | Focal Loss + 过采样 + 分层评估 |
| 无版本管理 | 无法复现 | MLflow/DVC + 完整实验日志 |
| 部署后不做持续监控 | 数据漂移致性能衰退 | 性能基线 + 自动告警 |
| 标注只用一轮 | 标注噪声大 | 双盲标注 + 仲裁 + 定期抽检 |
7.2 产品与流程陷阱
| 错误 | 后果 | 正确做法 |
|---|---|---|
| 先做技术再找场景 | 产品没人用 | 先跟科室蹲点两周 |
| 忽视 IT 集成难度 | 项目延期 6+ 个月 | 提前做 PACS/HIS/RIS 接口调研 |
| 只给结论不给解释 | 医生不信任 | 热力图 + 置信度 + 参考案例 |
| 注册证策略不清晰 | 产品做完不能卖 | 第一天就拉法规团队入场 |
| 忽视数据治理 | 垃圾进垃圾出 | 数据质控看板 + 质量阈值拦截 |
7.3 标注质量:AI 医疗的命门
标注流程(以肺结节检测为例):
1. 标注医师 A 独立标注(至少 5 年影像科经验)
2. 标注医师 B 独立标注
3. 一致性检查:IoU >= 0.5 视为一致
4. 不一致案例:高年资医师 C 仲裁
5. 标注结果入库 + 版本管理
6. 定期抽检(每月 5% 复核)
八、技术选型决策树
需求分析
|
+-- 影像分析类
| +-- 2D 单帧(X-ray, 眼底)
| | -> ResNet/EfficientNet + GradCAM
| +-- 3D 体积(CT, MRI)
| | -> nnU-Net (分割) / 3D ResNet (分类)
| +-- 病理切片(WSI)
| -> MIL + Vision Transformer
|
+-- 文本分析类
| +-- 结构化提取(NER + RE)
| | -> MC-BERT / PCL-MedBERT + CRF
| +-- 临床决策支持
| | -> 知识图谱 + 规则引擎
| +-- 报告生成
| -> LLM Fine-tuning (Qwen-Med / HuatuoGPT-II)
|
+-- 多模态融合
-> 影像 + 文本 + 检验 -> 联合模型
2025+ 趋势:医学基础模型 (BiomedCLIP, Med-Gemini)
九、落地路线图
| 阶段 | 时长 | 核心任务 | 关键产出 |
|---|---|---|---|
| Phase 0 | 2-3 月 | 科室调研、数据可行性、监管预评估 | 可行性报告 |
| Phase 1 | 3-4 月 | 算法开发、内部数据集构建 | 研究原型 + 初步指标 |
| Phase 2 | 6-12 月 | 多中心临床试验、性能优化 | 临床试验报告 |
| Phase 3 | 6-12 月 | 资料准备、技术审评、现场检查 | NMPA 注册证 |
| Phase 4 | 持续 | 医院部署、培训、售后、迭代 | 装机量 + 续费率 |
总周期 18-30 个月。这就是为什么 AI 医疗是马拉松,不是百米冲刺。
十、总结
AI 辅助诊断不是纯技术问题,而是技术、监管、临床、商业四位一体的系统工程。技术只占 30%,剩下的 70% 是数据治理、监管策略、临床验证和人机协作设计。
三条核心建议:
- 法规前置:第一天就把法规团队拉进来,注册策略决定产品策略
- 数据为王:高质量标注数据比花哨模型架构重要 10 倍
- 临床为本:最终裁判是"医生愿不愿意用、患者有没有获益"
Maurice | maurice_wen@proton.me