2020-12-26

深度学习, 复习 ,图像分类 LeNet VGG ResNet
转 Datawhale

import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F

from torch.autograd import Variable

LeNet network

‘’’
LeNet分为卷积层块和全连接层块两个部分。下面我们分别介绍这两个模块。

卷积层块里的基本单位是卷积层后接最大池化层:卷积层用来识别图像里的空间模式,如线条和物体局部,之后的最大池化层则用来降低卷积层对位置的敏感性。
卷积层块由两个这样的基本单位重复堆叠构成。在卷积层块中,每个卷积层都使用5×5的窗口,并在输出上使用sigmoid激活函数。第一个卷积层输出通道数为6,
第二个卷积层输出通道数则增加到16。这是因为第二个卷积层比第一个卷积层的输入的高和宽要小,所以增加输出通道使两个卷积层的参数尺寸类似。
卷积层块的两个最大池化层的窗口形状均为2×2,且步幅为2。由于池化窗口与步幅形状相同,池化窗口在输入上每次滑动所覆盖的区域互不重叠。

卷积层块的输出形状为(批量大小, 通道, 高, 宽)。当卷积层块的输出传入全连接层块时,全连接层块会将小批量中每个样本变平(flatten)。
也就是说,全连接层的输入形状将变成二维,其中第一维是小批量中的样本,第二维是每个样本变平后的向量表示,且向量长度为通道、高和宽的乘积。
全连接层块含3个全连接层。它们的输出个数分别是120、84和10,其中10为输出的类别个数。
‘’’

class Net(nn.Module):
def init(self):
super().init()
num_classes = 0
self.conv1 = nn.Conv2d(3, 6, 5) # in_channels, out_channels, kernel_size, stride=1…
self.conv2 = nn.Conv2d(6, 16, 5)
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, num_classes)

def forward(self, x):
    x = F.max_pool2d(F.relu(self.conv1(x)), kernel_size=(2, 2))
    x = F.max_pool2d(F.relu(self.conv2(x)), kernel_size=(2, 2))

    x = x.view(x.size([0], -1))
    x = F.relu(self.fc1(x))
    x = F.relu(self.fc2(x))
    x = self.fc3(x)
    return x

‘’’
通过多次卷积和池化,CNN的最后一层将输入的图像像素映射为具体的输出。如在分类任务中会转换为不同类别的概率输出,
然后计算真实标签与CNN模型的预测结果的差异,并通过反向传播更新每层的参数,并在更新完成后再次前向传播,如此反复直到训练完成 。
与传统机器学习模型相比,CNN具有一种端到端(End to End)的思路。在CNN训练的过程中是直接从图像像素到最终的输出,
并不涉及到具体的特征提取和构建模型的过程,也不需要人工的参与。
‘’’

VGG

‘’’
使用了3个3x3卷积核来代替7x7卷积核,使用了2个3x3卷积核来代替5*5卷积核。这样做的主要目的是在保证具有相同感知野的条件下,
提升了网络的深度(因为多层非线性层可以增加网络深度来保证学习更复杂的模式),在一定程度上提升了神经网络的效果。

设输入通道数和输出通道数都为C, 3个步长为1的3x3卷积核的一层层叠加作用可看成一个大小为7的感受野(其实就表示3个3x3连续卷积相当于一个7x7卷积),
其参数总量为 3*(9* C^2),如果直接使用7x7卷积核,其参数总量为 49* C^2 很明显,27C^2 小于49 C^2 即减少了参数;
而且3x3卷积核有利于更好地保持图像性质。

VGGNet的结构非常简洁,整个网络都使用了同样大小的卷积核尺寸(3x3)和最大池化尺寸(2x2)。
几个小滤波器(3x3)卷积层的组合比一个大滤波器(5x5或7x7)卷积层好:
验证了通过不断加深网络结构可以提升性能。
缺点是VGG耗费更多计算资源,并且使用了更多的参数,这里不是3x3卷积的原因,其中绝大多数的参数都是来自于第一个全连接层。
‘’’

