更新
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2858f745",
|
||||
"metadata": {
|
||||
"origin_pos": 0
|
||||
},
|
||||
"source": [
|
||||
"# 计算机视觉\n",
|
||||
":label:`chap_cv`\n",
|
||||
"\n",
|
||||
"近年来,深度学习一直是提高计算机视觉系统性能的变革力量。\n",
|
||||
"无论是医疗诊断、自动驾驶,还是智能滤波器、摄像头监控,许多计算机视觉领域的应用都与我们当前和未来的生活密切相关。\n",
|
||||
"可以说,最先进的计算机视觉应用与深度学习几乎是不可分割的。\n",
|
||||
"有鉴于此,本章将重点介绍计算机视觉领域,并探讨最近在学术界和行业中具有影响力的方法和应用。\n",
|
||||
"\n",
|
||||
"在 :numref:`chap_cnn`和 :numref:`chap_modern_cnn`中,我们研究了计算机视觉中常用的各种卷积神经网络,并将它们应用到简单的图像分类任务中。\n",
|
||||
"本章开头,我们将介绍两种可以改进模型泛化的方法,即*图像增广*和*微调*,并将它们应用于图像分类。\n",
|
||||
"由于深度神经网络可以有效地表示多个层次的图像,因此这种分层表示已成功用于各种计算机视觉任务,例如*目标检测*(object detection)、*语义分割*(semantic segmentation)和*样式迁移*(style transfer)。\n",
|
||||
"秉承计算机视觉中利用分层表示的关键思想,我们将从物体检测的主要组件和技术开始,继而展示如何使用*完全卷积网络*对图像进行语义分割,然后我们将解释如何使用样式迁移技术来生成像本书封面一样的图像。\n",
|
||||
"最后在结束本章时,我们将本章和前几章的知识应用于两个流行的计算机视觉基准数据集。\n",
|
||||
"\n",
|
||||
":begin_tab:toc\n",
|
||||
" - [image-augmentation](image-augmentation.ipynb)\n",
|
||||
" - [fine-tuning](fine-tuning.ipynb)\n",
|
||||
" - [bounding-box](bounding-box.ipynb)\n",
|
||||
" - [anchor](anchor.ipynb)\n",
|
||||
" - [multiscale-object-detection](multiscale-object-detection.ipynb)\n",
|
||||
" - [object-detection-dataset](object-detection-dataset.ipynb)\n",
|
||||
" - [ssd](ssd.ipynb)\n",
|
||||
" - [rcnn](rcnn.ipynb)\n",
|
||||
" - [semantic-segmentation-and-dataset](semantic-segmentation-and-dataset.ipynb)\n",
|
||||
" - [transposed-conv](transposed-conv.ipynb)\n",
|
||||
" - [fcn](fcn.ipynb)\n",
|
||||
" - [neural-style](neural-style.ipynb)\n",
|
||||
" - [kaggle-cifar10](kaggle-cifar10.ipynb)\n",
|
||||
" - [kaggle-dog](kaggle-dog.ipynb)\n",
|
||||
":end_tab:\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"language_info": {
|
||||
"name": "python"
|
||||
},
|
||||
"required_libs": []
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,267 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "722f8846",
|
||||
"metadata": {
|
||||
"origin_pos": 0
|
||||
},
|
||||
"source": [
|
||||
"# 区域卷积神经网络(R-CNN)系列\n",
|
||||
":label:`sec_rcnn`\n",
|
||||
"\n",
|
||||
"除了 :numref:`sec_ssd`中描述的单发多框检测之外,\n",
|
||||
"区域卷积神经网络(region-based CNN或regions with CNN features,R-CNN) :cite:`Girshick.Donahue.Darrell.ea.2014`也是将深度模型应用于目标检测的开创性工作之一。\n",
|
||||
"本节将介绍R-CNN及其一系列改进方法:快速的R-CNN(Fast R-CNN) :cite:`Girshick.2015`、更快的R-CNN(Faster R-CNN) :cite:`Ren.He.Girshick.ea.2015`和掩码R-CNN(Mask R-CNN) :cite:`He.Gkioxari.Dollar.ea.2017`。\n",
|
||||
"限于篇幅,我们只着重介绍这些模型的设计思路。\n",
|
||||
"\n",
|
||||
"## R-CNN\n",
|
||||
"\n",
|
||||
"*R-CNN*首先从输入图像中选取若干(例如2000个)*提议区域*(如锚框也是一种选取方法),并标注它们的类别和边界框(如偏移量)。 :cite:`Girshick.Donahue.Darrell.ea.2014`然后,用卷积神经网络对每个提议区域进行前向传播以抽取其特征。\n",
|
||||
"接下来,我们用每个提议区域的特征来预测类别和边界框。\n",
|
||||
"\n",
|
||||
"\n",
|
||||
":label:`fig_r-cnn`\n",
|
||||
"\n",
|
||||
" :numref:`fig_r-cnn`展示了R-CNN模型。具体来说,R-CNN包括以下四个步骤:\n",
|
||||
"\n",
|
||||
"1. 对输入图像使用*选择性搜索*来选取多个高质量的提议区域 :cite:`Uijlings.Van-De-Sande.Gevers.ea.2013`。这些提议区域通常是在多个尺度下选取的,并具有不同的形状和大小。每个提议区域都将被标注类别和真实边界框;\n",
|
||||
"1. 选择一个预训练的卷积神经网络,并将其在输出层之前截断。将每个提议区域变形为网络需要的输入尺寸,并通过前向传播输出抽取的提议区域特征;\n",
|
||||
"1. 将每个提议区域的特征连同其标注的类别作为一个样本。训练多个支持向量机对目标分类,其中每个支持向量机用来判断样本是否属于某一个类别;\n",
|
||||
"1. 将每个提议区域的特征连同其标注的边界框作为一个样本,训练线性回归模型来预测真实边界框。\n",
|
||||
"\n",
|
||||
"尽管R-CNN模型通过预训练的卷积神经网络有效地抽取了图像特征,但它的速度很慢。\n",
|
||||
"想象一下,我们可能从一张图像中选出上千个提议区域,这需要上千次的卷积神经网络的前向传播来执行目标检测。\n",
|
||||
"这种庞大的计算量使得R-CNN在现实世界中难以被广泛应用。\n",
|
||||
"\n",
|
||||
"## Fast R-CNN\n",
|
||||
"\n",
|
||||
"R-CNN的主要性能瓶颈在于,对每个提议区域,卷积神经网络的前向传播是独立的,而没有共享计算。\n",
|
||||
"由于这些区域通常有重叠,独立的特征抽取会导致重复的计算。\n",
|
||||
"*Fast R-CNN* :cite:`Girshick.2015`对R-CNN的主要改进之一,是仅在整张图象上执行卷积神经网络的前向传播。\n",
|
||||
"\n",
|
||||
"\n",
|
||||
":label:`fig_fast_r-cnn`\n",
|
||||
"\n",
|
||||
" :numref:`fig_fast_r-cnn`中描述了Fast R-CNN模型。它的主要计算如下:\n",
|
||||
"\n",
|
||||
"1. 与R-CNN相比,Fast R-CNN用来提取特征的卷积神经网络的输入是整个图像,而不是各个提议区域。此外,这个网络通常会参与训练。设输入为一张图像,将卷积神经网络的输出的形状记为$1 \\times c \\times h_1 \\times w_1$;\n",
|
||||
"1. 假设选择性搜索生成了$n$个提议区域。这些形状各异的提议区域在卷积神经网络的输出上分别标出了形状各异的兴趣区域。然后,这些感兴趣的区域需要进一步抽取出形状相同的特征(比如指定高度$h_2$和宽度$w_2$),以便于连结后输出。为了实现这一目标,Fast R-CNN引入了*兴趣区域汇聚层*(RoI pooling):将卷积神经网络的输出和提议区域作为输入,输出连结后的各个提议区域抽取的特征,形状为$n \\times c \\times h_2 \\times w_2$;\n",
|
||||
"1. 通过全连接层将输出形状变换为$n \\times d$,其中超参数$d$取决于模型设计;\n",
|
||||
"1. 预测$n$个提议区域中每个区域的类别和边界框。更具体地说,在预测类别和边界框时,将全连接层的输出分别转换为形状为$n \\times q$($q$是类别的数量)的输出和形状为$n \\times 4$的输出。其中预测类别时使用softmax回归。\n",
|
||||
"\n",
|
||||
"在Fast R-CNN中提出的兴趣区域汇聚层与 :numref:`sec_pooling`中介绍的汇聚层有所不同。在汇聚层中,我们通过设置汇聚窗口、填充和步幅的大小来间接控制输出形状。而兴趣区域汇聚层对每个区域的输出形状是可以直接指定的。\n",
|
||||
"\n",
|
||||
"例如,指定每个区域输出的高和宽分别为$h_2$和$w_2$。\n",
|
||||
"对于任何形状为$h \\times w$的兴趣区域窗口,该窗口将被划分为$h_2 \\times w_2$子窗口网格,其中每个子窗口的大小约为$(h/h_2) \\times (w/w_2)$。\n",
|
||||
"在实践中,任何子窗口的高度和宽度都应向上取整,其中的最大元素作为该子窗口的输出。\n",
|
||||
"因此,兴趣区域汇聚层可从形状各异的兴趣区域中均抽取出形状相同的特征。\n",
|
||||
"\n",
|
||||
"作为说明性示例, :numref:`fig_roi`中提到,在$4 \\times 4$的输入中,我们选取了左上角$3\\times 3$的兴趣区域。\n",
|
||||
"对于该兴趣区域,我们通过$2\\times 2$的兴趣区域汇聚层得到一个$2\\times 2$的输出。\n",
|
||||
"请注意,四个划分后的子窗口中分别含有元素0、1、4、5(5最大);2、6(6最大);8、9(9最大);以及10。\n",
|
||||
"\n",
|
||||
"\n",
|
||||
":label:`fig_roi`\n",
|
||||
"\n",
|
||||
"下面,我们演示了兴趣区域汇聚层的计算方法。\n",
|
||||
"假设卷积神经网络抽取的特征`X`的高度和宽度都是4,且只有单通道。\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "52b05409",
|
||||
"metadata": {
|
||||
"execution": {
|
||||
"iopub.execute_input": "2023-08-18T07:03:10.049147Z",
|
||||
"iopub.status.busy": "2023-08-18T07:03:10.048156Z",
|
||||
"iopub.status.idle": "2023-08-18T07:03:11.581462Z",
|
||||
"shell.execute_reply": "2023-08-18T07:03:11.580563Z"
|
||||
},
|
||||
"origin_pos": 2,
|
||||
"tab": [
|
||||
"pytorch"
|
||||
]
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"tensor([[[[ 0., 1., 2., 3.],\n",
|
||||
" [ 4., 5., 6., 7.],\n",
|
||||
" [ 8., 9., 10., 11.],\n",
|
||||
" [12., 13., 14., 15.]]]])"
|
||||
]
|
||||
},
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import torch\n",
|
||||
"import torchvision\n",
|
||||
"\n",
|
||||
"X = torch.arange(16.).reshape(1, 1, 4, 4)\n",
|
||||
"X"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c5c9da14",
|
||||
"metadata": {
|
||||
"origin_pos": 4
|
||||
},
|
||||
"source": [
|
||||
"让我们进一步假设输入图像的高度和宽度都是40像素,且选择性搜索在此图像上生成了两个提议区域。\n",
|
||||
"每个区域由5个元素表示:区域目标类别、左上角和右下角的$(x, y)$坐标。\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "d5f4463d",
|
||||
"metadata": {
|
||||
"execution": {
|
||||
"iopub.execute_input": "2023-08-18T07:03:11.585300Z",
|
||||
"iopub.status.busy": "2023-08-18T07:03:11.584758Z",
|
||||
"iopub.status.idle": "2023-08-18T07:03:11.589192Z",
|
||||
"shell.execute_reply": "2023-08-18T07:03:11.588365Z"
|
||||
},
|
||||
"origin_pos": 6,
|
||||
"tab": [
|
||||
"pytorch"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"rois = torch.Tensor([[0, 0, 0, 20, 20], [0, 0, 10, 30, 30]])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "dad0e007",
|
||||
"metadata": {
|
||||
"origin_pos": 8
|
||||
},
|
||||
"source": [
|
||||
"由于`X`的高和宽是输入图像高和宽的$1/10$,因此,两个提议区域的坐标先按`spatial_scale`乘以0.1。\n",
|
||||
"然后,在`X`上分别标出这两个兴趣区域`X[:, :, 0:3, 0:3]`和`X[:, :, 1:4, 0:4]`。\n",
|
||||
"最后,在$2\\times 2$的兴趣区域汇聚层中,每个兴趣区域被划分为子窗口网格,并进一步抽取相同形状$2\\times 2$的特征。\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "9c4ab6ca",
|
||||
"metadata": {
|
||||
"execution": {
|
||||
"iopub.execute_input": "2023-08-18T07:03:11.592473Z",
|
||||
"iopub.status.busy": "2023-08-18T07:03:11.592023Z",
|
||||
"iopub.status.idle": "2023-08-18T07:03:11.598392Z",
|
||||
"shell.execute_reply": "2023-08-18T07:03:11.597591Z"
|
||||
},
|
||||
"origin_pos": 10,
|
||||
"tab": [
|
||||
"pytorch"
|
||||
]
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"tensor([[[[ 5., 6.],\n",
|
||||
" [ 9., 10.]]],\n",
|
||||
"\n",
|
||||
"\n",
|
||||
" [[[ 9., 11.],\n",
|
||||
" [13., 15.]]]])"
|
||||
]
|
||||
},
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"torchvision.ops.roi_pool(X, rois, output_size=(2, 2), spatial_scale=0.1)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6eb55aab",
|
||||
"metadata": {
|
||||
"origin_pos": 12
|
||||
},
|
||||
"source": [
|
||||
"## Faster R-CNN\n",
|
||||
"\n",
|
||||
"为了较精确地检测目标结果,Fast R-CNN模型通常需要在选择性搜索中生成大量的提议区域。\n",
|
||||
"*Faster R-CNN* :cite:`Ren.He.Girshick.ea.2015`提出将选择性搜索替换为*区域提议网络*(region proposal network),从而减少提议区域的生成数量,并保证目标检测的精度。\n",
|
||||
"\n",
|
||||
"\n",
|
||||
":label:`fig_faster_r-cnn`\n",
|
||||
"\n",
|
||||
" :numref:`fig_faster_r-cnn`描述了Faster R-CNN模型。\n",
|
||||
"与Fast R-CNN相比,Faster R-CNN只将生成提议区域的方法从选择性搜索改为了区域提议网络,模型的其余部分保持不变。具体来说,区域提议网络的计算步骤如下:\n",
|
||||
"\n",
|
||||
"1. 使用填充为1的$3\\times 3$的卷积层变换卷积神经网络的输出,并将输出通道数记为$c$。这样,卷积神经网络为图像抽取的特征图中的每个单元均得到一个长度为$c$的新特征。\n",
|
||||
"1. 以特征图的每个像素为中心,生成多个不同大小和宽高比的锚框并标注它们。\n",
|
||||
"1. 使用锚框中心单元长度为$c$的特征,分别预测该锚框的二元类别(含目标还是背景)和边界框。\n",
|
||||
"1. 使用非极大值抑制,从预测类别为目标的预测边界框中移除相似的结果。最终输出的预测边界框即是兴趣区域汇聚层所需的提议区域。\n",
|
||||
"\n",
|
||||
"值得一提的是,区域提议网络作为Faster R-CNN模型的一部分,是和整个模型一起训练得到的。\n",
|
||||
"换句话说,Faster R-CNN的目标函数不仅包括目标检测中的类别和边界框预测,还包括区域提议网络中锚框的二元类别和边界框预测。\n",
|
||||
"作为端到端训练的结果,区域提议网络能够学习到如何生成高质量的提议区域,从而在减少了从数据中学习的提议区域的数量的情况下,仍保持目标检测的精度。\n",
|
||||
"\n",
|
||||
"## Mask R-CNN\n",
|
||||
"\n",
|
||||
"如果在训练集中还标注了每个目标在图像上的像素级位置,那么*Mask R-CNN* :cite:`He.Gkioxari.Dollar.ea.2017`能够有效地利用这些详尽的标注信息进一步提升目标检测的精度。\n",
|
||||
"\n",
|
||||
"\n",
|
||||
":label:`fig_mask_r-cnn`\n",
|
||||
"\n",
|
||||
"如 :numref:`fig_mask_r-cnn`所示,Mask R-CNN是基于Faster R-CNN修改而来的。\n",
|
||||
"具体来说,Mask R-CNN将兴趣区域汇聚层替换为了\n",
|
||||
"*兴趣区域对齐*层,使用*双线性插值*(bilinear interpolation)来保留特征图上的空间信息,从而更适于像素级预测。\n",
|
||||
"兴趣区域对齐层的输出包含了所有与兴趣区域的形状相同的特征图。\n",
|
||||
"它们不仅被用于预测每个兴趣区域的类别和边界框,还通过额外的全卷积网络预测目标的像素级位置。\n",
|
||||
"本章的后续章节将更详细地介绍如何使用全卷积网络预测图像中像素级的语义。\n",
|
||||
"\n",
|
||||
"## 小结\n",
|
||||
"\n",
|
||||
"* R-CNN对图像选取若干提议区域,使用卷积神经网络对每个提议区域执行前向传播以抽取其特征,然后再用这些特征来预测提议区域的类别和边界框。\n",
|
||||
"* Fast R-CNN对R-CNN的一个主要改进:只对整个图像做卷积神经网络的前向传播。它还引入了兴趣区域汇聚层,从而为具有不同形状的兴趣区域抽取相同形状的特征。\n",
|
||||
"* Faster R-CNN将Fast R-CNN中使用的选择性搜索替换为参与训练的区域提议网络,这样后者可以在减少提议区域数量的情况下仍保证目标检测的精度。\n",
|
||||
"* Mask R-CNN在Faster R-CNN的基础上引入了一个全卷积网络,从而借助目标的像素级位置进一步提升目标检测的精度。\n",
|
||||
"\n",
|
||||
"## 练习\n",
|
||||
"\n",
|
||||
"1. 我们能否将目标检测视为回归问题(例如预测边界框和类别的概率)?可以参考YOLO模型 :cite:`Redmon.Divvala.Girshick.ea.2016`的设计。\n",
|
||||
"1. 将单发多框检测与本节介绍的方法进行比较。他们的主要区别是什么?可以参考 :cite:`Zhao.Zheng.Xu.ea.2019`中的图2。\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7d4eaf53",
|
||||
"metadata": {
|
||||
"origin_pos": 14,
|
||||
"tab": [
|
||||
"pytorch"
|
||||
]
|
||||
},
|
||||
"source": [
|
||||
"[讨论区](https://discuss.d2l.ai/t/3207)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"language_info": {
|
||||
"name": "python"
|
||||
},
|
||||
"required_libs": []
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,6 @@
|
||||
id,label
|
||||
1,airplane
|
||||
2,deer
|
||||
3,horse
|
||||
4,frog
|
||||
5,cat
|
||||
|
@@ -0,0 +1,584 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8082691a",
|
||||
"metadata": {
|
||||
"origin_pos": 0
|
||||
},
|
||||
"source": [
|
||||
"# 转置卷积\n",
|
||||
":label:`sec_transposed_conv`\n",
|
||||
"\n",
|
||||
"到目前为止,我们所见到的卷积神经网络层,例如卷积层( :numref:`sec_conv_layer`)和汇聚层( :numref:`sec_pooling`),通常会减少下采样输入图像的空间维度(高和宽)。\n",
|
||||
"然而如果输入和输出图像的空间维度相同,在以像素级分类的语义分割中将会很方便。\n",
|
||||
"例如,输出像素所处的通道维可以保有输入像素在同一位置上的分类结果。\n",
|
||||
"\n",
|
||||
"为了实现这一点,尤其是在空间维度被卷积神经网络层缩小后,我们可以使用另一种类型的卷积神经网络层,它可以增加上采样中间层特征图的空间维度。\n",
|
||||
"本节将介绍\n",
|
||||
"*转置卷积*(transposed convolution) :cite:`Dumoulin.Visin.2016`,\n",
|
||||
"用于逆转下采样导致的空间尺寸减小。\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "1f39b5ef",
|
||||
"metadata": {
|
||||
"execution": {
|
||||
"iopub.execute_input": "2023-08-18T07:05:22.451701Z",
|
||||
"iopub.status.busy": "2023-08-18T07:05:22.451411Z",
|
||||
"iopub.status.idle": "2023-08-18T07:05:24.490785Z",
|
||||
"shell.execute_reply": "2023-08-18T07:05:24.489970Z"
|
||||
},
|
||||
"origin_pos": 2,
|
||||
"tab": [
|
||||
"pytorch"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import torch\n",
|
||||
"from torch import nn\n",
|
||||
"from d2l import torch as d2l"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f1007d54",
|
||||
"metadata": {
|
||||
"origin_pos": 4
|
||||
},
|
||||
"source": [
|
||||
"## 基本操作\n",
|
||||
"\n",
|
||||
"让我们暂时忽略通道,从基本的转置卷积开始,设步幅为1且没有填充。\n",
|
||||
"假设我们有一个$n_h \\times n_w$的输入张量和一个$k_h \\times k_w$的卷积核。\n",
|
||||
"以步幅为1滑动卷积核窗口,每行$n_w$次,每列$n_h$次,共产生$n_h n_w$个中间结果。\n",
|
||||
"每个中间结果都是一个$(n_h + k_h - 1) \\times (n_w + k_w - 1)$的张量,初始化为0。\n",
|
||||
"为了计算每个中间张量,输入张量中的每个元素都要乘以卷积核,从而使所得的$k_h \\times k_w$张量替换中间张量的一部分。\n",
|
||||
"请注意,每个中间张量被替换部分的位置与输入张量中元素的位置相对应。\n",
|
||||
"最后,所有中间结果相加以获得最终结果。\n",
|
||||
"\n",
|
||||
"例如, :numref:`fig_trans_conv`解释了如何为$2\\times 2$的输入张量计算卷积核为$2\\times 2$的转置卷积。\n",
|
||||
"\n",
|
||||
"\n",
|
||||
":label:`fig_trans_conv`\n",
|
||||
"\n",
|
||||
"我们可以对输入矩阵`X`和卷积核矩阵`K`(**实现基本的转置卷积运算**)`trans_conv`。\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "e6931d90",
|
||||
"metadata": {
|
||||
"execution": {
|
||||
"iopub.execute_input": "2023-08-18T07:05:24.494981Z",
|
||||
"iopub.status.busy": "2023-08-18T07:05:24.494307Z",
|
||||
"iopub.status.idle": "2023-08-18T07:05:24.499745Z",
|
||||
"shell.execute_reply": "2023-08-18T07:05:24.498885Z"
|
||||
},
|
||||
"origin_pos": 5,
|
||||
"tab": [
|
||||
"pytorch"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def trans_conv(X, K):\n",
|
||||
" h, w = K.shape\n",
|
||||
" Y = torch.zeros((X.shape[0] + h - 1, X.shape[1] + w - 1))\n",
|
||||
" for i in range(X.shape[0]):\n",
|
||||
" for j in range(X.shape[1]):\n",
|
||||
" Y[i: i + h, j: j + w] += X[i, j] * K\n",
|
||||
" return Y"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6d64431b",
|
||||
"metadata": {
|
||||
"origin_pos": 6
|
||||
},
|
||||
"source": [
|
||||
"与通过卷积核“减少”输入元素的常规卷积(在 :numref:`sec_conv_layer`中)相比,转置卷积通过卷积核“广播”输入元素,从而产生大于输入的输出。\n",
|
||||
"我们可以通过 :numref:`fig_trans_conv`来构建输入张量`X`和卷积核张量`K`从而[**验证上述实现输出**]。\n",
|
||||
"此实现是基本的二维转置卷积运算。\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "a7c6e2fd",
|
||||
"metadata": {
|
||||
"execution": {
|
||||
"iopub.execute_input": "2023-08-18T07:05:24.503202Z",
|
||||
"iopub.status.busy": "2023-08-18T07:05:24.502646Z",
|
||||
"iopub.status.idle": "2023-08-18T07:05:24.531448Z",
|
||||
"shell.execute_reply": "2023-08-18T07:05:24.530730Z"
|
||||
},
|
||||
"origin_pos": 7,
|
||||
"tab": [
|
||||
"pytorch"
|
||||
]
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"tensor([[ 0., 0., 1.],\n",
|
||||
" [ 0., 4., 6.],\n",
|
||||
" [ 4., 12., 9.]])"
|
||||
]
|
||||
},
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"X = torch.tensor([[0.0, 1.0], [2.0, 3.0]])\n",
|
||||
"K = torch.tensor([[0.0, 1.0], [2.0, 3.0]])\n",
|
||||
"trans_conv(X, K)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c6698e0d",
|
||||
"metadata": {
|
||||
"origin_pos": 8
|
||||
},
|
||||
"source": [
|
||||
"或者,当输入`X`和卷积核`K`都是四维张量时,我们可以[**使用高级API获得相同的结果**]。\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "b9de6d80",
|
||||
"metadata": {
|
||||
"execution": {
|
||||
"iopub.execute_input": "2023-08-18T07:05:24.535386Z",
|
||||
"iopub.status.busy": "2023-08-18T07:05:24.534826Z",
|
||||
"iopub.status.idle": "2023-08-18T07:05:24.544484Z",
|
||||
"shell.execute_reply": "2023-08-18T07:05:24.543747Z"
|
||||
},
|
||||
"origin_pos": 10,
|
||||
"tab": [
|
||||
"pytorch"
|
||||
]
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"tensor([[[[ 0., 0., 1.],\n",
|
||||
" [ 0., 4., 6.],\n",
|
||||
" [ 4., 12., 9.]]]], grad_fn=<ConvolutionBackward0>)"
|
||||
]
|
||||
},
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"X, K = X.reshape(1, 1, 2, 2), K.reshape(1, 1, 2, 2)\n",
|
||||
"tconv = nn.ConvTranspose2d(1, 1, kernel_size=2, bias=False)\n",
|
||||
"tconv.weight.data = K\n",
|
||||
"tconv(X)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "80936d2e",
|
||||
"metadata": {
|
||||
"origin_pos": 12
|
||||
},
|
||||
"source": [
|
||||
"## [**填充、步幅和多通道**]\n",
|
||||
"\n",
|
||||
"与常规卷积不同,在转置卷积中,填充被应用于的输出(常规卷积将填充应用于输入)。\n",
|
||||
"例如,当将高和宽两侧的填充数指定为1时,转置卷积的输出中将删除第一和最后的行与列。\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "cd114de1",
|
||||
"metadata": {
|
||||
"execution": {
|
||||
"iopub.execute_input": "2023-08-18T07:05:24.548040Z",
|
||||
"iopub.status.busy": "2023-08-18T07:05:24.547398Z",
|
||||
"iopub.status.idle": "2023-08-18T07:05:24.553659Z",
|
||||
"shell.execute_reply": "2023-08-18T07:05:24.552864Z"
|
||||
},
|
||||
"origin_pos": 14,
|
||||
"tab": [
|
||||
"pytorch"
|
||||
]
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"tensor([[[[4.]]]], grad_fn=<ConvolutionBackward0>)"
|
||||
]
|
||||
},
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"tconv = nn.ConvTranspose2d(1, 1, kernel_size=2, padding=1, bias=False)\n",
|
||||
"tconv.weight.data = K\n",
|
||||
"tconv(X)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "22272c8b",
|
||||
"metadata": {
|
||||
"origin_pos": 16
|
||||
},
|
||||
"source": [
|
||||
"在转置卷积中,步幅被指定为中间结果(输出),而不是输入。\n",
|
||||
"使用 :numref:`fig_trans_conv`中相同输入和卷积核张量,将步幅从1更改为2会增加中间张量的高和权重,因此输出张量在 :numref:`fig_trans_conv_stride2`中。\n",
|
||||
"\n",
|
||||
"\n",
|
||||
":label:`fig_trans_conv_stride2`\n",
|
||||
"\n",
|
||||
"以下代码可以验证 :numref:`fig_trans_conv_stride2`中步幅为2的转置卷积的输出。\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "48064406",
|
||||
"metadata": {
|
||||
"execution": {
|
||||
"iopub.execute_input": "2023-08-18T07:05:24.557362Z",
|
||||
"iopub.status.busy": "2023-08-18T07:05:24.556727Z",
|
||||
"iopub.status.idle": "2023-08-18T07:05:24.563081Z",
|
||||
"shell.execute_reply": "2023-08-18T07:05:24.562365Z"
|
||||
},
|
||||
"origin_pos": 18,
|
||||
"tab": [
|
||||
"pytorch"
|
||||
]
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"tensor([[[[0., 0., 0., 1.],\n",
|
||||
" [0., 0., 2., 3.],\n",
|
||||
" [0., 2., 0., 3.],\n",
|
||||
" [4., 6., 6., 9.]]]], grad_fn=<ConvolutionBackward0>)"
|
||||
]
|
||||
},
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"tconv = nn.ConvTranspose2d(1, 1, kernel_size=2, stride=2, bias=False)\n",
|
||||
"tconv.weight.data = K\n",
|
||||
"tconv(X)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "79ac62fd",
|
||||
"metadata": {
|
||||
"origin_pos": 20
|
||||
},
|
||||
"source": [
|
||||
"对于多个输入和输出通道,转置卷积与常规卷积以相同方式运作。\n",
|
||||
"假设输入有$c_i$个通道,且转置卷积为每个输入通道分配了一个$k_h\\times k_w$的卷积核张量。\n",
|
||||
"当指定多个输出通道时,每个输出通道将有一个$c_i\\times k_h\\times k_w$的卷积核。\n",
|
||||
"\n",
|
||||
"同样,如果我们将$\\mathsf{X}$代入卷积层$f$来输出$\\mathsf{Y}=f(\\mathsf{X})$,并创建一个与$f$具有相同的超参数、但输出通道数量是$\\mathsf{X}$中通道数的转置卷积层$g$,那么$g(Y)$的形状将与$\\mathsf{X}$相同。\n",
|
||||
"下面的示例可以解释这一点。\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "5e7033d7",
|
||||
"metadata": {
|
||||
"execution": {
|
||||
"iopub.execute_input": "2023-08-18T07:05:24.566613Z",
|
||||
"iopub.status.busy": "2023-08-18T07:05:24.565990Z",
|
||||
"iopub.status.idle": "2023-08-18T07:05:24.577437Z",
|
||||
"shell.execute_reply": "2023-08-18T07:05:24.576434Z"
|
||||
},
|
||||
"origin_pos": 22,
|
||||
"tab": [
|
||||
"pytorch"
|
||||
]
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"True"
|
||||
]
|
||||
},
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"X = torch.rand(size=(1, 10, 16, 16))\n",
|
||||
"conv = nn.Conv2d(10, 20, kernel_size=5, padding=2, stride=3)\n",
|
||||
"tconv = nn.ConvTranspose2d(20, 10, kernel_size=5, padding=2, stride=3)\n",
|
||||
"tconv(conv(X)).shape == X.shape"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9908cdc8",
|
||||
"metadata": {
|
||||
"origin_pos": 24
|
||||
},
|
||||
"source": [
|
||||
"## [**与矩阵变换的联系**]\n",
|
||||
":label:`subsec-connection-to-mat-transposition`\n",
|
||||
"\n",
|
||||
"转置卷积为何以矩阵变换命名呢?\n",
|
||||
"让我们首先看看如何使用矩阵乘法来实现卷积。\n",
|
||||
"在下面的示例中,我们定义了一个$3\\times 3$的输入`X`和$2\\times 2$卷积核`K`,然后使用`corr2d`函数计算卷积输出`Y`。\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"id": "260d5c6d",
|
||||
"metadata": {
|
||||
"execution": {
|
||||
"iopub.execute_input": "2023-08-18T07:05:24.581485Z",
|
||||
"iopub.status.busy": "2023-08-18T07:05:24.580866Z",
|
||||
"iopub.status.idle": "2023-08-18T07:05:24.589179Z",
|
||||
"shell.execute_reply": "2023-08-18T07:05:24.588233Z"
|
||||
},
|
||||
"origin_pos": 25,
|
||||
"tab": [
|
||||
"pytorch"
|
||||
]
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"tensor([[27., 37.],\n",
|
||||
" [57., 67.]])"
|
||||
]
|
||||
},
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"X = torch.arange(9.0).reshape(3, 3)\n",
|
||||
"K = torch.tensor([[1.0, 2.0], [3.0, 4.0]])\n",
|
||||
"Y = d2l.corr2d(X, K)\n",
|
||||
"Y"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d5cb87b2",
|
||||
"metadata": {
|
||||
"origin_pos": 27
|
||||
},
|
||||
"source": [
|
||||
"接下来,我们将卷积核`K`重写为包含大量0的稀疏权重矩阵`W`。\n",
|
||||
"权重矩阵的形状是($4$,$9$),其中非0元素来自卷积核`K`。\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"id": "d9f6ce2b",
|
||||
"metadata": {
|
||||
"execution": {
|
||||
"iopub.execute_input": "2023-08-18T07:05:24.592769Z",
|
||||
"iopub.status.busy": "2023-08-18T07:05:24.592164Z",
|
||||
"iopub.status.idle": "2023-08-18T07:05:24.602392Z",
|
||||
"shell.execute_reply": "2023-08-18T07:05:24.601439Z"
|
||||
},
|
||||
"origin_pos": 28,
|
||||
"tab": [
|
||||
"pytorch"
|
||||
]
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"tensor([[1., 2., 0., 3., 4., 0., 0., 0., 0.],\n",
|
||||
" [0., 1., 2., 0., 3., 4., 0., 0., 0.],\n",
|
||||
" [0., 0., 0., 1., 2., 0., 3., 4., 0.],\n",
|
||||
" [0., 0., 0., 0., 1., 2., 0., 3., 4.]])"
|
||||
]
|
||||
},
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def kernel2matrix(K):\n",
|
||||
" k, W = torch.zeros(5), torch.zeros((4, 9))\n",
|
||||
" k[:2], k[3:5] = K[0, :], K[1, :]\n",
|
||||
" W[0, :5], W[1, 1:6], W[2, 3:8], W[3, 4:] = k, k, k, k\n",
|
||||
" return W\n",
|
||||
"\n",
|
||||
"W = kernel2matrix(K)\n",
|
||||
"W"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "12f9b037",
|
||||
"metadata": {
|
||||
"origin_pos": 30
|
||||
},
|
||||
"source": [
|
||||
"逐行连结输入`X`,获得了一个长度为9的矢量。\n",
|
||||
"然后,`W`的矩阵乘法和向量化的`X`给出了一个长度为4的向量。\n",
|
||||
"重塑它之后,可以获得与上面的原始卷积操作所得相同的结果`Y`:我们刚刚使用矩阵乘法实现了卷积。\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"id": "1fb803d0",
|
||||
"metadata": {
|
||||
"execution": {
|
||||
"iopub.execute_input": "2023-08-18T07:05:24.606249Z",
|
||||
"iopub.status.busy": "2023-08-18T07:05:24.605496Z",
|
||||
"iopub.status.idle": "2023-08-18T07:05:24.612872Z",
|
||||
"shell.execute_reply": "2023-08-18T07:05:24.611900Z"
|
||||
},
|
||||
"origin_pos": 31,
|
||||
"tab": [
|
||||
"pytorch"
|
||||
]
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"tensor([[True, True],\n",
|
||||
" [True, True]])"
|
||||
]
|
||||
},
|
||||
"execution_count": 10,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"Y == torch.matmul(W, X.reshape(-1)).reshape(2, 2)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "27394a2c",
|
||||
"metadata": {
|
||||
"origin_pos": 33
|
||||
},
|
||||
"source": [
|
||||
"同样,我们可以使用矩阵乘法来实现转置卷积。\n",
|
||||
"在下面的示例中,我们将上面的常规卷积$2 \\times 2$的输出`Y`作为转置卷积的输入。\n",
|
||||
"想要通过矩阵相乘来实现它,我们只需要将权重矩阵`W`的形状转置为$(9, 4)$。\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"id": "f1a55ff1",
|
||||
"metadata": {
|
||||
"execution": {
|
||||
"iopub.execute_input": "2023-08-18T07:05:24.616575Z",
|
||||
"iopub.status.busy": "2023-08-18T07:05:24.615826Z",
|
||||
"iopub.status.idle": "2023-08-18T07:05:24.623063Z",
|
||||
"shell.execute_reply": "2023-08-18T07:05:24.622144Z"
|
||||
},
|
||||
"origin_pos": 34,
|
||||
"tab": [
|
||||
"pytorch"
|
||||
]
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"tensor([[True, True, True],\n",
|
||||
" [True, True, True],\n",
|
||||
" [True, True, True]])"
|
||||
]
|
||||
},
|
||||
"execution_count": 11,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"Z = trans_conv(Y, K)\n",
|
||||
"Z == torch.matmul(W.T, Y.reshape(-1)).reshape(3, 3)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "9614cf7b",
|
||||
"metadata": {
|
||||
"origin_pos": 36
|
||||
},
|
||||
"source": [
|
||||
"抽象来看,给定输入向量$\\mathbf{x}$和权重矩阵$\\mathbf{W}$,卷积的前向传播函数可以通过将其输入与权重矩阵相乘并输出向量$\\mathbf{y}=\\mathbf{W}\\mathbf{x}$来实现。\n",
|
||||
"由于反向传播遵循链式法则和$\\nabla_{\\mathbf{x}}\\mathbf{y}=\\mathbf{W}^\\top$,卷积的反向传播函数可以通过将其输入与转置的权重矩阵$\\mathbf{W}^\\top$相乘来实现。\n",
|
||||
"因此,转置卷积层能够交换卷积层的正向传播函数和反向传播函数:它的正向传播和反向传播函数将输入向量分别与$\\mathbf{W}^\\top$和$\\mathbf{W}$相乘。\n",
|
||||
"\n",
|
||||
"## 小结\n",
|
||||
"\n",
|
||||
"* 与通过卷积核减少输入元素的常规卷积相反,转置卷积通过卷积核广播输入元素,从而产生形状大于输入的输出。\n",
|
||||
"* 如果我们将$\\mathsf{X}$输入卷积层$f$来获得输出$\\mathsf{Y}=f(\\mathsf{X})$并创造一个与$f$有相同的超参数、但输出通道数是$\\mathsf{X}$中通道数的转置卷积层$g$,那么$g(Y)$的形状将与$\\mathsf{X}$相同。\n",
|
||||
"* 我们可以使用矩阵乘法来实现卷积。转置卷积层能够交换卷积层的正向传播函数和反向传播函数。\n",
|
||||
"\n",
|
||||
"## 练习\n",
|
||||
"\n",
|
||||
"1. 在 :numref:`subsec-connection-to-mat-transposition`中,卷积输入`X`和转置的卷积输出`Z`具有相同的形状。他们的数值也相同吗?为什么?\n",
|
||||
"1. 使用矩阵乘法来实现卷积是否有效率?为什么?\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "bcd86378",
|
||||
"metadata": {
|
||||
"origin_pos": 38,
|
||||
"tab": [
|
||||
"pytorch"
|
||||
]
|
||||
},
|
||||
"source": [
|
||||
"[Discussions](https://discuss.d2l.ai/t/3302)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"language_info": {
|
||||
"name": "python"
|
||||
},
|
||||
"required_libs": []
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
Reference in New Issue
Block a user