当前位置: 首页 > article >正文

深度学习——现代卷积神经网络(七)

深度卷积神经网络

学习表征

  1. 观察图像特征的提取⽅法。
  2. 在合理地复杂性前提下,特征应该由多个共同学习的神经⽹络层组成,每个层都有可学习的参数。

当年缺少数据和硬件支持

AlexNet

  1. AlexNet⽐相对较⼩的LeNet5要深得多。 AlexNet由⼋层组成:五个卷积层、两个全连接隐藏层和⼀个全连接输出层。
  2. AlexNet使⽤ReLU⽽不是sigmoid作为其激活函数。

模型设计

在AlexNet的第⼀层,卷积窗⼝的形状是11×11。由于ImageNet中⼤多数图像的宽和⾼⽐MNIST图像的多10倍以上,因此,需要⼀个更⼤的卷积窗⼝来捕获⽬标。第⼆层中的卷积窗⼝形状被缩减为5× 5,然后是3× 3。此外,在第⼀层、第⼆层和第五层卷积层之后,加⼊窗⼝形状为3× 3、步幅为2的最⼤汇聚层。⽽且, AlexNet的卷积通道数⽬是LeNet的10倍。

激活函数

AlexNet将sigmoid激活函数改为更简单的ReLU激活函数。

  1. ReLU激活函数的计算更简单
  2. 当使⽤不同的参数初始化⽅法时, ReLU激活函 数使训练模型更加容易。

容量控制

AlexNet通过暂退法(4.6节)控制全连接层的模型复杂度,⽽LeNet只使⽤了权重衰减。为了进⼀步扩充数据, AlexNet在训练时增加了⼤量的图像增强数据,如翻转、裁切和变⾊。这使得模型更健壮,更⼤的样本量有效地减少了过拟合。

使用块的网络(VGG)

经典的卷积神经网络基本组成部分:

  1. 带填充以保持分辨率的卷积层;
  2. ⾮线性激活函数,如ReLU;
  3. 汇聚层,如最⼤汇聚层。

而⼀个VGG块与之类似,由⼀系列卷积层组成,后⾯再加上⽤于空间下采样的最⼤汇聚层。

VGG块

import torch
import torch.nn as nn 
import d2l.torch as d2l
# 卷积层的数量num_convs、输⼊通道的数量in_channels 和输出通道的数量out_channels.
def Vgg_block(num_convs, in_channels, out_channels):
    layers = []
    for _ in range(num_convs):
        layers.append(nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1))
        layers.append(nn.ReLU())
        in_channels = out_channels
    layers.append(nn.MaxPool2d(kernel_size=2, stride=2))
    return nn.Sequential(*layers)
    

VGG网络

# 超参数变量conv_arch。该变量指定了每个VGG块⾥卷积层个数和输出通道数。
conv_arch = ((1, 64), (1, 128), (2, 256), (2, 512), (2, 512))
in_channels = 1
def VGG(in_channels, conv_arch):
    conv_blks = []
    for (num_convs, out_channels) in conv_arch:
        conv_blks.append(Vgg_block(num_convs, in_channels, out_channels))
        in_channels = out_channels
    net = nn.Sequential(*conv_blks, nn.Flatten(),
                        nn.Linear(out_channels * 7 * 7, 4096), nn.ReLU(), nn.Dropout(0.5),
                        nn.Linear(4096, 4096), nn.ReLU(), nn.Dropout(0.5),
                        nn.Linear(4096, 10))
    return net

net = VGG(in_channels, conv_arch)
net

网络中的网络(NiN)

LeNet、 AlexNet和VGG都有⼀个共同的设计模式:通过⼀系列的卷积层与汇聚层来提取空间结构特征;然后通过全连接层对特征的表征进⾏处理。 AlexNet和VGG对LeNet的改进主要在于如何扩⼤和加深这两个模块。** **
卷积层的输⼊和输出由四维张量组成,张量的每个轴分别对应样本、通道、⾼度和宽度。 NiN的想法是在每个像素位置(针对每个⾼度和宽度)应⽤⼀个全连接层 ,

如果我们将权重连接到每个空间位置,我们可以将其视为1 × 1卷积层 。从另⼀个⻆度看,即将空间维度中的每个像素视为单个样本,将通道维度视为不同特征(feature)。
NiN块以⼀个普通卷积层开始,后⾯是两个1 × 1的卷积层。这两个1 × 1卷积层充当带有ReLU激活函数的逐像素全连接层。

import torch 
import torch.nn as nn
import d2l.torch as d2l

