pytorch学习笔记(三)

pytorch学习笔记(三)

1.前言

​ 上次我们简单讲了一个神经网络的构成,这次我们小试牛刀,来试着基于CIFAR-10来训练一个分类器,CIFAR-10共有60000张彩色图像,这些图像是32*32,分为10个类,每类6000张图。这里面有50000张用于训练,构成了5个训练批,每一批10000张图;另外10000用于测试,单独构成一批。测试批的数据里,取自10类中的每一类,每一类随机取1000张。抽剩下的就随机排列组成了训练批。注意一个训练批中的各类图像并不一定数量相同,总的来看训练批,每一类都有5000张图。

​ 我们将按照如下步骤训练一个图像分类器:

  • 用torchvision包载入并标准化CIFAR-10数据集
  • 定义一个卷积网络
  • 定义一个损失函数
  • 用训练集训练网络
  • 用测试集测试网络

2.训练一个分类器

2.1载入并标准化CIFAR-10数据集

先看代码:

import torch
import torchvision
import torchvision.transforms as transforms

transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))])
trainset = torchvision.datasets.CIFAR10(root='./data',train = True,download=True,transform = transform)
trainloader = torch.utils.data.DataLoader(trainset,batch_size=4,shuffle=True,num_workers=2)
testset = torchvision.datasets.CIFAR10(root='./data',train = False,download=True,transform = transform)
testloader = torch.utils.data.DataLoader(testset,batch_size=4,shuffle=False,num_workers=2)

classes = ('Plane','Car','Bird','Cat','Deer','Dog','Frog','Horse','Ship','Truck')

就部分函数进行说明

Compse

这就相当于一个函数的嵌套,例如把f和g组合起来就是f(g(x)).x是g的参数,执行的结果作为f的参数再执行,最后的结果就是组合函数的结果.这里就是把规则化的数据集传给ToTensor函数

torchvision.transforms.Normalize(mean,std)

我们本来的数据集是RGB在[0,1]范围内的图像数据集,我们通过标准化将其转化为[-1,1]的数据集,其中两个参数分别为输入的均值和标准差,1*3对应了RGB三个channels,公式如下:

o u t p u t [ c h a n n e l ] = ( i n p u t [ c h a n n e l ] m e a n [ c h a n n e l ] ) / s t d [ c h a n n e l ]

torchvision.transforms.ToTensor

我们原本的数据集是PILImage格式,我们要将其转换成我们需要的张量tensor格式

torch.utils.data.DataLoader(trainset,batch_size=4,shuffle=True,num_workers=2)

简单解释一下这行代码的意思,读取训练数据集,一次读四个样本,每次读取都会重洗数据集随机读取,有两个子程序用于数据读取

输出:

Downloading http://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz to ./data/cifar-10-python.tar.gz
Files already downloaded and verified

我们来简单看看一些训练图像:

import matplotlib.pyplot as plt
import numpy as np


#functions to show an image

def imshow(img):
    img = img/2+0.5#unnormalize
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg,(1,2,0)))

#get some random training images
dataiter = iter(trainloader)
images,labels = dataiter.__next__()

#show images
imshow(torchvision.utils.make_grid(images))
#print labels
print(''.join('%5s'%classes[labels[j]] for j in range(4)))

输出

这里写图片描述

其中有一点,教程中代码给的dataiter.next(),但我这样得不到图像,而且根据编辑器的暗示用了加下划线的就显示了图像,这点我觉得不是很重要,就没有细究

2.2定义一个卷积神经网络

入门时,我们就用我们之前写的很简单的一个网络,不过在第一层卷积输入时要把1 channel输入换成3 channel

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

