深度学习经典论文简单讲解与实现——ResNet

ResNet简单原理讲解与代码实现

目录

二、创新点

三、实现

1.Shortcut connections,Basicblock

2.Bottleneck

四、实验

五、代码实现

六、发展

总结


前言

本篇文章是对ResNet论文精读,主要包括论文的动机、创新点和实现代码


 

一、动机与背景

 从LeNet到AlexNet,可以直观的认为“神经网络应该是越深,学习效果越好”,但是根据实验结果来看,并不是如此,甚至使得网络无法训练。造成这种情形的原因也不是过拟合,因为在实验过程中训练误差也增大。当时普遍认为造成这种结果的原因是:网络太深,出现梯度爆炸或者梯度消失。为了应对这个问题,通常有两种方法:1)适当的权重初始化,让权重处于一个适中的大小从而应对。2)加入Normaliza,例如BN等,经过大量实验表明(如上图所示)这两种方法可以使得网络继续训练,但是性能依旧变差。

二、创新点

为了解决上述问题,我们来进行思考。首先,我们直观的假设一个网络例如AlexNet不对其进行加深,其在ImageNet的top -5上可以达到17%,此时我们去加深它,以我们的角度来看,后添加的神经元最差的情况就是变成Identity Mapping(直接投影),但是神经网络自己并不能达到这较优解,于是我们人为的去添加这一种解法,于是Residual就出现了。

简答来说它的原理就是:将输出和输入合并成新的输出 

这样就可以让网络达到我们期望的较优解了,它是如何做到的呢?极端的来看,我们后续的网络就是不学习,我们输出最差也是我们一开始的没有加深的网络的结果。理论上的解释可以从梯度去解释,大家可以自己尝试去求导就知道了,因为本质上确实不复杂,这里就不带大家推导了。

三、实现

 理论到实现还有一段路要走。

1.Shortcut connections,Basicblock

 如何连接输入和输出?我们的问题其实是在于经过神经元的运算,我们的输入和输出会形状不同,于是在本篇文章中提出两种解决方法:1)0 padding:用0填充;2)投影:1*1卷积改变输入的通道数,其长宽变化,我们合理调整卷积核大小和步长就可以,本篇论文的设计就如图左侧所示,具体代码在文章底部。

2.Bottleneck

网络越深,网络的通道数就需要越多(因为越深,特征的模式就越多,我们粗浅的将通道数=模式数便于理解),如果还是使用左侧的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年就有人提出,但是论文作者是真正将其应用到神经网络的人,原理真的不复杂,希望可以作为大家入门深度学习的一篇论文。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值