PyTorch-08 Cifar10与ResNet18实战(Lenet-5和Cifar10,ResNet和Cifar10)

PyTorch-08 Cifar10与ResNet18实战(Lenet-5和Cifar10,ResNet和Cifar10)

一、Lenet-5和Cifar10详细步骤说明

1、首先针对training创建一次加载一个数据的loader

导入包

import torch
from torch.utils.data import DataLoader
#PyTorch 团队专门开发了一个视觉工具包torchvision,这个包独立于 PyTorch,需通过 pip instal torchvision 安装。
#torchvision 主要包含三部分:
#1、models:提供深度学习中各种经典网络的网络结构以及预训练好的模型,包括 AlexNet 、VGG 系列、ResNet 系列、Inception 系列等;
#2、datasets: 提供常用的数据集加载,设计上都是继承 torch.utils.data.Dataset,主要包括 MNIST、CIFAR10/100、ImageNet、COCO等;
#datasets都是 torch.utils.data.Dataset的子类,所以,他们也可以通过torch.utils.data.DataLoader使用多线程(python的多进程)。
#3、transforms:提供常用的数据预处理操作,主要包括对 Tensor 以及 PIL Image 对象的操作;
from torchvision import datasets
from torchvision import transforms
from torch import nn, optim

#########################################下面开始加载数据#########################################
#########################################下面开始加载数据#########################################

下面的代码,一次只能加载一个数据,并不能加载一批次的数据。

    #首先引入数据:
    #参数1:是数据存储的根目录Root directory of dataset,数据存在这个文件目录下'cifar'
    #参数2:是否为train训练集
    #参数3:transform数据变换
    #参数4:download是否下载
    cifar_train = datasets.CIFAR10('cifar', True, transform=transforms.Compose([
        transforms.Resize((32,32)), #调整照片的大小
        transforms.ToTensor(), #直接将数据类型转变为tensor
        #希望像素值再0周围均匀分布,就需要使用normalize,mean=[R通道上像素的均值,G...,B...]
        transforms.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225])
    ]),download=True)

2、一次加载一批数据
一次加载一批就需要使用多线程的特性。
此外batch_size参数可以设置大一些,太小的话training不会很稳定,因为梯度计算,是把当前batch_size的平均方向做update的方向,太小的话这个方向就不一定准确了,就有一定随机性了。

#将datasets进行多线程下载
	batchsz = 32
    cifar_train = DataLoader(cifar_train,batch_size=batchsz, shuffle=True)
    #其中参数cifar_train是上面只加载一次的过程。

3、同样的流程,针对test创建一次加载一批
这里需要注意参数2要为False,意思为:不是train训练集。

	#参数2:是否为train训练集
    cifar_test = datasets.CIFAR10('cifar', False, transform=transforms.Compose([
        transforms.Resize((32,32)),
        transforms.ToTensor(),
        #如果加了normalize,train和test必须都添加
        transforms.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225])
    ]),download=True)
    cifar_test = DataLoader(cifar_test,batch_size=batchsz, shuffle=True) 

4、查看一下加载的数据情况,并将数据集和标签分开

    #通过iter()函数获取这些可迭代对象的迭代器。然后,我们可以对获取到的迭代器不断使⽤next()函数来获取下⼀条数据。
    #iter()后数据的类型
    print(type(iter(cifar_train).next()))
    #查看长度,为2,说明一个是数据集,一个是标签
    print(len(iter(cifar_train).next()))
    #一个是数据集,一个是标签
    x, label = iter(cifar_train).next()
    #查看其数据结构
    print('x:',x.shape,'laberl:',label.shape)
    #32个图片,每个图片三个通道,32*32的大小,均符合batch_size以及transforms.Resize后的结果。

在这里插入图片描述
###########################################结束加载数据###########################################
###########################################结束加载数据###########################################

附加了解的知识点:

1、pytorch之Dataloader,enumerate:

