深度学习之CNN

发布于:2024-04-18 ⋅ 阅读:(32) ⋅ 点赞:(0)

目录

我们为什么要用CNN,或者说究竟是因为什么我们要用CNN

卷积操作的实现原理

 补充知识

torch.nn.Conv2d()

注意

torch.nn.functional.conv2d()

torch.nn.functional.conv2d()和torch.nn.Conv2d()区别

关于padding填充

torch.nn.BatchNorm2d(out_channel)

sklearn.metrics

回归指标

回归方差(反应自变量与因变量之间的相关程度)

平均绝对误差

方差

中值绝对误差

R平方值

分类指标

 精度sklearn.metrics.accuracy_score

ROC曲线下的面积auc

根据预测得分计算平均精度(AP)average_precision_score

brier损失函数brier_score_loss()

混淆矩阵confusion_matrix()

fi_score()

对数损耗

查准率precision_score()

recall_score()

roc_auc_score()

roc_curve()

交叉熵函数Cross-Entropy的补充

卷积后的大小计算

代码示例 

代码运行结果

池化计算 

代码示例 

 代码运行结果

 卷积实战之猫狗分类数据集

导入操作库

 使用类创建数据集(猫狗分类)

利用DataLoader加载数据集

搭建CNN神经网络

反向传播并且输出准确率

图像显示 

完整源码 

运行结果

总结 

VGG16

vgg16的网络结构

代码

ResNet18 

代码


我们为什么要用CNN,或者说究竟是因为什么我们要用CNN

CNN,就是我们的卷积神经网络,在了解它之前,我们首先回忆一下我们的BP神经网络。关于BP神将网络我们可以看这篇博客:深度学习之感知机,激活函数,梯度消失,BP神经网络-CSDN博客

我们知道在我们训练图片识别种类的BP神经网络中,我们首先要做的就是将图片进行展平,每个像素点就相当于一个输入层神经元,而后面和隐藏层之间的关系就是我们的全连接层。

就是这个样子,这样做的缺点很明显:

1.参数开销过大,运行时间过长。

2.如果在隐藏层层数不够甚至只有一层的情况下,并且我们训练分类的训练集的有效部份都在图片同一个位置,那我们训练出来的这个模型就不能用于有效部份在图片其他位置的识别,或者说,我们训练出来的模型里面只有一部分的参数是有用的,其他的参数是没用的。

例如我们用如下这样的图片训练的模型,标签为‘横折’。

 这样的模型是无法识别下面的图片的。

参数的有效是因为图片的有效部份才有效的,这是我们BP神经网络的一个缺点。

于是有人设想,(比如就上面的图片示例),我们有没有什么方法可以将中间是有效部分的图片所学到的规律,或者说所训练的参数,也可以在其他的位置有效?答案是有,让不同位置共享同样的权重即可,而这也就是卷积操作的核心思想,也是为什么我们要用CNN卷积神经网络。

权重的共享是其和BP神经网络根本的区别。

卷积操作的实现原理

(该图片及上述图片来自网络,侵权必删。)

上图就是卷积操作的核心,权重共享的实现原理。 

下面就是卷积操作的基础知识的详细介绍了。

卷积神经网络(CNN)基础知识整理 (qq.com)

链接里面的文章里面提到的步幅stride,我认为解释成每次移动的长度更好一些,为1是就是每次移动一位,从第一列到第二列......,从第一行到第二行......,为2就是每次移动两位,从第一列到第三列......,中间隔着一列,从第一行到第二行......,中间隔着一行。

w就是输入尺寸,k是过滤器尺寸,p是填充的大小,s是步幅。

 补充知识

torch.nn.Conv2d()

dilation = 2

蓝色部份就是 我们要进行卷积操作的数据。

groups参数

进行分组卷积的组数,这个不常用一般就是默认值,默认值为一。

官方文档:

当groups等于1时就是普通的卷积操作。

import torch
import torch.nn as nn
N,C_in,H,W,C_out =  10,4,16,16,6
x = torch.randn(N,C_in,H,W).float()
conv = nn.Conv2d(C_in,C_out,kernel_size=3,stride=3,padding=1)
y = conv(x)
print(y.size())
'''torch.Size([10, 6, 6, 6])'''

 当groups=2时,就相当于把我们的输入的数据分成两份子数据了,每份数据的通道数就跟上面的计算公式一样,都和原来的数据相比除了个2,我们的卷积核也是,这样的话,我们卷积核就相当于被拆分成了两份或者说两层,这两份对应着拆分的两份数据,最后的到结果,进行拼接,就是我们的输出结果。

import torch
import torch.nn as nn
N,C_in,H,W,C_out =  10,4,16,16,6
x = torch.randn(N,C_in,H,W).float()
conv = nn.Conv2d(C_in,C_out,kernel_size=3,stride=3,padding=1)
conv_group = nn.Conv2d(C_in,C_out,kernel_size=3,stride=3,padding=1,groups = 2)
y = conv(x)
y_group = conv_group(x)
print(y.size())
print(y_group.size())
'''torch.Size([10, 6, 6, 6])'''
'''torch.Size([10, 6, 6, 6])'''

其余的类似。

dilation 参数

  这个参数决定了是否采用空洞卷积默认为1(不采用)。该参数解释起来有些麻烦,直接看样例吧。

dilation=1时的卷积:

dilation=2

注意

Padding即所谓的图像填充,后面的int型常数代表填充的多少(行数、列数),默认为0。需要注意的是这里的填充包括图像的上下左右,以padding = 1为例,若原始图像大小为32x32,那么padding后的图像大小就变成了34x34,而不是33x33。

padding_mode :即padding的模式,默认采用零填充

