标签:深度学习|医学影像|ConvNeXt|CT肺癌分类|Streamlit部署
摘要:肺癌不同病理亚型的治疗方案差异巨大,依靠人工阅片处理大批量肺部CT切片,效率与一致性都存在瓶颈。本文完整复盘一套可直接运行的ConvNeXt肺部CT四分类工程,区分腺癌、大细胞癌、正常肺组织、鳞状细胞癌,讲解网络结构、数据集构建、迁移学习训练、全套评估指标以及网页端推理部署,同时复盘实验过程中遇到的域偏移、过拟合、类别不平衡等真实坑点,适合课程设计、医学影像入门项目参考。
一、项目背景
肺癌是国内恶性肿瘤中发病率、死亡率均居高不下的病种,临床常见病理亚型包含肺腺癌、大细胞癌、肺鳞癌,同时需要和正常肺组织做区分。不同亚型对靶向药物、放化疗的响应完全不同,分型结果直接指导临床治疗路径。
CT是肺部筛查最常用的影像学手段,但CT切片数量多,医生长时间阅片容易疲劳;部分亚型影像纹理特征相似度高,主观判断会带来个体差异。基于深度学习的计算机辅助诊断(CAD)可以作为前置筛查工具,对单张CT切片完成快速亚型预判,给临床医生提供参考依据。
传统ResNet系列卷积网络特征提取能力有限,而纯卷积架构ConvNeXt吸收了ViT Transformer的设计思想,在不引入复杂自注意力模块的前提下,对残差块、激活函数、归一化方式做了系统性改造,兼顾卷积网络对局部纹理的捕捉能力与长距离依赖建模能力,同时提供tiny/small/base/large不同尺寸的模型,硬件适配灵活。本项目选用ConvNeXt‑tiny作为骨干网络,完成4分类任务:
- 0:Adenocarcinoma(肺腺癌)
- 1:Large cell carcinoma(大细胞癌)
- 2:normal(正常肺组织)
- 3:Squamous cell carcinoma(鳞状细胞癌)

