近年来,在深度学习和计算机视觉领域取得了一系列突破性进展。特别是随着非常深的卷积神经网络的引入,这些模型在图像识别和图像分类等问题上帮助实现了最先进的结果。随着时间的推移,深度学习架构变得越来越深(增加更多层)以解决越来越复杂的任务,这也有助于提高分类和识别任务的性能,并使它们更加稳健。但是,当不断向神经网络添加更多层时,训练变得非常困难,模型的准确性开始饱和,然后也会下降。这时,ResNet出现了,帮助解决了这个问题。
ResNet简介
残差网络(ResNet)是由Shaoqing Ren、Kaiming He、Jian Sun和Xiangyu Zhang在他们的论文《Deep Residual Learning for Image Recognition》中介绍的一种著名的深度学习模型。ResNet模型是迄今为止最流行和最成功的深度学习模型之一。
残差块
通过引入残差块,训练非常深的网络的问题得到了缓解,ResNet模型就是由这些块组成的。在上述图中,首先可以注意到有一个直接跳过模型一些层的连接。这个连接被称为“跳过连接”,是残差块的核心。如果没有跳过连接,输入'X'会乘以层的权重,然后加上一个偏置项。然后是激活函数f(),得到输出H(x)。H(x)=f(wx+b)或H(x)=f(x)。现在引入了新的跳过连接技术,输出H(x)被改变为H(x)=f(x)+x。但是,输入的维度可能与输出的维度不同,这可能发生在卷积层或池化层。因此,这个问题可以通过这两种方法来处理:零填充跳过连接以增加其维度;或者在输入中添加1×1卷积层以匹配维度。在这种情况下,输出是:H(x)=f(x)+w1.x。这里增加了一个额外的参数w1,而在使用第一种方法时没有增加额外的参数。
ResNet面临的挑战
梯度消失:ResNets防止梯度变得太小,允许更好地训练深度神经网络。训练难度:ResNets通过引入跳过连接,使深度神经网络更容易训练。噪声敏感性:ResNets对数据中的噪声更加鲁棒,从而提高了性能。准确性限制:ResNets在包括图像识别和自然语言处理在内的各种任务上都取得了最先进的准确性。
ResNet架构
架构中有一个34层的普通网络,灵感来自VGG-19,其中添加了快捷连接或跳过连接。这些跳过连接或残差块然后将架构转换为残差网络,如下所示。
使用Keras构建ResNet
Keras是一个开源的深度学习库,可以在TensorFlow上运行。Keras Applications提供了以下ResNet版本:ResNet50、ResNet50V2、ResNet101、ResNet101V2、ResNet152、ResNet152V2。
从头构建ResNet
让以上面的图像作为参考,开始构建网络。ResNet架构多次使用CNN块,因此让创建一个CNN块的类,它接受输入通道和输出通道。每个卷积层后都有batchnorm2d。
import torch
import torch.nn as nn
class block(nn.Module):
def __init__(self, in_channels, intermediate_channels, identity_downsample=None, stride=1):
super(block, self).__init__()
self.expansion = 4
self.conv1 = nn.Conv2d(in_channels, intermediate_channels, kernel_size=1, stride=1, padding=0, bias=False)
self.bn1 = nn.BatchNorm2d(intermediate_channels)
self.conv2 = nn.Conv2d(intermediate_channels, intermediate_channels, kernel_size=3, stride=stride, padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(intermediate_channels)
self.conv3 = nn.Conv2d(intermediate_channels, intermediate_channels * self.expansion, kernel_size=1, stride=1, padding=0, bias=False)
self.bn3 = nn.BatchNorm2d(intermediate_channels * self.expansion)
self.relu = nn.ReLU()
self.identity_downsample = identity_downsample
self.stride = stride
def forward(self, x):
identity = x.clone()
x = self.conv1(x)
x = self.bn1(x)
x = self.relu(x)
x = self.conv2(x)
x = self.bn2(x)
x = self.relu(x)
x = self.conv3(x)
x = self.bn3(x)
if self.identity_downsample is not None:
identity = self.identity_downsample(identity)
x += identity
x = self.relu(x)
return x
然后创建一个ResNet类,它接受输入的块数、层数、图像通道数和类别数。在下面的代码中,函数‘_make_layer’创建ResNet层,它接受块、残差块的数量、输出通道和步长。
class ResNet(nn.Module):
def __init__(self, block, layers, image_channels, num_classes):
super(ResNet, self).__init__()
self.in_channels = 64
self.conv1 = nn.Conv2d(image_channels, 64, kernel_size=7, stride=2, padding=3, bias=False)
self.bn1 = nn.BatchNorm2d(64)
self.relu = nn.ReLU()
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
本质上,整个ResNet架构都在下面的四行代码中。
self.layer1 = self._make_layer(block, layers[0], intermediate_channels=64, stride=1)
self.layer2 = self._make_layer(block, layers[1], intermediate_channels=128, stride=2)
self.layer3 = self._make_layer(block, layers[2], intermediate_channels=256, stride=2)
self.layer4 = self._make_layer(block, layers[3], intermediate_channels=512, stride=2)
然后定义不同版本的ResNet。对于ResNet50,层序列是[3, 4, 6, 3];对于ResNet101,层序列是[3, 4, 23, 3];对于ResNet152,层序列是[3, 8, 36, 3]。(参考‘Deep Residual Learning for Image Recognition’论文)
def ResNet50(img_channel=3, num_classes=1000):
return ResNet(block, [3, 4, 6, 3], img_channel, num_classes)
def ResNet101(img_channel=3, num_classes=1000):
return ResNet(block, [3, 4, 23, 3], img_channel, num_classes)
def ResNet152(img_channel=3, num_classes=1000):
return ResNet(block, [3, 8, 36, 3], img_channel, num_classes)
然后编写一个小测试代码来检查模型是否正常工作。
def test():
net = ResNet101(img_channel=3, num_classes=1000)
device = "cuda" if torch.cuda.is_available() else "cpu"
y = net(torch.randn(4, 3, 224, 224)).to(device)
print(y.size())
test()