背景/问题提出
深度网络融合浅层/中层/高层提取到的特征进行分类,而且是以端到端训练的方式,特征等级随网络叠加变得丰富。
网络深度至关重要。
“学习一个更好的网络是否和堆叠更多的层一样容易?”,过深的网络会有以下问题:
从 浅层模型 构造 深层模型 的方案:添加层时恒等映射,其他层直接拷贝。
这种构造深层模型方法训练误差不比浅层大(增加的层起到的作用是恒等映射),但是也不能找到等优或更优解(或者不能在规定时间内找到)。拟合一个恒等映射代价大效果差
深度残差网络,期望几个堆叠层拟合一个残差函数 F F F,而不是原本存在的函数 H H H
假设映射函数 H ( x ) H(x) H(x)是我们最终期望的获得的,堆叠的非线性层去拟合 F ( x ) = H ( x ) − x F(x)=H(x)-x F(x)=H(x)−x,那么原映射函数变成 H ( x ) = F ( x ) + x H(x)=F(x)+x H(x)=F(x)+x。假设优化残差映射 F ( x ) F(x) F(x)比优化原始映射 H ( x ) H(x) H(x)要容易
F ( x ) + x F(x)+x F(x)+x可以通过在前向网络捷径连接实现,残差块如下图:
捷径可以跨一层,也可以跨多层;可以是恒等映射,也可以是投影映射,恒等映射既没有引入额外参数,又没有增加计算复杂度
实验说明/贡献
ImageNet上验证
CIFAR-10、ILSVRC、COCO等若干数据集和比赛效果都不错,模型不限定在特定的数据集上
残差表示:VLAD、Fisher Vector编码残差向量(两者都是用于图像检索和分类的强大浅层表示)。求解偏微分方程,用分层预处理(依赖于表示两个标度之间的残差矢量的变量)替代多重网格法,收敛速度更快。
捷径连接:通过捷径连接实现中间层响应,梯度和传播误差的方法;“起始”层由捷径分支和更深的分支组成;带有门函数的highway network
(门与数据相关且有参数)。
令 H ( x ) H(x) H(x)表示为堆叠层拟合表示的映射, x x x表示第一层的输入。假设多非线性层可以逼近相近的复杂函数,它们就能渐近逼近残差函数 H ( x ) − x H(x)-x H(x)−x,令 F ( x ) = H ( x ) − x F(x)=H(x)-x F(x)=H(x)−x,那么 H ( x ) = F ( x ) + x H(x)=F(x) + x H(x)=F(x)+x。尽管两种形式都应能渐近地逼近所需的函数,但学习的难易程度可能有所不同。
添加的层如果是拟合恒等映射,那么较深的模型的训练集错误率不会比较浅模型高。但是用多个非线性层去拟合恒等变换更加困难。(举例,如果恒等映射是最优的,那么残差函数接近零就能实现,好于用非线性层拟合一个简单的恒等映射)
恒等映射, x x x和 F ( x ) F(x) F(x)维数相等时
y = F ( x , { W i } ) + x y=F(x,\{W_i\})+x y=F(x,{Wi})+x
x , y x,y x,y分别是输入向量和输出向量
F ( x , { W i } ) F(x,\{W_i\}) F(x,{Wi})表示要学习的残差映射,如下图
这样的形式,没有引入额外参数或计算复杂性。
非恒等映射, x x x和 F ( x ) F(x) F(x)维数不相等时
y = F ( x , { W i } ) + W s x y=F(x,\{W_i\})+W_sx y=F(x,{Wi})+Wsx
残差函数 F F F说明
用于ImageNet的两个模型:
baselines
在上述普通网络的基础上,插入捷径连接,形成相应的残差网络
当输入和输出的尺寸相同时,使用恒等映射
当维度增加时(特征通道数变多,单个通道的尺寸减半),有两种选择:
34层比18层训练集上错误率反而上升了,发生了退化现象,由于BN,不可能是梯度消失引起的。
推测,深层普通网络的收敛速度可能呈指数级降低,这会影响训练误差的降低(未来研究)
基线架构与上述普通网络相同,除了添加了捷径连接实现相应的残差网络。
网络配置
捷径连接都使用恒等映射
对增加的维度使用零填充
所以没有额外的参数
观察到的结论:纵向比较,没有退化;横向比较,误差降低;时间比较,收敛更快。
捷径连接方案选择,以下三种方案:
性能上:3 > 2 > 1,但是都差不多。作者用的是第二种。捷径连接方式的不同对解决退化问题并非至关重要。
对于每个残差函数 F F F,使用3层堆叠。 其中1x1卷积层负责改变通道数(先变小,再变大)不改变尺寸,3x3卷积层更改尺寸。3x3层通道数少,1x1多,所以称为瓶颈。
无参数恒等连接对于瓶颈结构特别重要。 如果将图中的恒等映射替换为投影,则时间复杂度和模型大小增加了一倍。 因此,恒等映射方式可以为瓶颈设计提供更有效的模型。
较浅网络用2层,较深网络用3层
34层基础上,3层瓶颈结构,方案2。
使用更多的3层模块来构建101层和152层ResNet。
尽管深度显着增加,但152层ResNet的复杂度仍低于VGG-16/19网络。
50/101/152层ResNet比34层ResNet准确度高得多。
关注点是极端深度网络的行为而不是针对结果,所以使用简单的网络。
都使用恒等映射、零填充方案。
不是很懂这张图含义与意义
将n设置为200,形成1202层网络。 结果显示:没有优化困难,该千层网络能够实现训练误差小于0.1%。 其测试误差仍然相当不错。
但是,仍然存在未解决的问题。 尽管1202层网络和110层网络的训练误差相似,但其测试结果却比110层网络的测试结果差。 作者认为这是由于过拟合。 对于该数据集,1202层网络可能会不必要地大。 应用强正则化(例如maxout 或dropout )以在此数据集上获得最佳结果。 我们不使用maxout / dropout,而只是通过设计通过深度和精简架构强加正则化,而不是将关注点分散到优化困难上。但是,结合更强的正则化可能会改善结果,我们将在以后进行研究。也不能无限叠加网络层。
在其他识别任务上泛化性能良好。
基线:PASCAL VOC 2007和2012和COCO。 采用Faster R-CNN作为检测方法。对用ResNet-101替换VGG-16,因为检测实现方式相同,因此收益只能归因于更好的网络。
最值得注意的是,在具有挑战性的COCO数据集上,COCO标准指标(mAP)提高了6.0%,相对提高了28%。 该收益完全归因于所学的表示。
基于深层残差网络,ILSVRC和COCO 2015竞赛的多个赛道上均获得了第一名:ImageNet检测,ImageNet本地化,COCO检测和COCO分割。
自己仿照Pytorch官方实现写的
class Block(nn.Module): # 跨三层结构
def __init__(self, input_channel, middle_channel, output_channel, stride=1, down_sample=None):
super(Block, self).__init__()
self.conv1 = nn.Sequential(
nn.Conv2d(input_channel, middle_channel, kernel_size=1),
nn.BatchNorm2d(middle_channel),
nn.ReLU(inplace=True)
)
self.conv2 = nn.Sequential(
nn.Conv2d(middle_channel, middle_channel, kernel_size=3, stride=stride, padding=1),
nn.BatchNorm2d(middle_channel),
nn.ReLU(inplace=True)
)
self.conv3 = nn.Sequential(
nn.Conv2d(middle_channel, output_channel, kernel_size=1),
nn.BatchNorm2d(output_channel),
)
self.down_sample = down_sample
def forward(self, x):
identity = x
x = self.conv1(x)
x = self.conv2(x)
x = self.conv3(x)
if self.down_sample:
identity = self.down_sample(identity)
out = identity + x
out = F.relu(out, inplace=True)
return out
class ResNet50(nn.Module):
def __init__(self, num_class):
super(ResNet50, self).__init__()
self.feature = nn.Sequential(
nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False),
nn.BatchNorm2d(64),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
)
def make_layer(input_channel, middle_channel, output_channel, stride, number):
down_sample = nn.Sequential(
nn.Conv2d(input_channel, output_channel, 1, stride),
nn.BatchNorm2d(output_channel)
)
layers = [Block(input_channel, middle_channel, output_channel, stride, down_sample)]
layers.extend([Block(output_channel, middle_channel, output_channel)] for _ in range(1, num_class))
return nn.Sequential(*layers)
self.layer1 = make_layer(64, 64, 256, stride=1, number=3)
self.layer2 = make_layer(256, 128, 512, stride=2, number=4)
self.layer3 = make_layer(512, 256, 1024, stride=2, number=6)
self.layer4 = make_layer(1024, 512, 2048, stride=2, number=3)
self.avg_pool = nn.AdaptiveAvgPool2d((1, 1))
self.fc = nn.Linear(2048, num_class)
def forward(self, x):
x = self.feature(x)
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
x = self.avg_pool(x)
x = x.reshape(x.size(0), -1)
x = self.fc(x)
return x