二、ConvNeXt‑tiny网络结构解析
网络整体流程图(文本结构图,博客可直接渲染,markdown格式)
输入图像(3×224×224)
↓
Stem层 (4×4卷积 + LayerNorm + 步长4下采样) → 96通道特征图
↓
Stage1:3个 ConvNeXt Block | 通道96
↓
下采样层(2×2卷积 步长2)
↓
Stage2:3个 ConvNeXt Block | 通道192
↓
下采样层(2×2卷积 步长2)
↓
Stage3:9个 ConvNeXt Block | 通道384
↓
下采样层(2×2卷积 步长2)
↓
Stage4:3个 ConvNeXt Block | 通道768
↓
全局平均池化 GAP
↓
LayerNorm → 展平
↓
全连接层Linear(768 → 4) # 替换原ImageNet千分类头,适配本项目4分类
↓
Softmax输出4个类别的预测概率
ConvNeXt Block内部子结构
输入特征图
↓
Depthwise Conv 深度可分离卷积(7×7)
↓
LayerNorm归一化
↓
Pointwise Conv(1×1)升维 → GELU激活
↓
Pointwise Conv(1×1)降维
↓
Drop Path随机路径丢弃
↓
残差Shortcut相加输出
结构说明:
- 整个网络完全由卷积模块堆叠,没有注意力算子,推理速度快,对GPU显存压力小,
ConvNeXt‑tiny参数量规模适合医学小数据集; - 将BN批量归一化替换为LayerNorm,ReLU替换为GELU,借鉴Transformer组件;
- 深度卷积扩大感受野,捕捉CT影像当中病灶的纹理、形态特征;
- 工程代码中
create_model()函数直接加载torchvision内置ConvNeXt‑tiny预训练权重,替换最后一层classifier2全连接层输出维度为4,适配我们的肺部四分类任务。
三、工程整体架构与数据集处理
整套工程由三份核心代码构成:utils.py工具函数库、train.py训练脚本、infer.py Streamlit网页推理脚本。
数据集严格按照ImageFolder目录格式组织:
data
├─train
│ ├─Adenocarcinoma
│ ├─Large cell carcinoma
│ ├─normal
│ └─Squamous cell carcinoma
└─val
├─Adenocarcinoma
├─Large cell carcinoma
├─normal
└─Squamous cell carcinoma
医学影像样本数量普遍偏少,过拟合是最大挑战,因此训练上采用迁移学习+主干冻结策略:加载ImageNet预训练权重,可选择冻结主干网络,只训练最后的分类头,利用自然图像学习到的基础纹理特征,再适配肺部CT数据分布。
数据增强策略区分训练集与验证集:
- 训练集:Resize256 → 随机旋转90° → CenterCrop224 → ToTensor → ImageNet均值方差归一化;随机旋转增加样本多样性,模拟CT切片不同摆放角度。
- 验证集:Resize256 → CenterCrop224 → ToTensor → 归一化;不使用随机变换,保证验证指标稳定可信。
注意坑:CT图像是灰度单通道,这里数据集已经提前转为RGB三通道输入网络;如果直接送入单通道图片,会出现维度报错。
四、训练策略与全套评估体系
训练脚本train.py完整实现训练循环,优化器选用AdamW,搭配余弦退火LambdaLR学习率调度器。随着epoch增加学习率平滑下降,帮助模型在后期精细收敛。
医学分类不能只看准确率accuracy。如果某一类样本占比极高,模型无脑预测大类也能拿到很高准确率,指标具备欺骗性。
本项目训练循环每一轮都会计算完整指标:准确率、各类精确率Precision、召回率Recall、特异性Specificity、F1‑Score。训练结束自动输出大量分析图表:
- Loss‑Accuracy曲线:观察训练、验证loss是否发生过拟合;
- 学习率衰减曲线:确认余弦调度器是否正常生效;
- P/R/S/F1随epoch变化曲线图;
- 训练集、验证集两套混淆矩阵:直观看到哪一类亚型容易被模型混淆,例如大细胞癌与腺癌特征接近,容易出现错分;
- 多分类One‑vs‑Rest ROC曲线以及AUC值,衡量每一类的区分能力;
- 自动保存
best.pth(验证集最优权重)、last.pth(最后epoch权重),同时输出class_indices.json类别映射文件,推理阶段读取标签。
训练过程全部日志、超参、每epoch指标写入json文件留存,方便复现实验。
五、Streamlit网页推理部署
训练得到最优权重之后,通过infer.py搭建本地网页服务,不需要写前端HTML、JS。运行streamlit run infer.py即可启动网页。
网页功能:
- 默认加载一张本地CT测试切片作为示例,直接展示预测结果;
- 支持用户本地上传CT图像;
- 图像执行和训练完全一致预处理流程;
torch.no_grad()关闭梯度,降低显存占用;- softmax计算类别概率,输出top‑2 / top‑3预测结果与置信百分比。
踩坑:网页上传的图片对象是字节流,代码内部用PIL统一转为RGB,防止灰度图、RGBA图片维度异常导致推理崩溃。
六、实验中暴露出来的现实问题
在实际跑通整套工程之后,会发现实验室数据集表现和临床真实场景中间存在差距。
- 域偏移问题:预训练权重来自自然彩色图像,而CT是医学灰度影像,两者像素分布差异大;即便做迁移学习,泛化能力依然受限;
- 类别样本不均衡:四类样本数量如果差距较大,模型会偏向样本多的类别,混淆矩阵可以观察到小众类别召回率下降;
- 当前方案属于切片级图像分类,整张图片送入网络,背景肺组织、胸腔无关区域会带来噪声干扰,网络不一定聚焦病灶区域;
- CT存在扫描设备差异、窗宽窗位不同、切片伪影噪声,都会干扰模型判断。
重要提醒:该项目仅作为AI辅助筛查原型,不能替代病理活检的金标准,不可以直接用于临床诊断。
七、后续改进思路
- 扩充多中心数据集,引入窗宽窗位预处理,统一CT图像成像条件;使用重采样、类别加权损失缓解样本不均衡;
- 在ConvNeXt主干后增加注意力模块,强化病灶区域特征响应;
- 引入分割网络,先分割出病灶ROI区域,把ROI图像送入分类网络,过滤无关背景;
- 尝试ConvNeXt‑small/base对比不同模型尺寸下精度、速度的权衡;
- 增加测试集外部验证,评估模型在陌生数据上的泛化性能,而不是只看本地验证集指标。
八、小结
ConvNeXt凭借纯卷积、强特征提取、多模型尺度的优势,在肺部CT病理亚型分类任务上具备很好的实践价值。这套工程完整覆盖数据集处理、迁移学习训练、多维度指标评估、网页端推理,非常适合医学影像方向的学习实践。
做医学AI项目,实验室数据集指标好看只是第一步,如何克服域偏移、提升真实临床场景鲁棒性,才是落地真正要面对的难题。
(全文1722字)
博客小提示:发布时,网络结构图可以复制到mermaid工具渲染成图片,mermaid版本如下,可以直接生成可视化框图放到博客文章里:
#mermaid-svg-bCjDgzDYQtPoUlf9{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bCjDgzDYQtPoUlf9 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bCjDgzDYQtPoUlf9 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bCjDgzDYQtPoUlf9 .error-icon{fill:#552222;}#mermaid-svg-bCjDgzDYQtPoUlf9 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bCjDgzDYQtPoUlf9 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bCjDgzDYQtPoUlf9 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bCjDgzDYQtPoUlf9 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bCjDgzDYQtPoUlf9 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bCjDgzDYQtPoUlf9 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bCjDgzDYQtPoUlf9 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bCjDgzDYQtPoUlf9 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bCjDgzDYQtPoUlf9 .marker.cross{stroke:#333333;}#mermaid-svg-bCjDgzDYQtPoUlf9 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bCjDgzDYQtPoUlf9 p{margin:0;}#mermaid-svg-bCjDgzDYQtPoUlf9 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-bCjDgzDYQtPoUlf9 .cluster-label text{fill:#333;}#mermaid-svg-bCjDgzDYQtPoUlf9 .cluster-label span{color:#333;}#mermaid-svg-bCjDgzDYQtPoUlf9 .cluster-label span p{background-color:transparent;}#mermaid-svg-bCjDgzDYQtPoUlf9 .label text,#mermaid-svg-bCjDgzDYQtPoUlf9 span{fill:#333;color:#333;}#mermaid-svg-bCjDgzDYQtPoUlf9 .node rect,#mermaid-svg-bCjDgzDYQtPoUlf9 .node circle,#mermaid-svg-bCjDgzDYQtPoUlf9 .node ellipse,#mermaid-svg-bCjDgzDYQtPoUlf9 .node polygon,#mermaid-svg-bCjDgzDYQtPoUlf9 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-bCjDgzDYQtPoUlf9 .rough-node .label text,#mermaid-svg-bCjDgzDYQtPoUlf9 .node .label text,#mermaid-svg-bCjDgzDYQtPoUlf9 .image-shape .label,#mermaid-svg-bCjDgzDYQtPoUlf9 .icon-shape .label{text-anchor:middle;}#mermaid-svg-bCjDgzDYQtPoUlf9 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-bCjDgzDYQtPoUlf9 .rough-node .label,#mermaid-svg-bCjDgzDYQtPoUlf9 .node .label,#mermaid-svg-bCjDgzDYQtPoUlf9 .image-shape .label,#mermaid-svg-bCjDgzDYQtPoUlf9 .icon-shape .label{text-align:center;}#mermaid-svg-bCjDgzDYQtPoUlf9 .node.clickable{cursor:pointer;}#mermaid-svg-bCjDgzDYQtPoUlf9 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-bCjDgzDYQtPoUlf9 .arrowheadPath{fill:#333333;}#mermaid-svg-bCjDgzDYQtPoUlf9 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-bCjDgzDYQtPoUlf9 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-bCjDgzDYQtPoUlf9 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bCjDgzDYQtPoUlf9 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-bCjDgzDYQtPoUlf9 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bCjDgzDYQtPoUlf9 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-bCjDgzDYQtPoUlf9 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-bCjDgzDYQtPoUlf9 .cluster text{fill:#333;}#mermaid-svg-bCjDgzDYQtPoUlf9 .cluster span{color:#333;}#mermaid-svg-bCjDgzDYQtPoUlf9 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-bCjDgzDYQtPoUlf9 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-bCjDgzDYQtPoUlf9 rect.text{fill:none;stroke-width:0;}#mermaid-svg-bCjDgzDYQtPoUlf9 .icon-shape,#mermaid-svg-bCjDgzDYQtPoUlf9 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bCjDgzDYQtPoUlf9 .icon-shape p,#mermaid-svg-bCjDgzDYQtPoUlf9 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-bCjDgzDYQtPoUlf9 .icon-shape .label rect,#mermaid-svg-bCjDgzDYQtPoUlf9 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bCjDgzDYQtPoUlf9 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-bCjDgzDYQtPoUlf9 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-bCjDgzDYQtPoUlf9 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 输入图像3×224×224
Stem层 4×4卷积+下采样
Stage1:3个ConvNeXt‑Block 通道96
下采样层
Stage2:3个ConvNeXt‑Block 通道192
下采样层
Stage3:9个ConvNeXt‑Block 通道384
下采样层
Stage4:3个ConvNeXt‑Block 通道768
全局平均池化GAP
LayerNorm+展平
全连接层 768→4
Softmax输出4分类概率
下载:添加链接描述
