一 、为什么图片分类任务要用卷积神经网络?

前面我们学习了最简单的线性回归,但是其输入是一个向量,而图片分类任务的输入是图片,即使将其展开,也会因为参数量爆炸而不适合做训练,所以需要一种做法来提取特征,降低输入量。

二、卷积神经网络是什么?

1、网络结构

请添加图片描述

特征图:3x6x6,深度为3,形状为6x6

卷积核:3x3x3,深度为3,形状为3x3

在这里插入图片描述

​ 如图所示,3x3的卷积核每次会与特征图上3x3大小的面积上逐元素相乘累加,得到一幅新的特征图

​ 在深度为3时,会先得到深度为3的中间特征图,然后对应位置相加,就可以把深度变成1,然后全部拼起来,变成新特征图

新的特征图: 作为输出,可以与新的卷积核进行卷积

2、网络结构分析与参数量计算

在这里插入图片描述

1,3*3

在这里插入图片描述

可见,原特征图的宽被卷积了3次,所以新特征图应为3x3

2,2x2

和第一问同理

3,4x4

padding是一种防止新特征图不断缩小的方法,它会扩充原本的特征图,并填充0。例如本题padding=1,即:
在这里插入图片描述

可见,原特征图的宽均被卷积了4次,所以新特征图应为4x4

4,98x98

通过前面的分析,可以发现原特征图的宽总是被卷积 原特征图的宽 - 卷积核的宽 + 1 次,所以新特征图的宽总是为:

**原特征图的宽 - 卷积核的宽 + 1 **,本题中也就是 100 - 3 + 1 = 98 ,故形状为 98x98

5,100x100

padding=1,故原特征图的宽变为102,新特征图宽为 102 - 3 + 1 = 100 ,故形状为 100x100

6,2x2 7x7

特征图:8-7+1=2,故为2x2

参数量:卷积核参数量就是一共有多少个数,就是7x7

7,94x94

100-7+1=94

在这里插入图片描述

1,3

100+2*padding-7+1 = 100,解得 padding=3

2,224*224

224+2*2-5+1=224

3,3*3*3 27

和卷积核有关的参数有:卷积核数量,卷积核深度和形状

卷积核数量为1已知,卷积核深度与特征图深度相同为3,卷积核形状 4+2*1-x+1=4,解得x=3,故形状为3*3

卷积核参数量为 1*3*3*3=27

4,7*4*4

卷积核数量为7,故卷出7个新的特征图,每个特征图的大小为 4+2*1-3+1=4 即4*4,故为7*4*4

5,64*224*224

224+2-3+1=224,故为64*224*224

6,128*224*224 64 3*3*64*128

224+2-3+1=224,故为128*224*224

X=64,卷积核深度永远与特征图一致

128*64*3*3

3、特征图怎么变小

卷积可以减小特征图,但是不太够。所以又引进了扩大步长池化的方法

3.1扩大步长

在这里插入图片描述

在这里插入图片描述

3.2池化(Pooling)

在这里插入图片描述

在这里插入图片描述

三、卷积神经网络的基本网络结构和loss

1、基本网络结构

卷积核的数量会影响新特征图的深度,卷积核的形状会影响新特征图的形状,所以通过设计这两样参数,理论上我们可以获得任意的输出。

最少的情况下,一次卷积就可以获得想要的结果,但是实战中往往会设计更多的连接层来获得更好的效果

通过卷积和池化,我们缩小了特征图,减小了参数量,然后再进行展开,接入线性回归模型,就可以实现预测。

在这里插入图片描述

在这里插入图片描述

1024*7*7=50176

2、loss的计算

首先,我们可以看到,预测值和真实值往往有较大的差异,所以要先借助Softmax公式,将其映射为概率分布

在这里插入图片描述

接下来计算loss,下图第一张ppt说的是如何处理求将一个预测向量的loss,第二张ppt说的是如何处理一个batch的loss

在这里插入图片描述

四、经典卷积模型

1、AlexNet

1.1模型特点

AlexNet在当时主要的创新点在于relu激活函数,drop out,池化和归一化

drop put是随机丢弃一部分神经元

归一化就是减均值除以标准差

1.2模型分析

在这里插入图片描述

输入图片大小为3*224*224

放入卷积层Conv2d(3, 64, 11, 4,2),输出深度为64,输出宽度(224+2*2-11)/4+1=55.25,得到新特征图64*55*55

池化层为3,步长为2,(55-3+1)/2+1=27.5,故池化后为64*27*27

放入卷积层Conv2d(64, 192, 5, 1, 2),输出深度为192,输出宽度(27+2*2-5)/1+1=27,得到新特征图192*27*27

池化层为3,步长为2,(27-3+1)/2+1=13.5,故池化后为192*13*13

放入卷积层Conv2d(192, 384, 3, 1, 1),输出深度为384,输出宽度(13+1*2-3)/1+1=13,得到新特征图384*13*13

放入卷积层Conv2d(384, 256, 3, 1, 1),输出深度为256,输出宽度(13+1*2-3)/1+1=13,得到新特征图256*13*13