class Net(nn.Module):
    def __init__(self):
        super(Net,self).__init__()
        #3 input image channel,6 output channels,5x5 square convolution kernel
        self.conv1=nn.Conv2d(3,6,5)#生成一个1输入6输出的5x5的卷基层
        self.pool = nn.MaxPool2d(2,2)
        self.conv2=nn.Conv2d(6,16,5)#生成一个6输入16输出的5x5的卷基层
        #an affine operation:y=wx+b
        self.fc1=nn.Linear(16*5*5,120)#生成一个16*5*5输入120输出的线性变换层
        self.fc2=nn.Linear(120,84)#生成一个120输入84输出的线性变换层
        self.fc3=nn.Linear(84,10)#生成一个84输入10输出的线性变换层

    def forward(self,x):
        #Max pooling over a (2,2) window
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = x.view(-1,16*5*5)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

net = Net()

​ 你会感觉和我们之前写的代码有所不同,其实这里除了一些小的参数的改变,并没有什么变化,整个步骤还是按照我学习笔记(二)里面讲的来的,不同的其一是把池化函数放进了init里面,在这里顺便讲讲torch.nn和torch.nn.functional的 区别,在建图过程中,往往有两种层,一种如全连接层,卷积层等,当中有Variable,另一种如Pooling层,Relu层等,当中没有Variable。如果所有的层都用nn.functional来定义,那么所有的Variable,如weightsbias等,都需要用户来手动定义,非常不方便。而如果所有的层都换成nn来定义,那么即便是简单的计算都需要建类来做,而这些可以用更为简单的函数来代替的。所以在定义网络的时候,如果层内有Variable,那么用nn定义,反之,则用nn.functional定义。(来源)

​ 其二是少了一个num_flat_features函数,而是直接x.view(-1,16* 5* 5)代替,因为我们当时只是为了看看这个网络的输出,没实际意义,而这里的输入是有他的意义的,我们不要随意改变,所以直接把这些展成一维数组。

​ 其实本质上和我们前面的没什么区别,很好理解的

2.3定义一个损失函数和优化控制

import torch.optim as optim

criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(),lr = 0.001,momentum = 0.9)

这里我们用到的损失函数是Cross-Entropy loss和带动量的随机梯度下降方法更新参数

Cross-Entropy loss

