深度学习笔记——计算机视觉基础:卷积神经网络 CNN(LeNet、AlexNet、VGG、NiN、GoogLeNet、ResNet、DenseNet)、图像处理(目标检测、语义分割、样式迁移)
1 卷积神经网络(LeNet)
1.1 卷积的基本概念
卷积神经网络(Convolutional Neural Networks,CNN)是机器学习利用自然图像中一些已知结构的创造性方法。
1.1.1 从全连接层到卷积
MLP对一张图像的单通道处理过程:设MLP的输入为二维图像\mathbf{X},其同形状的隐藏表示\mathbf{H}为矩阵(二维张量),用[\mathbf{X}]_{i,j}和[\mathbf{H}]_{ij}分别表示输入图像和隐藏表示中位置 $(i,j)$ 处的像素。参考之前的操作,该全连接层的参数为四阶权重张量\mathsf{W}、偏置参数矩阵\mathbf{U}。则对于隐藏表示中任意位置(i,j)处的像素值[\mathbf{H}]_{ij},可通过在\mathbf{X}中以(i,j)为中心对输入像素进行上述两参数的加权求和得到,再经平移使权重变为\mathsf{V},可知结果依赖于该中心点(i,j),即
[\mathbf{H}]_{i,j} =[\mathbf{U}]_{i,j}+\sum\limits_k \sum\limits_l
[\mathsf{W} ]_{i,j,k,l}[\mathbf{X}]_{k,l} \\
=[\mathbf{U}]_{i,j}+\sum\limits_a \sum\limits_b [\mathsf{V}
]_{i,j,a,b}[\mathbf{X}]_{i+a,j+b}\ (k=i+a,\ l=j+b)
CNN将空间不变性(Spatial Invariance)的这一概念系统化,从而使用较少的参数来学习有用的表示,相比于全连接层其模型更简洁、所需的参数更少。
(1)平移不变性(Translation Invariance):不管检测对象出现在图像中哪个位置,神经网络的前几层应对相同的图像区域具有相似的反应。
检测对象在输入\mathbf{X}中的平移应该仅导致隐藏表示\mathbf{H}中的平移,\mathsf{V}和\mathbf{U}不依赖于(i,j)的值,即[\mathsf{V}]_{i,j,a,b}=[\mathbf{V}]_{i,j}且\mathbf{U}为常数u。由此可简化\mathbf{H}的定义为
[\mathbf{H}]_{i,j} =u+\sum\limits_a \sum\limits_b [\mathbf{V} ]_{a,b}[\mathbf{X}]_{i+a,j+b}
这种使得维数降低的操作即为卷积(Convolution),能够大幅减少参数。
(2)局部性(Locality):神经网络的前面几层应该只探索输入图像中的局部区域,而不过度在意图像中相隔较远区域的关系。
为了收集用来训练\mathbf{H}参数的相关信息,不应偏离到距(i,j)很远的地方,即\forall\ |a|>\Delta \text{ or } |b|>\Delta,\ [\mathbf{V}]_{a,b}=0。由此可进一步重写\mathbf{H}为
[\mathbf{H}]_{i,j} =u+\sum\limits_{a=-\Delta}^\Delta \sum\limits_{b=-\Delta}^\Delta [\mathbf{V} ]_{a,b}[\mathbf{X}]_{i+a,j+b}
上述式子表示一个卷积层(Convolutional Layer)。\mathbf{V}称为卷积核(Convolution Kernel)或滤波器(Filter)或该卷积层的权重,是可学习的参数。
1.1.2 数学上的卷积
设函数f,g:\mathbb{R}^d \rightarrow \mathbb{R},则f与g的卷积即为当把一个函数“翻转”并移位\mathbf{x}时,测量这两个函数之间的重叠,定义为
(f*g)(\mathbf{x})=\int f(\mathbf{z})g(\mathbf{x}-\mathbf{z})\mathrm{d}\mathbf{z}
当为离散对象时,积分就变成求和。例如,对于由索引为\mathbb{Z}、平方可和的无限维向量集合中抽取的向量,定义为
(f*g)(i)=\sum\limits_a f(a)g(i-a)
对于二维张量,则为f的索引(a,b)和g的索引(i-a,j-b)上的对应加和,即
(f*g)(i,j)=\sum\limits_a\sum\limits_b f(a,b)g(i-a,j-b)
1.1.3 通道
图像一般包含三个通道/三种原色(红色、绿色和蓝色),一个二维图像输入实际是一个由高度、宽度和颜色组成的三维张量,因此将[\mathbf{X}]_{i,j}调整为[\mathsf{X}]_{i,j,k},将[\mathbf{V}]_{a,b}调整为[\mathsf{V}]_{a,b,c}。
由于输入图像是三维的,隐藏表示\mathbf{H}也应采用三维张量,即对于每一个空间位置应采用一组隐藏表示。因此可以把隐藏表示视为一系列具有二维张量的通道(Channel),或特征映射(Feature Map),因为每个通道都向后续层提供一组空间化的学习特征。上述的坐标c表示输入通道,可再添加一个坐标d表示输出通道。
综上所述,一个输入通道为c、输出通道为d的隐藏表示定义为
[\mathbf{H}]_{i,j,d} =\sum\limits_{a=-\Delta}^\Delta \sum\limits_{b=-\Delta}^\Delta \sum\limits_c [\mathsf{V} ]_{a,b,c,d}[\mathsf{X}]_{i+a,j+b,c}
1.2 图像卷积
1.2.1 互相关运算
此处先只考虑单通道的情况。在卷积层中,输入张量与核张量通过互相关运算(Cross-correlation)产生输出张量。在二维互相关运算中,卷积窗口从输入张量的左上角开始,从左到右、从上到下滑动。 当卷积窗口滑动到新一个位置时,包含在该窗口中的部分张量与卷积核张量进行按元素相乘,所得张量再求和得到一个标量,由此可得这一位置的输出张量值。
对于输入大小n_h\times n_w、卷积核大小k_h \times k_w,输出大小为
(n_h-k_h+1)\times (n_w-k_w+1)

