PyTorch学习(五)——卷积神经网络

本文介绍了计算机视觉的基本概念,包括灰度图、图像平滑和边缘提取。重点讨论了卷积神经网络(CNN)的结构,如卷积层、池化层、全连接层以及激活函数。通过实例解释了卷积层的参数计算,并分析了VGG16网络模型的参数数量。此外,还探讨了全连接层在参数冗余问题以及如何使用全局平均池化层替代。最后,给出了一个使用PyTorch实现VGG16网络的代码示例。
摘要由CSDN通过智能技术生成

计算机视觉基本概念

在这里插入图片描述 在这里插入图片描述

  • 灰度图即将c的通道变成1
  • 当描述数据时,我们有时候只需要关注其纹理信息,就不需要关注其彩色空间。如果想要模型对于颜色的变化不那么敏感,我们在训练模型的时候就用灰度图。在这里插入图片描述
    在这里插入图片描述
  • 高频部分指的是图像的噪点,边缘点。图像平滑,就是用该点的邻域点来表示它
    在这里插入图片描述
    在这里插入图片描述
  • 边缘提取算子
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 计算机视觉中的特征工程
    在这里插入图片描述
  • 如LDA算法,将x投影到x‘ (中层次特征) 直方图统计(低层次特征)
  • 特征提取 -> 特征选择 -> 建模 (前两个被CNN处理了 :人脸识别 / 图像检索)
  • 卷积运算:利用像素点领域点来计算,是一种特征提取
  • 分类问题:在经过特征提取之后,得到的向量中,是一组对应的概率分布。如手写数字识别中的,若数字是1,那么此时1向量中1对应的概率分布就比较高而其他数字的概率分布就比较低
  • 检测任务:其实是对图像中的某些局部区域是不是我们想要的目标,如果不是的话,那它离我们想要的目标相差的偏移是多少。(前者还是一个分类问题,我们使用一个卷积神经网,对该区域不断地卷积,最终拿到一个特征向量,用这个特征向量来进行分类或者回归。回归就是对于我们的这个偏移量来进行回归)
  • 图像分割是:对于图像中的每一个像素点所属的类别来进行判断。
  • 图像匹配:相似性的比较。图片所对应的两个向量之间的欧氏距离/余弦距离很小
  • 图像跟踪:单目标/多目标跟踪

卷积神经网络

在这里插入图片描述

卷积层
  • padding就是对那些卷积之后边缘没有值的地方进行填充
  • dilation,空洞/膨胀卷积:不经过pooling层的这个下采样,我们一样能增大感受野
    请添加图片描述
    在这里插入图片描述
  • 缩小图像(或称为下采样(subsampled)或降采样(downsampled))的主要目的有两个:1、使得图像符合显示区域的大小;2、生成对应图像的缩略图。
  • 放大图像(或称为上采样(upsampling)或图像插值(interpolating))的主要目的是放大原图像,从而可以显示在更高分辨率的显示设备上。
    在这里插入图片描述
  • 由图中可以看到,两个33的卷积核对应的感受野是55,而三个33的卷积核对应的感受野就是77
  • 一个55的卷积核其有25个元素,也就是进行了25次乘加操作,而两个33的卷积核进行的乘加操作是9+9=18次。
  • 用小卷积核代替大卷积核还有一个好处是加深网络。可以使网络的非线性表达能力增强,而且参数量和计算量也会下降。在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
池化层

在这里插入图片描述
在这里插入图片描述

  • 一般还是选择使用resize方法,因为其计算量一般来说会小一点,而且其复杂程度相对于转置卷积来说也会简单很多。
激活层/BN层/FC层/损失层
  • 卷积层可以对应为:y=wx+b x是输入卷积层,y是输出卷积层。 w和b对应我们输入卷积核的参数。线性的网络其表达能力是比较差的,所以需要添加一些非线性元素。在这里插入图片描述
  • ReLU函数是 max(0,x) 将小于0的部分都变成0
  • BatchNorm层
    在这里插入图片描述
    请添加图片描述
  • 疑问:全连接层如何将一个3 * 3 * 5 的输出变成一个 1 * 4096的形式?
  • 可以理解为在中间做了一个卷积。当我们用一个3x3x5的filter 去卷积激活函数的输出,得到的结果就是一个fully connected layer 的一个神经元的输出,这个输出就是一个值。因为我们有4096个神经元,所以我们实际就是用一个3x3x5x4096的卷积层去卷积激活函数的输出。
  • 这一步卷积一个非常重要的作用:就是把分布式特征representation映射到样本标记空间。即它把特征representation整合到一起,输出为一个值。这样做的好处是:大大减少特征位置对分类带来的影响。
    在这里插入图片描述
    在这里插入图片描述
  • 卷积层和y=wx+b的关系:在经过卷积之后,得到隐藏层的神经元。卷积核上的每个神经元对应于一个不同的w(weight),这里2*2的卷积核就对应4个weight。原输入经过卷积之后再加上一个bias,得到的就是输出神经元的值。隐藏层的每个神经元对应于同一个卷积核,也就是共享了这四个weight。所以说卷积神经网络共享了权重和偏置,在y = w*x + b中,w和b都是参数,所以也就是减少了参数的数目。所以feature map的大小 = (input_size - filter_size + 2 * padding) / stride + 1 (向上取整,边界不丢弃)
  • 举个例子:
    对于一个输入的二维神经元(28*28),若使用一个5*5的kernel,对于每一个kernel,需要5*5个权重参数,加上一个偏置bias,则总共有26个参数。
    如果有20个kernel,总共需要26*20=520个参数就可以定义CNN