如果要了解pytorch加载数据和循环batch去使用数据(Dataloader和enumerate),我们单独创建了一组数据用于实验了解: (这部分是针对=>[10、编写运行函数train部分 => #7、编写for循环 => batchidx,可以发现一共分了0-1562组batch]为什么是1563组batch进行解释的。)

from torch.utils.data import TensorDataset
import torch
from torch.utils.data import DataLoader

a = torch.tensor(
    [[1, 2, 3], [4, 5, 6], [7, 8, 9], [1, 2, 3], [4, 5, 6], [7, 8, 9], [1, 2, 3], [4, 5, 6], [7, 8, 9], [1, 2, 3],
     [4, 5, 6], [7, 8, 9]])
b = torch.tensor([44, 55, 66, 44, 55, 66, 44, 55, 66, 44, 55, 66])
train_ids = TensorDataset(a, b)  # 封装数据a与标签b
print('简单探索数据:')
print(a.shape) #torch.Size([12, 3])
print(b.shape) #torch.Size([12])
print(list(train_ids))
print(len([*train_ids]))
print('=' * 80)

# 切片输出
print('切片输出:')
print(train_ids[0:2])
print('=' * 80)

# 循环取数据
print('循环取数据:')
for x_train, y_label in train_ids:
    print(x_train, y_label)
print('=' * 80)

# DataLoader进行数据封装
print('DataLoader进行数据封装:')
#batch_size=4 shuffle=False不打乱,按照原来的顺序
#batch_size为4,这样可分为三组,每个batch有4个元素,刚好是4+4+4 = 12个元素。
#重要:会按照这样分布的方式将train_ids中的所有元素都分配完。
train_loader = DataLoader(dataset=train_ids, batch_size=4, shuffle=False)
print(train_loader)
print(list(train_loader))
print('=' * 80)

#batch_size=5 shuffle=True 先打乱,再取batch
#batch_size为5,这样也可分为三组,但是最后一组只有原来数组的2个元素,前两组个包含5个元素,5+5+2 = 12个元素
train_loader = DataLoader(dataset=train_ids, batch_size=5, shuffle=True)
print(train_loader)
print(list(train_loader))
print('=' * 80)

#batch_size=4 shuffle=True 先打乱,再取batch
train_loader = DataLoader(dataset=train_ids, batch_size=4, shuffle=True)
print(train_loader)
print(list(train_loader))
print('=' * 80)

#查看一下enumerate的效果
print('查看一下enumerate的效果:')
print(enumerate(train_loader))
print(list(enumerate(train_loader)))
print(len(list(enumerate(train_loader)))) #长度是3,因为上面batch_size=4,原本的数据是12元素,所以刚好可以分为3组
print('=' * 80)

for i, data in enumerate(train_loader):  # 注意enumerate返回值有两个,一个是序号,一个是数据(包含训练数据和标签)
    print('i:',i)
    print('data:',data)
    print('--------------------')
    print('将data分别赋予x_data和label:')
    x_data, label = data
    print(' batch:{0}\n x_data:{1}\nlabel: {2}'.format(i, x_data, label))
    print('--------------------')

print('#' * 80)
for i, data in enumerate(train_loader, 10):   #这里的参数2为10,表示i是从10开始的
    #注意enumerate返回值有两个,一个是序号,一个是数据(包含训练数据和标签)
    x_data, label = data
    print(' batch:{0}\n x_data:{1}\nlabel: {2}'.format(i, x_data, label))

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

2、torch.argmax 函数详解
该内容参考链接为:https://blog.csdn.net/GeneralJing/article/details/110829109
1、 函数介绍
torch.argmax(input, dim=None, keepdim=False)

返回指定维度最大值的序号
dim给定的定义是:the demention to reduce.也就是把dim这个维度的,变成这个维度的最大值的index。
在这里插入图片描述
举例说明:
例子1:torch.argmax()函数中dim表示该维度会消失。

这个消失是什么意思?

官方英文解释是:dim (int) – the dimension to reduce.

我们知道argmax就是得到最大值的序号索引,对于一个维度为(d0,d1) 的矩阵来说,我们想要求每一行中最大数的在该行中的列号,最后我们得到的就是一个维度为(d0,1) 的一维矩阵。这时候,列这一维度就要消失了。

因此,我们想要求每一行最大的列标号,我们就要指定dim=1,表示我们不要列了,保留行的size就可以了。

假如我们想求每一列的最大行标,就可以指定dim=0,表示我们不要行了,求出每一列的最大值的下标,最后得到(1,d1)的一维矩阵。

2、 实例演示
实例1:

import torch
a = torch.tensor(
              [
                  [1, 5, 5, 2],
                  [9, -6, 2, 8],
                  [-3, 7, -9, 1]
              ])
b = torch.argmax(a, dim=0)
print(b)
print(a.shape)

dim=0的维度为3,即在那3组数据中作比较,求得是每一列中的最大行标,因此为[1,2,0,4]。
在这里插入图片描述
实例2:

import torch
a = torch.tensor([
              [
                  [1, 5, 5, 2],
                  [9, -6, 2, 8],
                  [-3, 7, -9, 1]
              ],
 
              [
                  [-1, 7, -5, 2],
                  [9, 6, 2, 8],
                  [3, 7, 9, 1]
              ]])
b = torch.argmax(a, dim=0)
print(b)
print(a.shape)
 
"""
tensor([[0, 1, 0, 1],
        [1, 1, 1, 1],
        [1, 1, 1, 1]])
torch.Size([2, 3, 4])"""
 
# dim=0,即将第一个维度消除,也就是将两个[3*4]矩阵只保留一个,因此要在两组中作比较,即将上下两个[3*4]的矩阵分别在对应的位置上比较大小
 
b = torch.argmax(a, dim=1)
"""
tensor([[1, 2, 0, 1],
        [1, 2, 2, 1]])
torch.Size([2, 3, 4])
"""
# dim=1,即将第二个维度消除,这么理解:矩阵维度变为[2*4];
"""
[1, 5, 5, 2],
[9, -6, 2, 8],
[-3, 7, -9, 1];
纵向压缩成一维,因此变为[1,2,0,1];同理得到[1,2,2,1];
"""
b = torch.argmax(a,dim=2)
"""
tensor([[2, 0, 1],
        [1, 0, 2]])
"""
# dim=2,即将第三个维度消除,这么理解:矩阵维度变为[2*3]
"""
   [1, 5, 5, 2],
   [9, -6, 2, 8],
   [-3, 7, -9, 1];
横向压缩成一维
[2,0,1],同理得到下面的"""

++++++++++++++++++++++++++++++++++++开始编写一个Lenet5类++++++++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++开始编写一个Lenet5类++++++++++++++++++++++++++++++++++++++++

5、新创建一个类,表示为lenet5,并编写lenet5类的卷积操作单元
lenet5的卷积操作单元是全连接层之前的卷积操作内容。
导入包

import torch
from torch import nn #任何类都要继承自nn.Module
from torch.nn import functional as F

创建Lenet5类,类要继承自nn.Module,按照下图给定的模型来编写:
我们先写这部分:先经过一个卷积层,再经过一个pooling层,再经过一个卷积层,再经过一个pooling层。这些是全连接层之前的内容。
在这里插入图片描述

class Lenet5(nn.Module):
    """
    for cifar10 dataset.
    """
    # 调用这个类的初始化方法,来初始化这个父类。
    def __init__(self):
        super(Lenet5, self).__init__()

        #卷积层
        #网络结构写在Sequential中,可以方便的组织网络结构。
        self.conv_unit = nn.Sequential(
            #x:[b张照片batch_size, 3, 32, 32] 输入的训练图片
            #nn.Conv2d(3, 6)输入的是3个channel,输出的是6个channel,这里的输出的channel是由kernal的channel来决定。
            #kernal_size=5表示一次关注长宽各为5个像素点的窗口
            #stride表示kernal移动的步长
            #padding表示左右上下添加0,使得卷积后的照片和之前输入的照片size类似
            # 经过第一层后从x:[b, 3, 32, 32]=>到[b, 6, ]
            nn.Conv2d(3, 6, kernel_size=5, stride=1, padding=0) #第一层:新建一个卷积层


            #pooling层是不改变channel的,只改变长宽。
            #[b, 6, ] => [b, 6, ]
            ,nn.AvgPool2d(kernel_size=2,stride=2,padding=0) #第二层:Pooling层

            # [b, 6, ] => [b, 16, ]
            ,nn.Conv2d(6,16,kernel_size=5,stride=1,padding=0) #第三层:再建一个卷积层

            # [b, 16, ] => [b, 16, 5, 5]
            ,nn.AvgPool2d(kernel_size=2, stride=2, padding=0) #第四层:Pooling层

            #之后就是和全连接层连接了,需要做一个打平的操作,但是pytorch中没有打平flatten这个类。
            #所以flatten的操作先不编写
        )
        # conv out: torch.Size([b, 16, 5, 5])

6、编写一个假的输入数据,获取Lenet5类中卷积单元的计算结果
可以发现输入一个临时的batch数据到这个卷积单元中,输出结果为 (b,16,5,5)

		#这部分内容是写在Lenet5类里面的
        # 一个假的batch数据包[b,3,32,32],这个数据是模仿cifar_train的数据
        temp = torch.randn(2, 3, 32, 32)
        # 将这个假的batch送进取
        out = self.conv_unit(temp)
        # conv out: torch.Size([2, 16, 5, 5])
        print('conv out:', out.shape)

#这样实例化Lenet5,可以查看一下输出结果
def main():
    net = Lenet5()

if __name__ =='__main__':
    main()

在这里插入图片描述
7、编写lenet5类的全连接单元

        #fc unit
        #Full Connection(缩写fc)全连接层
        self.fc_unit = nn.Sequential(
            # 16*5*5,是根据假数据测试Lenet5类卷积单元计算结果所得到的,并经过flatten的结果,表示flatten的输入维度数量,目前这里暂时忽略flatten操作,直接手动的输入。
            nn.Linear(16*5*5,120),
            nn.ReLU(), #激活函数,sigmoid会有梯度离散的现象,这里选择ReLU
            nn.Linear(120,84),
            nn.ReLU(),
            nn.Linear(84,10) #这里的10表示分的10类
        )

8、编写lenet5类的forward单元
所有的网络结构都有一个forward函数,代表前项运算的流程。
为什么backward不用写呢,和numpy不一样,pytorch会自动记录前项的路径,当backward的时候就不需要自己来实现了,pytorch会自动的根据前面走过的路线,往回走一遍。

    def forward(self, x):
        """

        :param x: [b, 3, 32, 32]
        :return:
        """
        # x.size(0)是返回一个list(b,3,32,32)的第0号元素。
        # 0就是取这个list的第0号元素,就等于x.shape[0]
        batchsz = x.size(0)

        #1、经过卷积单元
        #[b,3,32,32] => [b,16,5,5]
        x = self.conv_unit(x)

        #2、经过Flatten打平操作:
        #经过flatten x= x.view()就可以实现flatten的效果
        #[b,16,5,5] => [b,16*5*5]
        #flatten是没有写到Sequential中的
        x = x.view(batchsz, 16*5*5)
        #还可以写成:x = x.view(batchsz, -1) 这里的-1表示除了第一个维度batchsz,剩下的全部归于第二个维度。
        #x = x.view(batchsz, 16*5*5) = x.view(batchsz, -1)

        #3、经过全连接层Full Connection
        #[b,16*5*5] => [b,10]
        logits = self.fc_unit(x)

        #返回logits, 一定要有返回值
        return logits

lenet5类的forward单元就编写好了,实现一个输入的数据x,经过卷积单元、Flatten打平、最后经过全连接层的过程。

9、编写一个假的数据测试一下Lenet5这个类

def main():
    net = Lenet5()

    # 一个假的batch数据包[b,3,32,32]
    temp = torch.randn(2, 3, 32, 32)
    # 将这个假的batch送进取
    out = net(temp)
    # Lenet5 out: torch.Size([b, 10])
    print('Lenet5 out:', out.shape)
    print(type(out))
    print(out)
    
if __name__ =='__main__':
    main()

在这里插入图片描述
++++++++++++++++++++++++++++++++++++结束编写一个Lenet5类++++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++结束编写一个Lenet5类++++++++++++++++++++++++++++++++++++

10、编写运行函数train部分

#将lenet5类引入
from lenet5_test import Lenet5

def main():
	#1、导入数据
    #==========这部分内容是步骤1、步骤2和步骤3的内容,导入数据===========
    batchsz = 32
    #首先引入数据:
    #参数1:是数据存储的根目录Root directory of dataset,数据存在这个文件目录下'cifar'
    #参数2:是否为train训练集
    #参数3:transform数据变换
    #参数4:download是否下载
    cifar_train = datasets.CIFAR10('cifar', True, transform=transforms.Compose([
        transforms.Resize((32,32)), #调整照片的大小
        transforms.ToTensor(), #直接将数据类型转变为tensor
        #希望像素值再0周围均匀分布,就需要使用normalize,mean=[R通道上像素的均值,G...,B...]
        transforms.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225])
    ]),download=True)
    #将datasets进行多线程下载
    cifar_train = DataLoader(cifar_train,batch_size=batchsz, shuffle=True)

    cifar_test = datasets.CIFAR10('cifar', False, transform=transforms.Compose([
        transforms.Resize((32,32)),
        transforms.ToTensor(),
        #如果加了normalize,train和test必须都添加
        transforms.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225])
    ]),download=True)
    cifar_test = DataLoader(cifar_test,batch_size=batchsz, shuffle=True)
	#================================================================
	
	#2、设置设备计算的GPU
	#这样就可以使用GPU来进行计算
    device = torch.device('cuda')
	
	#3、实例化一下模型
	#引入Lenet5类,并实例化Lenet5
    model = Lenet5().to(device)
	
	#4、打印输出一下模型网络结构
	# print可以非常方便的打印出这个类的结构。这一点tenseflow是做不到的。
    print('网络结构:')
    print(model) 

	#5、创建一个计算loss的criteon
    #nn.CrossEntropyLoss()是包含softmax的,既然包含,那么输入肯定是logits,而不是predict
    #predict与logits的区别在于,predict是logits经过了softmax操作的。
    criteon = nn.CrossEntropyLoss().to(device)
	
	#6、得到一个优化器
    #做测试一般都使用Adam这个包。
    #优化器optimizer就不需要转到GPU上面了
    #参数1是要传入模型需要优化的参数,参数2是学习率
    optimizer = optim.Adam(model.parameters(), lr=1e-3)

	#7、编写for循环
    for epoch in range(1000):
		
		#training部分
        #迭代每一次数据
        #batchidx表示第多少个batch了
        for batchidx, (x, label) in enumerate(cifar_train):
        	
            #如果这里我们打印batchidx,可以发现一共分了0-1562组batch,每个batch是DataLoader中batch_size所设置的32。
            #我们可以简单计算一下总共有多少张图片用于training,1563*32 = 50016张图片
            # print('batchidx:',batchidx)
        	
            #1、设置training数据和标签
            # x: [b,3,32,32]
            # label: [b]
            x, label = x.to(device), label.to(device)
			
			#2、将数据导入模型
            #将数据传入实例化的Lenet5中
            logits = model(x)
			
			#3、模型计算结果与标签计算loss
			#获得loss
            #logits: [b,10] logits必须给出每个维度的probability
            #label: [b] 没有经过one-hot-coding,label是不需要给出probability
            #loss: tensor scalar 长度为0的标量。
            loss = criteon(logits, label)
			
			#4、backpropagation 反向传播
            optimizer.zero_grad()#将梯度全部清0,如果不清0,则每次梯度更新后都会累加新的梯度。
            loss.backward() #回退进行梯度计算
            optimizer.step() #走一遍,更新了weight权值

		#这里我们打印一下
        print('epoch:',epoch,'loss:', loss.item()) #对标量使用item()将其转换为numpy,并打印出来


