周一早上八点,市体检中心的放射科门口排起了长队。今天有 1200 张胸片要出报告,科里却只有 4 位影像医生。主任上周做了一个决定:引入一套“AI 全科医生天团”——它不分内科外科,只做一件事,对每一张胸片在 30 秒内同时筛查 14 种胸部疾病,把可疑的挑出来交给人类医生复核。 这套 AI 天团的主角叫 EfficientNet——一个以“精干”著称的深度学习模型;它的培训教材,是斯坦福大学公开的 CheXpert 胸片数据集,里面包含约 22 万张真实胸片和 14 类疾病的专家标注。你可能会问:一张胸片怎么能同时查出 14 种病?模型凭什么认识胸片?罕见病会不会被漏掉?这些问题,正是这篇文章要讲清楚的。 我们会沿着一条完整的主线展开:先弄清要解决什么问题(一张片子、14 类多标签),再选出主力模型(精干的 EfficientNet 对阵老将 DenseNet),接着给它做“入职培训”(迁移学习),教会它像填体检表一样独立勾选每种病(多标签分类),处理罕见病被淹没的麻烦(数据不平衡),最后用一张公正的考卷验收(AUROC)。全篇用“体检中心放射科”这个比喻贯穿,你会发现,AI 读片和科室管理,道理是相通的。

岗位关联

掌握 EfficientNet 在 CheXpert 上的完整建模链路(模型选型、迁移学习、多标签、不平衡处理、AUROC 评估),是医学影像 AI 工程师从“跑通 demo”走向“落地可信赖产品”的关键一步。

目录
  1. 可视化演示
  2. 问题背景:一张片子,14 个勾选框
  3. 模型选型:精干新秀 vs 臃肿老将
  4. 复合缩放:扩建科室的黄金比例
  5. 迁移学习:全科医生的胸片专项培训
  6. 多标签分类:独立勾选的体检表
  7. 不平衡处理:别让罕见病被淹没
  8. 效果评估:用 AUROC 给科室打分
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示EfficientNet复合缩放策略如何平衡深度宽度分辨率,以及ImageNet预训练迁移到CheXpert胸片分类的微调过程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

EfficientNet复合缩放策略 深度宽度分辨率三维度同时缩放 B0基线按复合系数扩展到B7 更少参数达到更高精度 φ: d=α^φ, w=β^φ, r=γ^φ 复合缩放在参数与精度间找到最优帕累托平衡。

问题背景:一张片子,14 个勾选框

你刚被任命为市体检中心放射科的数字化负责人,接手的第一份材料,是斯坦福团队编写的《CheXpert 胸片培训手册》。手册封面印着一张胸片,旁边是一张 14 行的勾选清单:肺不张、心脏增大、实变、水肿、胸腔积液、肺炎、气胸、肺结节、骨折…… 每一行都不是单选题,而是可以同时打勾的复选框。

这正是医学影像里“多标签分类”最典型的场景:一张胸片可以同时存在多种疾病。例如肺炎患者常常合并胸腔积液,心力衰竭往往伴随肺水肿;如果模型只能回答“有没有病”,就会丢掉大量关键信息。CheXpert 数据集由斯坦福大学发布,包含约 22 万张真实正面胸片,每张都由医学专家标注出 14 类常见疾病发现,以“多标签”形式组织——每张胸片对应一个 14 维的 0/1 向量,1 表示该疾病存在。

从数学上看,数据集可以写成下面的形式。其中 N 是胸片总数,x_i 是图像的像素矩阵,y_i 是 14 维二值向量。注意 y_i 的各个分量之间并不互斥,这是它与“单标签分类”(比如手写数字 0~9 只能选一个)最本质的区别:单标签用 Softmax 输出一个概率分布,多标签则需要每个疾病各自独立地给出“有/无”的概率。

理解这一点,你就明白了“AI 读胸片”本质上不是让模型做一道选择题,而是填一张 14 行的体检勾选表。下一步,我们要为这张表挑选一位能干的主力医生——EfficientNet。

D={(xi,yi)}i=1N,xi∈RH×W×C,yi∈{0,1}14\mathcal{D} = \left\{ (x_i, y_i) \right\}_{i=1}^{N}, \quad x_i \in \mathbb{R}^{H \times W \times C}, \quad y_i \in \{0, 1\}^{14}
这个公式定义了“任务本身”:模型输入一张 H×W×C 的胸片,输出一个 14 维的 0/1 标签向量,每一位代表一种疾病是否存在。
  1. 样本集合等号左边是整个训练数据集 D,它由 N 条“胸片–标签”配对组成,就像科室档案柜里 N 份体检记录。
    D={(xi,yi)}i=1N\mathcal{D} = \left\{ (x_i, y_i) \right\}_{i=1}^{N}
  2. 胸片输入x_i 表示第 i 张胸片,H、W 是像素高和宽,C 是通道数;真实胸片虽是灰度,但送入神经网络时常复制为 3 通道。
    xi∈RH×W×Cx_i \in \mathbb{R}^{H \times W \times C}
  3. 多标签向量y_i 是 14 个分量的二值向量,每个分量对应一种疾病;各分量彼此独立、可以同时为 1,这正是“多标签”的含义。
    yi∈{0,1}14y_i \in \{0, 1\}^{14}