def nin_block(in_channels, out_channels, kernel_size, strides, padding):
    return nn.Sequential(
        nn.Conv2d(in_channels, out_channels, kernel_size, strides, padding),nn.ReLU(),
        nn.Conv2d(out_channels, out_channels, kernel_size=1), nn.ReLU(),
        nn.Conv2d(out_channels, out_channels, kernel_size=1), nn.ReLU()
        )

net = nn.Sequential(
        nin_block(20, 96, kernel_size=11, strides=4, padding=0),
        nn.MaxPool2d(3, stride=2),
        nin_block(96, 256, kernel_size=5, strides=1, padding=2),
        nn.MaxPool2d(3, stride=2),
        nin_block(256, 384, kernel_size=3, strides=1, padding=1),
        nn.MaxPool2d(3, stride=2),
        nn.Dropout(0.5),
        # 标签类别数是10
        nin_block(384, 10, kernel_size=3, strides=1, padding=1),
        nn.AdaptiveAvgPool2d((1, 1)),
        # 将四维的输出转成⼆维的输出,其形状为(批量⼤⼩,10)
        nn.Flatten())

X = torch.rand(size=(16, 20, 224, 224))
for layer in net:
    X = layer(X)
    print(layer.__class__.__name__,'output shape:\t', X.shape)

lr, num_epochs, batch_size = 0.1, 10, 128
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=224)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())

含并行连结的网络

inception块

在GoogLeNet中,基本的卷积块被称为Inception块(Inception block)。

import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l

class Inception(nn.Module):
    # c1--c4是每条路径的输出通道数
    def __init__(self, in_channels, c1, c2, c3, c4, **kwargs):
        super(Inception, self).__init__(**kwargs)
        # 线路1,单1x1卷积层
        self.p1_1 = nn.Conv2d(in_channels, c1, kernel_size=1)
        # 线路2, 1x1卷积层后接3x3卷积层
        self.p2_1 = nn.Conv2d(in_channels, c2[0], kernel_size=1)
        self.p2_2 = nn.Conv2d(c2[0], c2[1], kernel_size=3, padding=1)
        # 线路3, 1x1卷积层后接5x5卷积层
        self.p3_1 = nn.Conv2d(in_channels, c3[0], kernel_size=1)
        self.p3_2 = nn.Conv2d(c3[0], c3[1], kernel_size=5, padding=2)
        # 线路4, 3x3最⼤汇聚层后接1x1卷积层
        self.p4_1 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)
        self.p4_2 = nn.Conv2d(in_channels, c4, kernel_size=1)
    def forward(self, x):
        p1 = F.relu(self.p1_1(x))
        p2 = F.relu(self.p2_2(F.relu(self.p2_1(x))))
        p3 = F.relu(self.p3_2(F.relu(self.p3_1(x))))
        p4 = F.relu(self.p4_2(self.p4_1(x)))
    # 在通道维度上连结输出
        return torch.cat((p1, p2, p3, p4), dim=1)

GoogLeNet模型

批量规范化

训练深层神经⽹络是⼗分困难的,特别是在较短的时间内使他们收敛更加棘⼿。本节将介绍批量规范化(batch normalization),这是⼀种流⾏且有效的技术,可持续加速深层⽹络的收敛速度。再结合后面将介绍的残差块,批量规范化使得研究⼈员能够训练100层以上的⽹络。

、训练深层网络

为什么要批量规范化?

  • ⾸先,数据预处理的⽅式通常会对最终结果产⽣巨⼤影响。
  • 第⼆,对于典型的多层感知机或卷积神经⽹络。当我们训练时,中间层中的变量(例如,多层感知机中的仿射变换输出)可能具有更⼴的变化范围:不论是沿着从输⼊到输出的层,跨同⼀层中的单元,或是随着时间的推移,模型参数的随着训练更新变幻莫测。
  • 第三,更深层的⽹络很复杂,容易过拟合。这意味着正则化变得更加重要。
    批量规范化应⽤于单个可选层(也可以应⽤到所有层),其原理如下:在每次训练迭代中,我们⾸先规范化输⼊,即通过减去其均值并除以其标准差,其中两者均基于当前⼩批量处理。接下来,我们应⽤⽐例系数和⽐例偏移。正是由于这个基于批量统计的标准化,才有了批量规范化的名称。


    注意:我们在⽅差估计值中添加⼀个⼩的常量ϵ > 0,以确保我们永远不会尝试除以零,即使在经验⽅差估计值可能消失的情况下也是如此。估计值µ^Bσ^ B通过使⽤平均值和⽅差的噪声(noise)估计来抵消缩放问题。乍看起来,这种噪声是⼀个问题,⽽事实上它是有益的。
  • 第四,批量规范化层在”训练模式“(通过⼩批量统计数据规范化)和“预测模式”(通过数据集统计规范化)中的功能不同。在训练过程中,我们⽆法得知使⽤整个数据集来估计平均值和⽅差,所以只能根据每个⼩批次的平均值和⽅差不断训练模型。⽽在预测模式下,可以根据整个数据集精确计算批量规范化所需的平均值和⽅差。

