一、分类任务概述:

图像分类任务和之前的回归任务在逻辑框架大体上相同;但由于输入为图像,因此必须对图像进行处理,提取特征,把任务转化成为我们在回归任务中的类似流程(输出、loss计算等存在差异),这里采用卷积来处理图像。

二、卷积神经网络基础

1.如何提取图像特征?

我们知道图像是由RGB三通道组成的H*W矩阵,所以我们可以把图像展平,把各层矩阵中各元素都放到一个列向量上,每个元素就是一个特征;但是这样特征过于多了,计算过于复杂,同时特征过多会增加过拟合的风险,所以我们要作进一步处理。

2.卷积

卷积是一种针对图像等网格结构数据的特征提取操作,核心是通过卷积核在输入特征图上滑动对区域局部加权求和,从而生成包含局部特征的输出特征图

注:这里的卷积在工程中一般指的是互相关操作,其与数学卷积上的区别在:数学卷积在计算前需要翻转核(核矩阵分别以主对角线和副对角线来进行元素对称交换操作),而互相关操作不需要

卷积核(过滤器)

卷积核是一个可学习的小的权重矩阵,其通道数与输入特征图通道数相同,它在输入数据上滑动以执行卷积运算;卷积核的大小决定卷积的感受野的大小;卷积就其卷积核的特性,同一卷积核在卷积层的所有空间上权重共享,这里不同于全连接

填充Padding

Padding操作是在输入图像周围加几圈额外像素(通常为0),核心是防止边缘特征丢失,同时可以使输入特征图的大小和输出特征图大小相同

步长Stride

Stride决定了卷积核在输入特征图中每次滑动的步幅

多个过滤器和深度

每一个卷积层可以有多个卷积核,卷积核数量决定输出特征图的深度

一个卷积核的形状(K*K*特征图通道数),卷积结果特征图各通道点对点相加,得到一张特征图

输出特征图尺寸的计算

输出尺寸 = (I(输入特征图尺寸) - K(卷积核尺寸) + 2P(填充))/S(步幅)   +   1

卷积核参数量的计算

参数量 = 卷积核数量 * K * K * 输入通道数(不含偏置项)

3.池化

一个问题:当特征图保持224×224不变时,参数数量会随着卷积层增加而急剧膨胀(如3×3×64×128的参数量),需要通过降采样减少计算量;

降采样方法:

步长降采样:通过增大Stride来实现降采样,具有计算复杂度低,结构简单的特点,但它会丢失部分特征信息,步长过大时(S>3),尤为显著,故实际工程中一般采用S=2,来实现降采样。

池化:用局部的统计特征代替原始数据,从而实现降维,抗噪声,减少特征数,更不容易过拟合

最大池化:

顾名思义,取区域中最大值代替原始数据,保留最显著特征,计算复杂度低,CNN中最常用的池化方式;

平均值池化:

取区域中平均值代替原始数据,理论上更全面反应区域特征,实际上由于计算复杂且效果有限,使用频率低于最大值池化;

特殊变体:Adaptive Avg Pool(自适应平均池化):强制输出制定尺寸

三、卷积神经网络结构

卷积层设计

交替变化

卷积和池化交替进行,避免一次卷积+多次池化的简单结构

通道扩展

随着特征图尺寸(H*W)减小,通道数C应该相应增加,典型模式为“通道翻倍,尺寸减半”;参数量应控制在一个合理区间

全连接层设计

1.维度转化方法

(1)展平操作

将三维张量(如1024*7*7)拉伸为一维向量(50176)(*存在维度优先级)

(2)降维策略

通过MaxPooling将特征图直接降维至C*1*1

2.全连接设计

通过linear进行一层或多层全连接操作

输出层设计

1.OneHot编码

采用OneHot编码表示输出类别,通过 Softmax 激活将原始得分转化为概率分布,取概率向量中最大值对应的索引,该索引映射的 OneHot 编码即为预测类别

四、Softmax与损失函数

1.Softmax函数

将一个n维向量转化为概率分布,在分类任务中我们使用Softmax函数将原始得分转化为概率分布

2.交叉熵损失

五、经典卷积神经网络

1.AlexNet

创新点

1.Relu激活函数

Relu函数具有计算简单,缓解梯度消失问题(正区间梯度为1)、引入稀疏性,提升模型泛化能力(将所有负值置为0,使部分神经元休眠)

2.Dropout技术

Dropout技术的核心机制:在训练阶段,随机将部分神经元置为 “失活” 状态,不参与训练;

目标:模拟集成学习(多子模型加权平均→降低方差)、抑制神经元共适应(学习通用特征→提升鲁棒性)、隐式 L2 正则化(约束小权重→模型更简单);

效果:有效缓解模型过拟合问题

3.优化池化

