目录
1、插值
1.1 插值算法总结(mode参数)
1.2 align_corners参数讲解
2、反池化
2.1 平均池化和反平均池化
2.2 最大池化和反最大池化
3、转置卷积(反卷积)
深度学习中下采样的方式比较常用的有两种:池化和步长为2的卷积;而在上采样过程中常用的方式有三种:插值、反池化、反卷积。不论是语义分割、目标检测还是三维重建等模型,都需要将提取到的高层特征进行放大,此时就需要对feature map进行上采样。下面本文将具体对深度学习中上采样方法进行详细总结。
torch.nn.Upsample(size=None, scale_factor=None, mode='nearest', align_corners=None, recompute_scale_factor=None)
参数说明:
①size:可以用来指定输出空间的大小,默认是None;
②scale_factor:比例因子,比如scale_factor=2意味着将输入图像上采样2倍,默认是None;
③mode:用来指定上采样算法,有'nearest'、 'linear'、'bilinear'、'bicubic'、'trilinear',默认是'nearest'。上采样算法在本文中会有详细理论进行讲解;
④align_corners:如果True,输入和输出张量的角像素对齐,从而保留这些像素的值,默认是False。此处True和False的区别本文中会有详细的理论讲解;
⑤recompute_scale_factor:如果recompute_scale_factor是True,则必须传入scale_factor并且scale_factor用于计算输出大小。计算出的输出大小将用于推断插值的新比例。请注意,当scale_factor为浮点数时,由于舍入和精度问题,它可能与重新计算的scale_factor不同。如果recompute_scale_factor是False,那么size或scale_factor将直接用于插值。
torch.nn.functional.interpolate(input, size=None, scale_factor=None, mode='nearest', align_corners=None, recompute_scale_factor=None, antialias=False)
参数说明:
①input:输入张量;
②size:可以用来指定输出空间的大小,默认是None;
③scale_factor:比例因子,比如scale_factor=2意味着将输入图像上采样2倍,默认是None;
④mode:用来指定上采样算法,有'nearest'、 'linear'、'bilinear'、'bicubic'、'trilinear',默认是'nearest'。上采样算法在本文中会有详细理论进行讲解;
④align_corners:如果True,输入和输出张量的角像素对齐,从而保留这些像素的值,默认是False。此处True和False的区别本文中会有详细的理论讲解;
⑤recompute_scale_factor:如果recompute_scale_factor是True,则必须传入scale_factor并且scale_factor用于计算输出大小。计算出的输出大小将用于推断插值的新比例。请注意,当scale_factor为浮点数时,由于舍入和精度问题,它可能与重新计算的scale_factor不同。如果recompute_scale_factor是False,那么size或scale_factor将直接用于插值。
插值算法中常用的方法有最近邻插值(nearest interpolation)、单线性插值(linear interpolation)、双线性插值(bilinear interpolation)等,这里只讲解最常用的最邻近插值法和双线性插值法。
1.1.1最近邻插值
最近邻插值法会直接计算输出像素映射到输入图像坐标系下的点u和近邻四点(n1,n2,n3,n4)之间的距离,取距离u最近的像素点的像素值赋给u。最近邻插值法的计算速度很快,但是新图像局部破坏了原图的渐变关系。
1.1.2双线性插值法
双线性插值(bilinear interpolation)又称一阶插值,根据离待插值最近的2*2=4个已知值来计算待插值,每个已知值的权重由与待插值的距离决定,距离越近权重越大。双线性插值是分别在两个方向计算了共3次单线性插值,如下图所示,假设图中红点代表原图中已知像素值的像素点,它们的坐标分别为点,点,点,点,这4个红点的像素值分别表示为,其中,待插值的绿点坐标为,要求使用双线性插值法求出待插值点P的像素值。
计算过程如下:
1)按x轴方向进行两次单线性插值分别得到蓝点和的像素值和;
1)按y轴方向进行一次单线性插值即可得到点P的像素值。
align_corners参数设为True和False,其上采样结果是不同的,如下面代码运行结果所示。
import torch
input = torch.arange(1, 10, dtype=torch.float32).view(1, 1, 3, 3)
print(input)
m = torch.nn.Upsample(scale_factor=2, mode='bilinear')
output1 = m(input)
print(output1)
n = torch.nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True)
output2 = n(input)
print(output2)
之所以造成上采样结果不同,其主要原因是看待像素的方式不同:
①Centers-aligned:将像素看作一个有面积的方格,方格中心点位置代表这个像素。align_corners=False就是以这种方式看待像素的,像素的坐标并不是图像矩阵所对应的下标,而是需要将下标i,j
各加上0.5
才是此时每个像素在坐标系里的坐标(以左上角为原点 ,x轴向右为正,y轴向下为正)。
②Corners-aligned:将像素看作一个理想的点,这个点的位置就代表这个像素。align_corners=True是以这种方式看待像素的,每个像素的在矩阵里的下标i,j
被直接视作坐标系里的一个个的坐标点进行计算。
至于这两种情况下,上采样后各自结果是如何计算出来的小编不知道怎么表达才能让大家彻底明白,大家自行理解,这里放几张有助于理解的图。
反池化是池化的逆操作,是无法通过池化的结果还原出全部的原始数据,现如今很少使用这种方法去实现图像的上采样。因为池化的过程就只保留了主要信息,舍去部分信息。如果想从池化后的这些主要信息恢复出全部信息,则存在信息缺失,这时只能通过补位来实现最大程度的信息完整。池化有两种:最大池化和平均池化,其反池化也需要与其对应。
首先还原成原来的大小,然后将池化结果中的每个值都填入其对应原始数据区域中相应位置即可。 平均池化和反平均池化的过程如下:
要求在池化过程中记录最大激活值的坐标位置,然后在反池化时,只把池化过程中最大激活值所在位置坐标值激活,其他的值设置为0.当然,这个过程只是一种近似。因为在池化过程中,除了最大值的位置,其他的值也是不为0的。
最大池化和反最大池化的过程如下:
下面将详细讲解利用反卷积实现图像上采样的具体实现原理,以torch.nn.ConvTranspose2d()函数为例,该函数中的参数含义与torch.nn.Conv2d()函数中的参数含义基本相同。
torch.nn.ConvTranspose2d(in_channels, out_channels, kernel_size, stride=1, padding=0, output_padding=0, groups=1, bias=True, dilation=1, padding_mode='zeros', device=None, dtype=None)
参数说明:
in_channels:输入的通道数
out_channels:输出的通道数
kernel_size:卷积核的大小
stride:卷积核滑动的步长,默认是1
padding:怎么填充输入图像,此参数的类型可以是int , tuple或str , optional 。默认padding=0,即不填充。
dilation:设置膨胀率,即核内元素间距,默认是1。即如果kernel_size=3,dilation=1,那么卷积核大小就是3×3;如果kernel_size=3,dilation=2,那么卷积核大小为5×5
groups:通过设置这个参数来决定分几组进行卷积,默认是1,即默认是普通卷积,此时卷积核通道数=输入通道数
bias:是否添加偏差,默认true
padding_mode:填充时,此参数决定用什么值来填充,默认是'zeros',即用0填充,可选参数有'zeros', 'reflect', 'replicate'或'circular'
反卷积又被称为转置卷积、分数步长卷积,其实这个函数最准确的叫法应该是转置卷积,其原因与卷积和转置卷积的底层代码实现有关。
在解释这个之前,我们得先来看看正常的卷积在代码实现过程中的一个具体操作。对于正常的卷积,我们需要实现大量的相乘相加操作,而这种乘加的方式恰好是矩阵乘法所擅长的。 所以在代码实现的时候,通常会借助矩阵乘法快速的实现卷积操作, 那么这是怎么做的呢?假设输入图像尺寸为4 × 4 , 卷积核为3 × 3 , padding=0, stride=1,通过计算可知,卷积之后输出图像的尺寸为2×2,如下图所示:
常规卷积在代码实现时的具体过程就是:首先将代表输入图像的4 × 4 矩阵转换成16 × 1 的列向量,由于计算可知输出图像是2×2的矩阵,同样将其转换成4×1的列向量,那么由矩阵乘法可知,参数矩阵必定是4×16的,那么这个4×16的参数矩阵是怎么来的呢?从上图很明显可知4就是卷积核窗口滑动了4次就遍历完整个输入图像了,这个16就是先把3×3的9个权值拉成一行,然后根据窗口在输入图像上滑动的位置补7个0凑成16个参数,这16个参数就是输入图像16个像素各自对应的权重参数,即:
输出4×1的向量reshape一下就是代表输出图像的2×2的矩阵了。下图就是常规卷积的矩阵乘法示例:
现在我们看看转置卷积是怎样用代码实现的?转置卷积是一种上采样方法,输入的图像尺寸是比较小的,经过转置卷积之后,会输出一个更大的图像。假设输入图像尺寸为2 × 2 , 卷积核为3 × 3 ,padding=0, stride=1,通过转置卷积会得到4×4的输出图像,如下图所示:
转置卷积在代码实现时的具体过程就是:首先将代表输入图像的2 × 2 矩阵转换成4 × 1 的列向量,由于转置卷积后输出图像是4×4的矩阵,同样将其转换成16×1的列向量,那么由矩阵乘法可知,参数矩阵必定是16×4的,那么这个16×4的参数矩阵是怎么来的呢?从上图很明显可知16就是卷积核窗口滑动了16次就遍历完整个输入图像了,虽然这里的卷积核有9个权值,可是能与图像相乘的最多只有四个(也就是卷积核在中间的时候), 这便是参数矩阵中4的含义,即:
输出16×1的向量reshape一下就是代表输出图像的4×4的矩阵了。下图就是转置卷积的矩阵乘法示例:
通过常规卷积和转置卷积的代码实现过程不难发现,这两个卷积操作过程用到的卷积矩阵和在形状上恰恰是转置的关系,这也就是转置卷积的由来了。注意这里说的是形状,具体数值肯定是不一样的。