72.全卷积神经网络(FCN)及代码实现

news2025/1/18 2:06:44

语义分割是对图像中的每个像素分类。 全卷积网络(fully convolutional network,FCN)采用卷积神经网络实现了从图像像素到像素类别的变换 。 与我们之前在图像分类或目标检测部分介绍的卷积神经网络不同,全卷积网络将中间层特征图的高和宽变换回输入图像的尺寸:这是通过在 转置卷积(transposed convolution)实现的。 因此,输出的类别预测与输入图像在像素级别上具有一一对应关系:通道维的输出即该位置对应像素的类别预测。

  • FCN是用深度神经网络来做语义分割的奠基性工作
  • 它用转置卷积层替换CNN最后的全连接层,从而可以实现每个像素的预测

在这里插入图片描述

1. 构造模型

%matplotlib inline
import torch
import torchvision
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l

全卷积网络先使用卷积神经网络抽取图像特征,然后通过 1×1 卷积层将通道数变换为类别个数,最后通过转置卷积层将特征图的高和宽变换为输入图像的尺寸。 因此,模型输出与输入图像的高和宽相同,且最终输出通道包含了该空间位置像素的类别预测。

下面,使用在ImageNet数据集上预训练的ResNet-18模型来提取图像特征,并将该网络记为pretrained_net。 ResNet-18模型的最后几层包括全局平均汇聚层和全连接层,然而全卷积网络中不需要它们。

pretrained_net = torchvision.models.resnet18(pretrained=True)
# 列出最后3层
list(pretrained_net.children())[-3:]

运行结果:

在这里插入图片描述

接下来,我们创建一个全卷积网络net。 它复制了ResNet-18中大部分的预训练层,除了最后的全局平均汇聚层和最接近输出的全连接层。

net = nn.Sequential(*list(pretrained_net.children())[:-2])

给定高度为320和宽度为480的输入,net的前向传播将输入的高和宽减小至原来的 1/32 ,即10和15,通道数从3变成512.

# 在语义分割中,输入图片是320 x 480的图片,因为是像素级别的,所以图片相对来说比较大
# 和ImageNet是不同的,在ImageNet中是224 x 224
X = torch.rand(size=(1, 3, 320, 480))
net(X).shape

运行结果:

在这里插入图片描述

ps:卷积神经网络最好的地方就是:不管输入图片的高宽如何都能进行计算,不像全连接,全连接一旦定了,输入的大小是不能变的,卷积没关系,因为要进行学习的权重kernel(卷积核)和输入大小无关,是和卷积层的定义是相关的

接下来使用 1×1 卷积层将输出通道数转换为Pascal VOC2012数据集的类数(21类)。最后需要(将特征图的高度和宽度增加32倍),从而将其变回输入图像的高和宽。

回想一下 卷积层输出形状的计算方法: 由于 (320−64+16×2+32)/32=10 且 (480−64+16×2+32)/32=15 ,我们构造一个步幅为 32 的转置卷积层,并将卷积核的高和宽设为 64 ,填充为 16 。 我们可以看到如果步幅为 𝑠 ,填充为 𝑠/2 (假设 𝑠/2 是整数)且卷积核的高和宽为 2𝑠 ,转置卷积核会将输入的高和宽分别放大 𝑠 倍。

num_classes = 21 # VOC一共21类
# final_conv 的输出是num_classes=21,选择21是因为这是把计算减小最快捷的方式
# 当然是可以选择21-512之间的数
# 1x1 卷积的重要作用之一就是减小参数从而减小计算量,而且图片信息也有一部分通过权重与偏置保留下来
net.add_module('final_conv', nn.Conv2d(512, num_classes, kernel_size=1))
# 转置卷积层:输入通道和输出通道都是21(分类数)
net.add_module('transpose_conv', nn.ConvTranspose2d(num_classes, num_classes,
                                    kernel_size=64, padding=16, stride=32))

2. 初始化转置卷积层

在图像处理中,我们有时需要将图像放大,即上采样(upsampling)双线性插值(bilinear interpolation) 是常用的上采样方法之一,它也经常用于初始化转置卷积层

为了解释双线性插值,假设给定输入图像,我们想要计算上采样输出图像上的每个像素。

  1. 将输出图像的坐标 (𝑥,𝑦) 映射到输入图像的坐标 (𝑥′,𝑦′) 上。 例如,根据输入与输出的尺寸之比来映射。 请注意,映射后的 𝑥′ 和 𝑦′ 是实数。
  2. 在输入图像上找到离坐标 (𝑥′,𝑦′) 最近的4个像素。
  3. 输出图像在坐标 (𝑥,𝑦) 上的像素依据输入图像上这4个像素及其与 (𝑥′,𝑦′) 的相对距离来计算。

