博客
关于我
残差网络(RESNET)
阅读量:361 次
发布时间:2019-03-04

本文共 4876 字,大约阅读时间需要 16 分钟。

《动手学深度学习pytorch》部分学习笔记,仅用作自己复习。 

残差网络(RESNET)

对神经网络模型添加新的层,充分训练后的模型是否只可能更有效地降低训练误差?理论上,原模型解的空间只是新模型解的空间的⼦空间。也就是说,如果我们能将新添加的层训练成恒等映射 ,新模型和原模型将同样有效。由于新模型可能得出更更优的解来拟合训练数据集,因此添加层似乎更容易降低训练误差。然而在实践中,添加过多的层后训练误差往往不降反升。即使利用批量归一化带来的数值稳定性使训练深层模型更加容易,该问题仍然存在。针对这一问题,何恺明等人提出了残差⽹网络(ResNet)。它在2015年的ImageNet图像识别挑战赛夺魁,深刻影响了后来的深度神经网络的设计。

残差块

ResNet沿用了VGG全3 x 3卷积层的设计。残差块⾥首先有2个有相同输出通道数的 3 x 3卷积层。每个卷积层后接⼀个批量归⼀化层和ReLU激活函数。然后我们将输入跳过这两个卷积运算后直接加在最后的ReLU激活函数前。这样的设计要求两个卷积层的输出与输⼊形状一样,从⽽可以相加。如果想改变通道数,就需要引入⼀个额外的1 x 1 卷积层来将输⼊变换成需要的形状后再做相加运算。

残差块的实现如下。它可以设定输出通道数、是否使⽤用额外的 1 x 1卷积层来修改通道数以及卷积层的步幅。 

import timeimport torchfrom torch import nn, optimimport torch.nn.functional as Fimport syssys.path.append("..") import d2lzh_pytorch as d2ldevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')print(torch.__version__)print(device)
class Residual(nn.Module):  # 本类已保存在d2lzh_pytorch包中方便以后使用    def __init__(self, in_channels, out_channels, use_1x1conv=False, stride=1):        super(Residual, self).__init__()        self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1, stride=stride)        self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1)        if use_1x1conv:            self.conv3 = nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride)        else:            self.conv3 = None        self.bn1 = nn.BatchNorm2d(out_channels)        self.bn2 = nn.BatchNorm2d(out_channels)    def forward(self, X):        Y = F.relu(self.bn1(self.conv1(X)))        Y = self.bn2(self.conv2(Y))        if self.conv3:            X = self.conv3(X)        return F.relu(Y + X)

查看输入和输出形状⼀致的情况。

blk = Residual(3, 3)X = torch.rand((4, 3, 6, 6))blk(X).shape

输出:

torch.Size([4, 3, 6, 6])

也可以在增加输出通道数的同时减半输出的⾼和宽。

blk = Residual(3, 6, use_1x1conv=True, stride=2)blk(X).shape

输出:

torch.Size([4, 6, 3, 3])

RESNET模型

ResNet的前两层跟之前介绍的GoogLeNet中的⼀样:在输出通道数为64、步幅为2的 7 x7卷积层后接步幅为2的 3x3的最⼤池化层。不同之处在于ResNet每个卷积层后增加的批量归一化层。

net = nn.Sequential(        nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),        nn.BatchNorm2d(64),         nn.ReLU(),        nn.MaxPool2d(kernel_size=3, stride=2, padding=1))

GoogLeNet在后⾯接了4个由Inception块组成的模块。ResNet则使用4个由残差块组成的模块,每个模块使用若干个同样输出通道数的残差块。第⼀个模块的通道数同输入通道数一致。由于之前已经使⽤了步幅为2的最大池化层,所以无须减⼩高和宽。之后的每个模块在第⼀个残差块⾥将上一个模块的通道数翻倍,并将高和宽减半。

来实现这个模块。注意,这⾥对第一个模块做了特别处理。

def resnet_block(in_channels, out_channels, num_residuals, first_block=False):    if first_block:        assert in_channels == out_channels # 第一个模块的通道数同输入通道数一致    blk = []    for i in range(num_residuals):        if i == 0 and not first_block:            blk.append(Residual(in_channels, out_channels, use_1x1conv=True, stride=2))        else:            blk.append(Residual(out_channels, out_channels))    return nn.Sequential(*blk)

接着我们为ResNet加⼊所有残差块。这⾥每个模块使用两个残差块。

