【人工智能学习之卷积神经网络篇(二)】
1 残差网络
1.1 背景介绍
随着深度学习的不断发展,模型的层数越来越多,网络结构也越来越复杂。但是增加网络的层数之后,训练误差往往不降反
升。由此,Kaiming He等人提出了残差网络ResNet来解决上述问题。ResNet是2015年ImageNet比赛的冠军,将识别错误率降
低到了3.6%,这个结果甚至超出了正常人眼识别的精度。在ResNet中,提出了一个非常经典的结构—残差块(Residual
block)。
自从2015年ResNet进入人们的视线,并引发人们思考之后,许多研究界人员已经开始研究其成功的秘诀,并在架构中纳入了许多新的改进。ResNet可以实现高达数百,甚至数千个层的训练,且仍能获得超赞的性能。
自从AlexNet投入使用以来,最先进的卷积神经网络(CNN)架构越来越深。虽然AlexNet只有5层卷积层,但VGG网络和GoogleNet(代号也为Inception_v1)分别有19层和22层。
但是,如果只是通过简单地将层叠加在一起,增加网络深度并不会起到什么作用。随着网络层数的增加,就会出现梯度消失问题,这就会造成网络是难以进行训练,因为梯度反向传播到前层,重复乘法可能使梯度无穷小,这造成的结果就是,随着网络
加深,其性能趋于饱和,或者甚至开始迅速退化。
其实早在ResNet之前,已经有过方法来处理梯度消失问题,但遗憾的是,似乎没有一个方法可以真正解决这个问题。
ResNet的核心思想是引入所谓的“恒等映射(identity shortcutconnection)”,可以跳过一层或多层,如下图所示:
1.2 梯度消失问题分析
BP算法:链式求导(复合函数求导,导数连乘),连乘过程中(0.99^365)只要有一个值接近于0,整个乘法运算就接近于0,梯度则消失,模型失去了训练的意义。loss:对loss进行链式求导,该导数根据梯度下降法进行更新权重。
1.3 传统的连接方式
1.4 残差块的连接方式
在残差块中有一点变化,将a[l]直接向后拷贝神经网络的深层。
意味着最后的等式a[l+2] = g(z[l+2]) 变成了 a[l+2] = g(z[l+2] +a[l]), 也就是加上的这个a[l]产生了一个残差块。
当前a[l] 就是残差块, 也有另一个术语叫做 跳跃连接, 指的就是a[l] 跳过一层或者几层,从而将信息传递到神经网络的更深层。
将a[l+2]展开,那么a[l+2] = g(W[l+2]a[l+1] + b[l+2] + a[l]), 假如当前W[l+2] = 0, 同时b[l+2]=0,那么残差的表达式就变为:a[l+2] = g(a[l]) =a[l], 也就是当出现参数为0的极端情况下,残差块也可以很容易得出a[l+2]=a[l], 意味着,即使神经网络增加了这两层,它的效果也不会差。因为恒等函数很简单。当然这是在极端的情况下,参数都为0,如果这些隐藏层学到一些有用的信息,那么它可能会比学习恒等函数表现都更好。
而那些不含有残差块的的普通网络情况就不一样,当网络不断加深,学习起来就会很困难。
1.5 残差网络结构
残差块:
import torch
from torch import nn
import torch.nn.functional as F
class ResBlock(nn.Module):
def __init__(self, channel):
super().__init__()
self.conv1 = nn.Conv2d(channel, channel, kernel_size=3, padding=1)
self.conv2 = nn.Conv2d(channel, channel, kernel_size=3, padding=1)
def forward(self, x):
residual = x
ou1 = F.relu(self.conv1(x))
ou2 = self.conv2(ou1)
return F.relu(residual + ou2)
if __name__ == '__main__':
x = torch.randn(1, 64, 32, 32)
resblock = ResBlock(64)
print(resblock(x).shape)
残差网络:
import torch
import torch.nn.functional as F
from torch import nn
class Res_Block(nn.Module):
def __init__(self, c):
super().__init__()
self.conv1 = torch.nn.Conv2d(c, c, 3, padding=1)
self.conv2 = torch.nn.Conv2d(c, c, 3, padding=1)
def forward(self, x):
y = F.relu(self.conv1(x))
y = self.conv2(y)
return F.relu(y + x)
class Net(torch.nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = torch.nn.Conv2d(1, 16, 3, 1)
self.rblock1 = Res_Block(16)
self.conv2 = torch.nn.Conv2d(16, 32, 1, 1)
def forward(self, x):
out = self.conv1(x)
x = self.rblock1(out)
y = self.conv2(x)
return y