ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

动手学深度学习(D2L)全卷积网络 FCN 实战:从图像分类到逐像素语义分割

动手学深度学习(D2L)全卷积网络 FCN 实战:从图像分类到逐像素语义分割 人工智能深度学习机器学习教程【免费下载链接】d2l-zh《动手学深度学习》面向中文读者、能运行、可讨论。中英文版被70多个国家的500多所大学用于教学。项目地址https://gitcode.com/GitHub_Trending/d2/d2l-zh点击查看免费下载全卷积网络Fully Convolutional NetworkFCN是语义分割领域的基础模型它将卷积神经网络从整图分类拓展为逐像素分类。本文以《动手学深度学习》d2l-zh 仓库 chapter_computer-vision/fcn.md为骨架结合仓库 d2l/torch.py 等源码完整讲解 FCN 的模型构造、双线性插值初始化、Pascal VOC2012 数据集加载、训练与预测可视化的全流程读者学完后可以独立在 PyTorch以及 MXNet、PaddlePaddle环境下复现并扩展一个可运行的语义分割模型。什么是全卷积网络从整图分类到逐像素预测在 语义分割和数据集 一节中我们知道语义分割的任务是对图像中的每个像素做类别分类。全卷积网络FCN采用卷积神经网络实现了从图像像素到像素类别的变换。与我们之前在图像分类或目标检测部分介绍的卷积神经网络不同全卷积网络需要将中间层特征图的高和宽变换回输入图像的尺寸。这是通过转置卷积transposed convolution实现的其原理在 转置卷积 一节中已有详细推导。由此FCN 输出的类别预测与输入图像在像素级别上具有一一对应关系通道维的输出即该位置对应像素的类别预测。FCN 最基本的模型设计包含三个环节见下图先使用卷积神经网络CNN抽取图像特征通过 $1\times 1$ 卷积层将特征通道数变换为类别个数最后通过转置卷积层将特征图的高和宽变换回输入图像的尺寸。最终模型的输出与输入图像的高和宽相同且输出通道包含了该空间位置像素的类别预测——这正是全卷积的含义网络中不再有全连接层从头到尾都是卷积运算。构造模型复用 ImageNet 预训练 ResNet-18 提取特征FCN 的特征提取部分直接复用预训练的分类网络。在 fcn.md 中我们使用在 ImageNet 数据集上预训练的 ResNet-18 模型来提取图像特征并将该网络记为pretrained_netimport torch import torchvision from torch import nn from torch.nn import functional as F pretrained_net torchvision.models.resnet18(pretrainedTrue) list(pretrained_net.children())[-3:]ResNet-18 模型的最后几层包括全局平均汇聚层和全连接层这两类层都是为整图分类服务的全卷积网络中不需要它们。因此我们创建一个新的网络net复制 ResNet-18 中大部分的预训练层但去掉最后的全局平均汇聚层和最接近输出的全连接层net nn.Sequential(*list(pretrained_net.children())[:-2])验证一下空间尺寸的变化给定高度为 320、宽度为 480 的输入net的前向传播将输入的高和宽减小至原来的 $1/32$即 10 和 15X torch.rand(size(1, 3, 320, 480)) net(X).shape # torch.Size([1, 512, 10, 15])用 1×1 卷积与转置卷积恢复像素级输出特征图被压缩到原尺寸的 $1/32$ 后需要两步操作才能得到与原图同尺寸的逐像素预测通道数变换为类别个数使用 $1\times 1$ 卷积层把 512 通道的深层特征映射为 21 通道Pascal VOC2012 数据集的类别数空间尺寸放大 32 倍用步幅为 32 的转置卷积把 $10\times 15$ 的特征图放大回 $320\times 480$。转置卷积参数为什么是 kernel64、padding16、stride32参考 转置卷积 中关于填充与步幅的规则与常规卷积不同转置卷积的填充作用于输出步幅作用于中间结果。结合 填充和步幅 中卷积层输出形状的计算方法可以验证参数选取$$(320 - 64 16 \times 2 32) / 32 10,\qquad (480 - 64 16 \times 2 32) / 32 15$$因此我们构造一个步幅为 32 的转置卷积层卷积核高和宽设为 64、填充设为 16。更一般地如果步幅为 $s$、填充为 $s/2$假设 $s/2$ 是整数且卷积核的高和宽为 $2s$转置卷积层会把输入的高和宽分别放大 $s$ 倍。这里 $s32$$s/216$$2s64$恰好满足上述规则。num_classes 21 net.add_module(final_conv, nn.Conv2d(512, num_classes, kernel_size1)) net.add_module(transpose_conv, nn.ConvTranspose2d(num_classes, num_classes, kernel_size64, padding16, stride32))初始化转置卷积层双线性插值上采样在图像处理中我们有时需要将图像放大即上采样upsampling。双线性插值bilinear interpolation是常用的上采样方法之一它也经常用于初始化转置卷积层的卷积核使网络在训练开始时就具备合理的上采样先验。双线性插值的三步计算假设给定输入图像要计算上采样输出图像上的每个像素将输出图像的坐标 $(x, y)$ 映射到输入图像的坐标 $(x, y)$ 上例如根据输入与输出的尺寸之比来映射映射后的 $x$ 和 $y$ 是实数在输入图像上找到离坐标 $(x, y)$ 最近的 4 个像素输出图像在坐标 $(x, y)$ 上的像素依据输入图像上这 4 个像素及其与 $(x, y)$ 的相对距离来计算。双线性插值的上采样可以通过转置卷积层实现卷积核由bilinear_kernel函数构造PyTorch 实现如下MXNet 与 PaddlePaddle 版本见原文档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上采样实验用转置卷积放大图像 2 倍先用一个小实验验证转置卷积 双线性上采样构造一个将输入高和宽放大 2 倍的转置卷积层kernel_size4、padding1、stride2并将其卷积核用bilinear_kernel初始化conv_trans nn.ConvTranspose2d(3, 3, kernel_size4, padding1, stride2, biasFalse) conv_trans.weight.data.copy_(bilinear_kernel(3, 3, 4))读取仓库中的测试图片 img/catdog.jpg做前向传播并打印输入、输出图像img torchvision.transforms.ToTensor()(d2l.Image.open(img/catdog.jpg)) X img.unsqueeze(0) Y conv_trans(X) out_img Y[0].permute(1, 2, 0).detach() d2l.set_figsize() print(input image shape:, img.permute(1, 2, 0).shape) d2l.plt.imshow(img.permute(1, 2, 0)) print(output image shape:, out_img.shape) d2l.plt.imshow(out_img)可以看到转置卷积层将图像的高和宽分别放大了 2 倍除了坐标刻度不同双线性插值放大的图像和原图看上去没什么两样。初始化整个 FCN回到全卷积网络用双线性插值的上采样初始化转置卷积层对于 $1\times 1$ 卷积层使用 Xavier 初始化参数。W bilinear_kernel(num_classes, num_classes, 64) net.transpose_conv.weight.data.copy_(W)PaddlePaddle 版本使用net.transpose_conv.weight.set_value(W)MXNet 版本则通过init.Constant(W)与init.Xavier()完成同样的初始化见原文档。读取 Pascal VOC2012 数据集FCN 在 Pascal VOC2012 语义分割数据集上训练该数据集的加载函数封装在 d2l/torch.py 中直接调用d2l.load_data_voc即可batch_size, crop_size 32, (320, 480) train_iter, test_iter d2l.load_data_voc(batch_size, crop_size)这里指定随机裁剪的输出图像形状为 $320 \times 480$高和宽都可以被 32 整除与步幅 32 的转置卷积严格匹配保证前向传播后特征图能无损地恢复为输入尺寸。从源码看数据读取链路包含以下几个关键组件均定义于 d2l/torch.py数据集注册d2l.DATA_HUB[voc2012]指向VOCtrainval_11-May-2012.tard2l.download_extract(voc2012, VOCdevkit/VOC2012)负责下载并解压到data/VOCdevkit/VOC2012类别与配色VOC_CLASSES定义了背景background加 20 个目标类别共21 类见 d2l/torch.pyVOC_COLORMAP为每个类别定义了 RGB 标注颜色见 d2l/torch.py标签图像中颜色相同的像素属于同一个语义类别VOCSegDataset自定义Dataset内部用voc_colormap2label把 RGB 标签映射为类别索引用voc_rand_crop对输入图像与标签做同步随机裁剪保证裁剪区域一致见 d2l/torch.py。训练全卷积网络FCN 的损失函数和准确率计算与图像分类并没有本质上的不同但有两个细节需要特别注意由于使用转置卷积层的通道维来预测像素类别损失计算中需要指定通道维。PyTorch 版本的loss对每个样本求各空间位置交叉熵后取均值def loss(inputs, targets): return F.cross_entropy(inputs, targets, reductionnone).mean(1).mean(1)PaddlePaddle 版本需要先把张量转置为(N, H, W, C)布局再计算F.cross_entropy(inputs.transpose([0, 2, 3, 1]), targets, ...)见原文档对应#tab paddle代码块。模型基于每个像素的预测类别是否正确来计算准确率而非整图的分类正确率。训练超参数与优化器如下PyTorch 版本MXNet 使用gluon.loss.SoftmaxCrossEntropyLoss(axis1)PaddlePaddle 使用paddle.optimizer.SGD均可参考原文档num_epochs, lr, wd, devices 5, 0.001, 1e-3, d2l.try_all_gpus() trainer torch.optim.SGD(net.parameters(), lrlr, weight_decaywd) d2l.train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs, devices)d2l.train_ch13是仓库封装的多 GPU 训练循环见 d2l/torch.py它把网络包装进nn.DataParallel并部署到try_all_gpus()返回的设备列表每个 epoch 累计训练损失、训练准确率与像素总数并在每轮结束后用evaluate_accuracy_gpu评估测试集准确率。本实验在 5 个 epoch 内即可观察到训练损失的下降与逐像素准确率的提升。预测与可视化把类别索引映射回标注颜色训练完成后即可对测试图像做逐像素预测。预测时需要先把输入图像在各个通道做标准化并转成卷积神经网络所需的四维输入格式def predict(img): X test_iter.dataset.normalize_image(img).unsqueeze(0) pred net(X.to(devices[0])).argmax(dim1) return pred.reshape(pred.shape[1], pred.shape[2])其中normalize_image使用 ImageNet 预训练统计量做通道标准化mean[0.485, 0.456, 0.406]std[0.229, 0.224, 0.225]见 d2l/torch.py与预训练 ResNet-18 的输入约定保持一致。为了可视化预测的类别需要将预测类别索引映射回它们在数据集中的标注颜色def label2image(pred): colormap torch.tensor(d2l.VOC_COLORMAP, devicedevices[0]) X pred.long() return colormap[X, :]处理无法被 32 整除的大图测试数据集中的图像大小和形状各异。由于模型使用了步幅为 32 的转置卷积层当输入图像的高或宽无法被 32 整除时转置卷积层输出的高或宽会与输入图像的尺寸有偏差。解决办法是在图像中截取多块高和宽为 32 的整数倍的矩形区域分别对这些区域中的像素做前向传播这些区域的并集需完整覆盖输入图像当一个像素被多个区域覆盖时取它在不同区域前向传播中转置卷积层输出的平均值作为softmax运算的输入从而预测类别。为简单起见示例只读取几张较大的测试图像并从图像的左上角开始截取形状为 $320 \times 480$ 的区域用于预测然后逐一打印截取的区域、预测结果和标注的类别voc_dir d2l.download_extract(voc2012, VOCdevkit/VOC2012) test_images, test_labels d2l.read_voc_images(voc_dir, False) n, imgs 4, [] for i in range(n): crop_rect (0, 0, 320, 480) X torchvision.transforms.functional.crop(test_images[i], *crop_rect) pred label2image(predict(X)) imgs [X.permute(1, 2, 0), pred.cpu(), torchvision.transforms.functional.crop( test_labels[i], *crop_rect).permute(1, 2, 0)] d2l.show_images(imgs[::3] imgs[1::3] imgs[2::3], 3, n, scale2)输出中每一列依次为原图 → FCN 预测结果 → 人工标注的真值标签。可以看到FCN 对猫、狗等目标主体区域的逐像素预测与人工标注相当接近主要差异集中在目标边缘与细小结构上。小结全卷积网络先使用卷积神经网络抽取图像特征然后通过 $1\times 1$ 卷积层将通道数变换为类别个数最后通过转置卷积层将特征图的高和宽变换为输入图像的尺寸在全卷积网络中我们可以将转置卷积层初始化为双线性插值的上采样。练习与进一步探索替换初始化如果将转置卷积层改用 Xavier 随机初始化训练结果会有什么变化试着对比逐像素准确率与收敛速度。调节超参数调整学习率、批大小、epoch 数等超参数能进一步提升模型的精度吗全图预测预测测试图像中所有像素的类别注意处理高或宽无法被 32 整除的大图可参考上文的多区域前向传播取平均策略。复现论文结构最初的 FCN 论文还使用了某些卷积神经网络中间层的输出跳层连接融合不同分辨率的特征。尝试基于本仓库的 fcn.md 与 d2l/torch.py 实现这个想法观察对边缘细节的改善。如需在本地运行本文代码可先参照 安装指南 配置d2l包与对应深度学习框架PyTorch / MXNet / PaddlePaddle 三套后端均已支持#tab变体代码见 fcn.md 原文。赞分享人工智能深度学习机器学习教程【免费下载链接】d2l-zh《动手学深度学习》面向中文读者、能运行、可讨论。中英文版被70多个国家的500多所大学用于教学。项目地址https://gitcode.com/GitHub_Trending/d2/d2l-zh点击查看免费下载相关推荐Nextcloud AIO 完整部署指南30 分钟拉起一套自建网盘Nextcloud AIO 完整部署指南30 分钟拉起一套自建网盘 跟着本文走完后你用一行 docker compose f compose.yaml up人工智能深度学习机器学习教程Jetson Inference 语义分割实战使用 segNet 对图像进行逐像素分类FCN-ResNet18 全卷积网络Jetson Inference 语义分割实战使用 segNet 对图像进行逐像素分类FCN ResNet18 全卷积网络 导读 本篇指南聚焦于 jets人工智能计算机视觉深度学习微调《动手学深度学习》图像卷积实战互相关运算、卷积层与卷积核学习《动手学深度学习》图像卷积实战互相关运算、卷积层与卷积核学习 卷积神经网络CNN之所以能以远少于全连接网络的参数高效处理图像根源就在于卷积层所执行的核心人工智能深度学习机器学习教程上一篇Google Map React 错误处理终极指南优雅降级和用户友好的错误提示下一篇密钥高可用Tiny-Universe保证服务连续性创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表