这些参数的默认值:

padding_mode = ‘zeros’

bias = True

groups = 1

dilation = 1

padding = 0

具体详情可以看这篇博客Pytorch的nn.Conv2d()参数详解_nn.conv2d参数-CSDN博客

torch.nn.functional.conv2d()

torch.nn.functional.conv2d(input, weight, bias=None, stride=1, padding=0, dilation=1, groups=1) → Tensor

它里面的各种参数

返回值:一个Tensor变量

作用:在输入图像input中使用filters做卷积运算

参数的具体意义:

input:图像的大小(minibatch,in_channels,H,W),批次大小,通道数,图像的高,图像的宽

是一个四维tensor

filters:代表卷积核的大小(out_channels,in_channe/groups,H,W),是一个四维tensor

bias:代表每一个channel通道的bias偏置,是一个维数等于out_channels的tensor

stride:是一个数或者一个二元组(SH,SW),代表纵向和横向的步长

padding:是一个数或者一个二元组(PH,PW ),代表纵向和横向的填充值

dilation:和上文torch.nn.Conv2d()中的一样。

groups是一个数,代表分组卷积时分的组数,特别的当groups = in_channel时,就是在做深度卷积(depth-wise conv)(暂时先不提了)

torch.nn.functional.conv2d()和torch.nn.Conv2d()区别

PyTorch 27.2D卷积,nn.Conv2d和F.conv2d - 知乎 (zhihu.com)

关于padding填充

下面关于这部分的内容来源于这篇博客:

卷积的三种模式full, same, valid以及padding的same, valid - 知乎 (zhihu.com)

三种方式:

1. full mode

2。 same mode

当filter的中心(K)与image的边角重合时,开始做卷积运算。

并且卷积之后输出的feature map尺寸保持不变(相对于输入图片)。当然,same模式不代表完全输入输出尺寸一样,也跟卷积核的步长有关系。

same模式也是最常见的模式,因为这种模式可以在前向传播的过程中让特征图的大小保持不变,调参师不需要精准计算其尺寸变化(因为尺寸根本就没变化)。

3.valid

         当filter全部在image里面的时候,进行卷积运算,可见filter的移动范围较same更小了。

(注:filter就是过滤器也就是我们的卷积核) 

torch.nn.BatchNorm2d(out_channel)

sklearn.metrics

这个库内置了许多评价模型性能的功能模块,或者说各种评价指标

下面简单介绍一下:(图片来源网络,侵权必删)

回归指标

(y_true:真实的标签, y_pred:预测的结果)

回归方差(反应自变量与因变量之间的相关程度)

explained_variance_score(y_true, y_pred, sample_weight=None,multioutput=‘uniform_average’) 

平均绝对误差

mean_absolute_error(y_true,y_pred,sample_weight=None,multioutput=‘uniform_average’):

方差

mean_squared_error(y_true, y_pred, sample_weight=None, multioutput=‘uniform_average’)

中值绝对误差

median_absolute_error(y_true, y_pred) 

R平方值

r2_score(y_true, y_pred,sample_weight=None,multioutput=‘uniform_average’) 

分类指标

 精度sklearn.metrics.accuracy_score

sklearn.metrics.accuracy_score(y_true, y_pred, normalize=True, sample_weight=None)

normalize:默认值为True,返回正确分类的比例;如果为False,返回正确分类的样本数

(常用)


ROC曲线下的面积auc

auc(x, y, reorder=False) (较大的AUC代表了较好的performance,这个自己查吧,这里就不多解释了)

根据预测得分计算平均精度(AP)average_precision_score

average_precision_score(y_true, y_score, average=‘macro’, sample_weight=None)

brier损失函数brier_score_loss()

brier_score_loss(y_true, y_prob, sample_weight=None, pos_label=None)

这个就是一个loss函数,它的值越小越好,他就是一个损失函数

官方文档:

sklearn.metrics.brier_score_loss() scikit-learn官方教程 _w3cschool

混淆矩阵confusion_matrix()

confusion_matrix(y_true, y_pred, labels=None, sample_weight=None)

(通过计算混淆矩阵来评估分类的准确性 返回混淆矩阵)

fi_score()

f1_score(y_true, y_pred, labels=None, pos_label=1, average=‘binary’, sample_weight=None): F1值
  F1 = 2 * (precision * recall) / (precision + recall) precision(查准率)=TP/(TP+FP) recall(查全率)=TP/(TP+FN)

(返回对精确率与召回率进行平均的一个结果)

召回率 =提取出的正确信息条数 /样本中的信息条数。通俗地说,就是所有准确的条目有多少被检索出来了。一般用recall表示,有时候也称之为查全率。

对数损耗

log_loss(y_true, y_pred, eps=1e-15, normalize=True, sample_weight=None, labels=None)

对数损耗,又称逻辑损耗或交叉熵损耗

查准率precision_score()

precision_score(y_true, y_pred, labels=None, pos_label=1, average=‘binary’,)

查准率或者精度; precision(查准率)=TP/(TP+FP)

recall_score()

klearn.metrics.recall_score(y_true, y_pred, labels=None, pos_label=1,average='binary', sample_weight=None)

roc_auc_score()

roc_auc_score(y_true, y_score, average=‘macro’, sample_weight=None):计算ROC曲线下的面积就是AUC的值,the larger the better

roc_curve()