、批量规范化层

批量规范化和其他层之间的⼀个关键区别是,由于批量规范化在完整的⼩批量上运⾏,因此我们
不能像以前在引⼊其他层时那样忽略批量⼤⼩。

我们可以分为两种不同情况进行讨论:全连接层和卷积层

全连接层

我们将批量规范化层置于全连接层中的仿射变换和激活函数之间。设全连接层的输⼊为x,权重参数和偏置参数分别为Wb,激活函数为ϕ,批量规范化的运算符为BN。那么,使⽤批量规范化的全连接层的输出的计算详情如下:

卷积层

对于卷积层,我们可以在卷积层之后和⾮线性激活函数之前应⽤批量规范化。 当卷积有多个输出通道
时,我们需要对这些通道的“每个”输出执⾏批量规范化,每个通道都有⾃⼰的拉伸(scale)和偏移(shif)参数,这两个参数都是标量。

代码实现

# 1. 导入库
import torch
from torch import nn
from d2l import torch as d2l

# 2. 批量规范化实现

def batch_norm(x, gamma, beta, moving_mean, moving_var, eps, momentum):
    # 通过is_grad_enabled来判断当前模式是训练模式还是预测模式
    if not torch.is_grad_enabled():
        # 如果是在预测模式下,直接使⽤传⼊的移动平均所得的均值和⽅差
        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, 2, 3), keepdim=True)
            var = ((x - mean) ** 2).mean(dim=(0, 2, 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) * var
    Y = gamma * X_hat + beta # 缩放和移位
    return Y, moving_mean.data, moving_var.data

BatchNorm层

这个层将保持适当的参数:拉伸gamma和偏移beta,这两个参数将在训练过程中更新

# BatchNorm层实现
class BatchNorm(nn.Module):
    # num_features:完全连接层的输出数量或卷积层的输出通道数。
    # num_dims: 2表⽰完全连接层, 4表⽰卷积层
    def __init__(self, num_features, num_dims):
        super().__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和1
        self.moving_mean = torch.zeros(shape)
        self.moving_var = torch.ones(shape)

    def forward(self, x):
        # 如果X不在内存上,将moving_mean和moving_var
        # 复制到X所在显存上
        if self.moving_mean.device != x.device:
            self.moving_mean = self.moving_mean.to(x.device)
            self.moving_var = self.moving_var.to(x.device)
        # 保存更新过的moving_mean和moving_var
        Y, self.moving_mean, self.moving_var = batch_norm(x, self.gamma, self.beta, self.moving_mean,self.moving_var, eps=1e-5, momentum=0.9)
        return Y

使⽤批量规范化层的 LeNet

import torch.nn as nn 
import d2l.torch as d2l
import torch
net = nn.Sequential(
    nn.Conv2d(1, 6, kernel_size=5), BatchNorm(6, num_dims=4), nn.Sigmoid(),
    nn.AvgPool2d(kernel_size=2, stride=2),
    nn.Conv2d(6, 16,kernel_size=5), BatchNorm(16, num_dims=4), nn.Sigmoid(),
    nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(),
    nn.Linear(16 * 4 * 4, 120),BatchNorm(120, num_dims=2), nn.Sigmoid(),
    nn.Linear(120, 84),BatchNorm(84, num_dims=2), nn.Sigmoid(), 
    nn.Linear(84, 10))
lr, num_epochs, batch_size = 1.0, 30, 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())

print(net[1].gamma.reshape((-1,)), net[1].beta.reshape((-1,)))

残差神经网络(ResNet)

对于深度神经⽹络,如果我们能将新添加的层训练成恒等映射(identity function) f(x) = x,新模型和原模型将同样有效。同时,由于新模型可能得出更优的解来拟合训练数据集,因此添加层似乎更容易降低训练误差。
残差⽹络的核⼼思想是:每个附加层都应该更容易地包含原始函数作为其元素之⼀。