交叉熵损失,在机器学习领域,多分类问题很常见,在很多深度学习模型当中,网络的输出层就是一个softmax层,对于NN分类问题,输出是一个NN维的向量,向量元素介于[0,1]之间,且元素累加和为1(这是softmax性质所决定的); 将softmax层输出向量视为预测类别的概率分布q(x),用真实类别标签构造真实的类别概率分布p(x)(例如,令真实类别概率为1,其余类别概率为0),那么相对熵DKL(P||Q就可以评价预测结果q(x)的好坏了,我们只需要最小化它就好了。 交叉熵等于基于预测概率分布q(x)对符合p(x)分布的字符集进行编码之后的平均字符编码长度,通过最小化交叉熵可以使q(x)逼近真实分布p(x),也就使得预测模型更优。来源

H ( X ) = x p ( x ) l o g 1 q ( x )

其中有一个名词是softmax,我们简单说说这个,我们知道max,假如说我有两个数,a和b,并且a>b,如果取max,那么就直接取a,没有第二种可能但有的时候我不想这样,因为这样会造成分值小的那个饥饿。所以我希望分值大的那一项经常取到,分值小的那一项也偶尔可以取到,那么我用softmax就可以了 现在还是a和b,a>b,如果我们取按照softmax来计算取a和b的概率,那a的softmax值大于b的,所以a会经常取到,而b也会偶尔取到,概率跟它们本来的大小有关。所以说不是max,而是 Soft max 那各自的概率究竟是多少呢,我们下面就来具体看一下,假设我们有一个数组,V,Vi表示V中的第i个元素,那么这个元素的Softmax值就是 这段摘自
S = e V i j e V j

SGD with momentum

关于SGD我们不再多说,什么叫动量的呢,其实在这里我们更好地可以把它理解成一个摩擦系数,用来阻碍梯度的下降速度,使得梯度在下降到最低的时候不要再继续下降,更确切地说,不要再下降的过于迅猛

v = momentum * v - learning_rate * dx

这个代码就很直观的反映了momentum的作用

2.4训练网络

for epoch in range(2):#loop over the dataset multiple times

    running_loss = 0.0
    for i,data in enumerate(trainloader,0):
        #get the input
        inputs,labels = data

        #zero the parameter gradients
        optimizer.zero_grad()

        #forward+backward+optimize
        outputs = net(inputs)
        loss = criterion(outputs,labels)
        loss.backward()
        optimizer.step()

        #print statistics
        running_loss += loss.item()
        if i % 2000 == 1999:#print every 2000 mini-batches
            print('[%d %5d] loss:%.3f'%(epoch+1,i+1,running_loss/2000))
            running_loss = 0.0
print('Finished Training')

​ 这里面的代码都比较容易理解,整个思路就是在训练集中做两次大循环训练,每2000张训练完成后输出一次当前的平均损失值,直到循环结束。

​ 其中enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。使用起来非常便捷。

输出:

[1  2000] loss:1.919
[1  4000] loss:1.886
[1  6000] loss:1.862
[1  8000] loss:1.859
[1 10000] loss:1.846
[1 12000] loss:1.859
[2  2000] loss:1.838
[2  4000] loss:1.822
[2  6000] loss:1.812
[2  8000] loss:1.815
[2 10000] loss:1.825
[2 12000] loss:1.827
Finished Training

结果因电脑而异,本人电脑可能比较水,就没怎么把损失降下来。

2.5用测试集测试我们训练的网络

首先我们来随机展示几张测试集的图片:

dataier = iter(testloader)
images,labels = dataiter.__next__()

#print images
imshow(torchvision.utils.make_grid(images))
print('GroundTruth:',' '.join('%5s'%classes[labels[j]]for j in range(4)))

这里写图片描述

接下来把这些图片放入我们的网络中看会给它们什么标签:

outputs = net(images)
_,predicted = torch.max(outputs,1)
print('Predicted: ',' '.join('%5s'%classes[predicted[j]]for j in range(4)))

torch.max就是输出最可能的那个预测

Predicted:   Deer  Deer Plane Plane

一半对一半错,感觉还不错,那我们来看看整体正确率

correct = 0
total = 0
with torch.no_grad():
    for data in testloader:
        images,labels = data
        outputs = net(images)
        _,predicted = torch.max(outputs.data,1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()
print('Accuracy of the network on the 10000 test images: %d %%'%(100*correct/total))
Accuracy of the network on the 10000 test images: 30 %

感觉不是特别棒,但还是那句话,因电脑而异

再来看看各个标签的正确率

class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))
with torch.no_grad():
    for data in testloader:
        images,labels = data
        outputs = net(images)
        _,predicted = torch.max(outputs,1)
        c = (predicted==labels).squeeze()
        for i in range(4):
            label = labels[i]
            class_correct[label]+=c[i].item()
            class_total[label]+=1
for i in range(10):
    print('Accurary of %5s : %2d %%'%(classes[i],100*class_correct[i]/class_total[i]))
Accurary of Plane : 62 %
Accurary of   Car : 28 %
Accurary of  Bird :  4 %
Accurary of   Cat : 34 %
Accurary of  Deer : 37 %
Accurary of   Dog : 27 %
Accurary of  Frog : 20 %
Accurary of Horse : 50 %
Accurary of  Ship :  2 %
Accurary of Truck : 32 %

可以发现,在某些上预测还不错,但比如鸟和船的预测就没那么棒了,在我的直觉中,我的电脑可能把所有的船都当做飞船了吧

3.小结

这次我们用了一个比较简单的卷积网络训练了一个分类器,效果不是很好,但算是入门了吧,更多的是明白一些函数的用处和其中的一些原理,因为我用的是cpu版的pytorch,所以就不涉及cuda以及gpu的操作了,大家有兴趣的可以自己看看,我也比较建议学一学,这次内容不少,但不难,是把之前的整个串了起来

猜你喜欢

转载自blog.csdn.net/qq_35700335/article/details/81668091