放入卷积层Conv2d(256, 256, 3, 1, 1),输出深度为256,输出宽度(13+1*2-3)/1+1=13,得到新特征图256*13*13

池化层为3,步长为2,(13-3+1)/2+1=6.5,故池化后为256*6*6

adaPool(6)将尺寸固定为6*6

flatten拉直,拉直后特征量为256*6*6=9216

放入全连接层Linear(9216, 4096),特征量被映射为4096

放入全连接层Linear(4096, 4096),特征量被映射为4096

放入全连接层Linear(4096, 1000),特征量被映射为1000

1.3参数量统计

(每一层的卷积核总大小和偏置量,池化层没有卷积核也就没有参数)

Conv2d(3, 64, 11, 4,2) : 3*11*11*64+64 = 23296

Conv2d(64, 192, 5, 1, 2) : 307392

Conv2d(192, 384, 3, 1, 1):663936

Conv2d(384, 256, 3, 1, 1):884992

Conv2d(256, 256, 3, 1, 1):590080

Linear(9216, 4096) : 9216*4096+4096 = 37752832

Linear(4096, 4096) : 16781312

Linear(4096, 1000) : 4097000

总计:61100840

1.4代码

只有前向

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

alexnet = models.alexnet()
print(alexnet)


class myAlexnet(nn.Module):
    def __init__(self):
        super(myAlexnet, self).__init__()

        self.conv1 = nn.Conv2d(3, 64, 11, 4, padding=2)  #bias 64 23232
        self.pool1 = nn.MaxPool2d(3,2)
        self.conv2 = nn.Conv2d(64, 192, 5, 1, padding=2)
        self.pool2 = nn.MaxPool2d(3,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.pool4 = nn.AdaptiveAvgPool2d(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.pool1(x)
        x = self.conv2(x)
        x = self.pool2(x)
        x = self.conv3(x)
        x = self.conv4(x)
        x = self.conv5(x)
        x = self.pool3(x)
        x = self.pool4(x)

        x = x.view(x.size()[0], -1)        # view 变形   batch * tezheng  ,保留了第一个维度,也就是batch的维度,剩下的全部合并到第二个维度
        x = self.fc1(x)
        x = self.fc2(x)
        x = self.fc3(x)

        return x

mymodel = myAlexnet() #实例化

input = torch.ones((4, 3, 224, 224))     # batch * channel  *  H * W

out = mymodel(input)

print(out.shape)

def get_parameter_number(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_parameter_number((mymodel)))
# print(get_parameter_number((mymodel.conv1)))
# print(get_parameter_number((mymodel.conv2)))
# print(get_parameter_number((mymodel.conv3)))
# print(get_parameter_number((mymodel.conv4)))
# print(get_parameter_number((mymodel.conv5)))
# print(get_parameter_number((mymodel.fc1)))
# print(get_parameter_number((mymodel.fc2)))
# print(get_parameter_number((mymodel.fc3)))
# print(get_parameter_number((mymodel.conv1))['Total']+get_parameter_number((mymodel.conv2))['Total']+
#       get_parameter_number((mymodel.conv3))['Total']+get_parameter_number((mymodel.conv4))['Total']+
#       get_parameter_number((mymodel.conv5))['Total']+get_parameter_number((mymodel.fc1))['Total']+
#       get_parameter_number((mymodel.fc2))['Total']+get_parameter_number((mymodel.fc3))['Total'])

2、VggNet

2.1模型特点

在这里插入图片描述

在这里插入图片描述

用两次卷积代替了一次卷积

2.2模型分析

在这里插入图片描述

输入图片大小为3*224*224

VggNet每个layer都是固定两次卷积一次池化,会提升深度,降低图大小

例如layer1:

​ 放入卷积层Conv2d(3, 64, 3, 1, 1),新特征图64*224*224

​ 放入卷积层Conv2d(64, 64, 3, 1, 1),新特征图64*224*224

​ 最大池化MaxPool2d(2),窗口为2*2,步长为2,新特征图为64*112*112

接下来以此类推,新特征图依次为:

128*64*64

256*32*32

512*16*16

最后一次的池化变为自适应平均池化AdaptiveAvgPool2d(7),故输出新特征图512*7*7

flatten拉直,拉直后特征量为512*7*7=25088

放入全连接层,特征量依次被映射为4096,1000

2.3参数量统计

layer1 =(64*3*3*3+64)+(64*64*3*3+64)=38720

layer2 = 221440

layer3 = 885248

layer4 = 3539968

layer5 = 4719616

fc1 = 102764544

fc2 = 16781312

fc3 = 4097000

总计:133047848

2.4代码

只有前向

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


vgg = models.vgg13()
print(vgg)

class vggLayer(nn.Module):
    def __init__(self,in_cha, mid_cha, out_cha):
        super(vggLayer, self).__init__()
        self.relu = nn.ReLU()
        self.pool = nn.MaxPool2d(2)
        self.conv1 = nn.Conv2d(in_cha, mid_cha, 3, 1, 1)
        self.conv2 = nn.Conv2d(mid_cha, out_cha, 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



class MyVgg(nn.Module):
    def __init__(self):
        super(MyVgg, self).__init__()

        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.relu = nn.ReLU()
        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= self.adapool(x)
        x = x.view(x.size()[0], -1)

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

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

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

        return x


import torch

myVgg = MyVgg()

img = torch.zeros((1, 3, 224,224))

out = myVgg(img)

print(out.size())

def get_parameter_number(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_parameter_number(myVgg.layer1))
# print(get_parameter_number(myVgg.layer2))
# print(get_parameter_number(myVgg.layer3))
# print(get_parameter_number(myVgg.layer4))
# print(get_parameter_number(myVgg.layer5))
# print(get_parameter_number(myVgg.fc1))
# print(get_parameter_number(myVgg.fc2))
# print(get_parameter_number(myVgg.fc3))
# 
# print("\n")
# 
# print(get_parameter_number(myVgg.layer1)['Total']+get_parameter_number(myVgg.layer2)['Total']
#       +get_parameter_number(myVgg.layer3)['Total']+get_parameter_number(myVgg.layer4)['Total']
#       +get_parameter_number(myVgg.layer5)['Total']+get_parameter_number(myVgg.fc1)['Total']
#       +get_parameter_number(myVgg.fc2)['Total']+get_parameter_number(myVgg.fc3)['Total'])
# print(get_parameter_number(vgg))

3、ResNet

3.1模型特点

resNet的创新主要在于残差连接和1x1卷积

残差连接将输入x直接加到输出结果f(x)上,是的out=f(x)+x

在这里插入图片描述

残差连接缓解了梯度消失,因为我们知道网络层数较深时,使用链式求导法则展开很容易让式子变得特别长,

在这里插入图片描述

若每一项都小于1,很容易造成梯度特别特别小。而通过残差连接,out=f(x)+x,那么out’=f’(x)+1,可以让偏导数不会消失

如果输入和输出的维度不匹配会导致无法进行残差连接,比如:

在这里插入图片描述

输入维度和输出维度不匹配,于是考虑卷积核使用1x1卷积(也就是卷积核的尺寸为1x1),同时输出调整为128,通过这两个参数就实现了深度从64->128的变化,然后步长设置为2,这样尺寸的宽会缩小一半,也就是从56*56->28*28,padding不变,就实现了维度的控制。

在这里插入图片描述

3.2模型分析

ResNet有很多种结构,本例的代码实现的是18-layer结构。不同于AlexNet和VggNet的固定结构,ResNet这张表参数并不完整,需要自行计算+一些约定俗成的设计思想来完成填补

在这里插入图片描述

3.3代码

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


resNet = models.resnet18()

print(resNet)


class Residual_block(nn.Module):  # @save
    def __init__(self, input_channels, out_channels, down_sample=False, strides=1):     # 步长默认为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)     # 步长恒为1
        if input_channels != out_channels:      # 若输入通道数和输出通道数不一致,则需要一个1x1的卷积层
            self.conv3 = nn.Conv2d(input_channels, out_channels,
                                   kernel_size=1, stride=strides)
        else:   # 若输入通道数和输出通道数一致,则不需要一个1x1的卷积层
            self.conv3 = None
        self.bn1 = nn.BatchNorm2d(out_channels)     # 批量归一化层
        self.bn2 = nn.BatchNorm2d(out_channels)     # 批量归一化层
        self.relu = nn.ReLU()                       # ReLU层
    def forward(self, X):
        out = self.relu(self.bn1(self.conv1(X)))    # 经过第一层需要激活
        out= self.bn2(self.conv2(out))
        if self.conv3:
            X = self.conv3(X)                       # X进行维度变换
        out += X                                    # 残差连接
        return self.relu(out)


class MyResNet18(nn.Module):
    def __init__(self):
        super(MyResNet18, self).__init__()
        self.conv1 = nn.Conv2d(3, 64, 7, 2, 3)
        self.bn1 = nn.BatchNorm2d(64)       # 将64个通道的特征图做归一化处理
        self.pool1 = nn.MaxPool2d(3, stride=2, padding=1)
        self.relu = nn.ReLU()

        self.layer1 = nn.Sequential(    # 容器模块,用于按顺序堆叠多个神经网络层。
            Residual_block(64, 64),
            Residual_block(64, 64)
        )
        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.flatten = nn.Flatten()
        self.adv_pool = 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.adv_pool(x)
        x = self.flatten(x)
        x = self.fc(x)

        return x

myres = MyResNet18()

def get_parameter_number(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_parameter_number(myres.layer1))
print(get_parameter_number(myres.layer1[0].conv1))
print(get_parameter_number(resNet.layer1[0].conv1))

x = torch.rand((1,3,224,224))
out = resNet(x)

out = myres(x)


以上均为课后的个人总结,可能会存在一些问题,欢迎指正

更多推荐