pytorch快速上手(2)-----CIFAR-10训练测试完整流程示例


具体参考博客: https://blog.csdn.net/teeyohuang/article/details/79211798

一般来说,使用深度学习框架我们会经过下面几个流程:

模型定义(包括损失函数的选择) —>数据处理和加载 —> 训练(可能包含训练过程可视化) —> 测试

所以我们在自己写代码的时候也基本上就按照这四个大模块四步走就ok了
官方给的这个例子呢,是先进行的第二步数据处理和加载,然后定义网络,这其实没什么关系。

此例的步骤:

A、Load and normalizing the CIFAR10 training and test datasets using torchvision
B、Define a Convolution Neural Network
C、Define a loss function
D、Train the network on the training data
E、Test the network on the test data

1. 首先使用 torchvision加载和归一化我们的训练数据和测试数据。

a、torchvision这个东西,实现了常用的一些深度学习的相关的图像数据的加载功能,比如cifar10、Imagenet、Mnist等等的,保存在torchvision.datasets模块中。
b、同时,也封装了一些处理数据的方法。保存在torchvision.transforms模块中
c、还封装了一些模型和工具封装在相应模型中。可以从下一窥究竟:

  • torchvision.datasets 数据模块
  • torchvision.models 模型模块
  • torchvision.transforms 数据变换模块(预处理,增强)
  • torchvision.utils 其他工具集合模块 细节见torchvision的官方文档链接:http://pytorch.org/docs/0.3.0/torchvision/index.html

注意:load数据集时,num_workers>0 指多进程加载,需要在main函数中运行,

  • 解决方法1:加main函数,在main中调用
  • 解决方法2:num_workers改为0,单进程加载,有无main函数都行
#  首先当然肯定要导入torch和torchvision,至于第三个是用于进行数据预处理的模块
import torch
import torchvision
import torchvision.transforms as transforms