卷积层中的两个被训练的参数是卷积核权重(weight)和标量偏置(bias),参考全连接层,通常也随机初始化卷积核权重。高度和宽度分别为h与w的卷积核被称为h\times w卷积(核),将该卷积层称为h\times w卷积层。
# 构造一个二维卷积层,其具有1个输出通道和形状为(1,2)的卷积核
conv2d = nn.Conv2d(1,1, kernel_size=(1, 2), bias=False)
1.2.2 填充(Padding)
填充(Padding):在输入图像的边界填充元素(通常填0)。
若添加p_h行填充(大约一半在顶部,一半在底部)和p_w列填充(左侧大约一半,右侧一半),则输出的高度和宽度分别增加p_h和p_w,输出形状为
(n_h-k_h+p_h+1)\times (n_w-k_w+p_w+1)
常设置p_h=k_h-1和p_w=k_w-1,使输入和输出具有相同的高度和宽度。
常取卷积核的高度k_h和宽度k_w为奇数,好处为在保持空间维度的同时,可以在顶部和底部填充数量均为\frac {p_h}2的行,在左侧和右侧填充数量均为\frac {p_w}2的列。

# 当卷积核的高度和宽度不同时,可以填充不同的高度和宽度,使输出和输入具有相同的高度和宽度
conv2d = nn.Conv2d(1, 1, kernel_size=(5, 3), padding=(2, 1))
1.2.3 步幅(Stride)
步幅(Stride):每次滑动元素的数量。有时为了高效计算或是缩减采样次数,卷积窗口可以跳过中间位置,每次滑动多个元素。
通常,当垂直步幅为s_h、水平步幅为s_w时,输出形状为
\left \lfloor \frac {n_h-k_h+p_h+s_h}{s_h} \right \rfloor \times\left \lfloor \frac {n_w-k_w+p_w+s_w}{s_w} \right \rfloor
若设置了p_h=k_h-1和p_w=k_w-1,则上式可简化为
\left \lfloor \frac {n_h+s_h-1}{s_h} \right \rfloor \times\left \lfloor \frac {n_w+s_w-1}{s_w} \right \rfloor
若输入的高度和宽度可被垂直和水平步幅整除,则可进一步简化为
\left \lfloor \frac {n_h}{s_h} \right \rfloor \times\left \lfloor \frac {n_w}{s_w} \right \rfloor

# 将高度和宽度的步幅设置为2,从而将输入的高度和宽度减半
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1, stride=2)
# 高度和宽度可设置不同的步幅
conv2d = nn.Conv2d(1, 1, kernel_size=(3, 5), padding=(0, 1), stride=(3, 4))
为了简洁起见,当输入高度和宽度两侧的填充数量分别为p_h和p_w时称之为填充(p_h,p_w);当p_h=p_w=p时填充为p。同理,当高度和宽度上的步幅分别为s_h和s_w时称之为步幅(s_h,s_w);当s_h=s_w=s时步幅为s 。默认情况下填充为,步幅为1。实践中很少使用不一致的步幅或填充,即通常有p_h=p_w,\ s_h=s_w。
1.4 多输入多输出通道
多输入多输出通道可以用来扩展卷积层的模型。
1.4.1 多输入通道
当时输入通道c_i>1时,需构造一个具有相同输入通道数的卷积核,以便与输入数据进行互相关运算。若输入张量的形状为k_h\times k_w,则连结可得形状为c_i\times k_h \times k_w的卷积核。由此可对每个通道输入的二维张量和卷积核的二维张量进行互相关运算,再对通道求和(将c_i个结果相加)得到二维张量。

1.4.2 多输出通道
在最流行的神经网络架构中,随着神经网络层数的加深常会增加输出通道的维数,通过减少空间分辨率以获得更大的通道深度。直观地说,可以将每个通道看作对不同特征的响应。而现实可能更为复杂一些,因为每个通道不是独立学习的,而是为了共同使用而优化的。因此,多输出通道并不仅是学习多个单通道的检测器。
若c_i和c_o分别表示输入和输出通道数,k_h和k_w为卷积核的高度和宽度。为获得多个通道的输出,可以为每个输出通道创建一个形状为c_i\times k_h\times k_w的卷积核张量,这样卷积核的形状即为c_o\times c_i\times k_h\times k_w。
在互相关运算中,每个输出通道先获取所有输入通道,再以对应该输出通道的卷积核计算出结果。
1.4.3 1×1卷积核
1×1卷积(k_h=k_w=1)失去了卷积层在高度和宽度维度上识别相邻元素间相互作用的能力,其唯一计算发生在通道上。输入和输出具有相同的高度和宽度,输出中的每个元素都是从输入图像中同一位置的元素的线性组合。
作用:通常用于调整网络层的通道数量和控制模型复杂性。
当以每像素为基础应用时,1×1卷积相当于全连接层,以c_i个输入值转换为c_o个输出值。因为这仍然是一个卷积层,所以跨像素的权重是一致的。同时其需要的权重维度为c_o\times c_i,再额外加上一个偏置。

1.5 池化层(汇聚层)
池化层(Pooling,汇聚层)与卷积层类似,由一个形状固定为p\times q的窗口(常称为池化窗口)遍历的每个位置计算一个输出,同样可以指定填充和步幅。但池化层不包含参数,池化运算是确定性的,通常计算池化窗口中所有元素的最大值或平均值,这些操作分别称为最大池化层(Maximum Pooling)和平均池化层(Average Pooling)。
主要作用:减轻卷积层对位置的过度敏感;使用最大池化层以及大于1的步幅,可减少空间维度(如高度和宽度)。
与互相关运算符一样,池化窗口从输入张量的左上角开始,从左往右、从上往下的在输入张量内滑动。在池化窗口到达的每个位置,它计算该窗口中输入子张量的最大值或平均值。计算最大值或平均值是取决于使用了最大池化层还是平均池化层。

池化窗口形状为p\times q的池化层称为p\times q池化层,池化操作称为p\times q池化。
# 和卷积层类似,池化层同样可以设置填充和步幅
pool2d = nn.MaxPool2d(3, padding=1, stride=2)
在处理多通道输入数据时,池化层在每个输入通道上单独运算,而不是像卷积层一样在通道上对输入进行汇总。 这意味着池化层的输出通道数与输入通道数相同。
1.6 LeNet-5模型
LeNet是最早发布的卷积神经网络之一。LeNet-5由以下两个部分组成
- 卷积编码器:由两个卷积层组成。每个卷积块中的基本单元是一个卷积层、一个sigmoid激活函数和平均汇聚层。
- 全连接层密集块:由三个全连接层组成。为了将卷积块的输出传递给该稠密块,须在小批量中展平每个样本。

