目录
-
提出ResNet模型,深度残差神经网络模型。
-
论文发表于2015年,2016年CVPR最佳论文
-
作者何恺明、孙剑、张翔宇、任少卿(微软亚洲研究院)
参考:
ResNet为什么能解决网络退化问题 - 同济子豪兄 - bilibili
7.6. 残差网络(ResNet) — 动手学深度学习 2.0.0 documentation (d2l.ai)
解决了什么问题
针对很深的神经网络模型很难训练这一问题,即网络退化问题(degradation)从实验数据可以看到,仅仅堆叠神经网络的层数形成的网络模型,在测试集上的表现还如不较少层的模型。
使用残差神经网络后的情况如下
采用了什么方法
identity mapping:恒等映射
弧线为shortcut connection
为什么能起作用
直观理解
新加入的层可能会对模型的学习产生负面效果,也可能会产生正面效果,而使用残差网络,若新加入的层产生了负面效果就回退到原来的层,相当于直接忽略掉这一层的作用。用于解决梯度消失和梯度下降问题,使更深层的网络变得容易学习。
-
传统线性结构网络难以拟合 “恒等映射”,什么都不做有时候很重要,skip connections可以让模型自行选择要不要更新
-
恒等映射这一路的梯度为1,防止梯度消失
在反向传播层层计算梯度的时候,每层计算的导数都很小,链式法则相乘之后梯度就变得几乎为0,即梯度消失,导致参数不能更新,网络停止学习。
-
相当于多个网络的集成(类似dropout)
而且剪掉右图中的几条路径并不影响整个网络的表现
-
没什么好解释的,尝试出来的结果好就行了
(炼丹嘛,不寒掺)
网络架构
在ImageNet数据集上,应用了152层的深度残差神经网络,下图为34层Resnet
-
输入图片
3通道224x224
-
每一个卷积层之后应用批量归一化(batch normalization),激活函数之前
-
当输入和输出维度相同时可以直接走shortcut connections(图中实线)
当输入维度和输出维度不同时,有两种方法:1、添0 2、使用1x1的卷积改变通道数
-
注意两个conv的接口处要完成两项任务,这两项任务是在定义网络时通过stride和padding实现的
- 图片长宽尺寸变为原来的1/2
- 通道数变为原来的2倍
复现网络模型
整体结构
整个残差网络模型的构建包括3部分
-
残差块
2个卷积层以及批量归一化和激活函数,包括对加x的处理
-
由若干残差块构成的模块
模块中的第一个残差块需要对输入进行额外处理,中间残差块保持数据维度不变
-
其他部分
预处理卷积层和全连接层
定义残差块
一个残差块由两个卷积层构成,残差块中没有池化,但是有批量归一化BN和激活函数,其结构如下
注意:BN先于ReLU使用,这个图中没有表现出来
残差块有两种类型
- 大层次内部的残差块
- 大层次交接处第一个残差块
内部残差块输入维度和输出维度完全相同,且要加上x的维度和第二个卷积层输出维度完全相同,直接相加即可
交接处第一个残差块相比于内部残差块要多完成2项任务
- 改变输入维度,尺寸变为原来的1/2,通道数变为原来的2倍
- 输入x的维度与第二个卷积层输出的维度不同,要先对x使用1x1的卷积变换尺寸和通道数才能与第二个卷积层的输出相加
pytorch实现代码如下
import torch
from torch import nn
class Residual(nn.Module):
def __init__(self, input_channels, output_channels, use_1x1conv=False, strides=1):
super(Residual, self).__init__()
# conv1: 实例化Residual时如果没指定默认s=1,可以指定s=2,根据公式使得图片尺寸变为原来的1/2
self.conv1 = nn.Conv2d(input_channels, output_channels, kernel_size=3, padding=1, stride=strides)
# conv2: stride默认为1,padding=1,根据公式conv2输出和输入维度完全相同
self.conv2 = nn.Conv2d(output_channels, output_channels, kernel_size=3, padding=1)
# 实例化此类时如果指定use_1x1conv,则表明本层是两大层交接处,需要改变x的尺寸和通道数保证和输出维度相同
if use_1x1conv:
self.conv3 = nn.Conv2d(input_channels, output_channels, kernel_size=1, stride=strides)
else:
self.conv3 = None
self.bn1 = nn.BatchNorm2d(output_channels)
self.bn2 = nn.BatchNorm2d(output_channels)
def forward(self, x):
y = nn.ReLU(self.bn1(self.conv1(x)))
y = self.bn2(self.conv2(y))
if self.conv3:
x = self.conv3(x)
y = y + x
return nn.ReLU(y)
定义模块
实现代码如下
def resnet_block(input_channels, output_channels, num_residuals, first_block=False):
blk = [] # blk为网络中模块的集合
for i in range(num_residuals):
if i == 0 and not first_block: # 模块中的第一个残差块,第一个模块的第一个残差块不需要对输入做变换处理
blk.append(Residual(input_channels, output_channels, use_1x1conv=True, strides=2))
else: # 模块中的中间残差块保持数据维度不变,注意此处输入输出通道数均为output_channels
blk.append(Residual(output_channels, output_channels))
return blk
# 以下定义了论文中提到的34层残差网络
# b1为预处理卷积和池化操作
b1 = nn.Sequential(
nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3),
nn.BatchNorm2d(64), nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
)
b2 = nn.Sequential(*resnet_block(64, 64, 6, first_block=True)) # 第一个模块
b3 = nn.Sequential(*resnet_block(64, 128, 8))
b4 = nn.Sequential(*resnet_block(128, 256, 12))
b5 = nn.Sequential(*resnet_block(256, 512, 6))
net = nn.Sequential(b1, b2, b3, b4, b5,
nn.AdaptiveAvgPool2d((1, 1)),
nn.Flatten(), nn.Linear(512, 1000))
测试
X = torch.rand(size=(1, 3, 224, 224))
for layer in net:
X = layer(X)
print(layer.__class__.__name__, 'output shape:\t', X.shape)
运行结果
Sequential output shape: torch.Size([1, 64, 56, 56])
Sequential output shape: torch.Size([1, 64, 56, 56])
Sequential output shape: torch.Size([1, 128, 28, 28])
Sequential output shape: torch.Size([1, 256, 14, 14])
Sequential output shape: torch.Size([1, 512, 7, 7])
AdaptiveAvgPool2d output shape: torch.Size([1, 512, 1, 1])
Flatten output shape: torch.Size([1, 512])
Linear output shape: torch.Size([1, 1000])
标签:nn,Image,Residual,Deep,残差,channels,维度,output,self
From: https://www.cnblogs.com/dctwan/p/17091545.html