#由于torchvision的datasets的输出是[0,1]的PILImage,所以我们先先归一化为[-1,1]的Tensor
#首先定义了一个变换transform,利用的是上面提到的transforms模块中的Compose( ),把多个变换组合在一起,可以看到这里面组合了ToTensor和Normalize这两个变换
#前面的(0.5,0.5,0.5) 是 R G B 三个通道上的均值, 后面(0.5, 0.5, 0.5) 是三个通道的标准差,注意通道顺序是 R G B ,用过opencv的同学应该知道openCV读出来的图像是 BRG顺序
transform = transforms.Compose(
    [transforms.ToTensor(),
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

# 定义了我们的训练集,名字就叫trainset,至于后面这一堆,其实就是一个类:
# torchvision.datasets.CIFAR10( )也是封装好了的,就在我前面提到的torchvision.datasets模块中,不必深究,其实就是在下载数据
# (不翻墙可能会慢一点吧)然后进行变换,可以看到transform就是我们上面定义的transform
trainset = torchvision.datasets.CIFAR10(root='E:\\pytorch\\data', train=True,
                                        download=True, transform=transform)
# trainloader其实是一个比较重要的东西,我们后面就是通过trainloader把数据传入网络,当然这里的trainloader其实是个变量名,
# 可以随便取,重点是他是由后面的torch.utils.data.DataLoader()定义的,这个东西来源于torch.utils.data模块,
# 网页链接http://pytorch.org/docs/0.3.0/data.html,这个类可见我后面图2
trainloader = torch.utils.data.DataLoader(trainset, batch_size=16,
                                          shuffle=True, num_workers=0)
#num_workers:用多少个子进程加载数据。0表示数据将在主进程中加载(默认: 0)本例中为2。这个值是什么意思呢,就是数据读入的速度到底有多快,你选的用来加载数据的
#子进程越多,那么显然数据读的就越快,这样的话消耗CPU的资源也就越多,所以这个值在自己跑实验的时候,可以自己试一试,既不要让花在加载数据上的时间太多,也不要占用太多电脑资源
# 对于测试集的操作和训练集一样,我就不赘述了
testset = torchvision.datasets.CIFAR10(root='./data', train=False,
                                       download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=10,
                                         shuffle=False, num_workers=0)
# 类别信息也是需要我们给定的
classes = ('plane', 'car', 'bird', 'cat',
           'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

#显示图片与训练无关,此处不写了,可以参考pytorch_tutorial,py里面示例

2. 定义模型

nn.Module是所有神经网络的基类,我们自己定义任何神经网络都要继承nn.Module!类 例:Net(nn.Module):
torch.nn中大多数layer在torch.nn.funtional中都有一个与之对应的函数。二者的区别在于:

  torch. nn.Module中实现layer的都是一个特殊的类,可以去查阅,他们都是以**class xxxx来定义的**,
  会自动提取可学习的参数。 而 nn.functional中的函数,更像是纯函数,由**def function( )定义**,只是进行简单的
  数学运算而已。 说到这里你可能就明白二者的区别了,**functional中的函数是一个确定的不变的运算公式,输入数据产生输
  出就ok**,而深度学习中会有很多权重是在不断更新的,不可能每进行一次forward就用新的权重重新来定义一遍函数来进行
  计算,所以说就会采用类的方式,以确保能在参数发生变化时仍能使用我们之前定好的运算步骤。

所以从这个分析就可以看出什么时候该用nn.Module中的layer了:

   如果模型有可学习的参数,最好使用nn.Module对应的相关layer,否则二者都可以使用,没有什么区别。

比如此例中的Relu其实没有可学习的参数,只是进行一个运算而已,所以使用的就是functional中的relu函数,
而卷积层和全连接层都有可学习的参数,所以用的是nn.Module中的类。
不具备可学习参数的层,将它们用函数代替,这样可以不用放在构造函数中进行初始化。 定义网络模型,主要会用到的就是torch.nn 和 torch.nn.funtional这两个模块,这两个模块值得去细细品味一番

# 首先是调用Variable、 torch.nn、torch.nn.functional
from torch.autograd import Variable  # 这一步还没有显式用到variable,但是现在写在这里也没问题,后面输入数据和标签转换会用到
import torch.nn as nn
import torch.nn.functional as F

class Net(nn.Module):  # 我们定义网络时一般是继承的torch.nn.Module创建新的子类
    def __init__(self):
        super(Net, self).__init__()  # 第二、三行都是python类继承的基本操作,此写法应该是python2.7的继承格式,但python3里写这个好像也可以
        self.conv1 = nn.Conv2d(3, 6, 5)  # 添加第一个卷积层,调用了nn里面的Conv2d(in_channel,out_channel,kernel_size,stride=1,padding=0,dilation=1,groups=1,bias=True)
        self.pool = nn.MaxPool2d(2, 2)  # 最大池化层MaxPool2d(kernel_size,stride=None,padding=0,dilation=1,return_indices=False,ceil_mode=False)
        self.conv2 = nn.Conv2d(6, 16, 5)  # 同样是卷积层
        self.fc1 = nn.Linear(16 * 5 * 5, 120)  # 接着三个全连接层Linear(in_features,out_features,bias=True)
        self.fc2 = nn.Linear(120, 84)#输入C 120通道,输出84
        self.fc2 = nn.Linear(120, 84)#
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):  # 这里定义前向传播的方法,为什么没有定义反向传播的方法呢?这其实就涉及到torch.autograd模块了,
        # 但说实话这部分网络定义的部分还没有用到autograd的知识,所以后面遇到了再讲
        x = self.pool(F.relu(self.conv1(x)))  # F是torch.nn.functional的别名,这里调用了relu函数 F.relu()
        x = self.pool(F.relu(self.conv2(x)))
        x = x.view(-1, 16 * 5 * 5)  # .view( )是一个tensor的方法,使得tensor改变size但是元素的总数是不变的。
        #  第一个参数-1是说这个参数由另一个参数确定, 比如矩阵在元素总数一定的情况下,确定列数就能确定行数。
        #  那么为什么这里只关心列数不关心行数呢,因为马上就要进入全连接层了,而全连接层说白了就是矩阵乘法,
        #  你会发现第一个全连接层的首参数是16*5*5,所以要保证能够相乘,在矩阵乘法之前就要把x调到正确的size
        # 更多的Tensor方法参考Tensor: http://pytorch.org/docs/0.3.0/tensors.html
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x
# 和python中一样,类定义完之后实例化就很简单了,我们这里就实例化了一个net
net = Net()

#判断当前设备是否有GPU,有的话,将模型上传到GPU上跑,后续数据也需要
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(device)
net.to(device)

3. 定义损失函数

pytorch将深度学习中常用的优化方法全部封装在torch.optim之中,所有的优化方法都是继承基类optim.Optimizier
如果想要把模型搬到GPU上跑,就要在定义优化器之前就完成.cuda( )这一步
损失函数是封装在神经网络工具箱torch.nn中的,包含很多损失函数,用时自选

  CLASS torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones, gamma=0.1, last_epoch=-1)

当迭代数epoch达到某个里程碑时,每个参数组的学习率将被gamma衰减。注意,这种衰减可以与此调度程序外部对学习率的其他更改同时发生。当last_epoch=-1时,将初始lr设置为lr。

参数:

optimizer (Optimizer) – 封装的优化器
milestones (list) –迭代epochs指数列表. 列表中的值必须是增长的.
gamma (float) – 学习率衰减的乘数因子。Default: 0.1.
last_epoch (int) – 最后一个迭代epoch的索引. Default: -1.
import torch.optim as optim  # 导入torch.potim模块

criterion = nn.CrossEntropyLoss()  # 同样是用到了神经网络工具箱 nn 中的交叉熵损失函数
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)  # optim模块中的SGD梯度优化方式---随机梯度下降
#在指定的epoch值,如[10,15,25,30]处对学习率进行衰减,lr = lr * gamma
scheduler = optim.lr_scheduler.MultiStepLR(optimizer, milestones=[15,30], gamma= 0.1)