模型选型:精干新秀 vs 臃肿老将

科室要招人,简历上有两位候选。一位是有多年资历的 DenseNet 老专家——经验丰富,在 ImageNet 上成绩不错,但体型偏胖,每次出诊都要占用大量计算资源;另一位是刚毕业的 EfficientNet 新人——体重只有老专家的几分之一,但基础执照考试(ImageNet 分类)的成绩反而更高。

DenseNet 确实是医学影像领域的“稳妥之选”:它的密集连接让每一层都能直接看到前面所有层的特征图,梯度传递顺畅,因此 CheXpert 官方基线常以 DenseNet-121 作为对照模型。但其代价也明显——密集连接需要保存大量中间特征,参数和显存开销都不小。EfficientNet 则是谷歌 2019 年提出的模型家族,核心贡献是把“网络有多深、多宽、看多大图”这三个维度统一成了一个可调优的整体。

EfficientNet-B0 的基础模块是 MBConv(移动反向瓶颈卷积),由深度可分离卷积、反向残差结构和 SE 注意力机制组成。深度可分离卷积把“逐通道卷积”和“逐点卷积”拆开做,计算量大幅下降;SE 注意力让网络学会“重点关注哪块区域”。正因这些精打细算的设计,EfficientNet-B0 仅用约 530 万参数,就在 ImageNet 上拿到 77.1% 的 top-1 准确率,而参数量更大的 DenseNet-121(约 800 万参数)只有约 74.6%;B7 更是以 6600 万参数追平了当时参数量为它 8 倍多的 SOTA 模型——这就是“精干的效率”。

在 CheXpert 这类医学任务上,DenseNet 是坚实的基线,而 EfficientNet 的优势在于“用更少的资源办同样的事”。但它的效率并非凭空而来——从 B0 到 B7 家族逐渐壮大,靠的是一套独特的扩建方法,这就是下一节的复合缩放。

Params(EfficientNet-B0)≈5.3 Mvs.Params(DenseNet-121)≈8.0 Mvs.Params(DenseNet-201)≈20 M\text{Params}(\text{EfficientNet-B0}) \approx 5.3 \, \text{M} \quad \text{vs.} \quad \text{Params}(\text{DenseNet-121}) \approx 8.0 \, \text{M} \quad \text{vs.} \quad \text{Params}(\text{DenseNet-201}) \approx 20 \, \text{M}
同样的任务,EfficientNet 用更少的参数拿到更高的精度;参数更少意味着显存占用小、推理更快,这对医院海量胸片场景非常友好。
  1. B0 参数量EfficientNet 最小型号约有 530 万参数,轻到可以塞进移动设备。
    Params(EfficientNet-B0)≈5.3 M\text{Params}(\text{EfficientNet-B0}) \approx 5.3 \, \text{M}
  2. DenseNet-121 参数量老牌基线模型约 800 万参数,在 ImageNet 上精度却落后于 B0。
    Params(DenseNet-121)≈8.0 M\text{Params}(\text{DenseNet-121}) \approx 8.0 \, \text{M}
  3. 更重的老将DenseNet-201 达到约 2000 万参数,是 B0 的近 4 倍,精度优势却并不明显。
    Params(DenseNet-201)≈20 M\text{Params}(\text{DenseNet-201}) \approx 20 \, \text{M}

复合缩放:扩建科室的黄金比例

假设你决定引入 EfficientNet,并想让它变得更强。直觉上你会怎么做?多招几个医生(加深网络)?让每个医生多学几种技能(加宽网络)?把诊室从 10 平米扩到 20 平米(提高输入分辨率)?单独做任何一件事,效果都会很快触顶:只加医生但诊室太小,大家会互相碍事;只扩诊室但不加人手,查得再细也忙不过来。

前人的研究印证了这一点:只缩放深度、宽度或分辨率之一,模型精度很快饱和。EfficientNet 作者先通过神经架构搜索找到了一个基线网络 B0,然后提出复合缩放——用一个全局系数 φ 同时控制三个维度,让深度 d、宽度 w、分辨率 r 都按固定比例指数增长,公式如下。

公式中 α、β、γ 是通过小规模网格搜索确定的常数(基线值 α=1.2、β=1.1、γ=1.15)。约束条件 α·β²·γ² ≈ 2 是从“计算量”推导出来的:因为卷积计算的成本与深度成正比、与宽度的平方成正比、与分辨率的平方成正比,这个约束保证 φ 每增加 1,整体计算量大约翻一番,预算完全可以预期。B0 对应 φ=0,B7 对应 φ=7,于是形成了一个从小到大的完整模型家族。

