深度学习医学影像实战:PyTorch肺炎分类系统全流程解析

发布时间:2026/8/31 10:42:41
深度学习医学影像实战:PyTorch肺炎分类系统全流程解析 简介本资源是一个面向高校本科生的深度学习课程设计与毕业设计实践项目聚焦胸部X光影像的肺炎二分类任务解决医学影像中自动化、高精度辅助诊断的实际需求。压缩包共10个文件含7个核心Python模块如model.py构建迁移学习模型、dataset.py实现数据加载与增强、train.py封装训练流程、evaluate.py提供准确率/F1等量化评估、1个依赖清单requirements.txt、1个说明文档README.md及1个.gitignore配置文件整体仅23KB轻量易部署。目前已有30人学习下载适合深度学习入门者开展图像识别实战读者可直接运行完整端到端流程掌握从数据预处理、模型微调、训练监控到性能评估的全流程开发范式并复现基于预训练CNN的医疗影像分类方案。 最近在整理之前做的一个医学影像项目就是那个经常被下载的“基于深度学习的胸部x光肺炎分类系统”压缩包。很多人拿到这个zip之后问我要环境配置、要训练细节我干脆把整套思路、踩过的坑、还有最终跑的通的方案一次性写清楚。这个项目本质很简单用深度学习模型对胸部X光片Chest X-Ray做二分类区分正常肺部Normal和肺炎Pneumonia。但它背后牵出来的东西其实不少包括数据怎么洗、模型怎么选、训练怎么调参、结果怎么评估甚至最后模型怎么落地部署。这篇文章适合两类人看一是深度学习入门阶段想找个医疗影像实战项目练手的二是已经在做CV分类任务想看看医学图像场景下有哪些特殊坑的。下面全部按照我实际操作的过程来讲代码都基于PyTorch环境是Ubuntu CUDA没有用很新的花活主打一个稳。1. 项目拆解胸部X光肺炎分类到底难在哪1.1 任务定义与问题本质先说清楚任务形式。输入是一张胸部X光片通常是灰度图医学上叫Chest Radiograph输出是这张片子对应的诊断标签二分类就是“肺炎阳性”或“正常”。听起来跟CIFAR-10、ImageNet的分类任务没什么两样但医疗图像和自然图像有本质区别。自然图像分类比如猫和狗特征差异巨大颜色、纹理、轮廓都很明显。而肺部X光片所有样本都是胸腔正位片骨骼、心脏、肺野的解剖结构都差不多肺炎病灶往往只是局部一小片磨玻璃影或实变影跟正常肺纹理的变化极其细微。我见过很多刚上手的同学拿ImageNet的思维直接套结果发现训练半天loss就是不降或者是val集上AUC很高但可视化一看模型根本没在看肺野而是在看片子角落的英文字母标记。这就是医学图像的第一大坑正负样本类间差异极小而噪声信号很强。再有一个实际层面的问题——数据量。公开数据集里ChestX-Ray2017这类肺炎数据集通常只有几千张图训练深度学习模型动辄上百万参数的ResNet、EfficientNet不靠预训练和强数据增强基本会过拟合到怀疑人生。所以整个项目的技术核心总结下来是三件事数据质量、迁移学习、参数调优。能做到这三点二分类准确率做到90%以上问题不大。1.2 项目目录结构说明zip解压之后通常是这样chest_xray_pneumonia/ ├── data/ │ ├── train/ │ │ ├── NORMAL/ │ │ └── PNEUMONIA/ │ ├── val/ │ │ ├── NORMAL/ │ │ └── PNEUMONIA/ │ └── test/ │ ├── NORMAL/ │ └── PNEUMONIA/ ├── models/ │ ├── resnet50_pneumonia.pth │ └── resnet50_onnx.onnx ├── src/ │ ├── dataset.py │ ├── train.py │ ├── evaluate.py │ └── inference.py ├── notebooks/ │ └── EDA.ipynb ├── requirements.txt └── README.md我刻意把数据、模型权重、训练逻辑、推理脚本分开目的就是让项目可以直接被别人复跑。你在自己搭建类似系统时也建议这么做不要图省事把所有代码堆在同一个脚本里后面调试和换模型会非常痛苦。1.3 医疗AI项目与通用视觉项目的不同节奏医疗影像项目跟打比赛还不一样。打比赛只要指标高就行但医疗场景至少要额外考虑两点可解释性和鲁棒性。医生不会因为你的Accuracy是0.98就信你他们想知道模型是看哪个区域给出的判断所以后面我专门加了Grad-CAM可视化。鲁棒性方面不同医院不同设备拍出来的X光片亮度、对比度、大小都不一样训练时如果没做针对性的泛化处理模型换个设备采集的片子大概率崩。这些点听起来很虚但它们在后续的决策中会直接影响你的模型选型和数据增强策略我建议你从一开始就心里有数。2. 数据准备80%的精力都该花在这2.1 数据集来源与细节我用的是公开的胸部X光肺炎数据集由正常肺部影像和肺炎影像组成训练集大约有5200多张验证集和测试集各几百张。每张图像都是JPEG格式的灰度图但读取后会是三通道的RGB图这一点后面会坑到不少人。数据需要划分必须保证训练集、验证集、测试集完全隔离。很多人随手用train_test_split一划就完事但医学图像数据经常存在同一个患者的多张片子如果不按患者级别去重划分就会发生数据泄露——模型在训练时已经见过测试患者的片子测出来的指标虚高部署时打回原形。怎么尽量避免合理的做法是优先查找数据集的元数据看是否有patient_id按patient_id分组划分如果确实没有只能从文件命名规律上尽量猜测。这个项目里文件命名是按图片序号来的存在同患者多张片子的可能性但公开数据集没有完整标注属于一个已知限制在论文或报告里要写清楚。2.2 预处理流程细节图像预处理是整个流程里最机械但最重要的环节。第一是缩放。原图尺寸不统一我在dataset里统一Resize到224x224这也是ResNet输入的标准尺寸。别用128x128虽然训练快但X光片里的小病灶真的可能被缩没了。第二是归一化。用ImageNet的均值和标准差做标准化即mean[0.485, 0.456, 0.406]std[0.229, 0.224, 0.225]。很多人问X光是灰度图为什么用ImageNet的RGB均值答案是如果你的模型用了ImageNet预训练权重输入就必须对齐预训练时的分布这样迁移学习才有效。我用的是迁移学习方案所以这里不能乱改。第三是数据处理。刚开始PIL读进来是灰度但显示为三通道其实如果原图就是灰度JPEGPIL打开后是modeL的单通道图有些人的写法里会把单通道的图片转换成三通道img.convert(RGB)这样模型输入的3个通道的值完全一样没问题。但也有人的load代码写成np.array(img)后shape是(H, W)直接丢给模型就报维度错误。统一做法如下from PIL import Image import torchvision.transforms as transforms def load_image(path): img Image.open(path).convert(RGB) transform transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) return transform(img).unsqueeze(0)2.3 数据增强的医学影像专属策略自然图像常用的RandomCrop、Cutout、MixUp医学图像不能直接乱用。因为X光片有明确的解剖结构你把它随机裁剪掉一半或者拿黑色块遮挡住肺野区域模型会被你教坏以为肺可以凭空消失。我最终采用的数据增强策略是随机水平翻转解剖结构左右对称安全性高随机旋转±10度角度太大会让心脏轮廓位置发生不合理偏移随机缩放0.9到1.1倍颜色抖动随机调整亮度、对比度模拟不同设备、不同曝光条件这套增强组合的考虑是前三个增强模拟患者拍摄角度和位置的细微差异颜色抖动模拟不同X光机参数差异而不会破坏病灶本身的结构特征。训练时我用的是增强版本测试和验证时只用ResizeNormalize保证评估的时候不受随机性干扰。2.4 DataLoader与样本均衡肺炎和正常样本的数量并不均衡大概在1.6:1左右不算太严重但依然需要处理。我的做法是给torch.utils.data.DataLoader的sampler传入WeightedRandomSampler根据类别频率反比计算采样权重让每个batch里正负样本大致均衡。from torch.utils.data import WeightedRandomSampler def make_weights(labels): counts np.bincount(labels) class_weights 1.0 / counts weights [class_weights[label] for label in labels] return torch.DoubleTensor(weights) sampler WeightedRandomSampler(weightsmake_weights(train_labels), num_sampleslen(train_labels), replacementTrue)这个细节很重要。如果不做均衡模型很容易偏向预测多数类结果就是准确率看着还行但是肺炎的召回率低得没法用。对于一个“找病”的系统来说漏诊比误诊严重得多。3. 模型选型与网络设计考量3.1 为什么没有自己从零搭CNN有个同学问过我为什么不自己写一个5层卷积网络从头训练说实话如果图省事或者只是想跑通流程自己搭个小CNN完全没问题数据量小训练快。但我们最终目的是拿到一个能用的分类器而不是交个作业。在几千张医学图像这种中等规模数据集上从头训练一个深度CNN的效果通常比微调预训练模型差不少原因在于ImageNet预训练模型已经学会了丰富的底层视觉特征——边缘、纹理、形状——这些特征迁移到X光片上依然有用即使两个域差异很大底层特征的迁移价值仍然存在。所以我的方案是用ImageNet上预训练的ResNet50作为主干网络冻结前面几层的BN层参数和统计量微调后面的stage以及新增的分类头。这种做法业界叫迁移学习也是这类小样本医学影像项目的常规操作。3.2 主干网络对比我实际上试过三个模型简单列一下各自的体感和效果模型参数量训练速度验证集AUC说明ResNet1811M快0.94左右轻量但特征表达能力一般适合快速验证ResNet5025M中等0.97左右性价比最高我用它作为最终方案DenseNet1218M中等0.97左右参数少效果好但显存占用略高EfficientNetB05M中等0.96左右理论上效率高但在我这个数据集上训练收敛慢ResNet50是综合权衡下的选择。医学图像相对自然图像纹理更平滑、结构更固定不需要特别深的网络提取过细的语义特征ResNet50的深度刚好比ResNet18能多学一些细节又比ResNet101训练快、更不容易过拟合。DenseNet121也不错特征复用机制在小数据集上天然友好但ResNet50在PyTorch里接口最顺手部署工具链最成熟所以我最终用了ResNet50。3.3 分类头的设计细节很多人直接model.fc nn.Linear(2048, 2)就完事了。我建议做一个小小的加强在后面加一个Dropout层再接输出层import torch.nn as nn class PneumoniaClassifier(nn.Module): def __init__(self, backbone, num_classes2): super().__init__() self.backbone backbone in_features backbone.fc.in_features backbone.fc nn.Sequential( nn.Dropout(0.3), nn.Linear(in_features, num_classes) ) def forward(self, x): return self.backbone(x)Dropout在这里非常关键ResNet50的最后池化层直接接FC输出2048维特征如果不做随机失活全连接层容易过拟合尤其训练样本只有几千张的时候。0.3这个值是我试出来的太大了模型欠拟合太小了起不到正则作用。3.4 BatchNorm与冻结策略微调ResNet50时有一个细节必须注意BatchNorm层的running_mean和running_var在预训练阶段已经统计好了如果你的batch_size比较小比如8或16BN层的统计量会变得很不稳定导致验证集指标剧烈抖动。所以我冻结了backbone前几层设置为requires_gradFalse并且将BN层切换到eval模式只训练后面的层。这个操作可以有效避免小batch_size下的BN统计量漂移问题。def freeze_backbone(model, freeze_bnTrue): for name, param in model.named_parameters(): if name.startswith(backbone.) and int(name.split(.)[1]) 6: param.requires_grad False if freeze_bn: for module in model.modules(): if isinstance(module, nn.BatchNorm2d): module.eval()需要注意冻结BN层会改变运行时的batch统计数据行为所以训练阶段如果你使用了model.train()前面这些BN层又会切换回训练模式。我的处理方式是训练时对backbone的BN层手动维持eval状态这个在PyTorch里稍麻烦但只能这样细心处理。4. 训练过程调参的完整记录4.1 优化器与损失函数优化器我最终选了AdamW初始学习率1e-4weight_decay设置为1e-4。为什么不用SGDSGD在这个小数据集上收敛慢对学习率的调度要求更高需要我花更多时间做warmup和cosine退火。AdamW自带自适应学习率在迁移学习阶段更省心而且它的权重衰减实现比普通Adam合理不容易把预训练特征冲掉。学习率的选择经验是如果预训练模型权重比较可靠用1e-4左右微调即可如果从头训练一般用1e-3千万不要一上来用3e-4以下的大学习率。损失函数用的是带类别权重的交叉熵class_weights torch.tensor([1.0, 0.9]).cuda() criterion nn.CrossEntropyLoss(weightclass_weights)这里的权重需要根据你的数据分布调整。如果负样本是正样本的1.5倍权重按反比设即可。我前面已经用WeightedRandomSampler做了采样均衡所以损失函数里的类别权重只做微调。两个机制都保留避免某一种方法出问题时整个训练失效。4.2 学习率调度与训练轮数我用的是CosineAnnealingLRT_max20即20个epoch内从1e-4逐渐衰减到接近0。训练轮数一共30轮最后10个epoch保持最低学习率往下微调。实际操作中模型在第18到22轮之间验证集效果最好后面的epoch只是因为还没过拟合在慢慢打磨边界。这里给个具体的训练循环骨架注意学习率调度器在每次step之前更新for epoch in range(epochs): model.train() running_loss 0.0 for images, labels in train_loader: images, labels images.cuda(), labels.cuda() optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() scheduler.step() val_loss, val_acc, val_auc evaluate(model, val_loader) print(fEpoch {epoch1}: train_loss{running_loss/len(train_loader):.4f}, fval_loss{val_loss:.4f}, val_acc{val_acc:.4f}, val_auc{val_auc:.4f}) if val_auc best_auc: best_auc val_auc torch.save(model.state_dict(), models/resnet50_pneumonia.pth)注意scheduler.step()必须放在验证之后如果你用的是CosineAnnealingLR通常在每个epoch结束后调用一次。如果放在optimizer.step()之后学习率会在每个batch都变化效果完全不同。4.3 早停策略与模型保存我自己在实践里加了EarlyStopping的变种不是看验证loss多少个epoch不下降就停而是记录最佳AUC如果连续10个epoch没有刷新最佳AUC就停止训练。这个项目里第22轮达到最佳AUC之后第25、26、27轮开始有轻微过拟合迹象到第28轮AUC下降明显所以早停触发在28轮省了两轮时间。模型保存时我建议同时保存以下内容方便后续复现torch.save({ model_state_dict: model.state_dict(), optimizer_state_dict: optimizer.state_dict(), best_auc: best_auc, epoch: epoch, class_names: [NORMAL, PNEUMONIA], }, models/resnet50_pneumonia_full.pth)只保存state_dict最方便加载推理但保存完整checkpoint更有助于恢复训练。如果想做进一步部署记得单独导出ONNX后面会讲。4.4 显存不足与batch_size选择ResNet50在224分辨率下batch_size设为32单张RTX 2080Ti11GB显存能稳稳跑住。如果你只有6GB显存把batch_size降到16同时把图片resize改成(192, 192)也能凑合跑。但我不建议轻易降低分辨率前面说了病灶可能很小。另一个更优雅的方案是使用梯度累积accumulation_steps 4 for step, (images, labels) in enumerate(train_loader): loss criterion(outputs, labels) / accumulation_steps loss.backward() if (step 1) % accumulation_steps 0: optimizer.step() optimizer.zero_grad()这样做相当于模拟了更大的batch_size但要注意BN层的统计量还是按实际batch更新的如果batch_size太小小于8BN层效果依然会打折扣。这也是我前面冻结BN层的原因之一。4.5 训练日志与可视化我会在每次实验里把训练指标写入一个CSV文件方便画学习曲线。远程服务器上可以装TensorBoard本地看一眼loss和AUC的趋势比什么都有用。关键观察点训练loss下降但验证loss不降或验证AUC先升后降——过拟合信号增加Dropout、增强数据增强、降低学习率训练loss和验证loss都降得很慢——学习率太小或模型容量不够考虑把冻结层解冻验证AUC在0.5附近徘徊——模型完全没有学到特征大概率是数据预处理或标签加载出了问题别急着调参先回去检查数据5. 评估与可解释性不只看准确率5.1 医学场景下的指标选择二分类任务最常用的指标是Accuracy但在医学场景里准确率是有欺骗性的。如果测试集里60%是肺炎、40%是正常模型无脑全预测肺炎准确率也有60%看起来还行实际上一张正常片子都不会被放过不对是正常片子全部被误诊。所以我重点看三个指标召回率Recall/Sensitivity、特异度Specificity、AUC。召回率代表“真正的肺炎患者有多少被找出来了”宁可多查也不能漏。特异度代表“正常人有多少被正确排除了”低了会造成过度诊断。这两个指标天然互相矛盾你可以通过调整分类阈值来权衡。我最终模型在测试集上的结果大致是指标数值Accuracy0.93Precision0.92Recall0.94Specificity0.90AUC0.97这个Recall和Specificity的组合在医疗AI辅助筛查场景里是可以接受的但距离临床直接决策还有距离。如果筛查用可以把阈值从默认的0.5降到0.3这样Recall能到0.97但要接受更多正常病例被标记为“疑似”。5.2 混淆矩阵与错误样本分析混淆矩阵我强烈建议每个做分类任务的人都要画。它不只是四个格子而是能告诉你模型在犯哪类错from sklearn.metrics import confusion_matrix, classification_report cm confusion_matrix(y_true, y_pred) print(classification_report(y_true, y_pred, target_names[NORMAL, PNEUMONIA]))我跑完测试集后专门去看那些被误分类的片子。发现规律是正常但被误判为肺炎的样本大多是新生儿X光片图像对比度低、肺部含气少纹理看起来确实像弥漫性病灶而肺炎但被误判为正常的样本病灶区域很小位于肺尖或肋膈角附近不易观察。这些错误的模式说明了模型的black-box决策在什么情况下会失效。把这些发现写进项目README里比单纯贴一个95%准确率显得专业得多。5.3 Grad-CAM可视化让模型“说出”判断依据Grad-CAM通过计算目标类别对最后一层卷积特征图的梯度得到每个特征通道的重要性权重然后加权求和生成热力图叠加在原图上就能看到模型关注的位置。对医疗场景来说这一步价值极高。它能帮助医生判断模型的依据是否合理也能帮你排查模型是不是“偷看”了不该看的东西。我实现了简化版Grad-CAM并跑了几张测试图结果比较理想模型在正常片上主要关注肺部纹理较均匀的区域在肺炎片上则聚焦于局部高密度影区域。但也发现个别样本热力图高亮在了心脏边缘或锁骨区域——这提示模型学了一些与肺炎弱相关的特征不过由于整体表现落在肺野说明模型的主要决策路径是合理的。PyTorch里实现Grad-CAM不需要额外库手动钩子即可class GradCAM: def __init__(self, model, target_layer): self.model model self.gradients None self.activations None target_layer.register_forward_hook(self.save_activation) target_layer.register_full_backward_hook(self.save_gradient) def save_activation(self, module, input, output): self.activations output.detach() def save_gradient(self, module, grad_input, grad_output): self.gradients grad_output[0].detach() def generate(self, input_tensor, class_idxNone): output self.model(input_tensor) if class_idx is None: class_idx output.argmax(dim1).item() self.model.zero_grad() one_hot torch.zeros_like(output) one_hot[0][class_idx] 1 output.backward(gradientone_hot) weights self.gradients.mean(dim(2, 3), keepdimTrue) cam (weights * self.activations).sum(dim1, keepdimTrue) cam F.relu(cam) cam F.interpolate(cam, size(224, 224), modebilinear, align_cornersFalse) cam (cam - cam.min()) / (cam.max() - cam.min() 1e-8) return cam.squeeze().cpu().numpy()5.4 ROC曲线与阈值选择最后评估时画一下ROC曲线AUC是0.97。AUC的意义在于它不受分类阈值影响描述的是模型把正样本排在负样本前面的能力。AUC越高说明模型对“肺炎可能性”的排序越可靠。再结合约登指数Youdens J Sensitivity Specificity - 1来选择最优阈值通常取ROC曲线上距离左上角最近的点。如果项目只是给医生做辅助参考我会保留默认0.5因为医生会结合自己的经验做最终判断如果是自动筛查我会用约登指数选出的阈值。6. 项目打包与部署zip里的东西怎么用6.1 模型导出ONNX训练完PyTorch模型不能直接给生产环境用因为PyTorch的推理依赖Python环境、依赖模型定义结构部署方还得装一套CUDA、PyTorch太繁琐。我导出成ONNX格式这样可以用ONNX Runtime做CPU推理部署环境立刻变得轻盈import torch import torch.onnx model PneumoniaClassifier(resnet50) checkpoint torch.load(models/resnet50_pneumonia_full.pth, map_locationcpu) model.load_state_dict(checkpoint[model_state_dict]) model.eval() dummy_input torch.randn(1, 3, 224, 224) torch.onnx.export( model, dummy_input, models/resnet50_pneumonia.onnx, input_names[input], output_names[output], dynamic_axes{input: {0: batch_size}, output: {0: batch_size}} ) print(ONNX exported.)ONNX导出时有几点要注意确认模型里的所有操作都支持ONNX算子集Dropout在推理模式下会被消除BN层在导出时会折叠成推理形式这些都是PyTorch自动处理的不用太担心。但如果你自定义了比较复杂的forward逻辑比如出现了Python控制流导出就会失败需要简化网络结构或改用torch.jit.trace。6.2 推理脚本与部署思路ONNX模型用ONNX Runtime加载推理CPU上单张图像大概30到50毫秒完全满足辅助诊断场景的实时性需求。推理脚本核心逻辑很简洁import onnxruntime as ort import numpy as np sess ort.InferenceSession(models/resnet50_pneumonia.onnx, providers[CPUExecutionProvider]) input_name sess.get_inputs()[0].name def predict(img_tensor): result sess.run(None, {input_name: img_tensor.numpy()}) probs np.exp(result[0]) / np.sum(np.exp(result[0]), axis1, keepdimsTrue) return probs[0]如果要做成Web服务用FastAPI包一层就行接受图片上传预处理后调用predict返回NORMAL或PNEUMONIA的概率和Grad-CAM热力图。但要注意如果只是简单演示加一个阈值开关来控制输出提示如果认真做产品必须加输入图像质量控制——比如不是X光片、分辨率不对、图像损坏都要在预处理阶段拦截不能让模型硬跑。6.3 依赖与环境说明requirements.txt里我列了主要依赖torch1.13.1 torchvision0.14.1 onnxruntime1.14.1 numpy1.21 Pillow9.0 scikit-learn1.0 matplotlib3.5 opencv-python4.6 tqdm4.64需要提醒的是这个项目是在PyTorch 1.13上训练的如果你用PyTorch 2.x跑大概率能直接加载权重但可能遇到torch.load的weights_only参数默认值变化之类的兼容性小坑遇到就google一下不是大问题。训练时CUDA版本我用了11.7部署推理根本不需要CUDACPU足够。7. 常见问题与排查技巧实录7.1 图像加载报错无法将PIL图像转为Tensor最常见的问题新手必踩TypeError: pic should be PIL Image or ndarray. Got class numpy.ndarray原因通常是transforms.ToTensor()之前已经手动把图像转成了numpy数组或者读图时用了cv2.imread返回的是BGR而ToTensor期望RGB。统一用PIL读取Image.open(path).convert(RGB)然后直接交给transform不要中间手动转numpy。如果非要用OpenCV读记得cv2.cvtColor(img, cv2.COLOR_BGR2RGB)。7.2 显存溢出OOM训练中段爆显存解决办法优先级调小batch_size这个最直接减小图片分辨率到192或160但测试集尽量保持训练尺度使用梯度累积模拟大batch检查代码里有没有把不需要的中间变量保留在显存里比如loss.item()之后把loss删掉检查是不是validation时也开了torch.no_grad()忘了写的话验证阶段也会占一份显存建议用torch.inference_mode()装饰评估函数。7.3 Loss不下降或者NaN先看数据预处理对不对图片有没有归一化到0-1标签有没有从0开始如果数据没问题看学习率是不是太大比如用1e-3微调预训练模型很可能导致loss飞升。再检查数据加载顺序是不是与标签错位了。如果loss一开始就是NaN大概率是数据里混入了损坏图像或者标签里有负数写个循环检查np.isfinite(img).all()。7.4 验证集AUC高但测试集表现差一个是前面提到的数据泄露风险一个是过拟合。前者需要重新划分数据后者建议检查训练和测试的预处理是否一致比如训练做了增强测试千万别用同一条transform管道查看训练曲线如果验证AUC在后期明显下降说明过拟合增加正则化或早停检查测试集是不是来自不同分布比如训练集都是儿童X光片测试集混入成人视觉差异会导致指标下降这时最好的办法是收集更多源头数据。7.5 预训练权重下载失败国内服务器下载ImageNet预训练权重经常卡死建议手动下载到本地放到~/.cache/torch/hub/checkpoints/目录下然后设置环境变量TORCH_HOME指定缓存路径。或者直接在代码里用torchvision.models.resnet50(weightsNone)然后手动load_state_dict指定本地权重文件。我在代码里就写了一个自动检测本地权重、再决定是否在线下载的逻辑免得每次换机器都卡半天。几点实操体会这个是个人经验项目从开始到能稳定复现花最多时间的不是写模型代码而是数据清洗和预处理。把数据管好了模型的底气就有一大半。另外我自己在做类似医学影像项目时的习惯是每跑完一次实验把模型权重、训练日志、关键指标、甚至当时的错误样本截图都归档到以日期命名的文件夹里。这个习惯帮我后面写报告、调参数省了大量的时间。这个zip项目里我把最终权重和推理脚本都整理好了就是希望大家拿到手能直接跑而不是对着代码抓瞎。最后分享一个小技巧如果你在起步阶段不确定用哪个主干网络先拿ResNet18跑一个快速实验把整个流程走通再换ResNet50做最终精调。这样既能快速搭建pipeline又不会因为大模型训练时间太长而浪费迭代周期。肺炎分类这个任务ResNet50 迁移学习 合理的数据增强已经完全够用了。本文还有配套的精品资源点击获取

相关新闻