• 【Note】CNN与现代卷积神经网络part1(附PyTorch代码)


    0 前言

    本《CNN与现代卷积神经网络》Note系列会共分为4个part,本文为part1。本文Markdown共6k字。

    1 卷积神经网络

    卷积神经网络(convolutional neural network,CNN)是一类强大的、为处理图像数据而设计的神经网络。

    基于卷积神经网络架构的模型在计算机视觉领域中已经占主导地位,当今几乎所有的图像识别目标检测语义分割相关的学术竞赛和商业应用都以这种方法为基础。

    现代卷积神经网络的设计得益于生物学、群论和一系列的补充实验。 卷积神经网络需要的参数少于全连接架构的网络,而且卷积也很容易用GPU并行计算。 因此卷积神经网络除了能够高效地采样从而获得精确的模型,还能够高效地计算。 久而久之,从业人员越来越多地使用卷积神经网络。即使在通常使用循环神经网络一维序列结构任务上(例如音频、文本和时间序列分析),卷积神经网络也越来越受欢迎。 通过对卷积神经网络一些巧妙的调整,也使它们在图结构数据推荐系统中发挥作用。

    在part1和part2的开始,我将介绍构成所有卷积网络主干的基本元素。 这包括卷积层本身、填充(padding)步幅(stride)的基本细节、用于在相邻区域汇聚信息的汇聚层(pooling)、在每一层中**多通道(channel)**的使用,以及有关现代卷积网络架构的仔细讨论。
    在part1和part2的最后,我将介绍一个完整的、可运行的 LeNet模型:这是第一个成功应用的卷积神经网络,比现代深度学习兴起时间还要早。

    在part3和part4中,我将深入研究一些流行的、相对较新的卷积神经网络架构的完整实现,这些网络架构涵盖了现代从业者通常使用的大多数经典技术

    1.1 从全连接层到卷积

    多层感知机十分适合处理表格数据,其中行对应样本,列对应特征。 对于表格数据,我们寻找的模式可能涉及特征之间的交互,但是我们不能预先假设任何与特征交互相关的先验结构。 此时,多层感知机可能是最好的选择,然而对于高维感知数据,这种缺少结构的网络可能会变得不实用。

    例如在之前猫狗分类的例子中:假设我们有一个足够充分的照片数据集,数据集中是拥有标注的照片,每张照片具有百万级像素,这意味着网络的每次输入都有一百万个维度。 即使将隐藏层维度降低到1000,这个全连接层也将有 1 0 6 × 1 0 3 = 1 0 9 10^6\times10^3=10^9 106×103=109个参数。 想要训练这个模型将不可实现,因为需要有大量的GPU、分布式优化训练的经验和超乎常人的耐心。

    有些读者可能会反对这个观点,认为要求百万像素的分辨率可能不是必要的。 然而,即使分辨率减小为十万像素,使用1000个隐藏单元的隐藏层也可能不足以学习到良好的图像特征,在真实的系统中我们仍然需要数十亿个参数。 此外,拟合如此多的参数还需要收集大量的数据。 然而,如今人类和机器都能很好地区分猫和狗:这是因为图像中本就拥有丰富的结构,而这些结构可以被人类和机器学习模型使用。 卷积神经网络(convolutional neural networks,CNN)是机器学习利用自然图像中一些已知结构的创造性方法

    1.1.1 不变性

    1. 平移不变性(translation invariance):不管检测对象出现在图像中的哪个位置,神经网络的前面几层应该对相同的图像区域具有相似的反应,即为“平移不变性”。

    2. 局部性(locality):神经网络的前面几层应该只探索输入图像中的局部区域,而不过度在意图像中相隔较远区域的关系,这就是“局部性”原则。最终,可以聚合这些局部特征,以在整个图像级别进行预测。

    1.1.2 多层感知机的限制

    1.1.2.1 平移不变性

    1.1.2.2 局部性

    1.1.3 卷积

    简要回顾一下为什么上面的操作被称为卷积。

    1.1.4 通道(channel)

    由于输入图像是三维的,我们的隐藏表示
    也最好采用三维张量。 换句话说,对于每一个空间位置,我们想要采用一组而不是一个隐藏表示。这样一组隐藏表示可以想象成一些互相堆叠的二维网格。 因此,我们可以把隐藏表示想象为一系列具有二维张量的通道(channel)。 这些通道有时也被称为特征映射(feature maps),因为每个通道都向后续层提供一组空间化的学习特征。 直观上可以想象在靠近输入的底层,一些通道专门识别边缘,而一些通道专门识别纹理。

    1.1.5 Summary

    • 图像的平移不变性使我们以相同的方式处理局部图像,而不在乎它的位置。
    • 局部性意味着计算相应的隐藏表示只需一小部分局部图像像素。
    • 在图像处理中,卷积层通常比全连接层需要更少的参数,但依旧获得高效用的模型。
    • 卷积神经网络(CNN)是一类特殊的神经网络,它可以包含多个卷积层。
    • 多个输入和输出通道使模型在每个空间位置可以获取图像的多方面特征。

    1.2 图像卷积

    上节我解析了卷积层的原理,现在我们看看它的实际应用。由于卷积神经网络的设计是用于探索图像数据,本节我们将以图像为例。

    1.2.1 互相关运算

    import torch
    from torch import nn
    from d2l import torch as d2l
    
    def corr2d(X, K):  #@save
        """计算二维互相关运算"""
        h, w = K.shape
        Y = torch.zeros((X.shape[0] - h + 1, X.shape[1] - w + 1))
        for i in range(Y.shape[0]):
            for j in range(Y.shape[1]):
                Y[i, j] = (X[i:i + h, j:j + w] * K).sum()
        return Y
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    1.2.2 卷积层

    卷积层对输入和卷积核权重进行互相关运算,并在添加标量偏置之后产生输出。 所以,卷积层中的两个被训练的参数是卷积核权重和标量偏置。 就像我们之前随机初始化全连接层一样,在训练基于卷积层的模型时,我们也随机初始化卷积核权重。

    基于上面定义的corr2d函数实现二维卷积层。在__init__构造函数中,将weight和bias声明为两个模型参数。前向传播函数调用corr2d函数并添加偏置。

    class Conv2D(nn.Module):
        def __init__(self, kernel_size):
            super().__init__()
            self.weight = nn.Parameter(torch.rand(kernel_size))
            self.bias = nn.Parameter(torch.zeros(1))
    
        def forward(self, x):
            return corr2d(x, self.weight) + self.bias
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    高度和宽度分别为 h h h w w w的卷积核可以被称为 h × w h\times w h×w卷积或 h × w h\times w h×w卷积核。 我们也将带有 h × w h\times w h×w卷积核的卷积层称为 h × w h\times w h×w卷积层。

    1.2.3 图像中目标的边缘检测

    (略)

    1.2.4 学习卷积核

    如果我们只需寻找黑白边缘,那么以上[1, -1]的边缘检测器足以。然而,当有了更复杂数值的卷积核,或者连续的卷积层时,我们不可能手动设计滤波器。那么我们是否可以学习由X生成Y的卷积核呢?

    现在让我们看看是否可以通过仅查看“输入-输出”对来学习由X生成Y的卷积核。 我们先构造一个卷积层,并将其卷积核初始化为随机张量。接下来,在每次迭代中,我们比较Y与卷积层输出的平方误差,然后计算梯度来更新卷积核。为了简单起见,我们在此使用内置的二维卷积层,并忽略偏置。

    # 构造一个二维卷积层,它具有1个输出通道和形状为(1,2)的卷积核
    conv2d = nn.Conv2d(1,1, kernel_size=(1, 2), bias=False)
    
    # 这个二维卷积层使用四维输入和输出格式(批量大小、通道、高度、宽度),
    # 其中批量大小和通道数都为1
    X = X.reshape((1, 1, 6, 8))
    Y = Y.reshape((1, 1, 6, 7))
    lr = 3e-2  # 学习率
    
    for i in range(10):
        Y_hat = conv2d(X)
        l = (Y_hat - Y) ** 2
        conv2d.zero_grad()
        l.sum().backward()
        # 迭代卷积核
        conv2d.weight.data[:] -= lr * conv2d.weight.grad
        if (i + 1) % 2 == 0:
            print(f'epoch {i+1}, loss {l.sum():.3f}')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这里插入图片描述
    在10次迭代之后,误差已经降到足够低。现在我们来看看我们所学的卷积核的权重张量。

    conv2d.weight.data.reshape((1, 2))
    
    • 1

    在这里插入图片描述
    细心的读者一定会发现,我们学习到的卷积核权重非常接近我们之前定义的卷积核K。

    1.2.5 互相关和卷积

    1.2.6 特征映射和感受野

    1.2.7 Summary

    • 二维卷积层的核心计算是二维互相关运算。最简单的形式是,对二维输入数据和卷积核执行互相关操作,然后添加一个偏置。
    • 我们可以设计一个卷积核来检测图像的边缘。
    • 我们可以从数据中学习卷积核的参数。
    • 学习卷积核时,无论用严格卷积运算或互相关运算,卷积层的输出不会受太大影响。
    • 当需要检测输入特征中更广区域时,我们可以构建一个更深的卷积网络。

    1.3 填充和步幅

    卷积的输出形状取决于输入形状和卷积核的形状。还有什么因素会影响输出的大小呢?本节我们将介绍填(padding)和步幅(stride)。

    1.3.1 填充

    在应用多层卷积时,我们常常丢失边缘像素。 由于我们通常使用小卷积核,因此对于任何单个卷积,我们可能只会丢失几个像素。 但随着我们应用许多连续卷积层,累积丢失的像素数就多了。 解决这个问题的简单方法即为填充(padding):在输入图像的边界填充元素(通常填充元素是0)。

    在下面的例子中,我们创建一个高度和宽度为3的二维卷积层,并在所有侧边填充1个像素。给定高度和宽度为8的输入,则输出的高度和宽度也是8。

    import torch
    from torch import nn
    
    # 为了方便起见,我们定义了一个计算卷积层的函数。
    # 此函数初始化卷积层权重,并对输入和输出提高和缩减相应的维数
    def comp_conv2d(conv2d, X):
        # 这里的(1,1)表示批量大小和通道数都是1
        X = X.reshape((1, 1) + X.shape)
        Y = conv2d(X)
        # 省略前两个维度:批量大小和通道
        return Y.reshape(Y.shape[2:])
    
    # 请注意,这里每边都填充了1行或1列,因此总共添加了2行或2列
    conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1)
    X = torch.rand(size=(8, 8))
    comp_conv2d(conv2d, X).shape
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    当卷积核的高度和宽度不同时,我们可以填充不同的高度和宽度,使输出和输入具有相同的高度和宽度。在如下示例中,我们使用高度为5,宽度为3的卷积核,高度和宽度两边的填充分别为2和1。

    conv2d = nn.Conv2d(1, 1, kernel_size=(5, 3), padding=(2, 1))
    comp_conv2d(conv2d, X).shape
    
    • 1
    • 2

    1.3.2 步幅

    在计算互相关时,卷积窗口从输入张量的左上角开始,向下、向右滑动。 在前面的例子中,我们默认每次滑动一个元素。 但是,有时候为了高效计算或是缩减采样次数,卷积窗口可以跳过中间位置,每次滑动多个元素。我们将每次滑动元素的数量称为步幅(stride)。

    下面,我们将高度和宽度的步幅设置为2,从而将输入的高度和宽度减半。

    conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1, stride=2)
    comp_conv2d(conv2d, X).shape
    
    • 1
    • 2

    在这里插入图片描述

    接下来,看一个稍微复杂的例子。

    conv2d = nn.Conv2d(1, 1, kernel_size=(3, 5), padding=(0, 1), stride=(3, 4))
    comp_conv2d(conv2d, X).shape
    
    • 1
    • 2

    在这里插入图片描述

    1.3.3 Summary

    • 填充可以增加输出的高度和宽度。这常用来使输出与输入具有相同的高和宽。
    • 步幅可以减小输出的高和宽,例如输出的高和宽仅为输入的高和宽的1/n(n是一个大于1的整数)。
    • 填充和步幅可用于有效地调整数据的维度。

    2 现代卷积神经网络(part3和part4内容)

    现代卷积神经网络的设计得益于生物学、群论和一系列的补充实验。 卷积神经网络需要的参数少于全连接架构的网络,而且卷积也很容易用GPU并行计算。 因此卷积神经网络除了能够高效地采样从而获得精确的模型,还能够高效地计算。 久而久之,从业人员越来越多地使用卷积神经网络。即使在通常使用循环神经网络的一维序列结构任务上(例如音频、文本和时间序列分析),卷积神经网络也越来越受欢迎。 通过对卷积神经网络一些巧妙的调整,也使它们在图结构数据推荐系统中发挥作用。

  • 相关阅读:
    Rust编程基础之引用与借用
    STARKs with small finite field:小域带来的迷人性能
    RK3399平台开发系列讲解(驱动篇)Regulator Framework
    CEAC之《计算机应用助理工程师》3
    【前端】Vue网络应用
    在 rider 里用配置 Perforce(P4)的注意事项
    Maven下的依赖管理
    解决 Elasticsearch 分页查询记录超过10000时异常
    MyBatis的xml里#{}的参数为null报错、将null作为参数传递报错问题
    手把手带你自定义 Gradle 插件 —— Gradle 系列(2)
  • 原文地址:https://blog.csdn.net/JishuFengyang/article/details/133861153