而若向之前一样两两相连的神经网络。首先输入层就有28*28=784个输入的神经元。如第一个隐藏层有30个神经元,那么两两连接,每个连接线上都对应一个weight,即有784*30个weight,另外每个神经元对应一个bias,所以有30个bias,则总共是 784*30+30=23550个参数,多了40倍参数!!!(参考该文)
在这里插入图片描述

经典卷积神经网络结构

在这里插入图片描述
串联结构的典型代表:LeNet,AlexNet,ZFNet,VGGNet
VGG16的网络模型如下图:
在这里插入图片描述

  • VGG16包含了16个隐藏层(13个卷积层和3个全连接层)VGG网络的结构非常一致,从头到尾全部使用的是3x3的卷积和2x2的max pooling。(VGG网络的动态演示
    在这里插入图片描述
    接下来讲解一下每层参数数量如何计算的(转载了这里的博客

第一个卷积层block1_conv1:

输入 224 ∗ 224 ∗ 3 其 中 224 ∗ 224 224*224*3 其中 224*224 2242243224224为图片分辨率 3为图片的通道数

输出 224 ∗ 224 ∗ 64 其 中 224 ∗ 224 224*224*64 其中224*224 22422464224224为图片分辨率 64为卷积核数量也就是特征图数量

因此我们采用的是大小为 3 ∗ 3 ∗ 3 3*3*3 333的卷积核一共64个

参数数量为 3 ∗ 3 ∗ 3 ∗ 64 3*3*3*64 33364(卷积核参数)+64(bias)=1792

第一个卷积层block1_conv2:

输入 224 ∗ 224 ∗ 64 其 中 224 ∗ 224 224*224*64 其中224*224 22422464224224为图片分辨率 64为特征图数量

输出 224 ∗ 224 ∗ 64 其 中 224 ∗ 224 224*224*64 其中224*224 22422464224224为图片分辨率 64为卷积核数量也就是特征图数量

因此我们采用的是大小为 3 ∗ 3 ∗ 64 3*3*64 3364的卷积核一共64个

参数数量为 3 ∗ 3 ∗ 64 ∗ 64 3*3*64*64 336464(卷积核参数)+64(bias)=36928

所有卷积层的参数计算以此类推,不再赘述

第一个全连接层fc1:

输入为 7 ∗ 7 ∗ 512 其 中 7 ∗ 7 7*7*512 其中7*7 7751277图片分辨率 512为图片的通道数

输出为4096个参数

全连接便是将输出的参数全部对应到输入的参数上去 因此参数个数为 7 ∗ 7 ∗ 512 ∗ 4096 7*7*512*4096 775124096(全连接参数)+4096(bias)=102764544

最后一个全连接层fc3,也就是上表中的prediction层:

输入为4096

输入为1000 也就是分类的类别数量

参数为 4096 ∗ 1000 ( 全 连 接 参 数 ) 4096*1000(全连接参数) 40961000+1000(bias)=4097000

  • 可以看到全连接层参数冗余(仅全连接层参数就可占整个网络参数80%左右),所以近期一些性能优异的网络模型如ResNet和GoogLeNet等均用全局平均池化(global average pooling,GAP)取代FC来融合学到的深度特征,最后仍用softmax等损失函数作为网络目标函数来指导学习过程。
  • softmax函数,又称归一化指数函数。它是二分类函数sigmoid在多分类上的推广,目的是将多分类的结果以概率的形式展现出来。转载至这里

首先,我们知道概率有两个性质:1)预测的概率为非负数;2)各种预测结果概率之和等于1。

softmax就是将在负无穷到正无穷上的预测结果按照这两步转换为概率的。

1)将预测结果转化为非负数

下图为y=exp(x)的图像,我们可以知道指数函数的值域取值范围是零到正无穷。softmax第一步就是将模型的预测结果转化到指数函数上,这样保证了概率的非负性。

在这里插入图片描述
2)各种预测结果概率之和等于1

为了确保各个预测结果的概率之和等于1。我们只需要将转换后的结果进行归一化处理。方法就是将转化后的结果除以所有转化后结果之和,可以理解为转化后结果占总数的百分比。这样就得到近似的概率。

下面为大家举一个例子,假如模型对一个三分类问题的预测结果为-3、1.5、2.7。我们要用softmax将模型结果转为概率。步骤如下:

1)将预测结果转化为非负数

y1 = exp(x1) = exp(-3) = 0.05

y2 = exp(x2) = exp(1.5) = 4.48

y3 = exp(x3) = exp(2.7) = 14.88

2)各种预测结果概率之和等于1

z1 = y1/(y1+y2+y3) = 0.05/(0.05+4.48+14.88) = 0.0026

