参考博客:http://www.cnblogs.com/wuliytTaotao/archive/2018/09/15/9560205.html
https://blog.csdn.net/wux_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) 问题,即准确率会先上升然后达到饱和,再持续增加深度会导致准确率下降。这并不是过拟合问题,因为不光在测试集上误差增大,训练集本身误差也会增大。原因是随着网络越来越深,训练变得原来越难,网络的优化变得越来越难。理论上,越深的网络,效果应该更好;但实际上,由于训练难度,过深的网络会产生退化问题,效果反而不如相对较浅的网络。而残差网络就可以解决这个问题的,残差网络越深,训练集上的效果会越好。(测试集上的效果可能涉及过拟合问题)

假设有比较浅的网络达到了饱和的准确率,那么后面再加上集几个 y = x y=x y\=x 的恒等映射层 (identity mapping),起码误差不会增加,即更深的网络不应该带来训练集上误差的上升。恒等映射层 y = x y=x y\=x 直接将前一层输出传到后面的思想,是 ResNet 的主要创新点。

残差模块 (Residual Block)
残差是指实际观察值与估计值(拟合值)之间的差,某个残差块的输入为 x x x,拟合的输出为 H (x) H(x) H(x), 如果我们直接把输入 x x x 直接传到输出作为观测结果,那么我们需要学习的残差就是 F ( x ) = H ( x ) − x F(x)=H(x)-x F(x)\=H(x)−x。下图是一个残差学习单元。
CNN: ResNet原理及其实现 - 图1

残差块的计算方式为: F (x) = W 2 ⋅ r e l u ( W 1 x ) F(x)=W{2}\cdot relu(W{1}x) F(x)\=W2⋅relu(W1x),
残差块的输出为 r e l u (H ( x) ) = r e l u ( F ( x ) + x ) relu(H(x))=relu(F(x)+x) relu(H(x))\=relu(F(x)+x).
残差块误差优化: 残差网络通过加入 shortcut connections(或称为 skip connections),变得更加容易被优化。在不用 skip 连接之前,假设输入是 x x x,最优输出是 x x x,此时的优化目标是预测输出 H (x) = x H(x)=x H(x)\=x,加入 skip 连接后,优化输出 H ( x ) H(x) H(x) 与输入 x x x 的差别,即为残差 F ( x ) = H ( x ) − x F(x)=H(x)-x F(x)\=H(x)−x,此时的优化目标是 F ( x ) F(x) F(x) 的输出值为 0。后者会比前者更容易优化。
用残差更容易优化:引入残差后的映射对输出的变化更敏感。设 H 1 (x) H{1}(x) H1(x) 是加入 skip 连接前的网络映射, H 2 ( x ) H{2}(x) H2(x) 是加入 skip 连接的网络映射。对于输入 x = 5 x=5 x\=5,设此时 H 1 ( 5 ) = 5.1 H{1}(5)=5.1 H1(5)\=5.1, H 2 ( x ) = 5.1 , H 2 ( 5 ) = F ( 5 ) + 5 , F ( 5 ) = 0.1 H{2}(x)=5.1,H{2}(5)=F(5)+5,F(5)=0.1 H2(x)\=5.1,H2(5)\=F(5)+5,F(5)\=0.1。当输出变为 5.2 时, H 1 ( x ) H{1}(x) H1(x) 由 5.1 变为 5.2, F ( x ) F(x) F(x) 由 0.1 变为 0.2,明显后者输出变化对权重的调整作用更大,所以效果更好。残差的思想都是去掉相同的主体部分,从而突出微小的变化。
简单的加法不会给网络增加额外的参数和计算量,同时可以大大增加模型的训练速度,提高训练效果。并且当模型的层数加深时,能够有效地解决退化问题。
残差网络为什么是有效的:对于大型的网络,无论把残差块添加到神经网络的中间还是末端,都不会影响网络的表现。因为可以给残差快中的 weight 设置很大的 L2 正则化水平,使得 F (x) = 0 F(x)=0 F(x)\=0,这样使得加入残差块至少不会使得网络变差,此时的残块等价于恒等映射。若此时残差块中的 weight 学到了有用的信息,那就会比恒等映射更好,对网络的性能有帮助。
总结: ResNet 有很多旁路支线可以将输入直接连到后面的层,使得后面的层可以直接学习残差,简化了学习难度。传统的卷积层和全连接层在信息传递时,或多或少会存在信息丢失,损耗等问题。ResNet 将输入信息绕道传到输出,保护了信息的完整性

ResNet 网络结构

在 ResNet 中,使用两个 3x3 的卷积层替换为 1x1 + 3x3 + 1x1 的卷积进行计算优化:
CNN: ResNet原理及其实现 - 图2

结构中的中间 3x3 的卷积层首先在一个降维 1x1 卷积层下减少了计算,然后在另一个 1x1 的卷积层下做了还原,既保持了精度又减少了计算量,这种结构称为bottleneck 模块。输入和输出要保持相同的维度,若特征图维度不同,对于卷积层的残差块,需要将 x x x 添加卷积核批标准化处理:
CNN: ResNet原理及其实现 - 图3