if __name__ == '__main__':
    main()

在这里插入图片描述

11、编写运行函数test部分

        #test部分
        #这部分是包含在for epoch in range(1000):内的
        #与training部分for batchidx, (x, label) in enumerate(cifar_train):平级的

        #将模型变为test模式
        #train和test的计算方法是不一样的。
        #比如说在test模式下,dropout的概率全为0,不进行drop out
        model.eval()

        # 告诉pytorch,下面这些内容是不要继承图的,不需要回溯,不需要计算梯度
        # test测试模块,是不需要记录梯度,即:forward的时候是不需要完成pytorch的继承图的,不需要backpropagation反向传播
        #不需要计算梯度的部分,包含在 with torch.no_grad():函数中,告诉pytorch 不需要记录继承图的。
        with torch.no_grad():

            # 做一个比对,统计对的,和总的数量
            total_correct = 0;
            total_num = 0

            for batchidx, (x, label) in enumerate(cifar_test):
                #1、设置test数据和标签
                # [b, 3, 32, 32]
                # [b]
                x, label = x.to(device), label.to(device)

                # 如果这里我们打印batchidx,可以发现一共分了0-312组batch,每个batch是DataLoader中batch_size所设置的32。
                # 我们可以简单计算一下总共有多少张图片用于test,313*32 = 10016张图片
                # print('batchidx:',batchidx)

                # 2、将数据导入模型
                # [b,10]
                logits = model(x) #这个模型是经过optimizer调整后的
                # print('logits:',logits.shape)
                #logits: torch.Size([32, 10])

                # 3、设置pred
                #取logits元素最大的位置作为predict
                #在1维上面选出数据最大的那个值所在的idex。
                #[b]
                pred = logits.argmax(dim=1)
                # print('pred:',pred)
                # print('label:',label)
                #下面是其中一组预测pred和label
                # pred: tensor([2, 0, 1, 6, 1, 4, 7, 4, 2, 0, 1, 6, 9, 1, 8, 8, 9, 7, 0, 4, 3, 7, 4, 5,
                #               1, 4, 4, 9, 9, 1, 9, 3], device='cuda:0')
                # label: tensor([5, 0, 4, 2, 1, 2, 7, 4, 5, 7, 1, 9, 9, 1, 8, 7, 3, 5, 3, 4, 8, 4, 6, 5,
                #                9, 2, 6, 7, 9, 1, 1, 2], device='cuda:0')

                # 4、预测pred与真实label进行比较,统计预测正确的图片数量
                #pred与label做比较
                #[b] vs [b] =>相同位置做eq比较,返回一个布尔矩阵 scalar tensor
                #统计预测正确的图片数量
                #这里自加是因为有313组batch,每组batch有32个图片,其中预测正确的数需要累加,此外照片总数也需要累加每组batch的照片数量。
                total_correct += torch.eq(pred, label).float().sum().item()
                #.item()将tensor类型转为numpy
                # print(type(torch.eq(pred, label).float().sum())) #打印出来的是<class 'torch.Tensor'>类型
                # print(type(torch.eq(pred, label).float().sum().item())) #打印出来的是<class 'float'>类型

                #print('total_correct:',total_correct) #预测正确的图片数量
                #total_correct: 4697.0

                # 5、统计总图片数
                total_num += x.size(0) #所有用于统计的图片总量10000张
                # print('total_num:',total_num)
                #total_num: 10000

                # print('x.shape:',x.shape)
                #x.shape: torch.Size([32, 3, 32, 32])

            #6、预测准确度accuracy
            acc = total_correct / total_num
            print('epoch:', epoch, 'acc:', acc)
  
if __name__ == '__main__':
    main()

在这里插入图片描述

二、将详细说明代码综合起来 Lenet-5和Cifar10

lenet5_test.py:

import torch
from torch import nn #任何类都要继承自nn.Module
from torch.nn import functional as F