提出最大池化代替平均池化,提出重叠池化

4.归一化

首次提出并应用 “局部响应归一化“;归一化(将数值约束到一个区间内,保持学习有效性、缓解梯度消失和梯度爆炸、抑制过拟合)

代码示写

import torch.nn as nn
import torchvision.models as models
import torch

alexNet = models.alexnet()

print(alexNet)


# 手动实现AlexNet模型
class myAlexNet(nn.Module):
    def __init__(self):
        super(myAlexNet, self).__init__()  # 继承myAlexNet父类的__init__,绑定到self上
        self.relu = nn.ReLU()
        self.drop = nn.Dropout(0.5)  # 训练阶段,每个神经元50%概率被丢弃

        self.conv1 = nn.Conv2d(in_channels=3, out_channels=64, kernel_size=11, stride=4, padding=2)
        self.pool1 = nn.MaxPool2d(3, stride=2)
        self.conv2 = nn.Conv2d(64, 192, 5, 1, 2)
        self.pool2 = nn.MaxPool2d(3, stride=2)
        self.conv3 = nn.Conv2d(192, 384, 3, 1, 1)
        self.conv4 = nn.Conv2d(384, 256, 3, 1, 1)
        self.conv5 = nn.Conv2d(256, 256, 3, 1, 1)
        self.pool3 = nn.MaxPool2d(3, 2)
        self.adaPool = nn.AdaptiveAvgPool2d(6)  # 将特征图尺寸转化为6*6
        self.fc1 = nn.Linear(9216, 4096)
        self.fc2 = nn.Linear(4096, 4096)
        self.fc3 = nn.Linear(4096, 1000)

    def forward(self, x):
        x = self.conv1(x)
        x = self.relu(x)
        x = self.pool1(x)

        x = self.conv2(x)
        x = self.relu(x)
        x = self.pool2(x)

        x = self.conv3(x)
        x = self.relu(x)
        x = self.conv4(x)
        x = self.relu(x)
        x = self.conv5(x)
        x = self.relu(x)

        x = self.pool3(x)
        x = self.adaPool(x)

        x = x.flatten(start_dim=1)  # 从维度1开始展平

        x = self.fc1(x)
        x = self.relu(x)
        x = self.drop(x)

        x = self.fc2(x)
        x = self.relu(x)
        x = self.drop(x)

        x = self.fc3(x)
        x = self.relu(x)

        return x


myModel = myAlexNet()


def get_parameters(model):
    total_num = sum(p.numel() for p in model.parameters())  # 统计所有参数
    trainable_num = sum(p.numel() for p in model.parameters() if p.requires_grad)  # 统计所有可训练参数
    return {'Total': total_num, 'Trainable': trainable_num}


print(get_parameters(myModel))  # 输出参数量

img = torch.zeros((4,3,224,224))  # 模拟四张224*224图像

out = myModel(img)  # 输出结果

print(out.size())  # 打印尺寸

2.VGGNet

创新点

确立了“小卷积核(采用大小为3*3的卷积核) + 深网络 + 逐步降维”的CNN设计范式;统一的结构设计(卷积块+池化)降低了 CNN 的复现成本;VGGNet对比以往网络更深,证明了深度是提升 CNN 性能的核心因素

VGG13代码简略实现

import torch.nn as nn
import torchvision.models as models
import torch

VGGNet = models.vgg13()

print(VGGNet)


# 神经网络子模块
class vggLayer(nn.Module):
    def __init__(self, input_size, mid_size, output_size):
        super(vggLayer, self).__init__()
        self.relu = nn.ReLU()
        self.pool = nn.MaxPool2d(2)

        self.conv1 = nn.Conv2d(input_size, mid_size, 3, 1, 1)
        self.conv2 = nn.Conv2d(mid_size, output_size, 3, 1, 1)

    def forward(self, x):
        x = self.conv1(x)
        x = self.relu(x)
        x = self.conv2(x)
        x = self.relu(x)
        x = self.pool(x)

        return x


# 模拟VGG13
class myVGG(nn.Module):
    def __init__(self):
        super(myVGG, self).__init__()
        self.relu = nn.ReLU()
        self.drop = nn.Dropout(0.5)
        self.layer1 = vggLayer(3, 64, 64)
        self.layer2 = vggLayer(64, 128, 128)
        self.layer3 = vggLayer(128, 256, 256)
        self.layer4 = vggLayer(256, 512, 512)
        self.layer5 = vggLayer(512, 512, 512)

        self.adaPool = nn.AdaptiveAvgPool2d(7)
        self.fc1 = nn.Linear(25088, 4096)
        self.fc2 = nn.Linear(4096, 4096)
        self.fc3 = nn.Linear(4096, 1000)

    def forward(self, x):
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)
        x = self.layer5(x)

        x = self.adaPool(x)
        x = x.flatten(start_dim=1)  # 从一维处展开

        x = self.fc1(x)
        x = self.relu(x)
        x = self.drop(x)

        x = self.fc2(x)
        x = self.relu(x)
        x = self.drop(x)

        x = self.fc3(x)
        x = self.relu(x)

        return x