若是在全连接层,对 x x x 进行线性映射变换维度,再连接到后面的层。
作者提出的 50、101、152 层的 ResNet 中应用了 bottleneck,而且不仅没有出现退化问题,错误率也大大降低,同时计算复杂度也保持在很低的程度。作者又提出了 1202 层的网络,对于这么深的网络,优化依然并不困难,但是出现了过拟合的问题。
CNN: ResNet原理及其实现 - 图4

实现上述表格中的 ResNet 结构:

  1. import torch
  2. import torch.nn as nn
  3. import torch.nn.functional as F
  4. class BasicBlock(nn.Module):
  5. expansion = 1
  6. def __init__(self, in_planes, planes, stride=1):
  7. super(BasicBlock, self).__init__()
  8. self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=3,
  9. stride=stride, padding=1, bias=False)
  10. self.bn1 = nn.BatchNorm2d(planes)
  11. self.conv2 = nn.Conv2d(planes, planes, kernel_size=3,
  12. stride=1, padding=1, bias=False)
  13. self.bn2 = nn.BatchNorm2d(planes)
  14. self.shortcut = nn.Sequential()
  15. if stride != 1 or in_planes != self.expansion*planes:
  16. self.shortcut = nn.Sequential(
  17. nn.Conv2d(in_planes, self.expansion*planes,
  18. kernel_size=1, stride=stride, bias=False),
  19. nn.BatchNorm2d(self.expansion*planes)
  20. )
  21. def forward(self, x):
  22. out = F.relu(self.bn1(self.conv1(x)))
  23. out = self.bn2(self.conv2(out))
  24. out += self.shortcut(x)
  25. out = F.relu(out)
  26. return out
  27. class Bottleneck(nn.Module):
  28. expansion = 4
  29. def __init__(self, in_planes, planes, stride=1):
  30. super(Bottleneck, self).__init__()
  31. self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=1, bias=False)
  32. self.bn1 = nn.BatchNorm2d(planes)
  33. self.conv2 = nn.Conv2d(planes, planes, kernel_size=3,
  34. stride=stride, padding=1, bias=False)
  35. self.bn2 = nn.BatchNorm2d(planes)
  36. self.conv3 = nn.Conv2d(planes, self.expansion*planes,
  37. kernel_size=1, bias=False)
  38. self.bn3 = nn.BatchNorm2d(self.expansion*planes)
  39. self.shortcut = nn.Sequential()
  40. if stride != 1 or in_planes != self.expansion*planes:
  41. self.shortcut = nn.Sequential(
  42. nn.Conv2d(in_planes, self.expansion*planes,
  43. kernel_size=1, stride=stride, bias=False),
  44. nn.BatchNorm2d(self.expansion*planes)
  45. )
  46. def forward(self, x):
  47. out = F.relu(self.bn1(self.conv1(x)))
  48. out = F.relu(self.bn2(self.conv2(out)))
  49. out = self.bn3(self.conv3(out))
  50. out += self.shortcut(x)
  51. out = F.relu(out)
  52. return out
  53. class ResNet(nn.Module):
  54. def __init__(self, block, num_blocks, num_classes=10):
  55. super(ResNet, self).__init__()
  56. self.in_planes = 64
  57. self.conv1 = nn.Conv2d(3, 64, kernel_size=3,
  58. stride=1, padding=1, bias=False)
  59. self.bn1 = nn.BatchNorm2d(64)
  60. self.layer1 = self._make_layer(block, 64, num_blocks[0], stride=1)
  61. self.layer2 = self._make_layer(block, 128, num_blocks[1], stride=2)
  62. self.layer3 = self._make_layer(block, 256, num_blocks[2], stride=2)
  63. self.layer4 = self._make_layer(block, 512, num_blocks[3], stride=2)
  64. self.linear = nn.Linear(512*block.expansion, num_classes)
  65. def _make_layer(self, block, planes, num_blocks, stride):
  66. strides = [stride] + [1]*(num_blocks-1)
  67. layers = []
  68. for stride in strides:
  69. layers.append(block(self.in_planes, planes, stride))
  70. self.in_planes = planes * block.expansion
  71. return nn.Sequential(*layers)
  72. def forward(self, x):
  73. out = F.relu(self.bn1(self.conv1(x)))
  74. out = self.layer1(out)
  75. out = self.layer2(out)
  76. out = self.layer3(out)
  77. out = self.layer4(out)
  78. out = F.avg_pool2d(out, 4)
  79. out = out.view(out.size(0), -1)
  80. out = self.linear(out)
  81. return out
  82. def ResNet18():
  83. return ResNet(BasicBlock, [2,2,2,2])
  84. def ResNet34():
  85. return ResNet(BasicBlock, [3,4,6,3])
  86. def ResNet50():
  87. return ResNet(Bottleneck, [3,4,6,3])
  88. def ResNet101():
  89. return ResNet(Bottleneck, [3,4,23,3])
  90. def ResNet152():
  91. return ResNet(Bottleneck, [3,8,36,3])
  92. def test():
  93. net = ResNet18()
  94. y = net(torch.randn(1,3,32,32))
  95. print(y.size())

https://blog.csdn.net/winycg/article/details/86709991