net.add_module("resnet_block1", resnet_block(64, 64, 2, first_block=True))net.add_module("resnet_block2", resnet_block(64, 128, 2))net.add_module("resnet_block3", resnet_block(128, 256, 2))net.add_module("resnet_block4", resnet_block(256, 512, 2))

最后,与GoogLeNet⼀样,加入全局平均池化层后接上全连接层输出。

net.add_module("global_avg_pool", d2l.GlobalAvgPool2d()) # GlobalAvgPool2d的输出: (Batch, 512, 1, 1)net.add_module("fc", nn.Sequential(d2l.FlattenLayer(), nn.Linear(512, 10)))

这⾥每个模块⾥有4个卷积层(不计算 1x1卷积层),加上最开始的卷积层和最后的全连接层,共计18层。这个模型通常也被称为ResNet-18。通过配置不同的通道数和模块⾥的残差块数可以得到不同的ResNet模型,例如更深的含152层的ResNet-152。虽然ResNet的主体架构跟GoogLeNet的类似,但ResNet结构更简单,修改也更方便。这些因素都导致了ResNet迅速被广泛使用。

在训练ResNet之前,我们来观察一下输入形状在ResNet不同模块之间的变化。

X = torch.rand((1, 1, 224, 224))for name, layer in net.named_children():    X = layer(X)    print(name, ' output shape:\t', X.shape)

输出:

0  output shape:	 torch.Size([1, 64, 112, 112])1  output shape:	 torch.Size([1, 64, 112, 112])2  output shape:	 torch.Size([1, 64, 112, 112])3  output shape:	 torch.Size([1, 64, 56, 56])resnet_block1  output shape:	 torch.Size([1, 64, 56, 56])resnet_block2  output shape:	 torch.Size([1, 128, 28, 28])resnet_block3  output shape:	 torch.Size([1, 256, 14, 14])resnet_block4  output shape:	 torch.Size([1, 512, 7, 7])global_avg_pool  output shape:	 torch.Size([1, 512, 1, 1])fc  output shape:	 torch.Size([1, 10])

获取数据和训练模型

在Fashion-MNIST数据集上训练ResNet。

batch_size = 256# 如出现“out of memory”的报错信息,可减小batch_size或resizetrain_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)lr, num_epochs = 0.001, 5optimizer = torch.optim.Adam(net.parameters(), lr=lr)d2l.train_ch5(net, train_iter, test_iter, batch_size, optimizer, device, num_epochs)

输出:

training on  cudaepoch 1, loss 0.0015, train acc 0.853, test acc 0.885, time 31.0 secepoch 2, loss 0.0010, train acc 0.910, test acc 0.899, time 31.8 secepoch 3, loss 0.0008, train acc 0.926, test acc 0.911, time 31.6 secepoch 4, loss 0.0007, train acc 0.936, test acc 0.916, time 31.8 secepoch 5, loss 0.0006, train acc 0.944, test acc 0.926, time 31.5 sec

小结

  • 残差块通过跨层的数据通道从⽽能够训练出有效的深度神经网络。
  • ResNet深刻影响了后来的深度神经⽹络的设计。

转载地址:http://lpfr.baihongyu.com/

你可能感兴趣的文章
mysql 两列互转
查看>>
MySQL 中开启二进制日志(Binlog)
查看>>
MySQL 中文问题
查看>>
MySQL 中日志的面试题总结
查看>>
mysql 中的all,5分钟了解MySQL5.7中union all用法的黑科技
查看>>
MySQL 中的外键检查设置:SET FOREIGN_KEY_CHECKS = 1
查看>>
Mysql 中的日期时间字符串查询
查看>>
mysql 中索引的问题
查看>>
MySQL 中锁的面试题总结
查看>>
MySQL 中随机抽样:order by rand limit 的替代方案
查看>>
MySQL 为什么需要两阶段提交?
查看>>
mysql 为某个字段的值加前缀、去掉前缀
查看>>
mysql 主从
查看>>
mysql 主从 lock_mysql 主从同步权限mysql 行锁的实现
查看>>
mysql 主从互备份_mysql互为主从实战设置详解及自动化备份(Centos7.2)
查看>>
mysql 主从关系切换
查看>>
MYSQL 主从同步文档的大坑
查看>>
mysql 主键重复则覆盖_数据库主键不能重复
查看>>
Mysql 事务知识点与优化建议
查看>>
Mysql 优化 or
查看>>