class Lenet5(nn.Module):
    """
    for cifar10 dataset.
    """
    # 调用这个类的初始化方法,来初始化这个父类。
    def __init__(self):
        super(Lenet5, self).__init__()

        #卷积层
        #网络结构写在Sequential中,可以方便的组织网络结构。
        self.conv_unit = nn.Sequential(
            #x:[b张照片batch_size, 3, 32, 32] 输入的训练图片
            #nn.Conv2d(3, 6)输入的是3个channel,输出的是6个channel,这里的输出的channel是由kernal的channel来决定。
            #kernal_size=5表示一次关注长宽各为5个像素点的窗口
            #stride表示kernal移动的步长
            #padding表示左右上下添加0,使得卷积后的照片和之前输入的照片size类似
            # 经过第一层后从x:[b, 3, 32, 32]=>到[b, 6, ]
            nn.Conv2d(3, 6, kernel_size=5, stride=1, padding=0) #第一层:新建一个卷积层


            #pooling层是不改变channel的,只改变长宽。
            #[b, 6, ] => [b, 6, ]
            ,nn.AvgPool2d(kernel_size=2,stride=2,padding=0) #第二层:Pooling层

            # [b, 6, ] => [b, 16, ]
            ,nn.Conv2d(6,16,kernel_size=5,stride=1,padding=0) #第三层:再建一个卷积层

            # [b, 16, ] => [b, 16, 5, 5]
            ,nn.AvgPool2d(kernel_size=2, stride=2, padding=0) #第四层:Pooling层

            #之后就是和全连接层连接了,需要做一个打平的操作,但是pytorch中没有打平flatten这个类。
        )

        #conv out: torch.Size([b, 16, 5, 5])
        #flatten: 16*5*5

        #fc unit
        #Full Connection(缩写fc)全连接层
        self.fc_unit = nn.Sequential(
            # 16*5*5表示flatten的输入维度数量
            nn.Linear(16*5*5,120),
            nn.ReLU(), #激活函数,sigmoid会有梯度离散的现象,这里选择ReLU
            nn.Linear(120,84),
            nn.ReLU(),
            nn.Linear(84,10)
        )

    def forward(self, x):
        """

        :param x: [b, 3, 32, 32]
        :return:
        """
        # x.size(0)是返回一个list(b,3,32,32)的第0号元素。
        # 0就是取这个list的第0号元素,就等于x.shape[0]
        batchsz = x.size(0)

        #1、经过卷积单元
        #[b,3,32,32] => [b,16,5,5]
        x = self.conv_unit(x)

        #2、经过Flatten打平操作:
        #经过flatten x= x.view()就可以实现flatten的效果
        #[b,16,5,5] => [b,16*5*5]
        #flatten是没有写到Sequential中的
        x = x.view(batchsz, 16*5*5)
        #还可以写成:x = x.view(batchsz, -1) 这里的-1表示除了第一个维度batchsz,剩下的全部归于第二个维度。
        #x = x.view(batchsz, 16*5*5) = x.view(batchsz, -1)

        #3、经过全连接层Full Connection
        #[b,16*5*5] => [b,10]
        logits = self.fc_unit(x)

        #返回logits, 一定要有返回值
        return logits

def main():
    net = Lenet5()

    # 一个假的batch数据包[b,3,32,32]
    temp = torch.randn(2, 3, 32, 32)
    # 将这个假的batch送进取
    out = net(temp)
    # Lenet5 out: torch.Size([2, 16, 5, 5])
    print('Lenet5 out:', out.shape)
    print(type(out))
    print(out)

if __name__ =='__main__':
    main()

在这里插入图片描述

main_test_lenet5.py:

import torch
from torch.utils.data import DataLoader

#PyTorch 团队专门开发了一个视觉工具包torchvision,这个包独立于 PyTorch,需通过 pip instal torchvision 安装。
#torchvision 主要包含三部分:
#1、models:提供深度学习中各种经典网络的网络结构以及预训练好的模型,包括 AlexNet 、VGG 系列、ResNet 系列、Inception 系列等;
#2、datasets: 提供常用的数据集加载,设计上都是继承 torch.utils.data.Dataset,主要包括 MNIST、CIFAR10/100、ImageNet、COCO等;
#datasets都是 torch.utils.data.Dataset的子类,所以,他们也可以通过torch.utils.data.DataLoader使用多线程(python的多进程)。
#3、transforms:提供常用的数据预处理操作,主要包括对 Tensor 以及 PIL Image 对象的操作;

from torchvision import datasets
from torchvision import transforms
from torch import nn, optim

#将lenet5引入
from lenet5_test import Lenet5
#将Resnet引入
# from resnet import ResNet18

def main():
    batchsz = 32
    #首先引入数据:
    #参数1:是数据存储的根目录Root directory of dataset,数据存在这个文件目录下'cifar'
    #参数2:是否为train训练集
    #参数3:transform数据变换
    #参数4:download是否下载
    cifar_train = datasets.CIFAR10('cifar', True, transform=transforms.Compose([
        transforms.Resize((32,32)), #调整照片的大小
        transforms.ToTensor(), #直接将数据类型转变为tensor
        #希望像素值再0周围均匀分布,就需要使用normalize,mean=[R通道上像素的均值,G...,B...]
        transforms.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225])
    ]),download=True)
    #将datasets进行多线程下载
    cifar_train = DataLoader(cifar_train,batch_size=batchsz, shuffle=True)

    cifar_test = datasets.CIFAR10('cifar', False, transform=transforms.Compose([
        transforms.Resize((32,32)),
        transforms.ToTensor(),
        #如果加了normalize,train和test必须都添加
        transforms.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225])
    ]),download=True)
    cifar_test = DataLoader(cifar_test,batch_size=batchsz, shuffle=True)

    #通过iter()函数获取这些可迭代对象的迭代器。然后,我们可以对获取到的迭代器不断使⽤next()函数来获取下⼀条数据。
    #iter()后数据的类型
    # print(type(iter(cifar_train).next()))
    #查看长度
    # print(len(iter(cifar_train).next()))
    #一个是数据集,一个是标签
    # x, label = iter(cifar_train).next()
    #查看其数据结构
    # print('x:',x.shape,'laberl:',label.shape)


    #这样就可以使用GPU来进行计算
    device = torch.device('cuda')

    #引入Lenet5类,并实例化Lenet5
    model = Lenet5().to(device)

    # print可以非常方便的打印出这个类的结构。这一点tenseflow是做不到的。
    print('网络结构:')
    print(model)

    # 创建一个计算loss的criteon
    # nn.CrossEntropyLoss()是包含softmax的,既然包含,那么输入肯定是logits,而不是predict
    # predict与logits的区别在于,predict是logits经过了softmax操作的。
    criteon = nn.CrossEntropyLoss().to(device)

    #得到一个优化器
    #做测试一般都使用Adam这个包。
    #优化器optimizer就不需要转到GPU上面了
    optimizer = optim.Adam(model.parameters(), lr=1e-3)

    for epoch in range(1000):

        #===================training部分========================
        # 迭代每一次数据
        #batchidx表示第多少个batch了
        for batchidx, (x, label) in enumerate(cifar_train):
            # x: [b,3,32,32]
            # label: [b]
            x, label = x.to(device), label.to(device)

            #如果这里我们打印batchidx,可以发现一共分了0-1562组batch,每个batch是DataLoader中batch_size所设置的32。
            #我们可以简单计算一下总共有多少张图片用于training,1563*32 = 50016张图片
            # print('batchidx:',batchidx)

            #将数据传入实例化的Lenet5中
            logits = model(x)

            #logits: [b,10] logits必须给出每个维度的probability
            #label: [b] 没有经过one-hot-coding,label是不需要给出probability
            #loss: tensor scalar 长度为0的标量。
            loss = criteon(logits, label)

            #backpropagation 反向传播
            optimizer.zero_grad()#将梯度全部清0
            loss.backward() #回退进行梯度计算
            optimizer.step() #走一遍,跟新了weight权值

        #这里我们打印一下
        print('epoch:',epoch,'loss:', loss.item()) #对标量使用item()将其转换为numpy,并打印出来


        #==================test部分========================

        #将模型变为test模式
        #train和test的计算方法是不一样的。
        #比如说在test模式下,dropout的概率全为0,不进行drop out
        model.eval()

        # 告诉pytorch,下面这些内容是不要继承图的,不需要回溯,不需要计算梯度
        # test测试模块,是不需要记录梯度,即:forward的时候是不需要完成pytorch的继承图的,不需要backpropagation反向传播
        #不需要计算梯度的部分,包含在 with torch.no_grad():函数中,告诉pytorch 不需要记录继承图的。
        with torch.no_grad():

            # 做一个比对,统计对的,和总的数量
            total_correct = 0;
            total_num = 0

            for batchidx, (x, label) in enumerate(cifar_test):
                #1、设置test数据和标签
                # [b, 3, 32, 32]
                # [b]
                x, label = x.to(device), label.to(device)

                # 如果这里我们打印batchidx,可以发现一共分了0-312组batch,每个batch是DataLoader中batch_size所设置的32。
                # 我们可以简单计算一下总共有多少张图片用于test,313*32 = 10016张图片
                # print('batchidx:',batchidx)

                # 2、将数据导入模型
                # [b,10]
                logits = model(x) #这个模型是经过optimizer调整后的
                # print('logits:',logits.shape)
                #logits: torch.Size([32, 10])

                # 3、设置pred
                #取logits元素最大的位置作为predict
                #在1维上面选出数据最大的那个值所在的idex。
                #[b]
                pred = logits.argmax(dim=1)
                # print('pred:',pred)
                # print('label:',label)
                #下面是其中一组预测pred和label
                # pred: tensor([2, 0, 1, 6, 1, 4, 7, 4, 2, 0, 1, 6, 9, 1, 8, 8, 9, 7, 0, 4, 3, 7, 4, 5,
                #               1, 4, 4, 9, 9, 1, 9, 3], device='cuda:0')
                # label: tensor([5, 0, 4, 2, 1, 2, 7, 4, 5, 7, 1, 9, 9, 1, 8, 7, 3, 5, 3, 4, 8, 4, 6, 5,
                #                9, 2, 6, 7, 9, 1, 1, 2], device='cuda:0')

                # 4、预测pred与真实label进行比较,统计预测正确的图片数量
                #pred与label做比较
                #[b] vs [b] =>相同位置做eq比较,返回一个布尔矩阵 scalar tensor
                #统计预测正确的图片数量
                #这里自加是因为有313组batch,每组batch有32个图片,其中预测正确的数需要累加,此外照片总数也需要累加每组batch的照片数量。
                total_correct += torch.eq(pred, label).float().sum().item()
                #.item()将tensor类型转为numpy
                # print(type(torch.eq(pred, label).float().sum())) #打印出来的是<class 'torch.Tensor'>类型
                # print(type(torch.eq(pred, label).float().sum().item())) #打印出来的是<class 'float'>类型

                #print('total_correct:',total_correct) #预测正确的图片数量
                #total_correct: 4697.0

                # 5、统计总图片数
                total_num += x.size(0) #所有用于统计的图片总量10000张
                # print('total_num:',total_num)
                #total_num: 10000

                # print('x.shape:',x.shape)
                #x.shape: torch.Size([32, 3, 32, 32])

            #6、预测准确度accuracy
            acc = total_correct / total_num
            print('epoch:', epoch, 'acc:', acc)