4. 训练(主调函数入口)

Pytorch其实利用的是Autograd模块来进行自动求导,反向传播。Autograd中最核心的类就是Variable了,它封装了Tensor,并几乎支持所有Tensor的操作,这里可以参考官方给的详细解释:http://pytorch.org/tutorials/beginner/blitz/autograd_tutorial.html#sphx-glr-beginner-blitz-autograd-tutorial-py,以上链接详细讲述了variable究竟是怎么能够实现自动求导的,怎么用它来实现反向传播的。

想要计算各个variable的梯度,只需调用根节点的backward方法,Autograd就会自动沿着整个计算图进行反向计算
而在此例子中,根节点就是我们的loss,所以:程序中的**loss.backward()**代码就是在实现反向传播,自动计算所有的梯度。
所以训练部分的代码其实比较简单: running_loss和后面负责打印损失值的那部分并不是必须的,所以关键行不多,总得来说分成三小节

  • 第一节:把最开始放在trainloader里面的数据给转换成variable,然后指定为网络的输入;
  • 第二节:每次循环新开始的时候,要确保梯度归零
  • 第三节:forward+backward,就是调用我们在第三步里面实例化的net()实现前传,loss.backward()实现后传
    每结束一次循环,要确保梯度更新
for epoch in range(30):  # loop over the dataset multiple times 指定训练一共要循环几个epoch

    running_loss = 0.0  # 定义一个变量方便我们对loss进行输出
    for i, data in enumerate(trainloader, 0):  # 这里我们遇到了第一步中出现的trailoader,代码传入数据
        # enumerate是python的内置函数,既获得索引也获得数据
        # get the inputs
        inputs, labels = data  # data是从enumerate返回的data,包含数据和标签信息,分别赋值给inputs和labels
        inputs, labels = inputs.to(device), labels.to(device)   #数据传到GPU上计算
        # wrap them in Variable
        inputs, labels = Variable(inputs), Variable(labels)  # 将数据转换成Variable,第二步里面我们已经引入这个模块
        # 所以这段程序里面就直接使用了,下文会分析
        # zero the parameter gradients
        optimizer.zero_grad()  # 要把梯度重新归零,因为反向传播过程中梯度会累加上一次循环的梯度

        # forward + backward + optimize
        outputs = net(inputs)  # 把数据输进网络net,这个net()在第二步的代码最后一行我们已经定义了
        print(outputs)
        print(labels)
        loss = criterion(outputs, labels)  # 计算损失值,criterion我们在第三步里面定义了
        loss.backward()  # loss进行反向传播,下文详解
        optimizer.step() # 当执行反向传播之后,把优化器的参数进行更新,以便进行下一轮


        # print statistics                   # 这几行代码不是必须的,为了打印出loss方便我们看而已,不影响训练过程
        #running_loss += loss.data[0]  # 从下面一行代码可以看出它是每循环0-1999共两千次才打印一次
        running_loss += loss.item() #loss.item是新版写法,就是把Tensor类型里变成数取出,而loss.data是Tensor类型,无法直接相加
        if i % 500 == 499:  # print every 2000 mini-batches   所以每个2000次之类先用running_loss进行累加
            print('[%d, %5d] loss: %.3f cur_lr: %f' %
                  (epoch + 1, i + 1, running_loss / 500, scheduler.get_last_lr()[0]))  # 然后再除以2000,就得到这两千次的平均损失值
            running_loss = 0.0  # 这一个2000次结束后,就把running_loss归零,下一个2000次继续使用

    scheduler.step()  # step()函数实现lr衰减就已经调用get_lr()一次,get_lr()函数实现lr的衰减,使用get_last_lr()才是单纯访问当前lr的值