myModel = myVGG()


def get_parameters(model):
    total_num = sum(p.numel() for p in model.parameters())  # 统计所有参数
    trainable_num = sum(p.numel() for p in model.parameters() if p.requires_grad)  # 统计所有可训练参数
    return {'Total': total_num, 'Trainable': trainable_num}


print(get_parameters(myModel))  # 输出参数量

img = torch.zeros((4, 3, 224, 224))  # 模拟四张224*224图像

out = myModel(img)  # 输出结果

print(out.size())  # 打印尺寸

3.ResNet

深度网络退化问题:

理论上CNN性能应随深度提高而提高,但实际上当网络深度超过 20 层(如 VGG19)后,训练集准确率会先饱和,再快速下降;本质:传统深度网络因需学习完整映射,导致深层难以拟合恒等映射,出现性能退化

残差连接

残差连接直接将浅层的输入跳过若干层,与深层的输出相加;简单公式表示:H(x) = F(x) + x,其中x是浅层输入,F(x)是深层网络学习到的“残差映射”,H(x)是最终的输出(若维度F(x)与x维度不匹配,将x通过1*1卷积变化成同维度)

ResNet18代码实现

import torch.nn as nn
import torch
import torchvision.models as models

resNet = models.resnet18()

print(resNet)


# 残差块
class Residual_block(nn.Module):
    def __init__(self, input_channels, out_channels, down_sample=False, strides=1):
        super().__init__()
        self.conv1 = nn.Conv2d(input_channels, out_channels, kernel_size=3, padding=1, stride=strides)
        self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1, stride=1)
        if input_channels != out_channels:  # 若输入通道数!=输出通道数,要将X进行维度变换
            self.conv3 = nn.Conv2d(input_channels, out_channels, kernel_size=1, stride=strides)
        else:
            self.conv3 = None
        self.bn1 = nn.BatchNorm2d(out_channels)  # 归一化
        self.bn2 = nn.BatchNorm2d(out_channels)
        self.relu = nn.ReLU()

    def forward(self, X):
        out = self.relu(self.bn1(self.conv1(X)))  # 保留X,用于残差连接
        out = self.bn2(self.conv2(out))

        if self.conv3:
            X = self.conv3(X)
        out += X
        return self.relu(out)


# ResNet18
class MyResNet(nn.Module):
    def __init__(self):
        super(MyResNet, self).__init__()
        self.relu = nn.ReLU()
        self.conv1 = nn.Conv2d(3, 64, 7, 2, 3)
        self.bn1 = nn.BatchNorm2d(64)  # 归一化
        self.pool1 = nn.MaxPool2d(3, 2)

        self.layer1 = nn.Sequential(
            Residual_block(64, 64),
            Residual_block(64, 64)
        )  # Sequential():把多个残差块打包成一个整体模块,有序;自动向前:把第一个输出传给第二个输入...
        self.layer2 = nn.Sequential(
            Residual_block(64, 128, strides=2),
            Residual_block(128, 128)
        )
        self.layer3 = nn.Sequential(
            Residual_block(128, 256, strides=2),
            Residual_block(256, 256)
        )
        self.layer4 = nn.Sequential(
            Residual_block(256, 512, strides=2),
            Residual_block(512, 512)
        )
        self.avgPool = nn.AdaptiveAvgPool2d(1)
        self.fc = nn.Linear(512, 1000)

    def forward(self, x):
        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)
        x = self.pool1(x)

        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)

        x = self.avgPool(x)
        x = x.flatten(start_dim=1)
        x = self.fc(x)

        return x


myModel = MyResNet()


def get_parameters(model):
    total_num = sum(p.numel() for p in model.parameters())  # 统计所有参数
    trainable_num = sum(p.numel() for p in model.parameters() if p.requires_grad)  # 统计所有可训练参数
    return {'Total': total_num, 'Trainable': trainable_num}


print(get_parameters(myModel))  # 输出参数量

img = torch.zeros((4, 3, 224, 224))  # 模拟四张224*224图像

out = myModel(img)  # 输出结果

print(out.size())  # 打印尺寸

 

 

Logo

脑启社区是一个专注类脑智能领域的开发者社区。欢迎加入社区,共建类脑智能生态。社区为开发者提供了丰富的开源类脑工具软件、类脑算法模型及数据集、类脑知识库、类脑技术培训课程以及类脑应用案例等资源。

更多推荐