class Net1(nn.Module):
def init(self):
super.classifier = nn.Sequential(
# MLP 卷积层1
nn.Conv2d(3, 192, kernel_size=5, stride=1, padding=2),
nn.Relu(inplace=True),
nn.Conv2d(192, 160, kernel_size=1, stride=1, padding=0),
nn.Relu(inplace=True),
nn.Conv2d(160, 96, kernel_size=1, stride=1, padding=0),
nn.Relu(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1),
nn.Dropout(0.5),

        # MLP 卷积层2
        nn.Conv2d(96, 192, kernel_size=5, stride=1, padding=2),
        nn.Relu(inplace=True),
        nn.Conv2d(192, 192, kernel_size=1, stride=1, padding=0),
        nn.Relu(inplace=True),
        nn.Conv2d(192, 192, kernel_size=1, stride=1, padding=0),
        nn.Relu(inplace=True),
        nn.AvgPool2d(kernel_size=3, stride=1, padding=0),
        nn.Dropout(0.5),

        # MLP 卷积层3
        nn.Conv2d(192, 192, kernel_size=3, stride=1, padding=2),
        nn.Relu(inplace=True),
        nn.Conv2d(192, 192, kernel_size=1, stride=1, padding=0),
        nn.Relu(inplace=True),
        nn.Conv2d(192, 10, kernel_size=1, stride=1, padding=0),
        nn.Relu(inplace=True),
        nn.AvgPool2d(kernel_size=3, stride=1, padding=0),
    )

def forward(self, x):
    x = self.classifier(x)
    x = F.avg_pool2d(x, kernel_size=x.size()[2:])
    return x

‘’’
NiN重复使用由卷积层和代替全连接层的1×1卷积层构成的NiN块来构建深层网络
1x1卷积等效于该像素点在所有特征上进行一次全连接的计算,起到了压缩通道,即降维的作用,减少了通道的数量。
NiN去除了容易造成过拟合的全连接输出层,而是将其替换成输出通道数等于标签类别数 的NiN块和全局平均池化层。
NiN的以上设计思想影响了后面一系列卷积神经网络的设计。
‘’’

批量归一化 Batch Normalization

‘’’
它能让较深的神经网络的训练变得更加容易。通常来说,数据标准化预处理对于浅层模型就足够有效了。
随着模型训练的进行,当每层中参数更新时,靠近输出层的输出较难出现剧烈变化。但对深层神经网络来说,即使输入数据已做标准化,
训练中模型参数的更新依然很容易造成靠近输出层输出的剧烈变化。这种计算数值的不稳定性通常令我们难以训练出有效的深度模型。

​批量归一化的提出正是为了应对深度模型训练的挑战。在模型训练时,批量归一化利用小批量上的 均值 和 标准差,不断调整神经网络中间输出,
从而使整个神经网络在各层的中间输出的数值更稳定。批量归一化和下一节将要介绍的残差网络为训练和设计深度模型提供了两类重要思路。

1.对全连接层做批量归一化
对卷积层来说,批量归一化发生在卷积计算之后、应用激活函数之前。如果卷积计算输出多个通道,我们需要对这些通道的输出分别做批量归一化,
且每个通道都拥有独立的拉伸和偏移参数,并均为标量。设小批量中有mm个样本。在单个通道上,假设卷积计算输出的高和宽分别为pp和qq。
我们需要对该通道中 m * p * q 个元素同时做批量归一化。对这些元素做标准化计算时,
我们使用相同的均值和方差,即该通道中m m * p * q 个元素的均值和方差。
2.预测时的批量归一化
使用批量归一化训练时,我们可以将批量大小设得大一点,从而使批量内样本的均值和方差的计算都较为准确。将训练好的模型用于预测时,
我们希望模型对于任意输入都有确定的输出。因此,单个样本的输出不应取决于批量归一化所需要的随机小批量中的均值和方差。
一种常用的方法是通过移动平均估算整个训练数据集的样本均值和方差,并在预测时使用它们得到确定的输出。
可见,和丢弃层一样,批量归一化层在训练模式和预测模式下的计算结果也是不一样的。
‘’’

定义一次batch normalizaton 运算的计算图

def batch_norm(is_training, X, gamma, beta, moving_mean, moving_var, eps, momentum):
# 计算当前模式是训练模式还是预测模式
if not is_training:
# 如果是在预测模式下,直接使用传入的移动平均所得的均值和方差
X_hat = (X - moving_mean) / torch.sqrt(moving_var + eps)
else:
assert len(X.shape) in (2, 4)
if len(X.shape) == 2:
# 使用全连接层的情况,计算特征维上的均值和方差
mean = X.mean(dim=0)
var = ((X - mean) ** 2).mean(dim=0)
else:
# 使用二位卷积层的情况,计算每个通道维上(axis = 1)的均值和方差
# 这里我们需要保持x的形状以便后面可以坐广播运算
mean = X.mean(dim=0, keepdim=True).mean(dim=2, keepdim=True).mean(dim=3, keepdim=True)
var = ((X - mean) ** 2).mean(dim=0, keepdim=True).mean(dim=2, keepdim=True).mean(dim=3, keepdim=True)
# 训练模式下用当前的均值和方差做标准化
X_hat = (X - mean) / torch.sqrt(var + eps)
# 更新移动平均的均值和方差
moving_mean = momentum * moving_mean + (1.0 - momentum) * mean
moving_var = momentum * moving_var + (1.0 - momentum) * mean
Y = gamma * X_hat + beta # 拉伸和偏移
return Y, moving_mean, moving_var