print('Finished Training')

5.测试

程序中的torch.max(outputs.data, 1),返回一个tuple(元组),而这里很明显,这个返回的元组的第一个元素是image data,即是最大的值,第二个元素是label,即是最大的值的索引!我们只需要label(最大值的索引),所以就会有 _ , predicted这样的赋值语句,表示忽略第一个返回值,把它赋值给 _,就是舍弃它的意思;
这里说一下,这第二个参数1,看清楚上面的说明是the dimension to reduce!而不是去这个dimension上面找最大,是那个维度不要了 所以这里dim=1,基于我们的a是 4行 x 4列这么一个维度,所以指的是消除列这个维度,按行找最大值和最大值下标返回 4行1列。会发现,返回的结果的第一个元素,即表示最大的值的那部分,其实是一个 size为【4,1】的Tensor,也就是其实它是在按照每行来找最大,所以结果是4行,然后因为只找一个最大值,所以是1列,整个size就是 4行 1列,然后参数dim=1,相当于调用了squeeze(1),这个操作,上面的说明也是这么写的,所以最后就得到结果是一个size为4的vector。
如果我们把上面的示例代码中的第三个参数keepdim=True(默认false)写上,torch.max(a,1,keepdim=True),就是输出的维度和输入保持一致 一般可以用torch.argmax(),和.max()用法相同,但只返回最大值下标,不返回最大的值了

#===========================================相当于打印当前测试情况,可以不写==================================
dataiter = iter(testloader)  # 创建一个python迭代器,读入的是我们第一步里面就已经加载好的testloader
images, labels = dataiter.next()  # 返回一个batch_size的图片,根据第一步的设置,应该是4张

# print images
import cv2
cv2.imshow(torchvision.utils.make_grid(images))  # 展示这四张图片
print('GroundTruth: ',
      ' '.join('%5s' % classes[labels[j]] for j in range(4)))  # python字符串格式化 ' '.join表示用空格来连接后面的字符串,参考python的join()方法

outputs = net(Variable(images))  # 注意这里的images是我们从上面获得的那四张图片,所以首先要转化成variable
_, predicted = torch.max(outputs.data, 1)
# 这个 _ , predicted是python的一种常用的写法,表示后面的函数其实会返回两个值
# 但是我们对第一个值不感兴趣,就写个_在那里,把它赋值给_就好,我们只关心第二个值predicted
# 比如 _ ,a = 1,2 这中赋值语句在python中是可以通过的,你只关心后面的等式中的第二个位置的值是多少
print('Predicted: ', ' '.join('%5s' % classes[predicted[j]] for j in range(4)))  # python的字符串格式化

#====================================================实际的测试统计========================================
correct = 0  # 定义预测正确的图片数,初始化为0
total = 0  # 总共参与测试的图片数,也初始化为0
for data in testloader:  # 循环每一个batch
    images, labels = data
    images, labels = images.to(device), labels.to(device)  # 数据传到GPU上计算
    outputs = net(Variable(images))  # 输入网络进行测试
    _, predicted = torch.max(outputs.data, 1)
    total += labels.size(0)  # 更新测试图片的数量
    correct += (predicted == labels).sum()  # 更新正确分类的图片的数量
#测试总的准确率
print('Accuracy of the network on the 10000 test images: %d %%' % (
        100 * correct / total))  # 最后打印结果

#测试每一类的准确率
class_correct = list(0. for i in range(10))  # 定义一个存储每类中测试正确的个数的 列表,初始化为0
class_total = list(0. for i in range(10))  # 定义一个存储每类中测试总数的个数的 列表,初始化为0
for data in testloader:  # 以一个batch为单位进行循环
    images, labels = data
    images, labels = images.to(device), labels.to(device)  # 数据传到GPU上计算
    outputs = net(Variable(images))
    _, predicted = torch.max(outputs.data, 1)
    c = (predicted == labels).squeeze()
    for i in range(10):  # 因为每个batch都有4张图片,所以还需要一个4的小循环
        label = labels[i]  # 对各个类的进行各自累加
        class_correct[label] += c[i]
        class_total[label] += 1

for i in range(10):
    print('Accuracy of %5s : %2d %%' % (
        classes[i], 100 * class_correct[i] / class_total[i]))
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值