对医学影像来说,复合缩放还有一个隐藏红利:分辨率维度意味着模型能处理更高清的输入(如 456×456 甚至 600×600),更细腻地捕捉早期病灶纹理,而这一切依旧维持着“团队按比例扩建”的秩序。选好了模型,下一步就是入职培训——迁移学习。

d=αϕ,w=βϕ,r=γϕ,s.t.α⋅β2⋅γ2≈2d = \alpha^{\phi}, \quad w = \beta^{\phi}, \quad r = \gamma^{\phi}, \quad \text{s.t.} \quad \alpha \cdot \beta^{2} \cdot \gamma^{2} \approx 2
  1. 深度缩放d 表示网络层数(医生数量),按 α 的 φ 次方增长,层数越多,能提取的抽象特征越丰富。
    d=αϕd = \alpha^{\phi}
  2. 宽度缩放w 表示每层通道数(每个医生的技能面),按 β 的 φ 次方增长,通道越宽,每层捕捉的细节模式越多。
    w=βϕw = \beta^{\phi}
  3. 分辨率缩放r 表示输入图像的边长,按 γ 的 φ 次方增长,分辨率越高,细小病灶越不容易被压缩掉。
    r=γϕr = \gamma^{\phi}
  4. 复合约束α·β²·γ² ≈ 2 这条“军规”,让三个维度的乘积保持恒定,从而把整支团队的计算成本稳稳控制在原来的 2 倍。
    α⋅β2⋅γ2≈2\alpha \cdot \beta^{2} \cdot \gamma^{2} \approx 2
  5. 复合系数 φ它是全局唯一的旋钮,从 0 到 7,对应 B0 到 B7,操作员只需调一个参数即可缩放整个网络。
    ϕ∈{0,1,…,7}\phi \in \{0, 1, \dots, 7\}

迁移学习:全科医生的胸片专项培训

新医生入职,你当然不会让他从小学课本重新学起。这位 EfficientNet 医生已经在“三甲综合医院”(ImageNet,1000 类自然图像)完成了规培,见过猎豹的斑纹、汽车的轮毂、水果的纹理,早就练出一双识别“边缘、拐角、纹理、明暗过渡”的锐利眼睛。这些视觉底层能力,对读懂胸片的骨骼、血管和病灶阴影同样通用。

迁移学习就是把这套“通用视觉能力”搬过来:先用 ImageNet 上百万张自然图像把 EfficientNet 训练成熟,然后放到 CheXpert 胸片数据上“继续教育”。与从头训练(随机初始化)相比,迁移学习有两个立竿见影的好处:收敛更快(通常十几个 epoch 就能达到理想效果),最终精度普遍更高——尤其当医学数据量不大时,优势更明显。

工程实现上有两个关键动作。第一,替换分类头:把 EfficientNet 原来的 1000 维输出层换成一个 14 维的新分类头。第二,选择微调策略:可以冻结骨干网络、只训练新分类头(相当于只给新医生教胸片特有的全局特征),也可以解冻全部层做端到端微调(相当于老医生也一起复训)。无论哪种,学习率都要放低——骨干网络通常用 1e-4~1e-5,新分类头用 1e-3,避免大步长更新破坏已经学好的底层特征。

完成培训后,这位医生就要正式上岗,学习它最重要的看家本领:一张胸片同时判断 14 种病。这就是下一节的多标签分类。

θ∗=arg⁡min⁡θ LCheXpert(θ),θinit=θImageNet\theta^{\ast} = \arg\min_{\theta} \ \mathcal{L}_{\text{CheXpert}} (\theta), \quad \theta_{\text{init}} = \theta_{\text{ImageNet}}
迁移学习的全部秘密都藏在“初始点”里:θ 的起点不是随机噪声,而是在 ImageNet 上练出的成熟参数。后续更新只是“加一节胸片培训班”,而不是重读医学院。
  1. 最优参数θ* 表示微调完成后模型的最优参数,是最终要部署上线的模型状态。
    θ∗\theta^{\ast}
  2. 目标函数arg min 表示不断调整参数,使 CheXpert 数据上的多标签损失 L 尽量小,相当于让医生在胸片考核中错误最少。
    arg⁡min⁡θ LCheXpert(θ)\arg\min_{\theta} \ \mathcal{L}_{\text{CheXpert}} (\theta)
  3. 初始权重θ_init 不是随机初始化,而是 ImageNet 预训练权重,这是迁移学习与从头训练唯一但决定性的区别。
    θinit=θImageNet\theta_{\text{init}} = \theta_{\text{ImageNet}}

多标签分类:独立勾选的体检表

医生正式上岗了。他面前有一张 14 行的电子体检表,每一行对应一种疾病,旁边是一个独立的勾选框。勾选框之间没有任何联动:勾了“肺炎”,不影响“胸腔积液”那一格是否打勾——事实上,临床上一半以上的肺炎患者同时合并胸腔积液,两者必须同时判断。

