参考博客:http://www.cnblogs.com/wuliytTaotao/archive/2018/09/15/9560205.html
https://blog.csdn.net/wu_x_j_/article/details/84823002
https://blog.csdn.net/wsp_1138886114/article/details/82080881
残差网络(residual network):
DNN为什么不是越深越好:
论文认为,可以训练一个 shallower 网络,然后在这个训练好的 shallower 网络上堆几层 identity mapping(恒等映射) 的层,即输出等于输入的层,构建出一个 deeper 网络。这两个网络(shallower 和 deeper)得到的结果应该是一模一样的,因为堆上去的层都是 identity mapping。这样可以得出一个结论:理论上,在训练集上,Deeper 不应该比 shallower 差,即越深的网络不会比浅层的网络效果差。
在不断加深神经网络的深度时,会出现退化(Degradation)问题,即准确率会先上升然后达到饱和,再持续增加深度会导致准确率下降。这并不是过拟合问题,因为不光在测试集上误差增大,训练集本身误差也会增大。原因是随着网络越来越深,训练变得原来越难,网络的优化变得越来越难。理论上,越深的网络,效果应该更好;但实际上,由于训练难度,过深的网络会产生退化问题,效果反而不如相对较浅的网络。而残差网络就可以解决这个问题的,残差网络越深,训练集上的效果会越好。(测试集上的效果可能涉及过拟合问题)
假设有比较浅的网络达到了饱和的准确率,那么后面再加上集几个的恒等映射层(identity mapping),起码误差不会增加,即更深的网络不应该带来训练集上误差的上升。恒等映射层直接将前一层输出传到后面的思想,是ResNet的主要创新点。
残差模块(Residual Block)
残差是指实际观察值与估计值(拟合值)之间的差,某个残差块的输入为,拟合的输出为,如果我们直接把输入直接传到输出作为观测结果,那么我们需要学习的残差就是。下图是一个残差学习单元。
残差块的计算方式为:,
残差块的输出为.
残差块误差优化: 残差网络通过加入 shortcut connections(或称为 skip connections),变得更加容易被优化。在不用skip连接之前,假设输入是,最优输出是,此时的优化目标是预测输出,加入skip连接后,优化输出与输入的差别,即为残差,此时的优化目标是的输出值为0。后者会比前者更容易优化。
用残差更容易优化:引入残差后的映射对输出的变化更敏感。设是加入skip连接前的网络映射,是加入skip连接的网络映射。对于输入,设此时,。当输出变为5.2时,由5.1变为5.2,由0.1变为0.2,明显后者输出变化对权重的调整作用更大,所以效果更好。残差的思想都是去掉相同的主体部分,从而突出微小的变化。
简单的加法不会给网络增加额外的参数和计算量,同时可以大大增加模型的训练速度,提高训练效果。并且当模型的层数加深时,能够有效地解决退化问题。
残差网络为什么是有效的:对于大型的网络,无论把残差块添加到神经网络的中间还是末端,都不会影响网络的表现。因为可以给残差快中的weight设置很大的L2正则化水平,使得,这样使得加入残差块至少不会使得网络变差,此时的残块等价于恒等映射。若此时残差块中的weight学到了有用的信息,那就会比恒等映射更好,对网络的性能有帮助。
总结: ResNet有很多旁路支线可以将输入直接连到后面的层,使得后面的层可以直接学习残差,简化了学习难度。传统的卷积层和全连接层在信息传递时,或多或少会存在信息丢失,损耗等问题。ResNet将输入信息绕道传到输出,保护了信息的完整性。
ResNet网络结构
在ResNet中,使用两个3x3的卷积层替换为1x1 + 3x3 + 1x1的卷积进行计算优化:
结构中的中间3x3的卷积层首先在一个降维1x1卷积层下减少了计算,然后在另一个1x1的卷积层下做了还原,既保持了精度又减少了计算量,这种结构称为bottleneck模块。输入和输出要保持相同的维度,若特征图维度不同,对于卷积层的残差块,需要将添加卷积核批标准化处理:
若是在全连接层,对进行线性映射变换维度,再连接到后面的层。
作者提出的50、101、152层的ResNet中应用了bottleneck,而且不仅没有出现退化问题,错误率也大大降低,同时计算复杂度也保持在很低的程度。作者又提出了1202层的网络,对于这么深的网络,优化依然并不困难,但是出现了过拟合的问题。
实现上述表格中的ResNet结构:
import torch
import torch.nn as nn
import torch.nn.functional as F
# 用于ResNet18和34的残差块,用的是2个3x3的卷积
class BasicBlock(nn.Module):
expansion = 1
def __init__(self, in_planes, planes, stride=1):
super(BasicBlock, self).__init__()
self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=3,
stride=stride, padding=1, bias=False)
self.bn1 = nn.BatchNorm2d(planes)
self.conv2 = nn.Conv2d(planes, planes, kernel_size=3,
stride=1, padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(planes)
self.shortcut = nn.Sequential()
# 经过处理后的x要与x的维度相同(尺寸和深度)
# 如果不相同,需要添加卷积+BN来变换为同一维度
if stride != 1 or in_planes != self.expansion*planes:
self.shortcut = nn.Sequential(
nn.Conv2d(in_planes, self.expansion*planes,
kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(self.expansion*planes)
)
def forward(self, x):
out = F.relu(self.bn1(self.conv1(x)))
out = self.bn2(self.conv2(out))
out += self.shortcut(x)
out = F.relu(out)
return out
# 用于ResNet50,101和152的残差块,用的是1x1+3x3+1x1的卷积
class Bottleneck(nn.Module):
# 前面1x1和3x3卷积的filter个数相等,最后1x1卷积是其expansion倍
expansion = 4
def __init__(self, in_planes, planes, stride=1):
super(Bottleneck, self).__init__()
self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=1, bias=False)
self.bn1 = nn.BatchNorm2d(planes)
self.conv2 = nn.Conv2d(planes, planes, kernel_size=3,
stride=stride, padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(planes)
self.conv3 = nn.Conv2d(planes, self.expansion*planes,
kernel_size=1, bias=False)
self.bn3 = nn.BatchNorm2d(self.expansion*planes)
self.shortcut = nn.Sequential()
if stride != 1 or in_planes != self.expansion*planes:
self.shortcut = nn.Sequential(
nn.Conv2d(in_planes, self.expansion*planes,
kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(self.expansion*planes)
)
def forward(self, x):
out = F.relu(self.bn1(self.conv1(x)))
out = F.relu(self.bn2(self.conv2(out)))
out = self.bn3(self.conv3(out))
out += self.shortcut(x)
out = F.relu(out)
return out
class ResNet(nn.Module):
def __init__(self, block, num_blocks, num_classes=10):
super(ResNet, self).__init__()
self.in_planes = 64
self.conv1 = nn.Conv2d(3, 64, kernel_size=3,
stride=1, padding=1, bias=False)
self.bn1 = nn.BatchNorm2d(64)
self.layer1 = self._make_layer(block, 64, num_blocks[0], stride=1)
self.layer2 = self._make_layer(block, 128, num_blocks[1], stride=2)
self.layer3 = self._make_layer(block, 256, num_blocks[2], stride=2)
self.layer4 = self._make_layer(block, 512, num_blocks[3], stride=2)
self.linear = nn.Linear(512*block.expansion, num_classes)
def _make_layer(self, block, planes, num_blocks, stride):
strides = [stride] + [1]*(num_blocks-1)
layers = []
for stride in strides:
layers.append(block(self.in_planes, planes, stride))
self.in_planes = planes * block.expansion
return nn.Sequential(*layers)
def forward(self, x):
out = F.relu(self.bn1(self.conv1(x)))
out = self.layer1(out)
out = self.layer2(out)
out = self.layer3(out)
out = self.layer4(out)
out = F.avg_pool2d(out, 4)
out = out.view(out.size(0), -1)
out = self.linear(out)
return out
def ResNet18():
return ResNet(BasicBlock, [2,2,2,2])
def ResNet34():
return ResNet(BasicBlock, [3,4,6,3])
def ResNet50():
return ResNet(Bottleneck, [3,4,6,3])
def ResNet101():
return ResNet(Bottleneck, [3,4,23,3])
def ResNet152():
return ResNet(Bottleneck, [3,8,36,3])
def test():
net = ResNet18()
y = net(torch.randn(1,3,32,32))
print(y.size())
# test()
来源:CSDN
作者:winycg
链接:https://blog.csdn.net/winycg/article/details/86709991