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:

  1. 领域术语密集:"双下肢凹陷性水肿(+)"中的"(+)"是阳性标记
  2. 缩写与方言:"BP 130/80"(血压)、"T 37.2"(体温)、"WBC 12.5"
  3. 否定检测:"未见明显异常"中"异常"不应被提取为阳性发现
  4. 时序推理:"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 输出。正确策略:

  1. 显示置信度:给置信区间,不只给结论
  2. 可解释性标注:在影像上标出 AI 关注区域(热力图/边界框)
  3. 暴露不确定性:边界案例标注"低置信度,请仔细复核"
  4. 闭环反馈:让医生标注"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% 是数据治理、监管策略、临床验证和人机协作设计。

三条核心建议:

  1. 法规前置:第一天就把法规团队拉进来,注册策略决定产品策略
  2. 数据为王:高质量标注数据比花哨模型架构重要 10 倍
  3. 临床为本:最终裁判是"医生愿不愿意用、患者有没有获益"

Maurice | maurice_wen@proton.me