if __name__ == '__main__':
    main()

在这里插入图片描述

三、ResNet和Cifar10详细步骤说明

1、首先加载数据,具体过程和Lenet-5和Cifar10详细步骤说明中的加载数据部分是相同的。

+++++++++++++++++++++++++++++++++++开始编写一个ResNet18类+++++++++++++++++++++++++++++++++++++++
+++++++++++++++++++++++++++++++++++开始编写一个ResNet18类+++++++++++++++++++++++++++++++++++++++
这里需要注意:这里的ResNet18并不是原paper上的那个。这是因为这里使用的Cifar10数据集3232。而原paper的接受的输入是224224。输入shape不同,所以不能完全照搬。

2、创建一个新类,为ResBlk类,ResNet18的一个单元,如果要完成ResNet18类,就先实现这个block
在这里插入图片描述

import torch
from torch import nn
from torch.nn import functional as F

#新类ResBlk
class ResBlk(nn.Module):
    """
    resnet block
    """
    def __init__(self, ch_in, ch_out, stride=1):
        """

        :param ch_in:
        :param ch_out:
        """
        super(ResBlk, self).__init__() #调用这个类的初始化方法,来初始化这个父类。

        #1、构建两个convolution单元
        self.conv1 = nn.Conv2d(ch_in, ch_out, kernel_size=3, stride=stride, padding=1)
        # Batch Normalization的目的是使我们的一批(Batch)feature map满足均值为0,方差为1的分布规律。
        self.bn1 = nn.BatchNorm2d(ch_out)
        self.conv2 = nn.Conv2d(ch_out, ch_out, kernel_size=3, stride=1, padding=1)
        self.bn2 = nn.BatchNorm2d(ch_out)

        #2、创建短接调整输入维度模块
        #先判断一下输入的channel与输出的channel是不是不相同
        #网络结构写在Sequential中,可以方便的组织网络结构。
        #这部分是短接的额外单元extra module
        if ch_out != ch_in:
            #[b,ch_in,h,w] => [b,ch_out,h,w]
            #这里使用的是1*1的卷积单元,为了是只见ch_in改变为ch_out,其他size都不变。
            self.extra = nn.Sequential(
                nn.Conv2d(ch_in, ch_out, kernel_size=1, stride=stride),
                nn.BatchNorm2d(ch_out)
            )
        else:
            #这里是相同的情况,相同的话就什么也不做。
            self.extra = nn.Sequential()

    #3、编写forward函数
    def forward(self, x):
        """

        :param x: [b,ch,h,w]
        :return:
        """
        #4、调用init构建好的两个convolution单元,并在其中添加一个relu激活函数
        out = F.relu(self.bn1(self.conv1(x)))
        out = self.bn2(self.conv2(out))

        #5、编写short cut部分
        #这里需要注意的是,输出与输入的维度相同才可以进行矩阵相加
        #所以我们给短接部分添加一个模块,这个模块用于调整输入的维度,使其能够和输出相加。
        #这部分叫extra module
        #如果ch_in != ch_out
        #通过 extra module: 将[b,ch_in,h,w] =>变为 [b,ch_out,h,w]
        # element.wise add 矩阵各个元素相加:
        out = x = self.extra(x) + out

        return out

def main():
    blk = ResBlk(64,1, stride=4)
    print(blk) #查看网络结构

    tmp = torch.randn(1, 64, 32, 32)
    out = blk(tmp)
    print('block shape:', out.shape)
    print('block:',out)

if __name__ == '__main__':
    main()

在这里插入图片描述

3、创建一个新类,为ResNet18
这个类包含1个初始卷积层、4个ResBlk模块、一个线性层linear用于转换为10类

ResNet18类中用到了ResBlk类。

import torch
from torch import nn
from torch.nn import functional as F

#这里是ResBlk类:
class ResBlk(nn.Module):
    """
    resnet block
    """
    def __init__(self, ch_in, ch_out, stride=1):
        """

        :param ch_in:
        :param ch_out:
        """
        super(ResBlk, self).__init__() #调用这个类的初始化方法,来初始化这个父类。

        #1、构建两个convolution单元
        #这里设置一个stride,用于减少数据量,衰减长和宽。
        self.conv1 = nn.Conv2d(ch_in, ch_out, kernel_size=3, stride=stride, padding=1)
        # Batch Normalization的目的是使我们的一批(Batch)feature map满足均值为0,方差为1的分布规律。
        self.bn1 = nn.BatchNorm2d(ch_out)
        self.conv2 = nn.Conv2d(ch_out, ch_out, kernel_size=3, stride=1, padding=1)
        self.bn2 = nn.BatchNorm2d(ch_out)

        #2、创建短接调整输入维度模块
        #先判断一下输入的channel与输出的channel是不是不相同
        #网络结构写在Sequential中,可以方便的组织网络结构。
        #这部分是短接的额外单元extra module
        if ch_out != ch_in:
            #[b,ch_in,h,w] => [b,ch_out,h,w]
            #这里使用的是1*1的卷积单元,为了是只见ch_in改变为ch_out,其他size都不变。
            #此外还要确保参数stride与最开始第一次卷积的stride保持一致。这样长和宽才是一致的。
            self.extra = nn.Sequential(
                nn.Conv2d(ch_in, ch_out, kernel_size=1, stride=stride),
                nn.BatchNorm2d(ch_out)
            )
        else:
            #这里是相同的情况,相同的话就什么也不做。
            self.extra = nn.Sequential()

    #3、编写forward函数
    def forward(self, x):
        """

        :param x: [b,ch,h,w]
        :return:
        """
        #4、调用init构建好的两个convolution单元,并在其中添加一个relu激活函数
        out = F.relu(self.bn1(self.conv1(x)))
        out = self.bn2(self.conv2(out))

        #5、编写short cut部分
        #上面卷积层的结果,需要和最开始的输入进行相加操作。
        #这里需要注意的是,输出与输入的维度相同才可以进行矩阵相加
        #所以我们给短接部分添加一个模块,这个模块用于调整输入的维度,使其能够和输出相加。
        #这部分叫extra module
        #如果ch_in != ch_out
        #通过 extra module: 将[b,ch_in,h,w] =>变为 [b,ch_out,h,w]
        # element.wise add 矩阵各个元素相加:
        out = x = self.extra(x) + out

        return out