在模型结构上,这意味着最后的分类头输出 14 个数值(logits,即未归一化的得分),每个数值经过 Sigmoid 函数独立映射为 0~1 之间的概率。Sigmoid 的图像是一条平滑的 S 形曲线,把任意实数压到 (0,1) 区间,恰好可以当作“这种病有多大概率存在”。

训练时使用的损失是二元交叉熵(BCE)。它与单标签分类常用的 Softmax+交叉熵的关键区别在于:BCE 把每个类别当作独立的二分类分别计算误差,再把 14 项误差汇总;而 Softmax 的归一化会强迫所有类别概率之和等于 1,这在一张胸片可能同时患多种病的前提下是自相矛盾的——模型想让“肺炎”概率高一点,就得把“胸腔积液”的概率压下去,显然不符合医学事实。

预测时,每个勾选框按自己的概率独立判断:默认把概率 ≥ 0.5 视为阳性(打勾),< 0.5 视为阴性。这个阈值并非不可更改——想少漏诊可以调低,想控制转诊量就调高。不过,训练过程中一个隐蔽的威胁正在逼近:14 种疾病的出现频率天差地别,危急的疾病往往极其罕见,模型很容易被“常见病”带偏。下一节就处理这个棘手的不平衡问题。

p^ij=σ(zij)=11+e−zij,LBCE=−1N∑i=1N∑j=114[yijlog⁡p^ij+(1−yij)log⁡(1−p^ij)]\hat{p}_{ij} = \sigma (z_{ij}) = \frac{1}{1 + e^{-z_{ij}}}, \quad \mathcal{L}_{\text{BCE}} = -\frac{1}{N} \sum_{i=1}^{N} \sum_{j=1}^{14} \left[ y_{ij} \log \hat{p}_{ij} + (1 - y_{ij}) \log \left( 1 - \hat{p}_{ij} \right) \right]
前半部分是 Sigmoid 公式,把得分转成概率;后半部分是二元交叉熵,衡量“预测概率”与“真实标签”之间的差距。两项分别惩罚“漏报阳性”和“误报阴性”。
  1. Sigmoid 压缩z 是模型的原始得分,Sigmoid 把它压到 (0,1),得分越高,输出越接近 1。
    σ(z)=11+e−z\sigma(z) = \frac{1}{1 + e^{-z}}
  2. 独立概率p̂_ij 表示第 i 张胸片、第 j 种疾病的预测概率,14 个概率互不干扰、各自独立。
    p^ij=σ(zij)\hat{p}_{ij} = \sigma (z_{ij})
  3. 正类惩罚项当真实标签 y_ij = 1 时,损失只看 log p̂_ij;p̂ 越接近 1,损失越接近 0,相当于“该勾没勾”要受罚。
    yijlog⁡p^ijy_{ij} \log \hat{p}_{ij}
  4. 负类惩罚项当真实标签 y_ij = 0 时,损失只看 log(1 − p̂_ij);p̂ 越接近 0 越好,相当于“不该勾却勾了”要受罚。
    (1−yij)log⁡(1−p^ij)(1 - y_{ij}) \log (1 - \hat{p}_{ij})
  5. 汇总均值对所有样本和全部 14 类疾病求和后取平均,得到一次训练迭代的总损失。
    −1N∑i=1N∑j=114[⋯ ]-\frac{1}{N} \sum_{i=1}^{N} \sum_{j=1}^{14} \left[ \cdots \right]

不平衡处理:别让罕见病被淹没

在体检中心,你统计了去年 10 万张胸片:九成左右“无异常”,肺炎、胸腔积液相对常见,而气胸、骨折可能一个月才遇上几例。主治医生抱怨:模型越练越“懒”——因为把所有片子都判成无异常,整体错误率也只有百分之几,它根本没必要为千分之一的气胸费心。

这就是类别不平衡问题的典型表现:模型在优化“全体样本的平均损失”时,会被数量占优的类别主导,导致稀有疾病几乎学不到。解决思路主要有三条:一是改变损失函数,让稀有类别犯错时付出更大代价(类别加权);二是改变数据分布,对稀有阳性样本过采样、对常见阴性样本欠采样;三是改变评估方式,放弃对不平衡敏感的准确率,改用 AUROC(下一节展开)。

类别加权是工程上最直接的手段:为第 j 类疾病设定权重 w_j,使其与阳性样本数 N_j^pos 成反比——阳性样本越少,权重越大,模型漏掉一个气胸的“懊悔”被放大到与漏掉成百上千个正常片相当。加权后的损失如下。

CheXpert 还提供一个特有的挑战:约 20% 的标签是“不确定”(专家也拿不准)。常见策略有三种:U-Zeros 把不确定当阴性,U-Ones 把不确定当阳性,U-Multi 为不确定单独设一个损失项;实践中 U-Ones 往往能带来更好的 AUC。经过加权与标注策略调整,再用 AUROC 考核,模型才算真正“心里有数”。考核环节来了。

