赞
踩
ResNet简单原理讲解与代码实现
目录
1.Shortcut connections,Basicblock
前言
本篇文章是对ResNet论文精读,主要包括论文的动机、创新点和实现代码
一、动机与背景
从LeNet到AlexNet,可以直观的认为“神经网络应该是越深,学习效果越好”,但是根据实验结果来看,并不是如此,甚至使得网络无法训练。造成这种情形的原因也不是过拟合,因为在实验过程中训练误差也增大。当时普遍认为造成这种结果的原因是:网络太深,出现梯度爆炸或者梯度消失。为了应对这个问题,通常有两种方法:1)适当的权重初始化,让权重处于一个适中的大小从而应对。2)加入Normaliza,例如BN等,经过大量实验表明(如上图所示)这两种方法可以使得网络继续训练,但是性能依旧变差。
为了解决上述问题,我们来进行思考。首先,我们直观的假设一个网络例如AlexNet不对其进行加深,其在ImageNet的top -5上可以达到17%,此时我们去加深它,以我们的角度来看,后添加的神经元最差的情况就是变成Identity Mapping(直接投影),但是神经网络自己并不能达到这较优解,于是我们人为的去添加这一种解法,于是Residual就出现了。
简答来说它的原理就是:将输出和输入合并成新的输出
这样就可以让网络达到我们期望的较优解了,它是如何做到的呢?极端的来看,我们后续的网络就是不学习,我们输出最差也是我们一开始的没有加深的网络的结果。理论上的解释可以从梯度去解释,大家可以自己尝试去求导就知道了,因为本质上确实不复杂,这里就不带大家推导了。
理论到实现还有一段路要走。
如何连接输入和输出?我们的问题其实是在于经过神经元的运算,我们的输入和输出会形状不同,于是在本篇文章中提出两种解决方法:1)0 padding:用0填充;2)投影:1*1卷积改变输入的通道数,其长宽变化,我们合理调整卷积核大小和步长就可以,本篇论文的设计就如图左侧所示,具体代码在文章底部。
网络越深,网络的通道数就需要越多(因为越深,特征的模式就越多,我们粗浅的将通道数=模式数便于理解),如果还是使用左侧的BasicBlock计算量就会过大,于是作者设计出右侧的Bottleneck降低计算量,原理就是,先使用1*1卷积核降维,输出时恢复到原来的维数,从而达到计算量的减少。
理解了这两个Block就可以理解实验设计图
具体实验细节不过多介绍了,在之后我会开启一个关于计算机视觉数据集介绍专栏来帮助大家理解所有的实验。
此代码不涉及Bottleneck,如果需求量大的话,我再补上。
ResNet网络设计文件
- import time
- import torch
- from torch import nn,optim
- import sys
- sys.path.append("..")
- import d2lzh_pytorch as d2l
- device=torch.device("cuda"if torch.cuda.is_available()else"cpu")
-
- #残差块
- class Residual(nn.Module):
- 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= d2l.F.relu(self.bn1(self.conv1(X)))
- Y=self.bn2(self.conv2(Y))
- if self.conv3:
- X=self.conv3(X)
- return d2l.F.relu(Y + X)
-
- #残差模型
- 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)
- #网络构建
- 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)
- )
- 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))
- net.add_module("global_avg_pool",d2l.GlobalAvgPool2d())
- net.add_module("fc",nn.Sequential(d2l.FlattenLayer(),nn.Linear(512,10)))
-
-
- #获取数据
- batch_size=256
- train_iter,test_iter=d2l.load_data_fashion_mnist(batch_size,resize=96)
- lr,num_epochs=0.001,5
- optimizer=optim.Adam(net.parameters(),lr=lr)
- d2l.train_ch5(net,train_iter,test_iter,batch_size,optimizer,device,num_epochs)
d2l.py
- import collections
- import math
- import os
- import random
- import sys
- import tarfile
- import time
- import json
- import zipfile
- from tqdm import tqdm
- from PIL import Image
- from collections import namedtuple
-
- from IPython import display
- from matplotlib import pyplot as plt
- import torch
- from torch import nn
- import torch.nn.functional as F
- import torchvision
- import torchvision.transforms as transforms
- import torchtext
- import torchtext.vocab as Vocab
- import numpy as np
-
- def load_data_fashion_mnist(batch_size, resize=None, root='~/Datasets/FashionMNIST'):
- """Download the fashion mnist dataset and then load into memory."""
- trans = []
- if resize:
- trans.append(torchvision.transforms.Resize(size=resize))
- trans.append(torchvision.transforms.ToTensor())
-
- transform = torchvision.transforms.Compose(trans)
- mnist_train = torchvision.datasets.FashionMNIST(root=root, train=True, download=True, transform=transform)
- mnist_test = torchvision.datasets.FashionMNIST(root=root, train=False, download=True, transform=transform)
- if sys.platform.startswith('win'):
- num_workers = 0 # 0表示不用额外的进程来加速读取数据
- else:
- num_workers = 4
- train_iter = torch.utils.data.DataLoader(mnist_train, batch_size=batch_size, shuffle=True, num_workers=num_workers)
- test_iter = torch.utils.data.DataLoader(mnist_test, batch_size=batch_size, shuffle=False, num_workers=num_workers)
-
- return train_iter, test_iter
-
-
- def train_ch5(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(dim=1) == y).sum().cpu().item()
- n += y.shape[0]
- batch_count += 1
- test_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))
现在大部分深度学习网络都是基于ResNet框架的,于是人们对其原理也展开了研究。其中一个有趣的现象是很深的网络去学习简单的数据集不发生overfitting。??为什么呢,明明参数增大了那么多倍??其中较为流行的一个解释是:Residual一定程度上降低了网络复杂度,因为就像我上面举例的极端情况,我们网络可以不去训练一些神经元,只用shallow的一些层去拟合,后面的层都是identity mapping。
ResNet文章的创新点其实也不是原创,其中Residual思想在上世纪90年就有人提出,但是论文作者是真正将其应用到神经网络的人,原理真的不复杂,希望可以作为大家入门深度学习的一篇论文。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。