#这里是ResNet18类:
class ResNet18(nn.Module):

    def __init__(self):
        super(ResNet18, self).__init__()

        #1、先创建一个卷积层,将ch_in=3转为ch_out=64。
        #这里的stride设置为3
        #padding设置为0
        self.conv1 = nn.Sequential(
            nn.Conv2d(3,64,kernel_size=3,stride=3,padding=0),
            nn.BatchNorm2d(64)

        )
		
        # 2、紧接着4个ResBlk部分followed 4 block
        # [b,64,h,w] => [b,128,h,w]
        self.blk1 = ResBlk(64, 128,stride=2)

        # [b,128,h,w] => [b,256,h,w]
        self.blk2 = ResBlk(128, 256, stride=2)

        # [b,256,h,w] => [b,512,h,w]
        self.blk3 = ResBlk(256, 512,stride=2)

        # [b,512,h,w] => [b,1024,h,w]
        self.blk4 = ResBlk(512, 512,stride=2)

        #3、跟一个线性层linear,变成10类
        self.outlayer = nn.Linear(512*1*1, 10)

    def forward(self,x):
        """

        :param x:
        :return:
        """
		#1、初始卷积层经过激活函数
        #将self.conv1(x)经过激活函数处理
        x = F.relu(self.conv1(x))
        print('经过初始卷积层和relu:',x.shape)

        #2、经过4个block
        # [b,64,h,w] => [b,1024,h,w]
        x = self.blk1(x)
        print('经过第一个blk1:',x.shape)

        x = self.blk2(x)
        print('经过第二个blk2:',x.shape)

        x = self.blk3(x)
        print('经过第三个blk3:',x.shape)

        x = self.blk4(x)
        print('经过第四个blk4:', x.shape)

        # print('after conv:', x.shape)  # [b, 512, 2, 2]
		
		#3、最终特征图大小设置为(1,1)
        #[b,512,h,w] => [b,512,1,1]
        #adaptive_avg_pool2d意思就是不管之前的特征图尺寸为多少,只要设置为(1,1),那么最终特征图大小都为(1,1) 
        x = F.adaptive_avg_pool2d(x,[1,1])
        print('经过adaptive_avg_pool2d:', x.shape) #after pool: torch.Size([2, 512, 1, 1])
		
		#4、图像打平处理
        #取第一个维度,即batch维度,其他维度相乘512*1*1
        x = x.view(x.size(0), -1)
        print('经过view:',x.shape) #torch.Size([2, 512])
		
		#5、跟一个线性层linear,将打平后的x输入线性层中,变成10类
        x = self.outlayer(x)

        return x
        
def main():
    #创建一个假的数据集
    #这里测试的目的是查看是否报错,如果报错,说明网络中的shape存在不匹配match的情况。
    #如果match,就不会报错,如果不match,就会报错。
    x = torch.randn(2,3,32,32)
    print('初始数据输入:',x.shape)
    model = ResNet18()
    out = model(x)
    print('resnet18结果:',out.shape)
    
if __name__ == '__main__':
    main()

在这里插入图片描述
+++++++++++++++++++++++++++++++++++结束编写一个ResNet18类+++++++++++++++++++++++++++++++++++++++
+++++++++++++++++++++++++++++++++++结束编写一个ResNet18类+++++++++++++++++++++++++++++++++++++++

4、编写主程序部分,这部分与lenet5的主程序是一致的,只是将lenet5模型换成了ResNet18

import torch
from torch.utils.data import DataLoader

#PyTorch 团队专门开发了一个视觉工具包torchvision,这个包独立于 PyTorch,需通过 pip instal torchvision 安装。
#torchvision 主要包含三部分:
#1、models:提供深度学习中各种经典网络的网络结构以及预训练好的模型,包括 AlexNet 、VGG 系列、ResNet 系列、Inception 系列等;
#2、datasets: 提供常用的数据集加载,设计上都是继承 torch.utils.data.Dataset,主要包括 MNIST、CIFAR10/100、ImageNet、COCO等;
#datasets都是 torch.utils.data.Dataset的子类,所以,他们也可以通过torch.utils.data.DataLoader使用多线程(python的多进程)。
#3、transforms:提供常用的数据预处理操作,主要包括对 Tensor 以及 PIL Image 对象的操作;

from torchvision import datasets
from torchvision import transforms
from torch import nn, optim

#将lenet5引入
# from lenet5_test import Lenet5
#将Resnet引入
from resnet_test import ResNet18

def main():
    batchsz = 32
    #首先引入数据:
    #参数1:是数据存储的根目录Root directory of dataset,数据存在这个文件目录下'cifar'
    #参数2:是否为train训练集
    #参数3:transform数据变换
    #参数4:download是否下载
    cifar_train = datasets.CIFAR10('cifar', True, transform=transforms.Compose([
        transforms.Resize((32,32)), #调整照片的大小
        transforms.ToTensor(), #直接将数据类型转变为tensor
        #希望像素值再0周围均匀分布,就需要使用normalize,mean=[R通道上像素的均值,G...,B...]
        transforms.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225])
    ]),download=True)
    #将datasets进行多线程下载
    cifar_train = DataLoader(cifar_train,batch_size=batchsz, shuffle=True)

    cifar_test = datasets.CIFAR10('cifar', False, transform=transforms.Compose([
        transforms.Resize((32,32)),
        transforms.ToTensor(),
        #如果加了normalize,train和test必须都添加
        transforms.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225])
    ]),download=True)
    cifar_test = DataLoader(cifar_test,batch_size=batchsz, shuffle=True)

    #通过iter()函数获取这些可迭代对象的迭代器。然后,我们可以对获取到的迭代器不断使⽤next()函数来获取下⼀条数据。
    #iter()后数据的类型
    # print(type(iter(cifar_train).next()))
    #查看长度
    # print(len(iter(cifar_train).next()))
    #一个是数据集,一个是标签
    # x, label = iter(cifar_train).next()
    #查看其数据结构
    # print('x:',x.shape,'laberl:',label.shape)


    #这样就可以使用GPU来进行计算
    device = torch.device('cuda')

    #引入Lenet5类,并实例化Lenet5
    # model = Lenet5().to(device)
    #引入ResNet18类
    model = ResNet18().to(device)

    # print可以非常方便的打印出这个类的结构。这一点tenseflow是做不到的。
    print('网络结构:')
    print(model)

    # 创建一个计算loss的criteon
    # nn.CrossEntropyLoss()是包含softmax的,既然包含,那么输入肯定是logits,而不是predict
    # predict与logits的区别在于,predict是logits经过了softmax操作的。
    criteon = nn.CrossEntropyLoss().to(device)

    #得到一个优化器
    #做测试一般都使用Adam这个包。
    #优化器optimizer就不需要转到GPU上面了
    optimizer = optim.Adam(model.parameters(), lr=1e-3)

    for epoch in range(1000):

        #===================training部分========================
        # 迭代每一次数据
        #batchidx表示第多少个batch了
        for batchidx, (x, label) in enumerate(cifar_train):
            # x: [b,3,32,32]
            # label: [b]
            x, label = x.to(device), label.to(device)

            #如果这里我们打印batchidx,可以发现一共分了0-1562组batch,每个batch是DataLoader中batch_size所设置的32。
            #我们可以简单计算一下总共有多少张图片用于training,1563*32 = 50016张图片
            # print('batchidx:',batchidx)

            #将数据传入实例化的Lenet5中
            logits = model(x)

            #logits: [b,10] logits必须给出每个维度的probability
            #label: [b] 没有经过one-hot-coding,label是不需要给出probability
            #loss: tensor scalar 长度为0的标量。
            loss = criteon(logits, label)

            #backpropagation 反向传播
            optimizer.zero_grad()#将梯度全部清0
            loss.backward() #回退进行梯度计算
            optimizer.step() #走一遍,跟新了weight权值

        #这里我们打印一下
        print('epoch:',epoch,'loss:', loss.item()) #对标量使用item()将其转换为numpy,并打印出来


        #==================test部分========================

        #将模型变为test模式
        #train和test的计算方法是不一样的。
        #比如说在test模式下,dropout的概率全为0,不进行drop out
        model.eval()

        # 告诉pytorch,下面这些内容是不要继承图的,不需要回溯,不需要计算梯度
        # test测试模块,是不需要记录梯度,即:forward的时候是不需要完成pytorch的继承图的,不需要backpropagation反向传播
        #不需要计算梯度的部分,包含在 with torch.no_grad():函数中,告诉pytorch 不需要记录继承图的。
        with torch.no_grad():

            # 做一个比对,统计对的,和总的数量
            total_correct = 0;
            total_num = 0

            for batchidx, (x, label) in enumerate(cifar_test):
                #1、设置test数据和标签
                # [b, 3, 32, 32]
                # [b]
                x, label = x.to(device), label.to(device)

                # 如果这里我们打印batchidx,可以发现一共分了0-312组batch,每个batch是DataLoader中batch_size所设置的32。
                # 我们可以简单计算一下总共有多少张图片用于test,313*32 = 10016张图片
                # print('batchidx:',batchidx)

                # 2、将数据导入模型
                # [b,10]
                logits = model(x) #这个模型是经过optimizer调整后的
                # print('logits:',logits.shape)
                #logits: torch.Size([32, 10])

                # 3、设置pred
                #取logits元素最大的位置作为predict
                #在1维上面选出数据最大的那个值所在的idex。
                #[b]
                pred = logits.argmax(dim=1)
                # print('pred:',pred)
                # print('label:',label)
                #下面是其中一组预测pred和label
                # pred: tensor([2, 0, 1, 6, 1, 4, 7, 4, 2, 0, 1, 6, 9, 1, 8, 8, 9, 7, 0, 4, 3, 7, 4, 5,
                #               1, 4, 4, 9, 9, 1, 9, 3], device='cuda:0')
                # label: tensor([5, 0, 4, 2, 1, 2, 7, 4, 5, 7, 1, 9, 9, 1, 8, 7, 3, 5, 3, 4, 8, 4, 6, 5,
                #                9, 2, 6, 7, 9, 1, 1, 2], device='cuda:0')

                # 4、预测pred与真实label进行比较,统计预测正确的图片数量
                #pred与label做比较
                #[b] vs [b] =>相同位置做eq比较,返回一个布尔矩阵 scalar tensor
                #统计预测正确的图片数量
                #这里自加是因为有313组batch,每组batch有32个图片,其中预测正确的数需要累加,此外照片总数也需要累加每组batch的照片数量。
                total_correct += torch.eq(pred, label).float().sum().item()
                #.item()将tensor类型转为numpy
                # print(type(torch.eq(pred, label).float().sum())) #打印出来的是<class 'torch.Tensor'>类型
                # print(type(torch.eq(pred, label).float().sum().item())) #打印出来的是<class 'float'>类型

                #print('total_correct:',total_correct) #预测正确的图片数量
                #total_correct: 4697.0

                # 5、统计总图片数
                total_num += x.size(0) #所有用于统计的图片总量10000张
                # print('total_num:',total_num)
                #total_num: 10000

                # print('x.shape:',x.shape)
                #x.shape: torch.Size([32, 3, 32, 32])

            #6、预测准确度accuracy
            acc = total_correct / total_num
            print('epoch:', epoch, 'acc:', acc)