双线性插值的上采样可以通过转置卷积层实现,内核由以下bilinear_kernel函数构造。 限于篇幅,我们只给出bilinear_kernel函数的实现,不讨论算法的原理。

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倍的转置卷积层,并将其卷积核用bilinear_kernel函数初始化。

# 构造出一个转置卷积层,会将输入的高和宽放大2倍
conv_trans = nn.ConvTranspose2d(3, 3, kernel_size=4, padding=1, stride=2,
                                bias=False)
# 用一个双线性的核bilinear_kernel出来的矩阵来初始化转置卷积层的权重
# 这样conv_trans就真的变成了双线性插值的操作了
conv_trans.weight.data.copy_(bilinear_kernel(3, 3, 4));

读取图像X,将上采样的结果记作Y。为了打印图像,我们需要调整通道维的位置。

# 把图片读进来,然后转成pytorch能用的形式
img = torchvision.transforms.ToTensor()(d2l.Image.open('drive/MyDrive/chapter13/img/catdog.jpg'))
X = img.unsqueeze(0)
# 对X做转置卷积的操作得到Y
Y = conv_trans(X)
out_img = Y[0].permute(1, 2, 0).detach()

可以看到,转置卷积层将图像的高和宽分别放大了2倍。 除了坐标刻度不同,双线性插值放大的图像和之前打印出的原图看上去没什么两样。

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);

运行结果:

在这里插入图片描述
由运行结果可以看出,输入图片是561x728的高宽,输出的图片把高宽加了一倍。

所以,假设构造了一个转置的卷积层,这个卷积层的作用是把高宽变大两倍,而且用了双线性插值的核来初始化它的权重,那么效果就真的变成了把图片放大。

双线性插值是一个很不错的用来初始化转置卷积的权重的方法。

全卷积网络用双线性插值的上采样初始化转置卷积层。对于 1×1 卷积层,我们使用Xavier初始化参数。

# 转置卷积初始化=转置卷积+双线性插值
W = bilinear_kernel(num_classes, num_classes, 64)
net.transpose_conv.weight.data.copy_(W);

3. 读取数据集

使用语义分割读取数据集。 指定随机裁剪的输出图像的形状为 320×480 :高和宽都可以被 32 整除。

# 批量大小为32,随机裁剪的输出图像的形状为 320×480
batch_size, crop_size = 32, (320, 480)
train_iter, test_iter = d2l.load_data_voc(batch_size, crop_size)

4. 训练

现在我们可以训练全卷积网络了。 这里的损失函数和准确率计算与图像分类中的并没有本质上的不同,因为我们使用转置卷积层的通道来预测像素的类别,所以需要在损失计算中指定通道维。 此外,模型基于每个像素的预测类别是否正确来计算准确率。

def loss(inputs, targets):
  # 之前只有cross_entropy,因为以前是一个标量
  # 现在一张图片中所有的像素都要做预测,所以要对每个像素做均值
  # 因此现在损失是一个矩阵,因此要在高和宽两个维度做均值
  # 这样每张图片的损失就拿到一个值
    return F.cross_entropy(inputs, targets, reduction='none').mean(1).mean(1)

num_epochs, lr, wd, devices = 5, 0.001, 1e-3, d2l.try_all_gpus()
trainer = torch.optim.SGD(net.parameters(), lr=lr, weight_decay=wd)
d2l.train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs, devices)

运行结果如下,可以看出精度不算特别高,很有可能是边缘做得不够好;另外,精度上升比较缓慢,损失下降明显,是因为一开始很容易把背景以及中间部分分好,边缘的损失一直在往下降,所以也许多迭代几个epoch,效果会更好:

在这里插入图片描述

5. 预测

在预测时,我们需要将输入图像在各个通道做标准化,并转成卷积神经网络所需要的四维输入格式。

def predict(img):
    # 用测试集的dataset的normalization,对RGB来做normalize
    X = test_iter.dataset.normalize_image(img).unsqueeze(0)
    # 把X copy到GPU上,在通道维度上做argmax,因为输出是
    # 每一个像素都有21个通道,在通道维度做argmax就会得到对每一个像素预测的标号
    pred = net(X.to(devices[0])).argmax(dim=1)
    # predict出来再reshape会得到和高宽一样的矩阵
    # 之前做图片分类是得到一个标号,就是一个单个的值
    # 现在在这里输出像素级别的值,就是一个和原图片高宽等同的矩阵
    return pred.reshape(pred.shape[1], pred.shape[2])