wj∝1Njpos,LwBCE=−1N∑i=1N∑j=114wj[yijlog⁡p^ij+(1−yij)log⁡(1−p^ij)]w_{j} \propto \frac{1}{N_{j}^{\text{pos}}}, \quad \mathcal{L}_{\text{wBCE}} = -\frac{1}{N} \sum_{i=1}^{N} \sum_{j=1}^{14} w_{j} \left[ y_{ij} \log \hat{p}_{ij} + (1 - y_{ij}) \log (1 - \hat{p}_{ij}) \right]
  1. 阳性样本计数N_j^pos 是第 j 种疾病在训练集中的阳性样本总数,比如“气胸”可能只有 300 例。
    NjposN_{j}^{\text{pos}}
  2. 反比权重w_j 与阳性数量成反比,阳性越少的病,权重越大;常见病权重被压低,模型不再“偏科”。
    wj∝1Njposw_{j} \propto \frac{1}{N_{j}^{\text{pos}}}
  3. 加权损失把 w_j 乘到每一项交叉熵上,让稀有病的错误贡献被放大,迫使模型认真对待每一个罕见阳性。
    LwBCE=−1N∑i,jwj[yijlog⁡p^ij+(1−yij)log⁡(1−p^ij)]\mathcal{L}_{\text{wBCE}} = -\frac{1}{N} \sum_{i,j} w_{j} \left[ y_{ij} \log \hat{p}_{ij} + (1 - y_{ij}) \log (1 - \hat{p}_{ij}) \right]

效果评估:用 AUROC 给科室打分

季度末,体检中心要对 AI 医生做执业考核。有人提议考核“准确率”:出 1000 张片,AI 报对 950 张,95% 正确。主任摇摇头——如果 1000 张里只有 5 张气胸,而 AI 全判正常,准确率也有 99.5%,可那 5 位病人每一位都有生命危险。准确率在这个场景里会被“绝大多数是正常片”的不平衡结构欺骗。

医学影像界更偏爱 AUROC(ROC 曲线下面积)。它考察的不是“对错”,而是“排序能力”:把模型对全部样本输出的疾病概率从高到低排序,看阳性样本是否普遍排在阴性样本前面。做法是不断移动判定阈值,对每个阈值都算出一对“真阳率 TPR”和“假阳率 FPR”,把这些 (FPR, TPR) 点连成 ROC 曲线,再算曲线下面积。

AUROC 取值 0.5 到 1:0.5 相当于瞎猜,0.8 以上具有临床参考价值,0.9 以上有较强的筛查能力。在 CheXpert 官方基准上,DenseNet-121 + U-Ones 的验证集平均 AUC 约为 0.93;EfficientNet 家族借助迁移学习与多标签训练,能够达到与之相当甚至更高的水平,同时模型更小、推理更快,更适合嵌入医院日常读片流程。临床落地时,还会根据科室需求在 ROC 曲线上选一个恰当的操作点(敏感度优先还是特异度优先),再汇报敏感性、特异性、阳性预测值等指标。

至此,完整流程走通了:拿着一张 14 行的体检勾选表,选中精干的 EfficientNet 当主力医生,用复合缩放控制团队扩建规模,靠迁移学习完成入职培训,学会多标签独立勾选,通过类别加权压下罕见病漏诊的代价,最后用 AUROC 这张公正考卷验收。这套方法论,正在越来越多的医院急诊科、体检中心和基层诊疗机构落地生根。

AUROC=∫01TPR(t) d FPR(t)AUROC = \int_{0}^{1} \mathrm{TPR}(t) \ d\, \mathrm{FPR}(t)
  1. 真阳率TPR 也叫灵敏度,表示“患病的人里被检出的比例”,越高越好。
    TPR=TPTP+FN\mathrm{TPR} = \frac{TP}{TP + FN}
  2. 假阳率FPR 表示“健康的人里被误报的比例”,越低越好。
    FPR=FPFP+TN\mathrm{FPR} = \frac{FP}{FP + TN}
  3. 遍历阈值从最高分到最低分一步步移动判定阈值 t,每移动一步得到一个 (FPR, TPR) 坐标点,把这些点连成 ROC 曲线。
    ROC curve={(FPR(t),TPR(t)):t∈R}\text{ROC curve} = \left\{ \left( \mathrm{FPR}(t), \mathrm{TPR}(t) \right) : t \in \mathbb{R} \right\}
  4. 曲线下面积对 ROC 曲线求定积分,得到 0~1 之间的面积,面积越大说明“阳性排在前面的能力”越强,与类别比例无关。
    AUROC=∫01TPR(t) d FPR(t)AUROC = \int_{0}^{1} \mathrm{TPR}(t) \ d\, \mathrm{FPR}(t)

AI 应用场景

急诊科胸片危急值优先预警

阶段:模型部署与推理

解决的问题:急诊医生需要优先识别气胸、肺水肿等危及生命的发现。传统人工读片按排队顺序进行,危急病人可能等待过久;AI 在影像采集完成的十几秒内先跑一遍 14 病预测,把危急概率高的片子自动置顶到医生工作列表。