if __name__ == '__main__':
    main()

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

四、将详细说明代码综合起来 ResNet和Cifar10

resnet_test.py

import torch
from torch import nn
from torch.nn import functional as F

class ResBlk(nn.Module):
    """
    resnet block
    """
    def __init__(self, ch_in, ch_out, stride=1):
        """

        :param ch_in:
        :param ch_out:
        """
        super(ResBlk, self).__init__() #调用这个类的初始化方法,来初始化这个父类。

        #1、构建两个convolution单元
        #这里设置一个stride,用于减少数据量,衰减长和宽。
        self.conv1 = nn.Conv2d(ch_in, ch_out, kernel_size=3, stride=stride, padding=1)
        # Batch Normalization的目的是使我们的一批(Batch)feature map满足均值为0,方差为1的分布规律。
        self.bn1 = nn.BatchNorm2d(ch_out)
        self.conv2 = nn.Conv2d(ch_out, ch_out, kernel_size=3, stride=1, padding=1)
        self.bn2 = nn.BatchNorm2d(ch_out)

        #2、创建短接调整输入维度模块
        #先判断一下输入的channel与输出的channel是不是不相同
        #网络结构写在Sequential中,可以方便的组织网络结构。
        #这部分是短接的额外单元extra module
        if ch_out != ch_in:
            #[b,ch_in,h,w] => [b,ch_out,h,w]
            #这里使用的是1*1的卷积单元,为了是只见ch_in改变为ch_out,其他size都不变。
            #此外还要确保参数stride与最开始第一次卷积的stride保持一致。这样长和宽才是一致的。
            self.extra = nn.Sequential(
                nn.Conv2d(ch_in, ch_out, kernel_size=1, stride=stride),
                nn.BatchNorm2d(ch_out)
            )
        else:
            #这里是相同的情况,相同的话就什么也不做。
            self.extra = nn.Sequential()

    #3、编写forward函数
    def forward(self, x):
        """

        :param x: [b,ch,h,w]
        :return:
        """
        #4、调用init构建好的两个convolution单元,并在其中添加一个relu激活函数
        out = F.relu(self.bn1(self.conv1(x)))
        out = self.bn2(self.conv2(out))

        #5、编写short cut部分
        #上面卷积层的结果,需要和最开始的输入进行相加操作。
        #这里需要注意的是,输出与输入的维度相同才可以进行矩阵相加
        #所以我们给短接部分添加一个模块,这个模块用于调整输入的维度,使其能够和输出相加。
        #这部分叫extra module
        #如果ch_in != ch_out
        #通过 extra module: 将[b,ch_in,h,w] =>变为 [b,ch_out,h,w]
        # element.wise add 矩阵各个元素相加:
        out = x = self.extra(x) + out

        return out

class ResNet18(nn.Module):

    def __init__(self):
        super(ResNet18, self).__init__()

        #1、先创建一个卷积层,将ch_in=3转为ch_out=64。
        #这里的stride设置为3
        #padding设置为0
        self.conv1 = nn.Sequential(
            nn.Conv2d(3,64,kernel_size=3,stride=3,padding=0),
            nn.BatchNorm2d(64)

        )

        # 紧接着4个ResBlk部分followed 4 block
        # [b,64,h,w] => [b,128,h,w]
        self.blk1 = ResBlk(64, 128,stride=2)

        # [b,128,h,w] => [b,256,h,w]
        self.blk2 = ResBlk(128, 256, stride=2)

        # [b,256,h,w] => [b,512,h,w]
        self.blk3 = ResBlk(256, 512,stride=2)

        # [b,512,h,w] => [b,1024,h,w]
        self.blk4 = ResBlk(512, 512,stride=2)

        #跟一个线性层linear,变成10类
        self.outlayer = nn.Linear(512*1*1, 10)

    def forward(self,x):
        """

        :param x:
        :return:
        """

        #将self.conv1(x)经过激活函数处理
        x = F.relu(self.conv1(x))
        # print('经过初始卷积层和relu:',x.shape)

        #经过4个block
        # [b,64,h,w] => [b,1024,h,w]
        x = self.blk1(x)
        # print('经过第一个blk1:',x.shape)

        x = self.blk2(x)
        # print('经过第二个blk2:',x.shape)

        x = self.blk3(x)
        # print('经过第三个blk3:',x.shape)

        x = self.blk4(x)
        # print('经过第四个blk4:', x.shape)

        # print('after conv:', x.shape)  # [b, 512, 2, 2]

        #[b,512,h,w] => [b,512,1,1]
        x = F.adaptive_avg_pool2d(x,[1,1])
        # print('经过adaptive_avg_pool2d:', x.shape)

        #取第一个维度,即batch维度,其他维度相乘512*1*1
        x = x.view(x.size(0), -1)
        # print('经过view:',x.shape) #torch.Size([2, 512])

        # 跟一个线性层linear,变成10类
        x = self.outlayer(x)

        return x

def main():
    # # 这里我们希望长和宽减半,减少数据量,因为如果不变的话,channel维度不断增加,会导致数据量翻倍。
    # #所以我们设置stride步长,让长宽减小
    # blk = ResBlk(64,128, stride=4)
    # # print(blk) #查看网络结构
    # # 创建一个假的数据集
    # tmp = torch.randn(2,64, 32, 32)
    # out = blk(tmp)
    # print('block shape:', out.shape) #block shape: torch.Size([2, 128, 8, 8])
    # # print('block:',out)

    #创建一个假的数据集
    #这里测试的目的是查看是否报错,如果报错,说明网络中的shape存在不匹配match的情况。
    #如果match,就不会报错,如果不match,就会报错。
    x = torch.randn(2,3,32,32)
    # print('初始数据输入:',x.shape)
    model = ResNet18()
    out = model(x)
    print('resnet18结果:',out.shape)