为了可视化预测的类别给每个像素,我们将预测类别映射回它们在数据集中的标注颜色。

# 给每个像素的预测值,把它变成img,把每个类别的RGB值做成tensor,
# 预测值做成index,最后去colormap中找到对应的预测的RGB值
def label2image(pred):
    colormap = torch.tensor(d2l.VOC_COLORMAP, device=devices[0])
    X = pred.long() # index
    return colormap[X, :]

测试数据集中的图像大小和形状各异。 由于模型使用了步幅为32的转置卷积层,因此当输入图像的高或宽无法被32整除时,转置卷积层输出的高或宽会与输入图像的尺寸有偏差。 为了解决这个问题,我们可以在图像中截取多块高和宽为32的整数倍的矩形区域,并分别对这些区域中的像素做前向传播。 请注意,这些区域的并集需要完整覆盖输入图像。 当一个像素被多个区域所覆盖时,它在不同区域前向传播中转置卷积层输出的平均值可以作为softmax运算的输入,从而预测类别。

为简单起见,我们只读取几张较大的测试图像,并从图像的左上角开始截取形状为 320×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, scale=2);

运行结果如下,第一行是原始图片,第二行是预测,第三行是标号,可以看到总的来看,还行,类别没有搞错,但是边缘没弄好,这就是上面的大概87%的精度。要改进的话,可以继续增大epoch,然后把网络参数,超参数调好一些:

在这里插入图片描述

6. Q&A

Q1:请问网络中间1x1卷积,将7x7x512压缩到7x7xclass_num,这样原来512通道的信息,被强制压缩到class_num,不会造成很大的信息损失吗?

A1: 在精度上会有比较大的损失,这里是从速度上考虑而设置的。可以把1x1的通道数设置得高一点会好一点

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/161941.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【正点原子FPGA连载】 第十九章 LED灯闪烁实验 摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0

1)实验平台:正点原子MPSoC开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id692450874670 3)全套实验源码手册视频下载地址: http://www.openedv.com/thread-340252-1-1.html 第十九章 LED灯闪…

聊聊Go与TLS 1.3

除了一些综述类文章和译文,我的文章选题多来源于实际工作和学习中遇到的问题。这次我们来聊聊近期遇到一个问题:如何加快基于TLS安全通信的海量连接的建连速度?TLS(Transport Layer Security)传输安全层的下面是TCP层,我们首先可能会想到的是…

【魅力开源】第4集:今天不讲ODOO,今天讲“中小企业的信息化如何做”

文章目录前言一、中小企业很重要二、企业全要素三、信息化逐步全面覆盖1. 信息化落地路径2. 消除信息孤岛是信息化的关键环节3. 中小企业信息化正在从产品市场向服务市场转变最后前言 在软件公司打补丁,我并不快乐 看到中小企业挣扎现状,让我痛苦 能够帮…

2023年最新黑马程序员Java微服务项目--学成在线

正式上线Java微服务项目《学成在线》 项目对程序员的重要性 不用播妞多说了吧 更重要的是 这次是完整!实战!企业级!项目! 划重点:全新发布!正式上线! 《学成在线》项目以在线教育业务为基础…

Spring AOP【用户登陆统一验证功能】

Spring AOP【用户登陆统一验证功能】🍎一. 用户登陆统一验证功能🍒1.1 用户登录验证的几种方法🍒1.2 创建前端页面🍒1.3 创建登陆方法和欢迎进入方法🍒1.4 自定义一个拦截器🍒1.5 验证拦截功能&#x1f349…

API--应用层之间的应用程序接口

API的前言互联网的应用特点是具有开放式的业务体系结构之一。关键的技术就是网络控制与应用层之间的应用程序接口--API。通过API接口很多问题便水到渠成,迎刃而解 。API到底是一种什么技术呢具有开放式的业务体系结构将是下一代网络的重要特征之一。其中&#xff0c…

干货 | APP和小程序在开发有什么区别?

