FCN_8S语义分割实战:ResNet50/101双主干与辅助分支调优指南

发布时间:2026/9/30 6:09:49
FCN_8S语义分割实战:ResNet50/101双主干与辅助分支调优指南 简介这份资源面向深度学习语义分割方向的研究者与开发者提供基于全卷积网络FCN-8S架构的完整实现与优化方案重点解决多尺度特征融合与主干网络选型问题。项目集成ResNet50与ResNet101双主干网络并支持辅助分支AuxiliaryBranch的启用配置便于在精度与效率之间灵活权衡。压缩包共30个文件约180KB以16个Python脚本为核心涵盖模型定义、训练流程、指标评估与预测推理等模块另含txt说明、json配置、png可视化结果及md与docx文档目录结构清晰。已有105人学习下载。读者可获得端到端可运行的训练与推理代码、双主干切换与辅助分支配置方法、mIoU等评估脚本及配套说明文档适合希望快速复现FCN-8S并在此基础上开展图像分割实验的中高级开发者参考。1. 从一张街景图说起FCN_8S 这套双主干语义分割资源到底能干什么如果你手里有一批街景、遥感或工业质检图像想把每个像素都分到「路、车、人、背景」这类类别里那语义分割就是绕不开的活。这次拆的资源是一套基于全卷积网络 FCN_8S 架构的语义分割实现最实在的地方在于它同时给了 ResNet50 和 ResNet101 两个主干还带一个可开关的辅助分支 AuxiliaryBranch。很多人第一次接触 FCN 会卡在「8S 到底和 32S、16S 差在哪」这套代码把上采样倍率和跳级连接都写死在配置里改一个参数就能对比效果。它适合两类人一类是想跑通语义分割全流程的新手另一类是拿它当 baseline 去调主干和损失函数的熟手。下面我按「是什么、怎么跑、坑在哪」的顺序把这份资源拆开讲。2. FCN_8S 与双主干选型为什么是 ResNet50 和 ResNet1012.1 FCN_8S 的上采样逻辑与跳级连接FCN 的核心思路是把分类网络的全连接层换成卷积层让网络输出从「一个类别」变成「一张类别热力图」。但直接对最后一层做 32 倍上采样边缘会糊得没法看。FCN_8S 的做法是先对 pool4 的输出做 2 倍上采样和 pool3 的特征相加再对融合结果做 8 倍上采样回到原图尺寸。这里的「8S」指的就是最终上采样倍率为 8相比 32S 保留了更多浅层空间信息相比 16S 又多融合了一层。代码里这个融合过程通常写成两步先interp放大低分辨率特征再add对应层特征。我一般会重点看两个参数upsample_factor和skip_layers。前者决定输出分辨率后者决定融合哪几层。如果数据集里小目标多把skip_layers从[pool4]改成[pool4, pool3]往往能救回一批漏检。2.2 ResNet50 与 ResNet101 的取舍ResNet50 和 ResNet101 的差别主要在 block 数量50 层用[3,4,6,3]101 层用[3,4,23,3]。多出来的 17 个 block 全堆在第三阶段意味着 ResNet101 的感受野更大、语义信息更丰富但显存占用和推理时间也上去了。我实测过同一批 512×512 的图ResNet50 单卡 batch size 能到 8ResNet101 只能到 4 左右。选型上有个简单判断如果你的目标类别少、形状规整比如路面和天空ResNet50 足够如果类别细碎、边界复杂比如遥感里的建筑和植被交错ResNet101 的深层特征更稳。资源里两个主干都给了预训练权重加载接口换主干只需要改配置里的backbone字段不用动网络结构代码。2.3 AuxiliaryBranch 辅助分支的作用与启用条件AuxiliaryBranch 是从中间层引出一条侧分支单独算一次损失再按权重加到主损失上。它的作用类似深监督让浅层也直接收到梯度缓解梯度消失。启用后总损失一般写成# 主损失 辅助损失加权求和 main_loss criterion(main_out, target) aux_loss criterion(aux_out, target) total_loss main_loss aux_weight * aux_loss # aux_weight 常见取 0.4aux_weight这个参数别乱设。设成 1.0 会让辅助分支喧宾夺主主分支收敛反而变慢设成 0.1 又几乎没效果。我一般从 0.4 起步观察验证集 mIoU 再微调。另外辅助分支只在训练时生效推理阶段要记得关掉否则白白多算一遍。3. 把资源跑起来环境、数据与训练配置3.1 环境依赖与目录结构确认这套代码依赖 PyTorch 和 torchvision常见做法是建一个干净虚拟环境再装。我一般会先锁版本避免 torchvision 和 PyTorch 版本错位导致预训练权重加载失败。# 创建虚拟环境并安装依赖 python -m venv fcn_env source fcn_env/bin/activate # Windows 用 fcn_env\Scripts\activate pip install torch1.13.1 torchvision0.14.1 pip install numpy opencv-python tqdm tensorboard装完后先别急着训练跑一遍python -c import torch; print(torch.cuda.is_available())确认 GPU 可用。如果返回 False后面训练会慢到怀疑人生。目录上重点确认三个位置configs/放配置文件datasets/放数据加载脚本models/放主干和 FCN 头。资源里双主干是通过配置文件切换的别去改models/里的网络定义。3.2 语义分割数据集的标注格式与加载语义分割数据集和分类数据集最大的区别是标签也是一张图每个像素的值就是类别 id。常见做法是 PNG 单通道存储背景为 0类别从 1 开始。如果你手头是 VOC 格式标注在SegmentationClass/下如果是自定义数据建议统一转成「原图 单通道 mask」的配对结构。# 自定义 Dataset 核心部分 class SegDataset(Dataset): def __init__(self, img_dir, mask_dir, transformNone): self.imgs sorted(os.listdir(img_dir)) self.img_dir img_dir self.mask_dir mask_dir self.transform transform def __getitem__(self, idx): img cv2.imread(os.path.join(self.img_dir, self.imgs[idx])) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) mask cv2.imread(os.path.join(self.mask_dir, self.imgs[idx].replace(.jpg, .png)), 0) if self.transform: augmented self.transform(imageimg, maskmask) img, mask augmented[image], augmented[mask] return img, mask.long()这里mask.long()很关键交叉熵损失要求标签是 int64。如果忘了转训练时会报expected scalar type Long。另外 mask 的像素值必须连续比如只有 0、1、2 三类不能出现 255 这种未映射的值否则损失会直接 NaN。3.3 训练参数配置与双主干切换配置文件里几个参数决定训练能不能收敛lr、batch_size、backbone、use_aux。我一般先用小学习率 warmup 几百步再切到主学习率。ResNet50 主学习率可以设 1e-3ResNet101 因为更深建议降到 5e-4。# config.yaml 关键字段 backbone: resnet50 # 可选 resnet50 / resnet101 use_aux: true # 是否启用辅助分支 aux_weight: 0.4 lr: 0.001 batch_size: 8 epochs: 50 num_classes: 3切换主干只改backbone一行但要注意预训练权重路径也要跟着换。资源里权重加载是按主干名拼路径的如果你手动改了目录记得同步改pretrained_path。训练启动命令一般是python train.py --config configs/config.yaml日志会打到runs/下用 tensorboard 看 loss 曲线最直观。4. 避坑与排查训练不收敛、显存爆、mIoU 上不去4.1 损失变成 NaN现象训练几十步后 loss 突然变 nan之后再也降不下来。原因通常是 mask 里有非法类别值或者学习率太大导致梯度爆炸。解决先统计 mask 的唯一值确认都在[0, num_classes-1]范围内再把学习率降一个数量级加梯度裁剪torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0)。4.2 显存不足 OOM现象报CUDA out of memory尤其切到 ResNet101 后必现。原因主干越深特征图越多加上辅助分支又多一份中间激活。解决先把batch_size减半再开混合精度训练torch.cuda.amp还不行就裁小输入尺寸。我一般优先降 batch size因为改输入尺寸会影响 mIoU 对比。4.3 mIoU 卡在低位不涨现象loss 在降但验证集 mIoU 一直 0.3 左右。原因可能是类别极不平衡背景占了 90% 以上。解决在损失里加类别权重或者改用 Dice Loss 和交叉熵的组合。另外检查一下验证时的上采样是不是用了align_cornersFalse这个参数设错会让预测图和标签错位mIoU 直接掉一截。4.4 辅助分支开了反而更差现象use_aux: true后验证指标比不开还低。原因aux_weight太大辅助损失压过了主损失。解决把aux_weight从 0.4 降到 0.2 甚至 0.1观察几个 epoch。如果还是不行说明你的数据集浅层特征本身噪声大直接关掉辅助分支更稳。4.5 推理结果全是同一类现象预测图整张都是背景色。原因模型没学到东西或者推理时忘了把输出做 argmax。解决先确认训练 loss 确实降了再检查推理代码里有没有torch.argmax(out, dim1)。另外别忘了推理前调model.eval()否则 BN 层还在用 batch 统计量结果会飘。5. 进阶技巧用 mIoU 验证与主干对比的实操习惯跑通之后别急着换数据集先把验证流程做扎实。我习惯在训练脚本里加一个evaluate函数每个 epoch 结束算一次 mIoU而不是只看 loss。mIoU 的计算核心是混淆矩阵把预测和标签展平后累加。def compute_miou(pred, target, num_classes): pred torch.argmax(pred, dim1).view(-1) target target.view(-1) hist torch.zeros(num_classes, num_classes) for p, t in zip(pred, target): hist[t.long(), p.long()] 1 iou [] for i in range(num_classes): inter hist[i, i] union hist[i, :].sum() hist[:, i].sum() - inter iou.append((inter / (union 1e-6)).item()) return sum(iou) / len(iou)这段代码里1e-6是防止除零别省。算完 mIoU 后我会固定随机种子把 ResNet50 和 ResNet101 各跑三遍取平均避免单次波动误导选型。对比时重点看两类指标整体 mIoU 和小类别的 IoU。ResNet101 往往整体高一点但如果你的小类别样本少它未必比 ResNet50 好。还有一个容易忽略的点FCN_8S 的输出尺寸和输入尺寸不一定完全相等。如果输入是奇数尺寸上采样后可能差一个像素。我一般会在推理后加一次resize对齐原图或者干脆把输入统一裁成 32 的倍数。这个细节不处理算 mIoU 时标签和预测对不上指标会莫名其妙偏低。从那以后我每次换主干或改上采样倍率都强制先跑一遍小样本过拟合测试拿 4 张图训练 200 步看能不能把 loss 压到接近 0。这一步能过再上全量数据才不浪费时间。希望帮到你。本文还有配套的精品资源点击获取