Skip to content

全卷积网络

一、什么是全卷积网络

之前我们学的语义分割是对图像里的每个像素进行分类,也就是给每个像素都打上类别标签,比如区分出图像里的狗、猫、背景这些区域。全卷积网络(FCN)就是用来做这个的,它是一种专门用于语义分割的卷积神经网络

和我们之前学的图像分类的卷积神经网络不一样,图像分类的网络最后会有全连接层,输出的是整个图像的类别;而全卷积网络没有全连接层,它的输出和输入图像的尺寸一样,每个像素都对应一个类别预测,也就是输出的通道数等于类别个数,每个通道对应一个类别的预测。

二、全卷积网络的构造

全卷积网络的构造 全卷积网络的构造很简单,主要分三步:

  1. 用卷积神经网络抽取图像特征:我们可以用预训练好的卷积神经网络,比如 ResNet-18,来提取图像的特征。不过要把预训练网络里的全局平均汇聚层和全连接层去掉,因为我们不需要这些,我们需要的是特征图。
  2. 用 1×1 卷积层转换通道数:把特征图的通道数转换成我们需要的类别个数,比如 Pascal VOC2012 数据集有 21 个类别,就把通道数转换成 21。
  3. 用转置卷积层恢复图像尺寸:因为卷积神经网络抽取特征的时候,会把图像的尺寸缩小,比如 ResNet-18 会把图像的尺寸缩小到原来的 1/32,所以我们需要用转置卷积层把特征图的尺寸恢复到和输入图像一样大,这样输出的每个像素都对应输入图像的一个像素。

简单来说,全卷积网络就是先把图像缩小提取特征,然后把通道数转换成类别数,最后再把图像放大回原来的尺寸,这样每个像素都有一个类别预测。

代码实现(PyTorch 版)

我们可以用 PyTorch 来实现全卷积网络:

python
import torch
import torchvision
from d2l import torch as d2l

# 加载预训练的ResNet-18模型
pretrained_net = torchvision.models.resnet18(pretrained=True)
# 去掉最后的全局平均汇聚层和全连接层,只保留前面的卷积层
net = torch.nn.Sequential(*list(pretrained_net.children())[:-2])

# 测试一下,输入一个320×480的图像,看看输出的尺寸
X = torch.rand(size=(1, 3, 320, 480))
print(net(X).shape)  # 输出是(1, 512, 10, 15),也就是尺寸缩小到了原来的1/32

# 添加1×1卷积层,把通道数转换成21(Pascal VOC2012的类别数)
num_classes = 21
net.add_module('conv1x1', torch.nn.Conv2d(512, num_classes, kernel_size=1))
# 添加转置卷积层,把尺寸恢复到原来的320×480
# 这里用步幅为32,卷积核大小为64,填充为16,这样就能把10×15的特征图放大到320×480
net.add_module('transpose_conv', torch.nn.ConvTranspose2d(num_classes, num_classes,
                                                         kernel_size=64, padding=16, stride=32))

三、转置卷积层的初始化

在全卷积网络里,我们通常用双线性插值来初始化转置卷积层,这样可以让放大的图像更平滑,不会出现锯齿。双线性插值是一种常用的上采样方法,它会根据周围的像素值来计算放大后像素的值,让图像看起来更自然。

我们可以自己实现双线性插值的卷积核,然后用它来初始化转置卷积层:

python
def bilinear_kernel(in_channels, out_channels, kernel_size):
    # 实现双线性插值的卷积核
    factor = (kernel_size + 1) // 2
    if kernel_size % 2 == 1:
        center = factor - 1
    else:
        center = factor - 0.5
    og = (torch.arange(kernel_size).reshape(-1, 1),
          torch.arange(kernel_size).reshape(1, -1))
    filt = (1 - torch.abs(og[0] - center) / factor) * \
           (1 - torch.abs(og[1] - center) / factor)
    weight = torch.zeros((in_channels, out_channels, kernel_size, kernel_size))
    weight[range(in_channels), range(out_channels), :, :] = filt
    return weight