lenet = nn.Sequential(
nn.Conv2d(1, 6, kernel_size=5, padding=2), nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2),
nn.Conv2d(6, 16, kernel_size=5), nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2),
nn.Flatten(),
nn.Linear(16 * 5 * 5, 120), nn.Sigmoid(),
nn.Linear(120, 84), nn.Sigmoid(),
nn.Linear(84, 10)
)
去掉部分激活的简化版LeNet如下

为了构造高性能的卷积神经网络,通常对卷积层进行排列,逐渐降低其表示的空间分辨率,同时增加通道数。在传统的卷积神经网络中,卷积块编码得到的表征在输出之前需由一个或多个全连接层进行处理。
2 现代卷积神经网络
2.1 深度卷积神经网络(AlexNet)
原文:ImageNet Classification with Deep Convolutional Neural Networks
AlexNet一举打破了计算机视觉研究的现状,以很大的优势赢得了2012年ImageNet图像识别挑战赛,其设计体现了计算机视觉方法论的改变。
AlexNet是更大更深的LeNet-5,由八层组成:五个卷积层、两个全连接隐藏层和一个全连接输出层。引入了Dropout、ReLU、最大池化和数据增强。10x参数个数,260x计算复杂度。

alexnet = nn.Sequential(
# 使用11*11的更大窗口来捕捉对象。同时步幅为4,以减少输出的高度和宽度。输出通道的数目远大于LeNet
nn.Conv2d(1, 96, kernel_size=11, stride=4, padding=1), nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2),
# 减小卷积窗口,使用填充为2来使得输入与输出的高和宽一致,且增大输出通道数
nn.Conv2d(96, 256, kernel_size=5, padding=2), nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2),
# 使用三个连续的卷积层和较小的卷积窗口。
# 除了最后的卷积层,输出通道的数量进一步增加。
# 在前两个卷积层之后,汇聚层不用于减少输入的高度和宽度
nn.Conv2d(256, 384, kernel_size=3, padding=1), nn.ReLU(),
nn.Conv2d(384, 384, kernel_size=3, padding=1), nn.ReLU(),
nn.Conv2d(384, 256, kernel_size=3, padding=1), nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2),
nn.Flatten(),
# 这里,全连接层的输出数量是LeNet中的好几倍。使用dropout层来减轻过拟合
nn.Linear(6400, 4096), nn.ReLU(),
nn.Dropout(p=0.5),
nn.Linear(4096, 4096), nn.ReLU(),
nn.Dropout(p=0.5),
# 最后是输出层。由于这里使用Fashion-MNIST,所以用类别数为10,而非论文中的1000
nn.Linear(4096, 10)
)
2.2 使用块的网络(VGG)
VGG网络是更大更深的AlexNet,与之一样可以分为两大部分:第一部分为一系列卷积块;第二部分由全连接层组成。卷积块与AlexNet的设计类似,同样主要由卷积层和汇聚层组成,为以下这个序列
- 带填充以保持分辨率的卷积层
- 非线性激活函数,如ReLU
- 汇聚层,如最大汇聚层
卷积块可以复用,使得网络定义的非常简洁,可以有效设计复杂的网络。不同的卷积块个数可得不同复杂度的变种架构,如VGG-11、VGG-16、VGG-19等。VGG网络的计算要比AlexNet慢得多。

原始的VGG网络使用8个卷积层和3个全连接层,因此通常被称为VGG-11,代码实现如下
def vgg_block(num_convs, in_channels, out_channels):
"""返回一个VGG块"""
layers = []
for _ in range(num_convs):
layers.append(nn.Conv2d(in_channels, out_channels,
kernel_size=3, padding=1))
layers.append(nn.ReLU())
in_channels = out_channels
layers.append(nn.MaxPool2d(kernel_size=2,stride=2))
return nn.Sequential(*layers)
def vgg11(conv_arch):
"""返回一个VGG-11网络"""
conv_blks = []
in_channels = 1
# 卷积层部分
for (num_convs, out_channels) in conv_arch:
conv_blks.append(vgg_block(num_convs, in_channels, out_channels))
in_channels = out_channels
return nn.Sequential(*conv_blks, nn.Flatten(),
# 全连接层部分
nn.Linear(out_channels * 7 * 7, 4096), nn.ReLU(), nn.Dropout(0.5),
nn.Linear(4096, 4096), nn.ReLU(), nn.Dropout(0.5),
nn.Linear(4096, 10))
conv_arch = ((1, 64), (1, 128), (2, 256), (2, 512), (2, 512))
net = vgg11(conv_arch) # conv_arch指定各VGG块中卷积层个数和输出通道数
2.3 网络中的网络(NiN)
NiN(网络中的网络)对VGG进行了改进,为了避免全连接层可能会完全放弃表征的空间结构的问题,在每个像素的通道上分别使用多层感知机,可以将其视1\times 1卷积层(注意与VGG的区别,参考下图)。
一个NiN块以一个普通卷积层开始,后面是两个1\times 1卷积层。这两个1\times 1卷积层充当带有ReLU激活函数的逐像素全连接层。 第一层的卷积窗口形状通常由用户设置。 随后的卷积窗口形状固定为1\times 1。
NiN和AlexNet之间的一个显著区别是NiN完全取消了容易造成过拟合的全连接层,将它们替换为全局平均汇聚层(Global Average Pooling Layer),其通道数量为所需的输出数量,生成一个对数几率 (Logits)。该设计能显著减少模型所需参数的数量,然而在实践中有时会增加训练模型的时间。

def nin_block(in_channels, out_channels, kernel_size, strides, padding):
"""返回一个NiN块"""
return nn.Sequential(nn.Conv2d(in_channels, out_channels, kernel_size, strides, padding),
nn.ReLU(),
nn.Conv2d(out_channels, out_channels, kernel_size=1), nn.ReLU(),
nn.Conv2d(out_channels, out_channels, kernel_size=1), nn.ReLU())
nin_net = nn.Sequential(
nin_block(1, 96, kernel_size=11, strides=4, padding=0),
nn.MaxPool2d(3, stride=2),
nin_block(96, 256, kernel_size=5, strides=1, padding=2),
nn.MaxPool2d(3, stride=2),
nin_block(256, 384, kernel_size=3, strides=1, padding=1),
nn.MaxPool2d(3, stride=2),
nn.Dropout(0.5),
# 标签类别数是10
nin_block(384, 10, kernel_size=3, strides=1, padding=1),
nn.AdaptiveAvgPool2d((1, 1)),
# 将四维的输出转成二维的输出,其形状为(批量大小,10)
nn.Flatten()
)
2.4 含并行连结的网络(GoogLeNet)
2.4.1 Inception块
GoogLeNet吸收了NiN中串联网络的思想,基本的卷积块称为Inception块(Inception Block),相当于一个有4条路径的子网络,通过不同窗口形状的卷积层和最大汇聚层来并行抽取信息,并使用1\times 1卷积层减少每像素级别上的通道维数从而降低模型复杂度。