具体案例:某三甲医院急诊科在 PACS 系统中嵌入 CheXpert 微调模型,对每张胸片实时输出 14 类疾病概率;系统将气胸、肺水肿概率超过阈值的检查单置顶,并以高亮提醒值班医生,疑似危急病例平均首诊响应时间明显缩短。

体检中心胸片批量预标注

阶段:迁移学习与微调

解决的问题:体检中心每日数千张胸片,医生逐张勾选 14 项慢性病(心脏增大、肺结节等)工作量大,疲劳后容易漏检。传统完全人工标注效率低。AI 用 ImageNet 预训练 + CheXpert 微调的 EfficientNet 做第一遍预标注,医生只复核 AI 打出的“异常”。

具体案例:某体检中心把 14 类疾病分成“AI 初筛 + 医师复核”流水线:模型先输出每张片的 14 个概率,正常片直接归档,只有概率超阈值的片子进入医生复核队列,人均日审片量提升约两倍,漏报率下降。

基层医院便携设备离线筛查

阶段:模型选型与边缘部署

解决的问题:基层卫生院缺少影像专家,且网络不稳定,无法完全依赖云端 AI。EfficientNet-B0 仅 5.3M 参数,INT8 量化后约 5MB,能在平板或便携影像机上离线运行,正好发挥“精干”优势。

具体案例:移动体检车开进乡村,医生在平板设备上打开本地模型,胸片数字化后 10 秒内给出 14 病概率与异常提示;只有可疑病例才通过卫星网络上传到上级医院复核,大幅降低对带宽和专家的依赖。

Python 演示

python90 行
import torchimport torch.nn as nnfrom torch.utils.data import Dataset, DataLoaderfrom torchvision import models # CheXpert 的 14 类疾病标签顺序CHEXPERT_TASKS = [    "无异常", "心脏增大", "肺不张", "实变", "水肿",    "胸腔积液", "肺炎", "气胸", "肺结节", "骨折",    "支持设备", "纵隔增宽", "肺浑浊", "胸膜其他",]  class CheXpertModel(nn.Module):    def __init__(self, num_classes=14, pretrained=True):        super().__init__()        # 加载 ImageNet 预训练的 EfficientNet-B0 作为骨干        self.backbone = models.efficientnet_b0(            weights="IMAGENET1K_V1" if pretrained else None        )        in_features = self.backbone.classifier[1].in_features        # 将 1000 类分类头替换为 14 类,不接 Softmax        self.backbone.classifier = nn.Sequential(            nn.Dropout(p=0.2),            nn.Linear(in_features, num_classes),        )     def forward(self, x):        return self.backbone(x)  # 输出 14 维 logits  class CheXpertDataset(Dataset):    """模拟 CheXpert 数据读取:records 为 (路径, 14 个 0/1 标签) 列表。"""     def __init__(self, records, transform=None):        self.records = records        self.transform = transform     def __len__(self):        return len(self.records)     def __getitem__(self, idx):        path, labels = self.records[idx]        image = torch.randn(3, 224, 224)  # 演示用随机张量代替真实读图        if self.transform is not None:            image = self.transform(image)        return image, torch.tensor(labels, dtype=torch.float32)  def train_one_epoch(model, loader, optimizer):    model.train()    criterion = nn.BCEWithLogitsLoss()  # 多标签二元交叉熵    total_loss = 0.0    for images, labels in loader:        optimizer.zero_grad()        logits = model(images)        loss = criterion(logits, labels)        loss.backward()        optimizer.step()        total_loss += loss.item()    return total_loss / len(loader)  def predict(model, images, threshold=0.5):    model.eval()    with torch.no_grad():        probs = torch.sigmoid(model(images))  # logits -> 0~1 概率        preds = (probs > threshold).float()   # 每类独立按阈值判阳性    return probs, preds  if __name__ == "__main__":    # 构造 64 条模拟记录,每条含 14 个标签(7 阳 7 阴)    fake_records = [        ("chexpert/train/%d.jpg" % i, [0, 1] * 7) for i in range(64)    ]    dataset = CheXpertDataset(fake_records)    loader = DataLoader(dataset, batch_size=8, shuffle=True)     model = CheXpertModel(num_classes=14, pretrained=True)    optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)  # 低学习率微调     for epoch in range(2):        loss = train_one_epoch(model, loader, optimizer)        print("epoch %d loss %.4f" % (epoch, loss))     images, labels = next(iter(loader))    probs, preds = predict(model, images)    print("14 类疾病独立概率:", probs[0].tolist())    print("按阈值 0.5 判定阳性:", preds[0].tolist())
目的:演示用 PyTorch 搭建 EfficientNet-B0 做 CheXpert 14 类多标签分类的完整流程——加载预训练权重、替换分类头、BCE 训练、Sigmoid 概率输出与阈值判定。
  1. self.backbone = models.efficientnet_b0(weights="IMAGENET1K_V1" if pretrained else None)加载 ImageNet 预训练权重,让模型带着“通用视觉能力”上岗
  2. self.backbone.classifier = nn.Sequential(nn.Dropout(p=0.2), nn.Linear(in_features, num_classes))把 1000 类分类头换成 14 类,这是迁移学习的关键改造
  3. criterion = nn.BCEWithLogitsLoss()多标签专用损失:内部先算 Sigmoid 再算二元交叉熵,数值更稳定
  4. logits = model(images)前向传播得到 14 维原始得分,而不是 0~1 概率
  5. probs = torch.sigmoid(model(images))每个 logit 独立映射到 0~1,互不挤压,体现多标签独立性
  6. preds = (probs > threshold).float()以 0.5 为阈值逐类判定阳性,类似体检表逐个格子打勾
  7. optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)用低学习率微调,避免破坏预训练学到的底层特征

