第一次接触语义分割时,最让我困惑的就是:为什么一个原本用来分类的VGG16网络,经过简单改造就能完成像素级的预测任务?这要从全卷积化(Convolutionalization)这个关键步骤说起。
传统的VGG16网络在最后三层使用了全连接层,这种设计有个致命缺陷——输入图片的尺寸必须固定为224x224。我在实际项目中就遇到过这个问题:当尝试处理不同尺寸的医疗影像时,系统总会报出维度不匹配的错误。而全卷积化改造完美解决了这个痛点。
具体操作其实很巧妙:把第一个全连接层(FC6)转换成7x7的卷积核,第二个全连接层(FC7)转换成1x1卷积核。我做过参数量的对比计算:
这个改造带来了三个关键优势:
有个有趣的实验现象:当输入大尺寸图片时,最后的特征图会呈现类似热力图的效果。比如输入一张500x500的猫图片,经过改造后的网络会在猫的主体区域产生高激活值。这个特性后来被广泛用于弱监督语义分割。
FCN-32s是最基础的版本,我把它比作"直筒电梯"——特征图经过32倍下采样后,直接通过转置卷积上采样32倍恢复原尺寸。这种设计简单粗暴,但存在明显的细节丢失问题。
在实际训练时发现个细节:原论文将第一个卷积层的padding设为100,这会导致两个问题:
经过多次实验验证,其实padding=3就足够处理绝大多数场景。这里有个小技巧:使用PyTorch实现时可以这样设置:
python复制# 更合理的padding设置
self.conv1 = nn.Conv2d(3, 64, kernel_size=3, padding=1)
FCN-16s引入了第一个重要创新——特征融合。它将pool4层的特征(下采样16倍)与主分支的特征进行相加。这种设计让网络能同时利用深层语义信息和浅层细节特征。
我在Cityscapes数据集上做过对比实验:
提升主要来自边缘细节的改善。实现时需要注意:两个特征图相加前要确保通道数一致,通常用1x1卷积进行降维:
python复制self.score_pool4 = nn.Conv2d(512, num_classes, 1) # 通道数调整
FCN-8s进一步融合了pool3层的特征(下采样8倍),形成了三级特征金字塔。这种结构对小型物体特别友好,我在处理遥感图像中的小型建筑物时,FCN-8s比FCN-16s的边界准确率提升了约15%。
训练时有个实用技巧:可以先训练FCN-32s,然后加载其权重作为FCN-16s的初始化,最后再微调FCN-8s。这种渐进式训练策略能显著加快收敛速度。
原论文采用双线性插值初始化转置卷积核,这个选择背后有深刻的数学考量。双线性插值可以看作是一种特殊的卷积操作,其卷积核权重固定为:
code复制[0.25 0.50 0.25
0.50 1.00 0.50
0.25 0.50 0.25]
这种初始化方式有两个优势:
在实际项目中,我发现当上采样倍数超过8时,直接使用框架内置的双线性插值往往比可学习的转置卷积效果更好。
转置卷积虽然灵活,但容易产生棋盘效应(checkerboard artifacts)。通过实验发现这些问题主要源于:
解决方案是:
python复制# 更稳健的转置卷积实现
self.upsample = nn.Sequential(
nn.ConvTranspose2d(in_ch, out_ch, kernel_size=4, stride=2, padding=1),
nn.ReLU(inplace=True)
)
FCN开创的特征融合思想影响深远。现代语义分割网络普遍采用类似策略,但有了更多创新:
在工业级应用中,我发现结合ASPP(Atrous Spatial Pyramid Pooling)模块的改进版FCN-8s,能在保持实时性的前提下达到SOTA效果。一个典型的推理速度对比:
经过多个语义分割项目的锤炼,总结出几条实用经验:
有个容易忽视的细节:当处理高分辨率图像时,建议将batch size设为1,改用累计梯度的方式进行训练。这样可以避免显存溢出,同时保持训练稳定性。