class Inception(nn.Module):
"""Inception块"""
def __init__(self, in_channels, c1, c2, c3, c4, **kwargs):
"""c1--c4是每条路径的输出通道数"""
super(Inception, self).__init__(**kwargs)
# 线路1,单1x1卷积层
self.p1_1 = nn.Conv2d(in_channels, c1, kernel_size=1)
# 线路2,1x1卷积层后接3x3卷积层
self.p2_1 = nn.Conv2d(in_channels, c2[0], kernel_size=1)
self.p2_2 = nn.Conv2d(c2[0], c2[1], kernel_size=3, padding=1)
# 线路3,1x1卷积层后接5x5卷积层
self.p3_1 = nn.Conv2d(in_channels, c3[0], kernel_size=1)
self.p3_2 = nn.Conv2d(c3[0], c3[1], kernel_size=5, padding=2)
# 线路4,3x3最大汇聚层后接1x1卷积层
self.p4_1 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)
self.p4_2 = nn.Conv2d(in_channels, c4, kernel_size=1)
def forward(self, x):
p1 = F.relu(self.p1_1(x))
p2 = F.relu(self.p2_2(F.relu(self.p2_1(x))))
p3 = F.relu(self.p3_2(F.relu(self.p3_1(x))))
p4 = F.relu(self.p4_2(self.p4_1(x)))
# 在通道维度上连结输出
return torch.cat((p1, p2, p3, p4), dim=1)
2.4.2 GoogLeNet模型
GoogLeNet在2014年的ImageNet图像识别挑战赛中大放异彩,和它的后继者们一度是ImageNet上最有效的模型之一:它以较低的计算复杂度提供了类似的测试精度。如下图所示,一共使用9个Inception块和其他层的堆叠来生成其估计值。