常见误区

误区“一张胸片要从 14 种病里挑一种,所以应该用 Softmax 把 14 个输出概率归一化,再加交叉熵训练。”

正解胸片可以同时患多种病,14 类标签互不排斥。Softmax 强制所有概率之和为 1,会让“肺炎+胸腔积液”同时存在时互相挤压,模型被迫二选一。正确做法是 14 个 Sigmoid + 二元交叉熵,每个病独立判阳性,就像 14 个互不联动的体检勾选框。

为何常见:初学者大多从 MNIST、CIFAR 这类单标签任务起步,习惯把“分类”等同于“从 N 个候选里选一个”,把这个惯性直接搬到了多标签医学任务上。

误区“反正已经有 ImageNet 权重了,直接用正常学习率 1e-2 训练,收敛肯定更快。”

正解预训练特征是脆弱的,学习率太大会在头几个 step 就把底层滤波器破坏掉,等于抛弃了预训练成果,退化成就地重训。应给骨干网络小学习率(1e-4~1e-5),给新分类头稍大学习率(1e-3),或者先冻结骨干训头、再解冻整体微调,像“先让老医生带教,再让全科复训”。

为何常见:“只要初始化好就高枕无忧”的直觉,忽略了梯度更新对已学特征的破坏力——微调不是从零学,而是小心翼翼地修正。

误区“模型在验证集上准确率 95%,说明它读片能力很强,可以上线了。”

正解当 90% 样本是“无异常”时,全判正常也有 90% 准确率,Acc 被先验分布绑架,根本看不出模型对罕见病(气胸、骨折)的识别能力。AUROC 只关心“患病的片子是否排在无异常前面”,对类别比例不敏感,才是医学影像的主流考核指标。

为何常见:通用分类任务习惯用 accuracy 汇报,人们直接把同一套指标搬进医学场景,忽略了医疗数据天然不平衡的本质。

误区“CheXpert 里那些专家都拿不准的标签留着也没用,直接删掉,用确定的样本训练更干净。”

正解不确定标签占比约 20%,全部丢弃既浪费数据,又可能引入选择偏差(被删的样本不一定是随机的)。更稳的做法是采用 U-Zeros / U-Ones / U-Multi 策略把这些样本纳入训练,官方基线中 U-Ones 往往优于直接删除——医学上的“不确定”本身也是信息,它标出了边界样本的难度。

为何常见:“脏数据要清洗”是常规数据处理思维,但医学不确定标签并不是错误标注,而是专家能力的边界,把它当噪声丢掉会损失诊断知识。

误区“EfficientNet-B0 参数最少、最精干,所以任何胸片任务都应该用 B0,选大模型就是浪费。”

正解B0 在资源受限场景下确实出色,但面对胸片上毫米级的早期小结节,更高输入分辨率与更宽的 B3/B4 往往能带来 AUROC 的实质提升。选择型号本质上是对算力、延时、精度的三角权衡:先做小规模对照实验,在可接受的推理耗时内选精度最高的型号,而不是无脑选最小。

为何常见:把“参数效率高”误解成“参数越少越好”,忽略了复合缩放中分辨率维度对细小病灶识别的影响——精干是针对同等精度的对比,不是绝对值。

面试题

Q1. CheXpert 胸片数据集共定义了多少类疾病发现?

单选题 · easy

  • A.10 类固定疾病标注
  • B.14 类疾病发现标签← 正确答案
  • C.1000 类 ImageNet 类别
  • D.100 类常见胸科疾病

答案:B。CheXpert 由斯坦福团队发布,每张胸片对应 14 类疾病发现的多标签向量。A 数量不足,C 混淆了 ImageNet 的类别数,D 属于干扰项。记住"14"这个数字,就像体检表有 14 行勾选框。

Q2. 多标签分类应使用哪种激活函数与损失函数组合?

单选题 · easy

  • A.Softmax 与交叉熵损失
  • B.Sigmoid 与二元交叉熵← 正确答案
  • C.ReLU 与均方误差损失
  • D.Tanh 与合页损失函数

答案:B。一张胸片可同时患多种病,标签互不排斥。Sigmoid 让每个疾病独立输出 0~1 概率,二元交叉熵分别惩罚每个类别的预测误差;Softmax 强制概率和为 1,与多病共存矛盾,因此 A 错误,C、D 更是南辕北辙。