if __name__ == '__main__':
    main()

在这里插入图片描述

main_test_ResNet.py

import torch
from torch.utils.data import DataLoader

#PyTorch 团队专门开发了一个视觉工具包torchvision,这个包独立于 PyTorch,需通过 pip instal torchvision 安装。
#torchvision 主要包含三部分:
#1、models:提供深度学习中各种经典网络的网络结构以及预训练好的模型,包括 AlexNet 、VGG 系列、ResNet 系列、Inception 系列等;
#2、datasets: 提供常用的数据集加载,设计上都是继承 torch.utils.data.Dataset,主要包括 MNIST、CIFAR10/100、ImageNet、COCO等;
#datasets都是 torch.utils.data.Dataset的子类,所以,他们也可以通过torch.utils.data.DataLoader使用多线程(python的多进程)。
#3、transforms:提供常用的数据预处理操作,主要包括对 Tensor 以及 PIL Image 对象的操作;

from torchvision import datasets
from torchvision import transforms
from torch import nn, optim

#将lenet5引入
# from lenet5_test import Lenet5
#将Resnet引入
from resnet_test import ResNet18

def main():
    batchsz = 32
    #首先引入数据:
    #参数1:是数据存储的根目录Root directory of dataset,数据存在这个文件目录下'cifar'
    #参数2:是否为train训练集
    #参数3:transform数据变换
    #参数4:download是否下载
    cifar_train = datasets.CIFAR10('cifar', True, transform=transforms.Compose([
        transforms.Resize((32,32)), #调整照片的大小
        transforms.ToTensor(), #直接将数据类型转变为tensor
        #希望像素值再0周围均匀分布,就需要使用normalize,mean=[R通道上像素的均值,G...,B...]
        transforms.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225])
    ]),download=True)
    #将datasets进行多线程下载
    cifar_train = DataLoader(cifar_train,batch_size=batchsz, shuffle=True)

    cifar_test = datasets.CIFAR10('cifar', False, transform=transforms.Compose([
        transforms.Resize((32,32)),
        transforms.ToTensor(),
        #如果加了normalize,train和test必须都添加
        transforms.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225])
    ]),download=True)
    cifar_test = DataLoader(cifar_test,batch_size=batchsz, shuffle=True)

    #通过iter()函数获取这些可迭代对象的迭代器。然后,我们可以对获取到的迭代器不断使⽤next()函数来获取下⼀条数据。
    #iter()后数据的类型
    # print(type(iter(cifar_train).next()))
    #查看长度
    # print(len(iter(cifar_train).next()))
    #一个是数据集,一个是标签
    # x, label = iter(cifar_train).next()
    #查看其数据结构
    # print('x:',x.shape,'laberl:',label.shape)


    #这样就可以使用GPU来进行计算
    device = torch.device('cuda')

    #引入Lenet5类,并实例化Lenet5
    # model = Lenet5().to(device)
    #引入ResNet18类
    model = ResNet18().to(device)

    # print可以非常方便的打印出这个类的结构。这一点tenseflow是做不到的。
    print('网络结构:')
    print(model)

    # 创建一个计算loss的criteon
    # nn.CrossEntropyLoss()是包含softmax的,既然包含,那么输入肯定是logits,而不是predict
    # predict与logits的区别在于,predict是logits经过了softmax操作的。
    criteon = nn.CrossEntropyLoss().to(device)

    #得到一个优化器
    #做测试一般都使用Adam这个包。
    #优化器optimizer就不需要转到GPU上面了
    optimizer = optim.Adam(model.parameters(), lr=1e-3)

    for epoch in range(1000):

        #===================training部分========================
        # 迭代每一次数据
        #batchidx表示第多少个batch了
        for batchidx, (x, label) in enumerate(cifar_train):
            # x: [b,3,32,32]
            # label: [b]
            x, label = x.to(device), label.to(device)

            #如果这里我们打印batchidx,可以发现一共分了0-1562组batch,每个batch是DataLoader中batch_size所设置的32。
            #我们可以简单计算一下总共有多少张图片用于training,1563*32 = 50016张图片
            # print('batchidx:',batchidx)

            #将数据传入实例化的Lenet5中
            logits = model(x)

            #logits: [b,10] logits必须给出每个维度的probability
            #label: [b] 没有经过one-hot-coding,label是不需要给出probability
            #loss: tensor scalar 长度为0的标量。
            loss = criteon(logits, label)

            #backpropagation 反向传播
            optimizer.zero_grad()#将梯度全部清0
            loss.backward() #回退进行梯度计算
            optimizer.step() #走一遍,跟新了weight权值

        #这里我们打印一下
        print('epoch:',epoch,'loss:', loss.item()) #对标量使用item()将其转换为numpy,并打印出来


        #==================test部分========================

        #将模型变为test模式
        #train和test的计算方法是不一样的。
        #比如说在test模式下,dropout的概率全为0,不进行drop out
        model.eval()

        # 告诉pytorch,下面这些内容是不要继承图的,不需要回溯,不需要计算梯度
        # test测试模块,是不需要记录梯度,即:forward的时候是不需要完成pytorch的继承图的,不需要backpropagation反向传播
        #不需要计算梯度的部分,包含在 with torch.no_grad():函数中,告诉pytorch 不需要记录继承图的。
        with torch.no_grad():

            # 做一个比对,统计对的,和总的数量
            total_correct = 0;
            total_num = 0

            for batchidx, (x, label) in enumerate(cifar_test):
                #1、设置test数据和标签
                # [b, 3, 32, 32]
                # [b]
                x, label = x.to(device), label.to(device)

                # 如果这里我们打印batchidx,可以发现一共分了0-312组batch,每个batch是DataLoader中batch_size所设置的32。
                # 我们可以简单计算一下总共有多少张图片用于test,313*32 = 10016张图片
                # print('batchidx:',batchidx)

                # 2、将数据导入模型
                # [b,10]
                logits = model(x) #这个模型是经过optimizer调整后的
                # print('logits:',logits.shape)
                #logits: torch.Size([32, 10])

                # 3、设置pred
                #取logits元素最大的位置作为predict
                #在1维上面选出数据最大的那个值所在的idex。
                #[b]
                pred = logits.argmax(dim=1)
                # print('pred:',pred)
                # print('label:',label)
                #下面是其中一组预测pred和label
                # pred: tensor([2, 0, 1, 6, 1, 4, 7, 4, 2, 0, 1, 6, 9, 1, 8, 8, 9, 7, 0, 4, 3, 7, 4, 5,
                #               1, 4, 4, 9, 9, 1, 9, 3], device='cuda:0')
                # label: tensor([5, 0, 4, 2, 1, 2, 7, 4, 5, 7, 1, 9, 9, 1, 8, 7, 3, 5, 3, 4, 8, 4, 6, 5,
                #                9, 2, 6, 7, 9, 1, 1, 2], device='cuda:0')

                # 4、预测pred与真实label进行比较,统计预测正确的图片数量
                #pred与label做比较
                #[b] vs [b] =>相同位置做eq比较,返回一个布尔矩阵 scalar tensor
                #统计预测正确的图片数量
                #这里自加是因为有313组batch,每组batch有32个图片,其中预测正确的数需要累加,此外照片总数也需要累加每组batch的照片数量。
                total_correct += torch.eq(pred, label).float().sum().item()
                #.item()将tensor类型转为numpy
                # print(type(torch.eq(pred, label).float().sum())) #打印出来的是<class 'torch.Tensor'>类型
                # print(type(torch.eq(pred, label).float().sum().item())) #打印出来的是<class 'float'>类型

                #print('total_correct:',total_correct) #预测正确的图片数量
                #total_correct: 4697.0

                # 5、统计总图片数
                total_num += x.size(0) #所有用于统计的图片总量10000张
                # print('total_num:',total_num)
                #total_num: 10000

                # print('x.shape:',x.shape)
                #x.shape: torch.Size([32, 3, 32, 32])

            #6、预测准确度accuracy
            acc = total_correct / total_num
            print('epoch:', epoch, 'acc:', acc)


if __name__ == '__main__':
    main()

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Henrik698

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

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

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

打赏作者

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

抵扣说明:

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

余额充值