手动实现版本BatchNormalization层的完整定义

class BatchNorm(nn.Module):
def init(self, num_features, num_dims):
super(BatchNorm, self).init()
if num_dims == 2:
shape = (1, num_features) # 全连接层输出神经元
else:
shape = (1, num_features, 1, 1) # 通道数
# 参与求梯度和迭代的拉伸和偏移参数,分别初始化维1和0
self.gamma = nn.Parameter(torch.ones(shape))
self.beta = nn.Parameter(torch.zeros(shape))
# 不参与求梯度和迭代的变量,全在内存上初始化成0
self.moving_mean = torch.zeros(shape)
self.moving_var = torch.zeros(shape)

def forward(self, X):
    # 如果x不在内存上,将moving_mean和moving_var复制到x所在显存上
    if self.moving_mean.device != X.devive:
        self.moving_mean = self.moving_mean.to(X.devive)
        self.moving_var = self.moving_var.to(X.devive)
    # 保存更新过的moving_mean 和moving_var, module实例的training属性默认为true, 调用.eval()后设为false
    Y, self.moving_mean, self.moving_var = batch_norm(self.training,
                                                      X, self.gamma, self.beta, self.moving_mean,
                                                      self.moving_var, eps=1e-5, momentum=0.9)
    return Y

残差网络(ResNet)

‘’’
设输入为x。假设我们希望学出的理想映射为f(x),从而作为图5.9上方激活函数的输入。左图虚线框中的部分需要直接拟合出该映射f(x),
而右图虚线框中的部分则需要拟合出有关恒等映射的残差映射f(x)−x。残差映射在实际中往往更容易优化。
以本节开头提到的恒等映射作为我们希望学出的理想映射f(x)。我们只需将图5.9中右图虚线框内上方的加权运算(如仿射)的权重和偏差参数学成0,
那么f(x)即为恒等映射。实际中,当理想映射f(x)极接近于恒等映射时,残差映射也易于捕捉恒等映射的细微波动。
图5.9右图也是ResNet的基础块,即残差块(residual block)。在残差块中,输入可通过跨层的数据线路更快地向前传播。
‘’’

3x3 convolution

def conv3x3(in_channels, out_channels, stride=1):
return nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False)

Residual block

class ResidualBlock(nn.Module):
def init(self, in_channels, out_channels, stride=1, downsample=None):
super(ResidualBlock, self).init()
self.conv1 = conv3x3(in_channels, out_channels, stride)
self.bn1 = nn.BatchNorm2d(out_channels)
self.relu = nn.Relu(in_channels, out_channels)
self.conv2 = conv3x3(out_channels, out_channels)
self.bn2 = nn.BatchNorm2d(out_channels)
self.downsample = downsample

def forward(self, x):
    residual = x
    out = self.conv1(x)
    out = self.bn1(out)
    out = self.relu(out)
    out = self.conv2(out)
    out = self.bn2(out)
    if self.downsample:
        residual = self.downsample(x)
    out += residual
    out = self.relu(out)
    return out

ResNet

class Net(nn.Module):
def init(self, block, layers, num_classes=10):
super(Net, self).init()
self.in_channels = 16
self.conv = conv3x3(3, 16)
self.bn = nn.BatchNorm2d(16)
self.relu = nn.Relu(inplace=True)
self.layer1 = self.make_layer(block, 16, layers[0])
self.layer2 = self.make_layer(block, 32, layers[1], 2)
self.layer3 = self.make_layer(block, 64, layers[2], 2)
self.avg_pool = nn.AvgPool2d(8)
self.fc = nn.Linear(64, num_classes)

def make_layer(self, block, out_channels, blocks, stride=1):
    downsample = None
    if (stride != 1) or (self.in_channels != out_channels):
        downsample = nn.Sequential(
            conv3x3(self.in_channels, out_channels, stride=stride),
            nn.BatchNorm2d(out_channels))
    layers = []
    layers.append(block(self.in_channels, out_channels, stride, downsample))
    self.in_channels = out_channels
    for i in range(1, blocks):
        layers.append(block(out_channels, out_channels))
    return nn.Sequential(*layers)

def forward(self, x):
    out = self.conv(x)
    out = self.bn(out)
    out = self.relu(out)
    out = self.layer1(out)
    out = self.layer2(out)
    out = self.layer3(out)
    out = self.avg_pool(out)
    out = self.view(out.size(0), -1)
    out = self.fc(out)
    return out
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值