Q3. EfficientNet 复合缩放的核心思想是什么?

单选题 · mid

  • A.只增加网络层数的深度
  • B.只增加每层通道的宽度
  • C.深度宽度分辨率按比例缩放← 正确答案
  • D.只提高输入图像的分辨率

答案:C。只缩放单一维度很快饱和。EfficientNet 用复合系数 φ 同时控制深度 d=α^φ、宽度 w=β^φ、分辨率 r=γ^φ,三者按黄金比例联动,就像扩建科室要同时加人、加技能面、加诊室,缺一不可。

Q4. 在 CheXpert 任务中,“迁移学习”通常指的是什么?

单选题 · mid

  • A.随机初始化后从头训练整个网络
  • B.用 ImageNet 预训练权重初始化再微调← 正确答案
  • C.同时训练多个模型并集成投票
  • D.把训练好的模型导出为 ONNX 文件

答案:B。迁移学习的本质是把 ImageNet 上学到的通用视觉特征作为初始点,再在 CheXpert 上微调。随机初始化是“从头训练”,集成投票属于模型集成,导出 ONNX 属于部署环节,三者都与迁移学习定义不符。

Q5. 类别不平衡严重时,更应关注哪个评估指标?

单选题 · mid

  • A.总体准确率 Accuracy
  • B.AUROC 曲线下面积← 正确答案
  • C.模型每秒推理的帧数
  • D.网络的参数量大小

答案:B。当正常片占 90% 时,全判正常也有 90% 准确率,Accuracy 会被先验分布欺骗。AUROC 衡量患病样本是否排在正常样本前面,对类别比例不敏感,是医学影像的标准考核指标;C、D 分别属于性能和效率范畴,不评估诊断能力。

Q6. CheXpert 的 U-Zeros 策略如何处理“不确定”标签?

单选题 · hard

  • A.直接把不确定样本全部丢弃
  • B.把不确定统一当作阴性即 0← 正确答案
  • C.把不确定统一当作阳性即 1
  • D.为不确定单独设一个损失项

答案:B。U-Zeros 把不确定标签当阴性,U-Ones 当阳性,U-Multi 为不确定单独设损失项。选项 A 是“直接删除”的误区,C 是 U-Ones,D 是 U-Multi。理解三策略的差别是处理 CheXpert 数据的关键考点。

Q7. 复合缩放约束 α·β²·γ² ≈ 2 的目的是什么?

单选题 · hard

  • A.使宽度恒为深度的平方倍
  • B.使整体计算量约随 φ 翻倍← 正确答案
  • C.使分辨率固定为 224 像素
  • D.使参数量等同 DenseNet

答案:B。卷积计算量与深度成正比、与宽度平方、分辨率平方成正比,因此该约束保证 φ 每增加 1 时总计算量约翻一番,资源预算可控。A、C、D 都不符合复合缩放的设计初衷——它是成本约束,不是几何比例或对标 DenseNet。

Q8. 关于 EfficientNet 与 DenseNet 的对比,下列说法正确的有?

多选题 · hard

  • A.EfficientNet 使用 MBConv 深度可分离卷积模块← 正确答案
  • B.同等精度下 EfficientNet 参数量通常更小← 正确答案
  • C.EfficientNet 必须使用更大的训练数据集
  • D.EfficientNet 无法应用于医学影像任务

答案:AB。MBConv 是 EfficientNet 的基础模块,由深度可分离卷积、反向残差和 SE 注意力组成;参数更少、精度更高正是它的核心卖点。C 错误,EfficientNet 靠迁移学习在小规模医学数据上也能表现良好;D 错误,本文正是它在 CheXpert 上的应用。

Q9. 处理胸片类别不平衡的常用手段包括哪些?

多选题 · mid

  • A.对稀有类别加大损失权重← 正确答案
  • B.对常见阴性样本做欠采样← 正确答案
  • C.丢弃全部稀有疾病的样本
  • D.用 AUROC 作为评估指标← 正确答案

答案:ABD。类别加权让稀有病犯错代价更大,欠采样常见阴性样本可平衡数据分布,AUROC 对类别比例不敏感适合评估。C 是错误做法——丢弃稀有样本会彻底失去学习该疾病的机会,与处理问题背道而驰。

Q10. 迁移学习微调胸片模型时,合理的做法有哪些?

多选题 · easy

  • A.使用较低的学习率进行微调← 正确答案
  • B.冻结骨干只训练新分类头← 正确答案
  • C.先训分类头再解冻整体微调← 正确答案
  • D.把学习率调得比从头训练更大

答案:ABC。预训练特征很脆弱,低学习率(1e-4 量级)保护底层滤波器,A 正确;冻结骨干只训分类头是轻量方案,B 正确;先训头再解冻是常见的两阶段微调策略,C 正确。D 会破坏预训练特征,属于经典误区。