class GoogLeNet(nn.Module):
def __init__(self):
super(Inception, self).__init__()
# 1个64通道、7x7的卷积层
self.b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
# 2个卷积层:第1个为64个通道、1x1卷积层,第2个使用将通道数量增加三倍的3x3卷积层
# 与Inception块中的第二条路线相同
self.b2 = nn.Sequential(nn.Conv2d(64, 64, kernel_size=1),
nn.ReLU(),
nn.Conv2d(64, 192, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
# 2个Inception块
self.b3 = nn.Sequential(Inception(192, 64, (96, 128), (16, 32), 32),
Inception(256, 128, (128, 192), (32, 96), 64),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
# 5个Inception块
self.b4 = nn.Sequential(Inception(480, 192, (96, 208), (16, 48), 64),
Inception(512, 160, (112, 224), (24, 64), 64),
Inception(512, 128, (128, 256), (24, 64), 64),
Inception(512, 112, (144, 288), (32, 64), 64),
Inception(528, 256, (160, 320), (32, 128), 128),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
# 2个Inception块
self.b5 = nn.Sequential(Inception(832, 256, (160, 320), (32, 128), 128),
Inception(832, 384, (192, 384), (48, 128), 128))
# 全局平均汇聚层+最终输出
self.out = nn.Sequential(nn.AdaptiveAvgPool2d((1,1)),
nn.Flatten(),
nn.Linear(1024, 10))
def forward(self, x):
x = self.out(self.b5(self.b4(self.b3(self.b2(self.b1(x))))))
return x
2.5 批量规范化
批量规范化(Batch Normalization)可持续加速深层网络的收敛速度。利用小批量的均值和标准差,不断调整神经网络的中间输出,使整个神经网络各层的中间输出值更加稳定。
设\mathbf{x} \in \mathcal{B}为一个来自给定样本均值为\hat \mu_\mathcal{B}、标准差为\hat \sigma_\mathcal{B}的小批量\mathcal{B}的输入,则批量规范化\text{BN}定义为
\text{BN}(\mathbf{x})=\mathbf{\gamma} \odot \frac{\mathbf{x}-\mathbf{\hat \mu_\mathcal{B}}}{\mathbf{\hat \sigma_\mathcal{B}}}+\mathbf{\beta}
其中\mathbf{\gamma}为拉伸(Scale),\mathbf{\beta}为偏移(Shift),它们的形状与\mathbf{x}相同,会与其他模型参数一起学习。应用标准化后,生成的小批量的平均值为0、单位方差为1。由于在训练过程中,中间层的变化幅度不能过于剧烈,而批量规范化将每一层主动居中并将它们重新调整为给定的平均值和大小。
可以计算出\mathbf{\hat \mu_\mathcal{B}}和\mathbf{\hat \sigma_\mathcal{B}}(在方差估计值中添加一个噪声\epsilon>0,以确保上式中除数始终非零),即
\mathbf{\hat \mu_\mathcal{B}}=\frac 1{|\mathcal{B}|}\sum\limits_{\mathbf{x} \in \mathcal{B}}\mathbf{x}\\
\mathbf{\hat \sigma^2_\mathcal{B}}=\frac 1{|\mathcal{B}|}\sum\limits_{\mathbf{x} \in \mathcal{B}}(\mathbf{x}-\mathbf{\hat \mu_\mathcal{B}})^2+\epsilon
对于全连接层,常将批量规范化层置于仿射变换和激活函数之间,形如\mathbf{h}=\text{ReLU}(\text{BN}(\mathbf{Wx}+\mathbf{b}))。
对于卷积层,常在卷积层之后和非线性激活函数之前应用批量规范化。当卷积有多个输出通道时,需要对这些通道的每个输出执行批量规范化,每个通道都有自己的拉伸和偏移(均为标量)。
在训练模式(通过小批量统计数据规范化)和预测模式(通过数据集统计规范化)中,批量规范化层的计算结果也是不一样的。
# 使用nn.BatchNorm2d或1d等来实现,需传入相应的通道数
net = nn.Sequential(
nn.Conv2d(1, 6, kernel_size=5), nn.BatchNorm2d(6), nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2),
nn.Conv2d(6, 16, kernel_size=5), nn.BatchNorm2d(16), nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(),
nn.Linear(256, 120), nn.BatchNorm1d(120), nn.Sigmoid(),
nn.Linear(120, 84), nn.BatchNorm1d(84), nn.Sigmoid(),
nn.Linear(84, 10)
)
2.6 残差网络(ResNet)
2.6.1 函数类
设有一类特定的神经网络架构\mathcal{F},所有f \in \mathcal{F}可由训练得到。设f^*为希望找到的函数,最理想情况为f^* \in \mathcal{F},但现实往往不会那么理想,因此常改为找\mathcal{F}中的最佳选择f^*_\mathcal{F},设其可由\mathbf{X}特性和\mathbf{y}标签的的数据集训练得到,则
f^*_\mathcal{F} := \argmax\limits_f L(\mathbf{X},\mathbf{y},f) \text{ subject to } f \in \mathcal{F}
要想得到更近似真正的f^*的函数,需要设计一个更强大的架构\mathcal{F'},使得f^*_\mathcal{F'}比f^*_\mathcal{F}更近似。
如下图所示,设复杂度由\mathcal{F}_{1}向\mathcal{F}_{6}递增。对于非嵌套函数(Non-nested Function)类, 常有\mathcal{F}_{i-1} \nsubseteq \mathcal{F}_{i},无法保证新的体系“更近似”,较复杂的函数类并不总向f^*靠拢。但对于嵌套函数(Nested Function)类,总有\mathcal{F}_{1} \subseteq \mathcal{F}_{2} \subseteq \cdots \subseteq \mathcal{F}_{6},可以避免上述问题。

因此,只有当较复杂的函数类包含较小的函数类时,才能确保提高它们的性能。 对于深度神经网络,若能将新添加的层训练成恒等映射(identity function)f(\mathbf{x})=\mathbf{x},新模型和原模型将同样有效。同时由于新模型可能得出更优的解来拟合训练数据集,因此添加层似乎更容易降低训练误差。
综上,何恺明等人提出了残差网络(ResNet),其在2015年的ImageNet图像识别挑战赛夺魁,并深刻影响了后来的深度神经网络的设计。
2.6.2 残差块
ResNet的核心思想:每个附加层都应该更容易地包含原始函数作为其元素之一。由此便产生了残差块(Residual Blocks)。
设原始输入为\mathbf{x},希望学成的理想映射为f(\mathbf{x})。如下图所示,正常块直接拟合出该映射\mathbf{x},而残差块则拟合出残差映射f(\mathbf{x})-\mathbf{x}。残差映射可以更容易地学习同一函数,例如将权重层中的参数近似为零。
利用残差块可以训练出一个更有效的深层神经网络:输入可通过跨层数据线路更快地向前传播。

ResNet的残差块沿用了VGG完整的3\times 3卷积层设计,可以选择加入快速通道(1\times1卷积层),如下所示


class Residual(nn.Module):
"""残差块"""
def __init__(self, input_channels, num_channels, use_1x1conv=False, strides=1):
super().__init__()
self.conv1 = nn.Conv2d(input_channels, num_channels,
kernel_size=3, padding=1, stride=strides)
self.conv2 = nn.Conv2d(num_channels, num_channels,
kernel_size=3, padding=1)
if use_1x1conv:
self.conv3 = nn.Conv2d(input_channels, num_channels,
kernel_size=1, stride=strides)
else:
self.conv3 = None
self.bn1 = nn.BatchNorm2d(num_channels)
self.bn2 = nn.BatchNorm2d(num_channels)
def forward(self, X):
Y = F.relu(self.bn1(self.conv1(X)))
Y = self.bn2(self.conv2(Y))
if self.conv3:
X = self.conv3(X)
Y += X
return F.relu(Y)
2.6.3 ResNet-18模型
ResNet-18的前两层跟之前介绍的GoogLeNet中的一样,不同之处在于ResNet每个卷积层后增加了批量规范化层。GoogLeNet在后面接了4个由Inception块组成的模块。 ResNet则使用4个由残差块组成的模块,每个模块使用若干个同样输出通道数的残差块。

class ResNet(nn.Module):
"""ResNet-18"""
def __init__(self):
super(ResNet, self).__init__()
self.b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),
nn.BatchNorm2d(64), nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
self.b2 = nn.Sequential(*resnet_block(64, 64, 2, first_block=True))
self.b3 = nn.Sequential(*resnet_block(64, 128, 2))
self.b4 = nn.Sequential(*resnet_block(128, 256, 2))
self.b5 = nn.Sequential(*resnet_block(256, 512, 2))
self.out = nn.Sequential(nn.AdaptiveAvgPool2d((1,1)),
nn.Flatten(),
nn.Linear(512, 10))
def resnet_block(self, input_channels, num_channels, num_residuals, first_block=False):
"""生成残差块"""
blk = []
for i in range(num_residuals):
if i == 0 and not first_block:
blk.append(Residual(input_channels, num_channels,
use_1x1conv=True, strides=2))
else:
blk.append(Residual(num_channels, num_channels))
return blk
def forward(self, x):
x = self.out(self.b5(self.b4(self.b3(self.b2(self.b1(x))))))
return x
2.7 稠密连接网络(DenseNet)
2.7.1 逻辑拓展
根据任意函数的泰勒展开式(Taylor expansion),即当x \rightarrow 0时,有
f(x)=f(0)+f'(0)x+\frac{f''(0)}{2!}x^2+\frac{f'''(0)}{3!}x^3+\cdots
ResNet将函数展开为f(\mathbf{x})=\mathbf{x}+g(\mathbf{x}),即将f分解为一个简单的线性项和一个复杂的非线性项。要想继续拓展成更多项,需使用DenseNet,如下所示

ResNet和DenseNet的关键区别在于,DenseNet输出是连结([,])而不是如ResNet的简单相加。 因此在应用越来越复杂的函数序列后,执行从\mathbf{x}到其展开式的映射
\mathbf{x}\rightarrow[\mathbf{x},f_1(\mathbf{x}),f_2([\mathbf{x},f_1(\mathbf{x})]),f_3(\mathbf{x},f_1(\mathbf{x}),f_2([\mathbf{x},f_1(\mathbf{x})])),\cdots]
最后将这些展开式“稠密连接”起来结合到多层感知机中再次减少特征的数量。

稠密连结网络(DenseNet)在某种程度上是ResNet的逻辑扩展,使用了ResNet改良版的“批量规范化、激活和卷积”架构,主要由2部分构成:稠密块(定义如何连接输入和输出)、过渡层(控制通道数量,使其不会太复杂)
2.7.2 稠密块体
一个稠密块(Dense Block)由多个卷积块组成,每个卷积块使用相同数量的输出通道。 在前向传播中将每个卷积块的输入和输出在通道维上连结。
class DenseBlock(nn.Module):
"""稠密块"""
def __init__(self, num_convs, input_channels, num_channels):
super(DenseBlock, self).__init__()
layer = []
for i in range(num_convs):
layer.append(conv_block(num_channels * i + input_channels, num_channels))
self.net = nn.Sequential(*layer)
def conv_block(input_channels, num_channels):
"""返回一个卷积块"""
return nn.Sequential(nn.BatchNorm2d(input_channels),
nn.ReLU(),
nn.Conv2d(input_channels, num_channels, kernel_size=3, padding=1))
def forward(self, X):
for blk in self.net:
Y = blk(X)
# 连接通道维度上每个块的输入和输出
X = torch.cat((X, Y), dim=1)
return X
2.7.3 过渡层
由于每个稠密块都会带来通道数的增加,使用过多则会过于复杂化模型。
过渡层(Transition Layer)通过1\times1卷积层来减小通道数,并使用步幅为2的平均汇聚层减半高和宽,从而减少通道的数量,降低模型复杂度。
def transition_block(input_channels, num_channels):
"""返回一个过渡层"""
return nn.Sequential(nn.BatchNorm2d(input_channels), nn.ReLU(),
nn.Conv2d(input_channels, num_channels, kernel_size=1),
nn.AvgPool2d(kernel_size=2, stride=2))
2.7.4 DenseNet模型
DenseNet与ResNet类似,使用一样的单卷积层和最大汇聚层
class DenseNet(nn.Module):
def __init__(self):
super(DenseNet, self).__init__()
b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),
nn.BatchNorm2d(64), nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
# num_channels为当前的通道数
num_channels, growth_rate = 64, 32
num_convs_in_dense_blocks = [4, 4, 4, 4]
blks = []
for i, num_convs in enumerate(num_convs_in_dense_blocks):
blks.append(DenseBlock(num_convs, num_channels, growth_rate))
# 上一个稠密块的输出通道数
num_channels += num_convs * growth_rate
# 在稠密块之间添加一个转换层,使通道数量减半
if i != len(num_convs_in_dense_blocks) - 1:
blks.append(transition_block(num_channels, num_channels // 2))
num_channels = num_channels // 2
self.net = nn.Sequential(b1, *blks,
nn.BatchNorm2d(num_channels), nn.ReLU(),
nn.AdaptiveAvgPool2d((1, 1)),
nn.Flatten(),
nn.Linear(num_channels, 10))
def forward(self, x):
x = self.net(x)
return x
3 改进模型泛化的方法
3.1 图像增强
图像增强对训练图像进行一系列的随机变化,生成相似但不同的训练样本,增强训练集的多样性,从而提高模型的泛化能力。

常用的图像增强方法
详见torchvision.transforms教程
- 翻转:翻转图像。不总是可行。
# 左右翻转 torchvision.transforms.RandomHorizontalFlip() # 上下翻转 torchvision.transforms.RandomVerticalFlip() - 切割:从图片中切割一块,然后变形到固定形状
# 随机高宽比、大小、位置 torchvision.transforms.RandomResizedCrop((200, 200), scale=(0.1, 1), ratio=(0.5, 2)) - 变色:改变亮度、对比度、饱和度和色调
# 随机亮度、对比度、饱和度和色调 torchvision.transforms.ColorJitter(brightness=0.5, contrast=0, saturation=0, hue=0) - ……
3.2 微调
一个神经网络一般可以分成两块:特征抽取(将原始像素变成容易线性分割的特征)、线性分类器(用来做分类)。
微调(Fine-tuning)通过使用在大数据上得到的预训练好的模型来初始化模型权重来完成提升精度。步骤如下:
- 在源数据集(例如ImageNet数据集)上预训练神经网络模型,即源模型。
- 创建一个新的神经网络模型,即目标模型,将源模型上的所有模型设计及其参数(输出层除外)复制至其中。
- 向目标模型添加输出层,其输出数是目标数据集中的类别数。然后随机初始化该层的模型参数。
- 在目标数据集(如椅子数据集)上训练目标模型。输出层将从头开始进行训练,而所有其他层的参数将根据源模型的参数进行微调。

预训练模型质量很重要。微调通常速度更快、精度更高。
4 目标检测基本策略
目标检测(Object Detection)或目标识别(Object Recognition)比图像分类更加复杂,需要进行多个物体的识别,还要找出每个物体的位置。目标检测的应用场景也更多。
4.1 边界框
边界框(Bounding Box)用来描述对象的空间位置
(1)通过矩形左上角的以及右下角的坐标决定:(x_\text{左上},y_\text{左上},x_\text{左下},y_\text{左下})
(2)通过边界框中心的坐标以及框的宽度和高度决定:(x_\text{中心},y_\text{中心},w,h)

目标检测算法通常会在输入图像中采样大量的区域,然后判断这些区域中是否包含我们感兴趣的目标,并调整区域边界从而更准确地预测目标的真实边界框(Ground-truth Bounding Box)。
4.2 锚框
预测真实边界框其中一种方法为:以每个像素为中心,生成多个缩放比(Scale)s和宽高比(Aspect Ratio)r不同的边界框, 这些边界框称为锚框(Anchor Box)。
生成锚框的方法:
(1)以图像的每个像素为中心生成不同形状的锚框:设输入图像的高度为h、宽度为w,缩放比s\in (0,1],宽高比r>0,则锚框的宽度和高度分别为hs\sqrt{r}和\frac{hs}r。 注意当中心位置给定时,已知宽和高的锚框是确定的。
(2)要生成多个不同形状的锚框,需设置许多缩放比s_1,\cdots,s_n和许多宽高比r_1,\cdots,r_m。 当使用这些比例和长宽比的所有组合以每个像素为中心时,输入图像将共有whnm个锚框。 在实践中常只考虑包含s_1或r_1的组合,即
(s_1,r_1),(s_1,r_2),\cdots,(s_1,r_m),(s_2,r_1),(s_3,r_1),\cdots,(s_n,r_1)
此时以同一像素为中心的锚框的数量为n+m-1,整个输入图像将共生成wh(n+m-1)个锚框。

4.3 交并比(IoU)
杰卡德系数(Jaccard)可以衡量两组之间的相似性。 给定集合\mathcal{A}和\mathcal{B},其杰卡德系数为他们交集的大小除以他们并集的大小,即
J(\mathcal{A},\mathcal{B})=\frac{|\mathcal{A} \cap \mathcal{B}|}{|\mathcal{A} \cup \mathcal{B}|}
可以将任何边界框的像素区域视为一组像素,两个像素区域集合的杰卡德系数称之为交并比(Intersection over Union,IoU)。易知交并比的取值范围为[0,1],其中0表示无重叠,1表示重合。

4.4 赋予锚框标号
在训练集中将每个锚框视为一个训练样本,为了训练目标检测模型,需要每个锚框的类别(Class)和偏移量(Offset)标签,其中前者是与锚框相关的对象的类别,后者是真实边界框相对于锚框的偏移量。
4.4.1 将真实边界框分配给锚框
给定图像,设锚框A_1,A_2,\cdots,A_{n_a},真实边界B_1,B_2,\cdots,B_{n_b},其中n_a≥n_b。定义矩阵\mathbf{X}\in \mathbb{R}^{n_a\times n_b},其第i行第j列的元素x_{ij}是锚框A_I和真实边界框B_j的IoU。将真实边界框分配给锚框的算法步骤如下
(1)在矩阵\mathbf{X}中找最大的元素,其行、列索引分别为i和j。将真实边界框B_{j}分配给锚框A_{i},然后丢弃第i行和第j列中所有元素。
因为这对是所有锚框和真实边界框配对中最相近的
(2)重复上一步操作,直至丢光n_b列中的所有元素,此时已经为n_b个锚框各分配了一个真实边界框。
(3)遍历剩下的n_a-n_b个锚框:对于锚框A_i,在矩阵\mathbf{X}第i行中找到与A_i的IoU最大的真实边界框B_j,只有当此IoU大于预定义的阈值时才将B_j分配给A_i。

4.4.2标记类别和偏移量
设锚框A被分配了真实边界框B。A的类别应与B相同;A的偏移量将根据A和B中心坐标的相对位置以及这两个框的相对大小进行标记,设A和B的中心坐标分别为(x_a,y_a),(x_b,y_b),宽度分别为w_a,w_b,高度分别为h_a,h_b,则A的偏移量标记为
(\frac{\frac{x_b-x_a}{w_a}-\mu_x}{\sigma_x},\frac{\frac{y_b-y_a}{h_a}-\mu_y}{\sigma_y},\frac{\log\frac{w_b}{w_a}-\mu_w}{\sigma_w},\frac{\log\frac{h_b}{h_a}-\mu_h}{\sigma_h})
其中常量的默认值为\mu_x=\mu_y=\mu_w=\mu_h=0,\sigma_x=\sigma_y=0.1,\sigma_w=\sigma_h=0.2
4.5 使用非极大值抑制(NMS)预测边界框
当有许多锚框时,可能会输出许多相似的具有明显重叠的预测边界框,都围绕着同一目标。
非极大值抑制(Non-maximum Suppression,NMS):合并属于同一目标的类似的预测边界框,从而简化输出。
对于一个预测边界框B,目标检测模型会计算每个类别的预测概率,则最大预测概率p所指的类别即为B的类别,称p为预测边界框B的置信度(Confidence)。在同一张图像中,所有预测的非背景边界框都按置信度降序排序生成列表L。通过以下步骤操作对列表L进行排序:
(1)从L中选取置信度最高的预测边界框 $B$ 作为基准,将所有与B的IoU(注意是J(X, B))超过预定阈值\epsilon的非基准预测边界框从L中移除。
此时
L保留了置信度最高的预测边界框,去除了与其太过相似的其他预测边界框,即那些具有非极大值置信度的边界框被抑制了。
(2)重复上一步操作,直到L中的所有预测边界框都曾被用作基准。
此时
L中任意一对预测边界框的IoU都小于预定阈值\epsilon,因此没有一对边界框过于相似。
(3)输出列表L中的所有预测边界框。
过程变化如下图所示

对于多尺度目标检测,可以利用深层神经网络在多个层次上对图像进行分层表示来实现,参见SSD。
4.6 区域卷积神经网络(R-CNN)系列
区域卷积神经网络(Region-based CNN 或 Regions with CNN features,R-CNN) 是将深度模型应用于目标检测的开创性工作之一。
4.6.1 R-CNN
原始的R-CNN首先从输入图像中选取若干锚框,并标注它们的类别和边界框(如偏移量),然后用卷积神经网络对每个提议区域进行前向传播以抽取其特征。之后用每个提议区域的特征来预测类别和边界框。具体包含以下4步骤:
- 使用选择性搜索(启发式搜索)算法来选择锚框。
- 使用预训练CNN模型来对每个锚框抽取特征。
- 将每个提议区域的特征连同其标注的类别作为一个样本,训练一个SVM(支持向量机)来对类别分类。
- 将每个提议区域的特征连同其标注的边界框作为一个样本,训练一个线性回归模型来预测边界框偏移。

尽管R-CNN模型通过预训练的卷积神经网络有效地抽取了图像特征,但它的速度很慢。庞大的计算量使得R-CNN在现实世界中难以被广泛应用。
4.6.2 Fast R-CNN
R-CNN需要对每个锚框进行CNN运算,这些特征抽取计算有重复,并且锚框数量大,特征抽取的计算量也大。
快速的R-CNN(Fast R-CNN)对R-CNN的主要改进之一,是仅在整张图象上执行卷积神经网络的前向传播。
兴趣区域池化层(Rol Pooling):使每个锚框都可以变成想要的形状。给定一个锚框,均匀分割成n\times m块,输出每块里的最大值。不管锚框多大,总是输出nm个值。

Fast R-CNN使用CNN对整张图片抽取特征(快的关键),再对每个锚框使用Rol Pooling(将在原图片中搜索到的锚框,映射到CNN得到的结果上)生成固定长度的特征。

4.6.3 Faster R-CNN
为了较精确地检测目标结果,Fast R-CNN模型通常需要在选择性搜索中生成大量的提议区域。
更快的R-CNN(Faster R-CNN)提出将选择性搜索替换为区域提议网络(Region Proposal Network)来获得更好的锚框。
Faster R-CNN将CNN结果输入到卷积层,然后生成许多锚框,这些锚框有好有坏。随后进行预测:
- 二元类别预测:预测这个锚框的好坏,即有没有有效的圈住物体
- 边界框预测:对锚框进行一些改进,最后用NMS(非极大值抑制)对锚框进行合并。

如图,Faster RCNN精度高但是速度慢

4.6.4 Mask R-CNN
掩码R-CNN(Mask R-CNN)引入了一个全卷积网络(FCN,详见4.3),能够有效地利用在训练集中标注的每个目标在图像上的像素级位置进一步提升目标检测的精度。
Mask R-CNN将兴趣区域池化层替换为了兴趣区域对齐层,使用双线性插值(Bilinear Interpolation)来保留特征图上的空间信息,从而更适于像素级预测。

4.7 单发多框检测(SSD)
单发多框检测(SSD)是一种多尺度目标检测模型。基于基础网络块和各个多尺度特征块,SSD生成不同数量和不同大小的锚框,并通过预测这些锚框的类别和偏移量检测不同大小的目标。
- 通过单个基础网络从输入图像中提取特征,常用VGG、ResNet等卷积神经网络。
- 其后的几个多尺度特征块将上一层提供的特征图的高和宽缩小(如减半)。
- 底部段来拟合小物体,顶部段来拟合大物体。
- 以每个像素为中心产生多个锚框,对每个锚框预测类别和边缘框。

如图,与R-CNN系列相反,SSD模型速度快但精度不高

4.8 YOLO ⭐️
持续更新中
SSD中锚框大量重叠,因此浪费了很多计算。
YOLO(You Only Look Once)将图片均匀分成S\times S个锚框,每个锚框预测B个边缘框。后续版本(V2、V3、V4、……)有持续改进。

如图,相同精度下YOLO比SSD速度快

5 语义分割
语义分割(Semantic Segmentation)将图像分割成属于不同语义类别的区域,其语义区域的标注和预测是像素级的。

计算机视觉领域中2个与语义分割相似的重要问题:
- 图像分割(Image Segmentation):将图像划分若干组成区域,这类问题的方法通常利用图像中像素之间的相关性。
- 实例分割/同时检测并分割(Simultaneous Detection and Segmentation)研究如何识别图像中各个目标实例的像素级区域。
语义分割的一个重要的数据集为Pascal VOC2012。
由于语义分割的输入图像和标签在像素上一一对应,输入图像会被随机裁剪为固定尺寸而不是缩放。
5.1 转置卷积
卷积不会增大输入的高宽,通常要么不变、要么减半。
转置卷积(Transposed Convolution)可以用来增大输入高宽。
5.1.1 基本操作
单通道的转置卷积基本操作可以表示为
Y[i:i+h,j:j+w] \xleftarrow{+} X[i,j]\cdot K

相关疑难解析:
- 称为“转置”的原因
- 对于卷积
Y=X*W,可以对W构造一个V,使得卷积等价于矩阵乘法Y'=VX',其中Y',X'是Y,X对应的向量版本。
转置卷积则等价于Y'=V^\text{T}X'。
- 对于卷积
- 转置卷积不是反卷积
- 数学上的反卷积(Deconvolution)是指卷积的逆运算——若
Y=\text{conv}(X,K),则X=\text{deconv}(Y,K)。反卷积很少用在深度学习中,所谓的反卷积神经网络通常指的是用了转置卷积的神经网络。 - 常规卷积将填充应用于输入,而转置卷积将填充应用于的输出。
- 数学上的反卷积(Deconvolution)是指卷积的逆运算——若

5.1.2 重新排列输入和核
转置卷积是一种卷积,它将输入和核进行了重新排列。同卷积一般是做下采样不同,它通常用作上采样。若卷积将输入从(h,w)变成了(h',w'),则同样超参数的转置卷积为从(h',w')变成(h,w)。
- 当填充为0、步幅为1时:
- 将输入填充
k-1(k为核窗口) - 将核矩阵上下、左右翻转
- 然后做正常卷积(填充0、步幅1)
- 将输入填充

- 当填充为
p、步幅为1时:- 将输入填充
k-p-1(k为核窗口) - 将核矩阵上下、左右翻转
- 然后做正常卷积(填充0、步幅1)
- 将输入填充

- 当填充为
p、步幅为s时:- 在行和列之间插入
s-1行或列 - 将输入填充
k-p-1(k为核窗口) - 将核矩阵上下、左右翻转
- 然后做正常卷积(填充0、步幅1)
- 在行和列之间插入

5.1.3 形状换算
设输入高、宽为n,核窗口为k,填充为p,步幅为s,则
转置卷积:
n'=sn+k-2p-s
卷积:
n'=\lfloor \frac{n-k-2p+s}{s} \rfloor \Rightarrow n ≥ sn'+k-2p-s
若让高宽成倍增加,则k=2p+s
5.2 全连接卷积神经网络(FCN)
全连接卷积神经网络(Fully Convolutional Network,FCN,全连接网络)是用深度神经网络来做语义分割的奠基性工作,它用转置卷积层来替换CNN最后的全连接层或全局池化层,从而可以实现每个像素的预测。
FCN先使用CNN抽取图像特征,然后通过1\times 1卷积层将通道数变换为类别个数,最后通过转置卷积层将特征图的高和宽变换为输入图像的尺寸。模型如下图所示

在全卷积网络中,可以将转置卷积层初始化为双线性插值的上采样。
6 样式迁移
样式迁移(Style Transfer)使用卷积神经网络,自动将一个图像(样式图像)中的样式应用在另一图像(内容图像)之上。

基于卷积神经网络的样式迁移方法如下图所示

样式迁移常用的损失函数由3部分组成:
- 内容损失:使合成图像与内容图像在内容特征上接近。
- 样式损失:令合成图像与样式图像在样式特征上接近。
- 全变分损失:有助于减少合成图像中的噪点。
可以通过预训练的卷积神经网络来抽取图像的特征,并通过最小化损失函数来不断更新合成图像来作为模型参数。
《计算机视觉基础:卷积神经网络(CNN)、图像处理》有2条评论