⽣成两种类型的⽹络:⼀种是当use_1x1conv=False时,应⽤ReLU⾮线性函数之前,将输⼊添加到输出。另⼀种是当use_1x1conv=True时,添加通过1 × 1卷积调整通道和分辨率

、残差块实现

# 残差块
import torch
import torch.nn as nn 
import d2l.torch as d2l
import torch.nn.functional as F

class Residual(nn.Module):
    def __init__(self, input_channels,num_channels, use_1x1_conv = False, strides = 1):
        super().__init__()
        self.conv1 = nn.Conv2d(input_channels, num_channels, kernel_size=3,stride=strides, padding = 1)
        self.bn1 = nn.BatchNorm2d(num_channels)
        self.conv2 = nn.Conv2d(num_channels, num_channels, kernel_size=3,  padding = 1)
        self.bn2 = nn.BatchNorm2d(num_channels)
        if use_1x1_conv:
            self.conv3 = nn.Conv2d(input_channels, num_channels,kernel_size=3, stride=strides, padding=1)
        else:
            self.conv3 = None
    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)
        Y += X
        return F.relu(Y)

残差神经网络模型

  • ResNet-18

import torch
import torch.nn as nn 

def res_block(input_channels, num_channels, num_residuals, first_block = False):
    blk = []
    for i in range(num_residuals):
        if i == 0 and not first_block:
            blk.append(Residual(input_channels, num_channels, use_1x1_conv=True, strides=2))
        else:
            blk.append(Residual(input_channels=input_channels, num_channels=num_channels))
    return blk

# 1. 卷积层
b1 = nn.Sequential(nn.Conv2d(1, 64,kernel_size=7,stride=2, padding=3),
                   nn.BatchNorm2d(64), nn.ReLU(),
                   nn.AvgPool2d(kernel_size=3, stride=2, padding=1))
# 2. 残差层
b2 = nn.Sequential(*res_block(64,64, num_residuals=2, first_block=True))
b3 = nn.Sequential(*res_block(64,128, num_residuals=2))
b4 = nn.Sequential(*res_block(128,256, num_residuals=2))
b5 = nn.Sequential(*res_block(256,512, num_residuals=2))

# 3. 汇聚层和全连接层
# ResNet-18
net = nn.Sequential(b1, b2, b3, b4, b5,
                    nn.AdaptiveAvgPool2d((1,1)),
                    nn.Flatten(), nn.Linear(512, 10))

'''
每个模块有4个卷积层(不包括恒等映射的1 × 1卷积层)。
加上第⼀个7 × 7卷积层和最后⼀个全连接层,共
有18层。因此,这种模型通常被称为ResNet-18。
'''

查看在ResNet中不同模块的输入形状是怎么变化的

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

:::color1
Sequential output shape: torch.Size([1, 64, 56, 56])
Sequential output shape: torch.Size([1, 64, 56, 56])
Sequential output shape: torch.Size([1, 128, 28, 28])
Sequential output shape: torch.Size([1, 256, 14, 14])
Sequential output shape: torch.Size([1, 512, 7, 7])
AdaptiveAvgPool2d output shape: torch.Size([1, 512, 1, 1])
Flatten output shape: torch.Size([1, 512])
Linear output shape: torch.Size([1, 10])

:::

模型训练

# 模型训练
lr, num_epochs, batch_size = 0.05, 10, 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())

http://www.kler.cn/a/445763.html

相关文章:

  • 使用 esrally race 测试 Elasticsearch 性能及 Kibana 可视化分析指南
  • Vue+element 回车查询页面刷新
  • BI与业务对象-华为流程与数字化中的关键要素
  • 【Vue3学习】ref,reactive,toRef,toRefs的功能与用法区别
  • Redis 持久化揭秘:选择 RDB、AOF 还是混合持久化?
  • Flink DataStream API 编程指南
  • #Java篇:非常火热的Spring Boot典型项目结构
  • OpenCV 学习记录:首篇
  • static_cast与dynamic_cast的区别
  • 基于蓝牙通信的手机遥控智能灯(论文+源码)
  • 透析Svchost.EXE进程清除木马的最大后门
  • 【算法练习】尺取法
  • pinglunhuifu 页面
  • 使用NodeJs 实现图片转PPT
  • 【实用技能】如何在 SQL Server 中处理 Null 或空值?
  • 基于Spring Boot的高校实验室预约系统
  • 【Unity3D】实现可视化链式结构数据(节点数据)
  • R-CNN算法详解及代码复现
  • 【快速上手Docker 简单配置方法】
  • Java项目--仿RabbitMQ的消息队列--统一硬盘操作