# 初始化转置卷积层
conv_trans = torch.nn.ConvTranspose2d(num_classes, num_classes,
                                     kernel_size=64, padding=16, stride=32, bias=False)
conv_trans.weight.data = bilinear_kernel(num_classes, num_classes, 64)
# 把初始化好的转置卷积层替换到网络里
net[-1] = conv_trans

四、训练全卷积网络

训练全卷积网络和训练图像分类的网络差不多,只是损失函数的计算有点不一样。因为我们是对每个像素进行分类,所以损失函数要计算每个像素的损失,然后求平均。

加载数据集

我们用 Pascal VOC2012 语义分割数据集,这个数据集里的每个图像都有对应的标签图像,标签图像里的每个像素都有对应的类别。我们可以用 d2l 里的函数来加载这个数据集:

python
batch_size = 32
crop_size = (320, 480)
train_iter, test_iter = d2l.load_data_voc(batch_size, crop_size)

定义损失函数和优化器

python
def loss(inputs, targets):
    # 计算每个像素的交叉熵损失,然后求平均
    return torch.nn.functional.cross_entropy(inputs, targets, reduction='none').mean(1).mean(1)

# 定义优化器
lr, wd, num_epochs = 0.001, 1e-3, 5
trainer = torch.optim.SGD(net.parameters(), lr=lr, weight_decay=wd)

训练模型

python
# 训练模型
d2l.train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs, d2l.try_all_gpus())

训练完成后,我们可以看到模型的训练准确率和测试准确率,一般训练几个 epoch 后,测试准确率能达到 80% 左右。

五、预测和可视化

训练完成后,我们可以用模型来预测图像的语义分割结果,然后把结果可视化出来。

预测函数

python
def predict(img):
    # 对图像进行预处理
    X = test_iter.dataset.normalize_image(img).unsqueeze(0)
    # 预测
    pred = net(X.to(d2l.try_gpu())).argmax(dim=1)
    # 把预测结果转换成图像
    return pred.reshape(pred.shape[1], pred.shape[2])

def label2image(pred):
    # 把预测的类别转换成对应的颜色
    colormap = torch.tensor(d2l.VOC_COLORMAP, device=d2l.try_gpu())
    X = pred.long()
    return colormap[X, :]

可视化预测结果

我们可以加载一张测试图像,然后预测它的语义分割结果,把原始图像、预测结果和真实标签都显示出来:

python
# 加载测试图像
img = d2l.plt.imread('../img/catdog.jpg')
# 预测
pred = predict(img)
# 显示图像
d2l.plt.subplot(1, 3, 1)
d2l.plt.imshow(img)
d2l.plt.title('原始图像')
d2l.plt.subplot(1, 3, 2)
d2l.plt.imshow(label2image(pred).cpu())
d2l.plt.title('预测结果')
d2l.plt.subplot(1, 3, 3)
# 加载真实标签
label = d2l.plt.imread('../img/catdog_label.png')
d2l.plt.imshow(label)
d2l.plt.title('真实标签')
d2l.plt.show()

这样我们就能看到模型的预测结果,和真实标签对比一下,看看模型的预测效果怎么样。

六、小结

  1. 全卷积网络是专门用于语义分割的卷积神经网络,它的输出和输入图像的尺寸一样,每个像素都对应一个类别预测。

  2. 全卷积网络的构造分为三步:用卷积神经网络抽取特征,用 1×1 卷积层转换通道数,用转置卷积层恢复图像尺寸。

  3. 我们可以用双线性插值来初始化转置卷积层,让放大的图像更平滑。

  4. 训练全卷积网络和训练图像分类的网络差不多,只是损失函数要计算每个像素的损失。

  5. 预测的时候,我们可以把预测结果转换成颜色,可视化出来,看看模型的预测效果。

(注:文档部分内容可能由 AI 生成) 源地址

京ICP备2024093538号-1