随着互联网的不断进步,移动终端在生活中的应用也越来越多。 据工信部数据显示,截至2022年11月底,国内市场监测到的APP数量为272万款,其中App Store(中国区)的APP数量为136万款,本土第三方应用商店(主要是安…

数据库连接池监控的另类方案

如果这篇对你有帮助,还请麻烦转发。谢谢。数据库的连接池的监控的重要性假如,我们在公有云上存在一个数据库数据库实例X。公有云配置中已经说明X所支持的最大连接数是1000。如果数据库X的实际连接数达到了1000,那么,新的连接就无法…

输入的文本就能演奏一段爵士乐? #Riffusion

riff diffusion 是 stable diffusion 的微调模型,以生成频谱图图像来转换音乐。能产生更精准的声音模型叫:Riffusion。它能对音频进行剪辑处理,或者是无限地修改提示符。Riffusion图源:riffusion 官网频谱图频谱图是音频声波的视…

【超分综述】

A comprehensive review on deep learning based remote sensing image super-resolution methods (基于深度学习的遥感图像超分辨率方法综述) 卫星图像是地球科学领域各种应用的重要地理信息源。然而,由于光学和传感器技术的局限性以及传感器和设备更新的高成本&…

迅为i.MX8M Mini开发板debug调试方法(Qir trl RIL驱动不工作)

可能导致 Quectel RIL 操作失败的原因有很多。一些常见的原因如下所示,用于故障排除。 1 输入以下命令用于检查 ril daemon 的状态。如果未返回任何值,或者返回了 Stopped 或 Restarting 等 值而不是 Running,则表明 RIL 守护进程未运行。 ge…

时序预测 | Python实现Attention-TCN注意力机制时间卷积神经网络的多元时间序列预测

时序预测 | Python实现Attention-TCN注意力机制时间卷积神经网络的多元时间序列预测 目录 时序预测 | Python实现Attention-TCN注意力机制时间卷积神经网络的多元时间序列预测预测效果基本介绍环境配置程序设计模型效果参考资料预测效果 基本介绍 使用时间注意卷积神经网络进行…

2023版大数据学习路线图(适合自学)

随着信息产业的迅猛发展,大数据应用逐渐落地,行业人才需求量逐年扩大。大数据成为目前最具前景的高薪行业之一,大数据分析工程师、大数据开发工程师等大数据人才也成为市场紧缺型人才,薪资一涨再涨。很多人想要加入到大数据开发行…

学会这几个方法,帮你轻松完成工作

方法一:分节 分节有比分页更体贴入微的分节功能。 “节”在word中是很重要的,它代表着文档中的标记。 分页只是视觉上产生了一个新页,分节让Word在内容上建立一个个不同的区域。 操作方法: 鼠标光标定位在需要分节的文档位置…

Java-Jstack-生产问题的排查死锁/泄露/cpu负载

文章目录 排查死锁jdk自带的jstack排查死锁jdk自带的jconsole排查死锁排查CPU爆满内存溢出、泄漏排查内存溢出定位 - 内存泄漏内存溢出实战排查死锁 首先如果是本地开发环境可以通过JVisualVM查看是否有长时间休眠的线程 注意:正式部署版本不会用JVisualVM,会留下漏洞jdk自带…

第一天总结之项目的搭建

第一天总结之项目的搭建: Mvc框架的搭建 1、创建一个javaWeb项目 创建项目 添加web支持 在Web-INF下创建 classes和lib文件夹 重构项目 配置tomcat 2、搭建 Mvc框架 在src下创建 com.edu文件夹 在其下分别创建controller dao entity filter service utils文件夹…

Clion配置openCV开发环境(Clion+MinGW+CMake+openCV)

所需资源 系统 win11(X64)MinGW-W64 GCC-8.1.0 x86_64-8.1.0-release-posix-seh.7z MinGW作用是opencv需要用到支持多线程(posix版本)的C编译环境。Clion-2022.3.1.exeCMake-3.25.1-windows-x86_64.msiopenCV4.6.0 安装步骤 解压ming.7z,安…

node.js全栈项目

一、项目介绍本项目适合作为一个课程设计或者毕业设计,最终实现了一个完整的博客系统,包括用户的登录、注册,图片上传,文章的发布、富文本编辑器、删除、编辑、修改、列表展示,评论的发布、删除、列表展示,…

K2P padavan固件下宽带与IPTV融合

一、我的需求 坐标江苏小城,原来手机用的99的套餐,可以免费带一个副卡,现在升级了电信129的5G融合套餐,送了一个iTV(一个月10元),但是副卡收费10元,哎,算来算去都没有运营…

【软考】系统集成项目管理工程师(十三)项目干系人管理

一、项目干系人管理基础二、干系人管理子过程1. 识别干系人2. 规划干系人管理3. 管理干系人4. 控制干系人参与一、项目干系人管理基础 项目干系人管理是指对项目干系人需求、希望和期望的识别,并通过沟通上的管理来满足 其需要、解决其问题的过程。 每个项目都有干系人,他们…