z2 = y2/(y1+y2+y3) = 4.48/(0.05+4.48+14.88) = 0.2308

z3 = y3/(y1+y2+y3) = 14.88/(0.05+4.48+14.88) = 0.7666

总结一下softmax如何将多分类输出转换为概率,可以分为两步:

1)分子:通过指数函数,将实数输出映射到零到正无穷。

2)分母:将所有结果相加,进行归一化。
在这里插入图片描述

  • VGGNet网络实现代码——Pytorch
import torch
import torch.nn as tnn
import torchvision.datasets as dsets
import torchvision.transforms as transforms

BATCH_SIZE = 10
LEARNING_RATE = 0.01
EPOCH = 50
N_CLASSES = 25

transform = transforms.Compose([
    transforms.RandomResizedCrop(224),
    transforms.RandomHorizontalFlip(),
    transforms.ToTensor(),
    transforms.Normalize(mean = [ 0.485, 0.456, 0.406 ],
                         std  = [ 0.229, 0.224, 0.225 ]),
    ])

trainData = dsets.ImageFolder('../data/imagenet/train', transform)
testData = dsets.ImageFolder('../data/imagenet/test', transform)

trainLoader = torch.utils.data.DataLoader(dataset=trainData, batch_size=BATCH_SIZE, shuffle=True)
testLoader = torch.utils.data.DataLoader(dataset=testData, batch_size=BATCH_SIZE, shuffle=False)

def conv_layer(chann_in, chann_out, k_size, p_size):
    layer = tnn.Sequential(
        tnn.Conv2d(chann_in, chann_out, kernel_size=k_size, padding=p_size),
        tnn.BatchNorm2d(chann_out),
        tnn.ReLU()
    )
    return layer

def vgg_conv_block(in_list, out_list, k_list, p_list, pooling_k, pooling_s):

    layers = [ conv_layer(in_list[i], out_list[i], k_list[i], p_list[i]) for i in range(len(in_list)) ]
    layers += [ tnn.MaxPool2d(kernel_size = pooling_k, stride = pooling_s)]
    return tnn.Sequential(*layers)

def vgg_fc_layer(size_in, size_out):
    layer = tnn.Sequential(
        tnn.Linear(size_in, size_out),
        tnn.BatchNorm1d(size_out),
        tnn.ReLU()
    )
    return layer

class VGG16(tnn.Module):
    def __init__(self, n_classes=1000):
        super(VGG16, self).__init__()

        # Conv blocks (BatchNorm + ReLU activation added in each block)
        self.layer1 = vgg_conv_block([3,64], [64,64], [3,3], [1,1], 2, 2)
        self.layer2 = vgg_conv_block([64,128], [128,128], [3,3], [1,1], 2, 2)
        self.layer3 = vgg_conv_block([128,256,256], [256,256,256], [3,3,3], [1,1,1], 2, 2)
        self.layer4 = vgg_conv_block([256,512,512], [512,512,512], [3,3,3], [1,1,1], 2, 2)
        self.layer5 = vgg_conv_block([512,512,512], [512,512,512], [3,3,3], [1,1,1], 2, 2)

        # FC layers
        self.layer6 = vgg_fc_layer(7*7*512, 4096)
        self.layer7 = vgg_fc_layer(4096, 4096)

        # Final layer
        self.layer8 = tnn.Linear(4096, n_classes)

    def forward(self, x):
        out = self.layer1(x)
        out = self.layer2(out)
        out = self.layer3(out)
        out = self.layer4(out)
        vgg16_features = self.layer5(out)
        out = vgg16_features.view(out.size(0), -1)
        out = self.layer6(out)
        out = self.layer7(out)
        out = self.layer8(out)

        return vgg16_features, out

      
vgg16 = VGG16(n_classes=N_CLASSES)
vgg16.cuda()

# Loss, Optimizer & Scheduler
cost = tnn.CrossEntropyLoss()
optimizer = torch.optim.Adam(vgg16.parameters(), lr=LEARNING_RATE)
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer)

# Train the model
for epoch in range(EPOCH):

    avg_loss = 0
    cnt = 0
    for images, labels in trainLoader:
        images = images.cuda()
        labels = labels.cuda()

        # Forward + Backward + Optimize
        optimizer.zero_grad()
        _, outputs = vgg16(images)
        loss = cost(outputs, labels)
        avg_loss += loss.data
        cnt += 1
        print("[E: %d] loss: %f, avg_loss: %f" % (epoch, loss.data, avg_loss/cnt))
        loss.backward()
        optimizer.step()
    scheduler.step(avg_loss)

# Test the model
vgg16.eval()
correct = 0
total = 0

for images, labels in testLoader:
    images = images.cuda()
    _, outputs = vgg16(images)
    _, predicted = torch.max(outputs.data, 1)
    total += labels.size(0)
    correct += (predicted.cpu() == labels).sum()
    print(predicted, labels, correct, total)
    print("avg acc: %f" % (100* correct/total))

# Save the Trained Model
torch.save(vgg16.state_dict(), 'cnn.pkl')
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

五月的天气

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值