网站模板 英文,百度一下打开网页,双滦区seo整站排名,ftp上传php网站实现残差网络(ResNet)
我们一般认为#xff0c;增加神经网络模型的层数#xff0c;充分训练后的模型理论上能更有效地降低训练误差。理论上#xff0c;原模型解的空间只是新模型解的空间的子空间。也就是说#xff0c;如果我们能将新添加的层训练成恒等映射f(x)xf(x) xf(…实现残差网络(ResNet)
我们一般认为增加神经网络模型的层数充分训练后的模型理论上能更有效地降低训练误差。理论上原模型解的空间只是新模型解的空间的子空间。也就是说如果我们能将新添加的层训练成恒等映射f(x)xf(x) xf(x)x新模型和原模型将同样有效。由于新模型可能得出更优的解来拟合训练数据集因此添加层似乎更容易降低训练误差。然而在实践中添加过多的层后训练误差往往不降反升。即使利用批量归一化带来的数值稳定性使训练深层模型更加容易该问题仍然存在。针对这一问题何恺明等人提出了残差网络ResNet。它在2015年的ImageNet图像识别挑战赛夺魁并深刻影响了后来的深度神经网络的设计。
残差块
残差块的结构在之前的blog中详细解释了感兴趣的可以去看。
ResNet沿用了VGG全3×33\times 33×3卷积层的设计。
残差块里首先有2个有相同输出通道数的3×33\times 33×3卷积层。每个卷积层后接一个批量归一化层和ReLU激活函数。然后输入跳过这两个卷积运算后直接加在最后的ReLU激活函数前。这样的设计要求两个卷积层的输出与输入形状一样从而可以相加。如果想改变通道数就需要引入一个额外的1×11\times 11×1卷积层来将输入变换成需要的形状后再做相加运算。
残差块的实现如下。它可以设定输出通道数、是否使用额外的1×11\times 11×1卷积层来修改通道数以及卷积层的步幅。
import time
import torch
from torch import nn, optim
import torch.nn.functional as Fdevice torch.device(cuda if torch.cuda.is_available() else cpu)class Residual(nn.Module): def __init__(self, in_channels, out_channels, use_1x1convFalse, stride1):super(Residual, self).__init__()self.conv1 nn.Conv2d(in_channels, out_channels, kernel_size3, padding1, stridestride)self.conv2 nn.Conv2d(out_channels, out_channels, kernel_size3, padding1)if use_1x1conv:self.conv3 nn.Conv2d(in_channels, out_channels, kernel_size1, stridestride)else:self.conv3 Noneself.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_1x1convTrue, stride2)
blk(X).shape # torch.Size([4, 6, 3, 3])ResNet模型
ResNet的前两层跟GoogLeNet中的一样
在输出通道数为64、步幅为2的7×77\times 77×7卷积层后接步幅为2的3×33\times 33×3的最大池化层。每个卷积层后增加批量归一化层。
net nn.Sequential(nn.Conv2d(1, 64, kernel_size7, stride2, padding3),nn.BatchNorm2d(64), nn.ReLU(),nn.MaxPool2d(kernel_size3, stride2, padding1))ResNet使用4个由残差块组成的模块每个模块使用若干个同样输出通道数的残差块。第一个模块的通道数同输入通道数一致无须减小高和宽(之前已经使用了步幅为2的最大池化层)。之后的每个模块在第一个残差块里将上一个模块的通道数翻倍并将高和宽减半。
def resnet_block(in_channels, out_channels, num_residuals, first_blockFalse):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_1x1convTrue, stride2))else:blk.append(Residual(out_channels, out_channels))return nn.Sequential(*blk)接着我们为ResNet加入所有残差块。这里每个模块使用两个残差块。
net.add_module(resnet_block1, resnet_block(64, 64, 2, first_blockTrue))
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))加入全局平均池化层后接上全连接层输出。
class GlobalAvgPool2d(nn.Module):# 全局平均池化层可通过将池化窗口形状设置成输入的高和宽实现def __init__(self):super(GlobalAvgPool2d, self).__init__()def forward(self, x):return F.avg_pool2d(x, kernel_sizex.size()[2:])class FlattenLayer(torch.nn.Module):def __init__(self):super(FlattenLayer, self).__init__()def forward(self, x): # x shape: (batch, *, *, ...)return x.view(x.shape[0], -1)net.add_module(global_avg_pool, GlobalAvgPool2d()) # GlobalAvgPool2d的输出: (Batch, 512, 1, 1)
net.add_module(fc, nn.Sequential(FlattenLayer(), nn.Linear(512, 10))) 这里每个模块里有4个卷积层不计算1×11\times 11×1卷积层加上最开始的卷积层和最后的全连接层共计18层。这个模型通常也被称为ResNet-18。通过配置不同的通道数和模块里的残差块数可以得到不同的ResNet模型例如更深的含152层的ResNet-152。虽然ResNet的主体架构跟GoogLeNet的类似但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])获取数据
def load_data_fashion_mnist(batch_size, resizeNone, root~/Datasets/FashionMNIST):Download the fashion mnist dataset and then load into memory.trans []if resize:trans.append(torchvision.transforms.Resize(sizeresize))trans.append(torchvision.transforms.ToTensor())transform torchvision.transforms.Compose(trans)mnist_train torchvision.datasets.FashionMNIST(rootroot, trainTrue, downloadTrue, transformtransform)mnist_test torchvision.datasets.FashionMNIST(rootroot, trainFalse, downloadTrue, transformtransform)if sys.platform.startswith(win):num_workers 0 # 0表示不用额外的进程来加速读取数据else:num_workers 4train_iter torch.utils.data.DataLoader(mnist_train, batch_sizebatch_size, shuffleTrue, num_workersnum_workers)test_iter torch.utils.data.DataLoader(mnist_test, batch_sizebatch_size, shuffleFalse, num_workersnum_workers)return train_iter, test_iterbatch_size 256
# 如出现“out of memory”的报错信息可减小batch_size或resize
train_iter, test_iter load_data_fashion_mnist(batch_size, resize96)训练模型
def train(net, train_iter, test_iter, batch_size, optimizer, device, num_epochs):net net.to(device)print(training on , device)loss torch.nn.CrossEntropyLoss()for epoch in range(num_epochs):train_l_sum, train_acc_sum, n, batch_count, start 0.0, 0.0, 0, 0, time.time()for X, y in train_iter:X X.to(device)y y.to(device)y_hat net(X)l loss(y_hat, y)optimizer.zero_grad()l.backward()optimizer.step()train_l_sum l.cpu().item()train_acc_sum (y_hat.argmax(dim1) y).sum().cpu().item()n y.shape[0]batch_count 1test_acc evaluate_accuracy(test_iter, net)print(epoch %d, loss %.4f, train acc %.3f, test acc %.3f, time %.1f sec% (epoch 1, train_l_sum / batch_count, train_acc_sum / n, test_acc, time.time() - start))lr, num_epochs 0.001, 5
optimizer torch.optim.Adam(net.parameters(), lrlr)
train(net, train_iter, test_iter, batch_size, optimizer, device, num_epochs)