roc_curve(y_true, y_score, pos_label=None, sample_weight=None, drop_intermediate=True);计算ROC曲线的横纵坐标值,TPR,FPR
  TPR = TP/(TP+FN) = recall(真正例率,敏感度) FPR = FP/(FP+TN)(假正例率,1-特异性

 我们知识简单介绍一下,记一下常用的就好,若想仔细了解可以自行查阅。

交叉熵函数Cross-Entropy的补充

推荐看这篇文章一文搞懂熵(Entropy),交叉熵(Cross-Entropy) - 知乎 (zhihu.com)

在猫和狗分类的博客中我们提到了他,但是只是简易的介绍了一下,这里做一下补充。

首先看一下上面推荐的博客。

核心思想,我们计算的是一个混乱程度,我们假设我们的分类是正确的,那么根据交叉熵公式,其混乱程度应该是最小的,所以我们应该不断训练这个函数然后求他的最小值。(这些上面推荐的博客里面都有解释)

公式:

我们的交叉熵函数,传入的label值可以是独热编码也可以是一个数,这个数表示的是一个下标,在我们的猫狗分类中,最后的神经元是两个,所以我们可以传[0,1],[1,0]这样独热编码的形式,也可以直接传入下表,表示该下标对应的概率是1.

卷积后的大小计算

例子:

代码示例 

print('------------------------------单通道卷积核及步长stride----------------------------------------')
import torch
import torch.nn.functional as F
#输入
input=torch.tensor([[1,1,1,0,0],
                    [0,1,1,1,0],
                    [0,0,1,1,1],
                    [0,0,1,1,0],
                    [0,1,1,0,0]])
#卷积核
kernel=torch.tensor([[1,0,1],
                     [0,1,0],
                     [1,0,1]])
print(input.shape)
print(kernel.shape)
#修改尺寸,可以让我们进行后面的操作,修改尺寸为:批次大小,通道数,hight,weight
input=torch.reshape(input,(1,1,5,5))
#卷积核的个数,卷积核的通道数,卷积核的h,卷积核的w
kernel=torch.reshape(kernel,(1,1,3,3))
#
print(input.shape)
print(kernel.shape)
#步长stride=1
output1=F.conv2d(input,kernel,stride=1)
print(output1)
#stride=2
output2=F.conv2d(input,kernel,stride=2)
print(output2)
print('padding操作--------------------------------------------')
output3=F.conv2d(input,kernel,stride=3,padding=2)
print(output3)
output4=F.conv2d(input,kernel,stride=1,padding=1)#默认填充零
print(output4)
output5=F.conv2d(input,kernel,stride=1,padding='same')#same时系统会自动给我们填充判定,vail类型时我们自己进行填充判定
print(output5)

代码运行结果

 

D:\Anaconda3\envs\pytorch\python.exe D:\learn_pytorch\学习过程\第五周的代码\代码一.py 
------------------------------单通道卷积核及步长stride----------------------------------------
torch.Size([5, 5])
torch.Size([3, 3])
torch.Size([1, 1, 5, 5])
torch.Size([1, 1, 3, 3])
tensor([[[[4, 3, 4],
          [2, 4, 3],
          [2, 3, 4]]]])
tensor([[[[4, 4],
          [2, 4]]]])
padding操作--------------------------------------------
tensor([[[[1, 1, 0],
          [0, 4, 0],
          [0, 1, 0]]]])
tensor([[[[2, 2, 3, 1, 1],
          [1, 4, 3, 4, 1],
          [1, 2, 4, 3, 3],
          [1, 2, 3, 4, 1],
          [0, 2, 2, 1, 1]]]])
tensor([[[[2, 2, 3, 1, 1],
          [1, 4, 3, 4, 1],
          [1, 2, 4, 3, 3],
          [1, 2, 3, 4, 1],
          [0, 2, 2, 1, 1]]]])

进程已结束,退出代码0

池化计算 

主要有两种池化方式,Max pooling/ avg pooling,通常情况下,池化区域是2*2大小,池化之后,4*4的图片,会变成2*2大小。

示例:

代码示例 

print('------------------------------池化层操作----------------------------------------')
import torch
import torch.nn.functional as F
#输入
input=torch.tensor([[1,1,1,0,0],
                    [0,1,1,1,0],
                    [0,0,1,1,1],
                    [0,0,1,1,0],
                    [0,1,1,0,0]],dtype=torch.float32)
#卷积核
maxsp=torch.nn.MaxPool2d(kernel_size=2)
avp=torch.nn.AvgPool2d(kernel_size=2)
input=torch.reshape(input,(1,1,5,5))
output1=maxsp(input)
print(output1)
print('---------------------------')
output2=avp(input)
print(output2)

 代码运行结果

D:\Anaconda3\envs\pytorch\python.exe D:\learn_pytorch\学习过程\第五周的代码\代码二.py 
------------------------------池化层操作----------------------------------------
tensor([[[[1., 1.],
          [0., 1.]]]])
---------------------------
tensor([[[[0.7500, 0.7500],
          [0.0000, 1.0000]]]])

进程已结束,退出代码0

(注意:关于池化和卷积原理的详细可以看这篇文章:(我的图片也是来自这里:卷积神经网络(CNN)基础知识整理 (qq.com))) 

 卷积实战之猫狗分类数据集

关于猫狗分类数据集的内容,在在我的这篇博客中阐述过:BP实战之猫狗分类数据集-CSDN博客,这里就不说明了,并且创建数据集的操作在这篇博客也提到过,可以说是一模一样,下面也不多加阐述。

导入操作库

import matplotlib.pyplot as plt
import numpy as np
from matplotlib import font_manager
from sklearn.metrics import accuracy_score
print('猫狗分类数据集---------------------')
import torch
from torch.utils.data import DataLoader, Dataset
import torchvision
from torchvision import transforms
from PIL import Image
import os

 使用类创建数据集(猫狗分类)

#使用类创建自己的猫狗分类数据集
class catanddog(Dataset):
    def __init__(self,rootpath,label_dir):
        self.rootpath=rootpath
        self.label_dir=label_dir
        self.path=os.path.join(rootpath,label_dir)
        self.imge_all=os.listdir(self.path)
        self.transform=transforms.Compose([transforms.Resize((224,224)),transforms.ToTensor()])
    def __getitem__(self, item):
        imge_name=self.imge_all[item]
        imge=Image.open(os.path.join(self.path,imge_name))
        imge=self.transform(imge)
        if self.label_dir=='Cat':
            target=0
        else:
            target=1
        if imge.shape[0]!=3:
            print(self.imge_all[item],target)
            os.remove(os.path.join(self.path,imge_name))
        return imge,target
    def __len__(self):
        return len(self.imge_all)
rootpath='D:\learn_pytorch\数据集\PetImages_test'
test_rootpath='D:\learn_pytorch\数据集\PetImages_test\Test'
cat='Cat'#标签对应0
dog='Dog'#标签对应1
catdatasets=catanddog(rootpath,cat)#猫的数据集
#print(len(catdatasets))#12500
dogdatasets=catanddog(rootpath,dog)#狗的数据集
testcat=catanddog(test_rootpath,cat)
testdog=catanddog(test_rootpath,dog)
traindata=catdatasets+dogdatasets
testdata=testcat+testdog

注意: 我们为了演示方便,这次另外制作了新的文件夹作为数据集,其中训练集dog1170张,cat1116张,测试集猫狗各39张。

利用DataLoader加载数据集

trainload=DataLoader(dataset=traindata,shuffle=True,batch_size=64)
testLoad=DataLoader(dataset=testdata,shuffle=False,batch_size=39*2)

搭建CNN神经网络

class CNNNetwork(torch.nn.Module):
    def __init__(self):
        super(CNNNetwork,self).__init__()
        #我们的每张图片都是224*224*3个像素点
        #第一个隐藏层
        self.cnn1=torch.nn.Conv2d(in_channels=3,out_channels=3,kernel_size=3)
        #激活函数,这里选择Relu
        self.relu1=torch.nn.ReLU()
        #池化层,最大池化
        self.max1=torch.nn.MaxPool2d(kernel_size=2)
        #第二个隐藏层
        self.cnn2=torch.nn.Conv2d(in_channels=3,out_channels=6,kernel_size=3)
        #激活函数,这里选择Relu
        self.relu2=torch.nn.ReLU()
        #池化层,最大池化
        self.max2=torch.nn.MaxPool2d(kernel_size=2)
        # #第三个隐藏层:
        # self.cnn3=torch.nn.Conv2d(in_channels=6,out_channels=12,kernel_size=3)
        # #激活函数,这里选择Relu
        # self.relu3=torch.nn.ReLU()
        # #池化层,最大池化
        # self.max3=torch.nn.MaxPool2d(kernel_size=2)
        #输出层
        self.linear4=torch.nn.Linear(6*54*54,2)
        # 激活函数
        self.softmax=torch.nn.LogSoftmax()
    #前向传播
    def forward(self,x):
        #前向传播
        x=self.cnn1(x)
        x=self.relu1(x)
        x=self.max1(x)
        #--------------------
        x=self.cnn2(x)
        x=self.relu2(x)
        x=self.max2(x)
        #------------------------
        # x=self.cnn3(x)
        # x=self.relu3(x)
        # x=self.max3(x)
        #------------------------
        # print('----------------')
        # print(x.shape)
        # print('----------------')
        x=torch.reshape(x,(x.shape[0],-1))
        x=self.linear4(x)
        x=self.softmax(x)#最后输出的数值我们需要利用到独热编码的思想
        #上面的这些都可以这几使用x=self.model(x)来代替,为什么能用它,我的理解是,我们继承的class moudle 然后对立面写好的模型框架进行定义,而这个方法就是可以直接调用我们定义好的神经网络
        return x

和我们bp网络的思路差不多,前向传播只不过用了卷积层,我们要清楚一件事就是,一般而言,我们卷积玩要进行激活函数的操作,位的就是引入非线性的操作,但是如果随着卷积层的深度加深引入的过多的话,有可能会出现梯度消失的现象。

反向传播并且输出准确率

#建立我们的神经网络对象
model=CNNNetwork()
# #定义损失函数
critimizer=torch.nn.NLLLoss()
#定义优化器
optimizer=torch.optim.SGD(model.parameters(),lr=0.001,momentum=0.9)
epochs=6
#每轮抽取次数的和
a=0
loss_=[]
a_=[]
font = font_manager.FontProperties(fname="C:\\Users\\ASUS\\Desktop\\Fonts\\STZHONGS.TTF")
# #开始预测
example=enumerate(testLoad)#从测试集里面随机抽64份并且记录下来里面的内容和下标
batch_index,(imagess,labelss)=next(example)
# bath_index=0
# imagess=0
# labelss=0
# for i,j in example:
#     bath_index=i
#     print(j)
    # (imagess, labelss)=j
score_=[]
for i in range(epochs):
    # 损失值参数
    sumloss = 0
    for imges,labels in trainload:
        a+=1
        #前向传播
        # print('imges.shape:',imges.shape)
        output=model(imges)
        #反向传播
        loss=critimizer(output,labels)
        loss.backward()
        #参数更新
        optimizer.step()
        #梯度清零
        optimizer.zero_grad()
        #损失累加
        sumloss+=loss.item()
    loss_.append(sumloss)
    a_.append(a)
    print(f"第{i+1}轮的损失:{sumloss},抽取次数和:{a}")
    print('正确率---------------------')
    labelss_ = labelss.numpy()
    pre_ = []
    for i in range(39 * 2):
        sample_ = torch.unsqueeze(imagess[i], dim=0)  # 升维度加1
        # print(a.shape)
        pre = model(sample_)  # 预测
        # 第一张图片对应的pre得格式:
        # 接下来我们要用到独热编码的思想,我们取最大的数,也就是最高的概率对应得下标,就相当于这个最高概率对应得独热编码里面的1,其他是0
        pro = list(pre.detach().numpy()[0])
        pre_label = pro.index(max(pro))
        pre_.append(pre_label)
        # print(pre_label)
    pre_ = np.array(pre_)
    sore = accuracy_score(labelss_, pre_)
    score_.append(sore)
    print('准确率:', sore)

图像显示 

#预测画图------------------------
fig=plt.figure()
for i in range(16):
    # print(imagess[i])
    # print(imagess[i].shape)
    a=torch.unsqueeze(imagess[i],dim=0)    #升维度加1
    # print('a.shape:',a.shape)
    pre=model(a)#预测
    #第一张图片对应的pre得格式:
    #接下来我们要用到独热编码的思想,我们取最大的数,也就是最高的概率对应得下标,就相当于这个最高概率对应得独热编码里面的1,其他是0
    # print(pre)
    pro = list(pre.detach().numpy()[0])
    pre_label=pro.index(max(pro))
    # print(pre_label)
    dict_={0:'猫',1:"狗"}
    #图像显示
    img=torch.squeeze(a)        #去掉维度中的一个‘1’,大小变成3*224*224 需要转换
    img_=img.permute(1,-1 , 0)                   #224*224*3这个我们的图像才可以显示
    imge=img_.numpy()
    # print(img_.shape)
    # print(img_)

    plt.subplot(4,4,i+1)
    plt.tight_layout()
    plt.imshow(imge,cmap='gray',interpolation='none')
    plt.title(f"预测值:{dict_[pre_label]}",fontproperties=font, fontsize=9)
    plt.xticks([])
    plt.yticks([])
plt.show()

完整源码 

import matplotlib.pyplot as plt
import numpy as np
from matplotlib import font_manager
from sklearn.metrics import accuracy_score
print('猫狗分类数据集---------------------')
import torch
from torch.utils.data import DataLoader, Dataset
import torchvision
from torchvision import transforms
from PIL import Image
import os
#使用类创建自己的猫狗分类数据集
class catanddog(Dataset):
    def __init__(self,rootpath,label_dir):
        self.rootpath=rootpath
        self.label_dir=label_dir
        self.path=os.path.join(rootpath,label_dir)
        self.imge_all=os.listdir(self.path)
        self.transform=transforms.Compose([transforms.Resize((224,224)),transforms.ToTensor()])
    def __getitem__(self, item):
        imge_name=self.imge_all[item]
        imge=Image.open(os.path.join(self.path,imge_name))
        imge=self.transform(imge)
        if self.label_dir=='Cat':
            target=0
        else:
            target=1
        if imge.shape[0]!=3:
            print(self.imge_all[item],target)
            os.remove(os.path.join(self.path,imge_name))
        return imge,target
    def __len__(self):
        return len(self.imge_all)
rootpath='D:\learn_pytorch\数据集\PetImages_test'
test_rootpath='D:\learn_pytorch\数据集\PetImages_test\Test'
cat='Cat'#标签对应0
dog='Dog'#标签对应1
catdatasets=catanddog(rootpath,cat)#猫的数据集
#print(len(catdatasets))#12500
dogdatasets=catanddog(rootpath,dog)#狗的数据集
testcat=catanddog(test_rootpath,cat)
testdog=catanddog(test_rootpath,dog)
traindata=catdatasets+dogdatasets
testdata=testcat+testdog
# print(traindata[12500])
# print(testdata[0])
# for x , y in testdata:
#     print(x,y)
#     pass
# 利用DataLoader加载数据集
trainload=DataLoader(dataset=traindata,shuffle=True,batch_size=64)
testLoad=DataLoader(dataset=testdata,shuffle=False,batch_size=39*2)

#搭建CNN神经网络
class CNNNetwork(torch.nn.Module):
    def __init__(self):
        super(CNNNetwork,self).__init__()
        #我们的每张图片都是224*224*3个像素点
        #第一个隐藏层
        self.cnn1=torch.nn.Conv2d(in_channels=3,out_channels=3,kernel_size=3)
        #激活函数,这里选择Relu
        self.relu1=torch.nn.ReLU()
        #池化层,最大池化
        self.max1=torch.nn.MaxPool2d(kernel_size=2)
        #第二个隐藏层
        self.cnn2=torch.nn.Conv2d(in_channels=3,out_channels=6,kernel_size=3)
        #激活函数,这里选择Relu
        self.relu2=torch.nn.ReLU()
        #池化层,最大池化
        self.max2=torch.nn.MaxPool2d(kernel_size=2)
        # #第三个隐藏层:
        # self.cnn3=torch.nn.Conv2d(in_channels=6,out_channels=12,kernel_size=3)
        # #激活函数,这里选择Relu
        # self.relu3=torch.nn.ReLU()
        # #池化层,最大池化
        # self.max3=torch.nn.MaxPool2d(kernel_size=2)
        #输出层
        self.linear4=torch.nn.Linear(6*54*54,2)
        # 激活函数
        self.softmax=torch.nn.LogSoftmax()
    #前向传播
    def forward(self,x):
        #前向传播
        x=self.cnn1(x)
        x=self.relu1(x)
        x=self.max1(x)
        #--------------------
        x=self.cnn2(x)
        x=self.relu2(x)
        x=self.max2(x)
        #------------------------
        # x=self.cnn3(x)
        # x=self.relu3(x)
        # x=self.max3(x)
        #------------------------
        # print('----------------')
        # print(x.shape)
        # print('----------------')
        x=torch.reshape(x,(x.shape[0],-1))
        x=self.linear4(x)
        x=self.softmax(x)#最后输出的数值我们需要利用到独热编码的思想
        #上面的这些都可以这几使用x=self.model(x)来代替,为什么能用它,我的理解是,我们继承的class moudle 然后对立面写好的模型框架进行定义,而这个方法就是可以直接调用我们定义好的神经网络
        return x
#建立我们的神经网络对象
model=CNNNetwork()
# #定义损失函数
critimizer=torch.nn.NLLLoss()
#定义优化器
optimizer=torch.optim.SGD(model.parameters(),lr=0.001,momentum=0.9)
epochs=6
#每轮抽取次数的和
a=0
loss_=[]
a_=[]
font = font_manager.FontProperties(fname="C:\\Users\\ASUS\\Desktop\\Fonts\\STZHONGS.TTF")
# #开始预测
example=enumerate(testLoad)#从测试集里面随机抽64份并且记录下来里面的内容和下标
batch_index,(imagess,labelss)=next(example)
# bath_index=0
# imagess=0
# labelss=0
# for i,j in example:
#     bath_index=i
#     print(j)
    # (imagess, labelss)=j
score_=[]
for i in range(epochs):
    # 损失值参数
    sumloss = 0
    for imges,labels in trainload:
        a+=1
        #前向传播
        # print('imges.shape:',imges.shape)
        output=model(imges)
        #反向传播
        loss=critimizer(output,labels)
        loss.backward()
        #参数更新
        optimizer.step()
        #梯度清零
        optimizer.zero_grad()
        #损失累加
        sumloss+=loss.item()
    loss_.append(sumloss)
    a_.append(a)
    print(f"第{i+1}轮的损失:{sumloss},抽取次数和:{a}")
    print('正确率---------------------')
    labelss_ = labelss.numpy()
    pre_ = []
    for i in range(39 * 2):
        sample_ = torch.unsqueeze(imagess[i], dim=0)  # 升维度加1
        # print(a.shape)
        pre = model(sample_)  # 预测
        # 第一张图片对应的pre得格式:
        # 接下来我们要用到独热编码的思想,我们取最大的数,也就是最高的概率对应得下标,就相当于这个最高概率对应得独热编码里面的1,其他是0
        pro = list(pre.detach().numpy()[0])
        pre_label = pro.index(max(pro))
        pre_.append(pre_label)
        # print(pre_label)
    pre_ = np.array(pre_)
    sore = accuracy_score(labelss_, pre_)
    score_.append(sore)
    print('准确率:', sore)

plt.figure()
plt.plot(a_, score_)
plt.title('准确率随着抽取总次数得变化情况:', fontproperties=font, fontsize=18)
plt.xlabel('抽取总次数', fontproperties=font, fontsize=18)
plt.ylabel('准确率', fontproperties=font, fontsize=18)
plt.legend(prop=font)
plt.show()

plt.figure()
plt.plot(a_,loss_)
plt.title('损失值随着抽取总次数得变化情况:',fontproperties=font, fontsize=18)
plt.show()
#预测画图------------------------
fig=plt.figure()
for i in range(16):
    # print(imagess[i])
    # print(imagess[i].shape)
    a=torch.unsqueeze(imagess[i],dim=0)    #升维度加1
    # print('a.shape:',a.shape)
    pre=model(a)#预测
    #第一张图片对应的pre得格式:
    #接下来我们要用到独热编码的思想,我们取最大的数,也就是最高的概率对应得下标,就相当于这个最高概率对应得独热编码里面的1,其他是0
    # print(pre)
    pro = list(pre.detach().numpy()[0])
    pre_label=pro.index(max(pro))
    # print(pre_label)
    dict_={0:'猫',1:"狗"}
    #图像显示
    img=torch.squeeze(a)        #去掉维度中的一个‘1’,大小变成3*224*224 需要转换
    img_=img.permute(1,-1 , 0)                   #224*224*3这个我们的图像才可以显示
    imge=img_.numpy()
    # print(img_.shape)
    # print(img_)

    plt.subplot(4,4,i+1)
    plt.tight_layout()
    plt.imshow(imge,cmap='gray',interpolation='none')
    plt.title(f"预测值:{dict_[pre_label]}",fontproperties=font, fontsize=9)
    plt.xticks([])
    plt.yticks([])
plt.show()


















运行结果

D:\Anaconda3\envs\pytorch\python.exe D:\learn_pytorch\学习过程\第五周的代码\代码三.py 
D:\Anaconda3\envs\pytorch\lib\site-packages\scipy\__init__.py:132: UserWarning: A NumPy version >=1.21.6 and <1.28.0 is required for this version of SciPy (detected version 1.21.5)
  warnings.warn(f"A NumPy version >={np_minversion} and <{np_maxversion}"
猫狗分类数据集---------------------
D:\learn_pytorch\学习过程\第五周的代码\代码三.py:101: UserWarning: Implicit dimension choice for log_softmax has been deprecated. Change the call to include dim=X as an argument.
  x=self.softmax(x)#最后输出的数值我们需要利用到独热编码的思想
第1轮的损失:24.896146833896637,抽取次数和:36
正确率---------------------
准确率: 0.4230769230769231
D:\learn_pytorch\学习过程\第五周的代码\代码三.py:101: UserWarning: Implicit dimension choice for log_softmax has been deprecated. Change the call to include dim=X as an argument.
  x=self.softmax(x)#最后输出的数值我们需要利用到独热编码的思想
第2轮的损失:24.687452137470245,抽取次数和:72
正确率---------------------
准确率: 0.44871794871794873
D:\learn_pytorch\学习过程\第五周的代码\代码三.py:101: UserWarning: Implicit dimension choice for log_softmax has been deprecated. Change the call to include dim=X as an argument.
  x=self.softmax(x)#最后输出的数值我们需要利用到独热编码的思想
第3轮的损失:24.54122930765152,抽取次数和:108
正确率---------------------
准确率: 0.5641025641025641
D:\learn_pytorch\学习过程\第五周的代码\代码三.py:101: UserWarning: Implicit dimension choice for log_softmax has been deprecated. Change the call to include dim=X as an argument.
  x=self.softmax(x)#最后输出的数值我们需要利用到独热编码的思想
第4轮的损失:24.266421258449554,抽取次数和:144
正确率---------------------
准确率: 0.5
D:\learn_pytorch\学习过程\第五周的代码\代码三.py:101: UserWarning: Implicit dimension choice for log_softmax has been deprecated. Change the call to include dim=X as an argument.
  x=self.softmax(x)#最后输出的数值我们需要利用到独热编码的思想
第5轮的损失:24.06421685218811,抽取次数和:180
正确率---------------------
准确率: 0.5641025641025641
D:\learn_pytorch\学习过程\第五周的代码\代码三.py:101: UserWarning: Implicit dimension choice for log_softmax has been deprecated. Change the call to include dim=X as an argument.
  x=self.softmax(x)#最后输出的数值我们需要利用到独热编码的思想
第6轮的损失:23.939453184604645,抽取次数和:216
正确率---------------------
准确率: 0.5512820512820513
No artists with labels found to put in legend.  Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
D:\learn_pytorch\学习过程\第五周的代码\代码三.py:101: UserWarning: Implicit dimension choice for log_softmax has been deprecated. Change the call to include dim=X as an argument.
  x=self.softmax(x)#最后输出的数值我们需要利用到独热编码的思想

进程已结束,退出代码0

 

 

 

 

总结 

本次示例并没有进行优化,优化方式可以从以下几种方面考虑。

1.卷积层的深度,一般而言,卷积模型的准确度会随着深度的增加而增加,但是会出现“退化现象”。

2.卷积层相关参数的设置。

3.池化方式

4.全连接层的层数

VGG16

我们简单的展示一下vgg16

vgg16的网络结构

代码

print('-------------------------------VGG16-------------------------------')
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import font_manager
from sklearn.metrics import accuracy_score
import torch
from torch.utils.data import DataLoader, Dataset
import torchvision
from torchvision import transforms
from PIL import Image
import os
class VGG16(torch.nn.Module):
    def __init__(self):
        super(VGG16).__init__()
        #定义网络模块
        self.conv1=torch.nn.Conv2d(3,64,kernel_size=1,stride=1,padding=1)
        self.relu1=torch.nn.ReLU(inplace=True)#inplace = False 时,不会修改输入对象的值,而是返回一个新创建的对象,nn.ReLU()函数默认inplace 默认是False
        self.conv2 = torch.nn.Conv2d(64,64,kernel_size=3,stride=1,padding=1)
        self.relu2 = torch.nn.ReLU(inplace=True)
        self.max_pooling_1=torch.nn.MaxPool2d(kernel_size=2,stride=2)

        self.conv3 = torch.nn.Conv2d(64,128,kernel_size=3,stride=1,padding=1)
        self.relu3 = torch.nn.ReLU(inplace=True)
        self.conv4 = torch.nn.Conv2d(128,128,kernel_size=3,stride=1,padding=1)
        self.relu4= torch.nn.ReLU(inplace=True)
        self.max_pooling_2 = torch.nn.MaxPool2d(kernel_size=2,stride=2)

        self.conv5 = torch.nn.Conv2d(128,256,kernel_size=3,stride=1,padding=1)
        self.relu5 = torch.nn.ReLU(inplace=True)
        self.conv6 = torch.nn.Conv2d(256,256,kernel_size=3,stride=1,padding=1)
        self.relu6 = torch.nn.ReLU(inplace=True)
        self.conv7 = torch.nn.Conv2d(256,256,kernel_size=3,stride=1,padding=1)
        self.relu7 = torch.nn.ReLU(inplace=True)
        self.max_pooling_3 = torch.nn.MaxPool2d(kernel_size=2,stride=2)

        self.conv8 = torch.nn.Conv2d(128,512,kernel_size=3,stride=1,padding=1)
        self.relu8 = torch.nn.ReLU(inplace=True)
        self.conv9 = torch.nn.Conv2d(512,512,kernel_size=3,stride=1,padding=1)
        self.relu9 = torch.nn.ReLU(inplace=True)
        self.conv10 = torch.nn.Conv2d(512,512,kernel_size=3,stride=1,padding=1)
        self.relu10 = torch.nn.ReLU(inplace=True)
        self.max_pooling_4= torch.nn.MaxPool2d(kernel_size=2,stride=2)

        self.conv11= torch.nn.Conv2d(512,512,kernel_size=3,stride=1,padding=1)
        self.relu11 = torch.nn.ReLU(inplace=True)
        self.conv12 = torch.nn.Conv2d(512,512,kernel_size=3,stride=1,padding=1)
        self.relu12 = torch.nn.ReLU(inplace=True)
        self.conv13 = torch.nn.Conv2d(512,512,kernel_size=3,stride=1,padding=1)
        self.relu13 = torch.nn.ReLU(inplace=True)
        self.max_pooling_5= torch.nn.MaxPool2d(kernel_size=2,stride=2)
        #全部连接层
        self.f1=torch.nn.Linear(512*7*7,4096)
        self.relu14=torch.nn.ReLU(inplace=True)
        self.f2=torch.nn.Linear(4096,4096)
        self.relu15=torch.nn.ReLU(inplace=True)
        self.dropout=torch.nn.Dropout()#是一种常用的正则化方法,通过随机将部分神经元的输出置为0来减少过拟合,默认惩罚因子p=0.5

        self.f3=torch.nn.Linear(4096,1000)
    def forward(self,x):
        x=self.conv1(x)
        x=self.relu1(x)
        x = self.conv2(x)
        x=self.relu2(x)
        x = self.max_pooling_1(x)

        x = self.conv3(x)
        x = self.relu3(x)
        x = self.conv4(x)
        x = self.relu4(x)
        x = self.max_pooling_2(x)

        x = self.conv5(x)
        x = self.relu5(x)
        x = self.conv6(x)
        x = self.relu6(x)
        x = self.conv7(x)
        x = self.relu7(x)
        x = self.max_pooling_3(x)

        x = self.conv8(x)
        x = self.relu8(x)
        x = self.conv9(x)
        x = self.relu9(x)
        x = self.conv10(x)
        x = self.relu10(x)
        x = self.max_pooling4(x)

        x = self.conv11(x)
        x = self.relu11(x)
        x = self.conv12(x)
        x = self.relu12(x)
        x = self.conv13(x)
        x = self.relu13(x)
        x = self.max_pooling_5(x)

        x=x.view(-1,512*7*7)
        x=self.f1(x)
        x=self.relu14(x)
        x = self.f2(x)
        x = self.relu15(x)

        x = self.f3(x)

        return x

ResNet18 

推荐看这篇博客。

ResNet-18超详细介绍!!!!_resnet18-CSDN博客

代码

print('-------------------------------ResNet18-------------------------------')
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import font_manager
from sklearn.metrics import accuracy_score
import torch
from torch.utils.data import DataLoader, Dataset
import torchvision
from torchvision import transforms
from PIL import Image
import torch.nn.functional  as F
import os
class CommonBlock(torch.nn.Module):
    def __init__(self,in_channel,out_channel,stride):
        super(CommonBlock,self).__init__()
        self.conv1=torch.nn.Conv2d(in_channel,out_channel,kernel_size=3,stride=stride,bias=False)
        self.bn1=torch.nn.BatchNorm2d(out_channel)#添加BatchNorm2d进行数据的归一化处理,这使得数据在进行Relu之前不会因为数据过大而导致网络性能的不稳定
        self.conv2 = torch.nn.Conv2d(in_channel, out_channel, kernel_size=3, stride=stride,bias=False)
        self.bn2=torch.nn.BatchNorm2d(out_channel)
    def forward(self,x):
        identity=x

        x=F.relu(self.bn1(self.conv1(x)),inplace=True)
        x=self.bn2(self.conv2(x))

        x+=identity
        return F.relu(x,inplace=True)


class SpecialBlock(torch.nn.Module):
    def __init__(self, in_channel, out_channel, stride):
        super(SpecialBlock, self).__init__()
        self.change_channel=torch.nn.Sequential(
            torch.nn.Conv2d(in_channel,out_channel,kernel_size=1,stride=stride[0],padding=0,bias=False),
            torch.nn.BatchNorm2d(out_channel)
        )

        self.conv1 = torch.nn.Conv2d(in_channel, out_channel, kernel_size=3, stride=stride[0],padding=1,bias=False)
        self.bn1 = torch.nn.BatchNorm2d(out_channel)  # 添加BatchNorm2d进行数据的归一化处理,这使得数据在进行Relu之前不会因为数据过大而导致网络性能的不稳定
        self.conv2 = torch.nn.Conv2d(in_channel, out_channel, kernel_size=3, stride=stride[1],padding=1,bias=False)
        self.bn2 = torch.nn.BatchNorm2d(out_channel)

    def forward(self, x):
        identity = self.change_channel(x)

        x = F.relu(self.bn1(self.conv1(x)), inplace=True)
        x = self.bn2(self.conv2(x))

        x += identity
        return F.relu(x, inplace=True)


class ResNet18(torch.nn.Module):
    def __init__(self,classes_num):
        super(ResNet18).__init__()
        #定义网络模块
        self.prepare=torch.nn.Sequential(
            torch.nn.Conv2d(3,64,7,2,3),
            torch.nn.BatchNorm2d(64),#Batch Normanlization简称BN,也就是数据归一化
            torch.nn.ReLU(inplace=True),
            torch.nn.MaxPool2d(3,2,1)
        )
        self.layer1=torch.nn.Sequential(
            CommonBlock(64,64,1),
            CommonBlock(64,64,1)
        )
        self.layer2=torch.nn.Sequential(
            SpecialBlock(64,128,[2,1]),
            CommonBlock(128,128,1)
        )
        self.layer3 = torch.nn.Sequential(
            SpecialBlock(128, 256, [2, 1]),
            CommonBlock(256, 256, 1)
        )
        self.layer4 = torch.nn.Sequential(
            SpecialBlock(256, 512, [2, 1]),
            CommonBlock(512, 512, 1)
        )
        self.pool=torch.nn.AdaptiveAvgPool2d(output_size=(1,1))
        self.fc=torch.nn.Sequential(
            # torch.nn.Dropout(p=0.5),
            # torch.nn.Linear(512,256),
            # torch.nn.ReLU(inplace=True),
            # torch.nn.Dropout(p=0.25),
            torch.nn.Linear(256,classes_num)
        )



    def forward(self,x):
        x=self.prepare(x)

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

        x=self.pool(x)
        x=x.reshape(x.shape[0],-1)

        x=self.fc(x)
        return x