pytorch学习(一)利用pytorch训练一个最简单的分类器-------(基于CIFAR10数据集)的句句讲解

刚学pytorch一个月,利用这个分类器学习pytorch的如何运用,由于学习时间较短,可能有所不足,望大家进行指正
转载请注明本文连接https://blog.csdn.net/sinat_42239797/article/details/90373471
卷积核的设置及使用[https://blog.csdn.net/sinat_42239797/article/details/90646935
个人数据集的处理及加载[https://blog.csdn.net/sinat_42239797/article/details/90641659

训练一个最简单分类网络分为以下几个步骤:
1数据的加载及预处理
2网络模型的设置
3.定义损失函数及优化器
4.用训练集训练网络
5.用测试集测试网络(查看网络的性能)

1.数据的加载及预处理

1.加载数据集(训练集和测试集)
2.扩充数据集防止过拟合

#加载使用的各种包
import torchvision as tv
import torch as t
import torchvision.transforms as transforms
#扩充数据集防止过拟合
from torchvision.transforms import ToPILImage
show = ToPILImage() #可以把Tensor转换成Image,方便可视化
transform = transforms.Compose([  #transforms.Compose就是将对图像处理的方法集中起来
    transforms.RandomHorizontalFlip(),#水平翻转
    transforms.RandomCrop((32, 32), padding=4),
    transforms.ToTensor(),#转为Tensor
    #在做数据归一化之前必须要把PIL Image转成Tensor,而其他resize或crop操作则不需要。
     transforms.Normalize((0.5, 0.5 ,0.5), (0.5, 0.5, 0.5)),#归一化   
    ])
#训练集
#训练集数据的下载
trainset = tv.datasets.CIFAR10(
root='/home/cy/data/',#设置数据集的根目录
    train=True,#训练集所以是True
    download=True,
    transform=transform
)
#训练集数据的加载方式
trainloader = t.utils.data.DataLoader(
    trainset,#设置为训练集
    batch_size=4,#设置每个batch有4个样本数据
    shuffle=True,#设置对每个epoch将数据集打乱
    num_workers=2#设置使用2个子进程用来加载数据
)
#测试集
#测试集下载
testset = tv.datasets.CIFAR10(
'/home/cy/data/',
train=False,
download=True,
transform=transform#用了之前定义的transform
)
#测试集加载方式
testloader = t.utils.data.DataLoader(
    testset,
    batch_size=4,
    shuffle=False,
    num_workers=2)
#数据集10个类的定义
classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

torchvision包,有四个模块:

  1. torchvision.datasets模块**:实现了常用的一些深度学习的相关的图像数据集的加载功能,比如cifar10、Imagenet、Mnist等
  2. torchvision.transforms模块:是pytorch中的图像预处理包,包含了各种图像处理的包
  3. torchvision.models模块:这个包中包含alexnet、densenet、inception、resnet、squeezenet、vgg等常用的网络结构,并且提供了预训练模型,可以通过简单调用来读取网络结构和预训练模型
  4. torchvision.utils是数据包
    细节见torchvision的官方文档链接:http://pytorch.org/docs/0.3.0/torchvision/index.html

首先加载数据集
数据加载在PyTorch中非常容易,因为pytorch的torchvision包中自带

  1. MNIST,Imagenet-12,CIFAR等数据集,所有的数据集都是torch.utils.data.Dataset的子类,都包含
    __ len _ (获取数据集长度)和 _ getItem _ _ (获取数据集中每一项)两个子方法。

其次是扩充数据集防止过拟合**
1. transform = transforms.Compose
就是把多个变换组合在一起集中进行,可以集中放到这个函数下

2. transforms.RandomHorizontalFlip()
#水平翻转,这里默认的翻转概率是0.5,就是一张图片翻转或不翻转的概率是0.5

3.transforms.RandomCrop((32, 32), padding=4),
对图片进行随机裁剪,裁剪的大小是32*32的,填充是4
对这个裁剪进行一下简单介绍

transforms.RandomCrop(size, padding=None, pad_if_needed=False, fill=0, padding_mode=‘constant’)

参数:size- (sequence or int),若为sequence,则为(h,w),若为int,则(size,size)
padding-(sequence or int, optional),此参数是设置填充多少个pixel。
当为int时,图像上下左右均填充int个,例如padding=4,则上下左右均填充4个pixel,若为32x32,则会变成40x40。
当为sequence时,若有2个数,则第一个数表示左右扩充多少,第二个数表示上下的。当有4个数时,则为左,上,右,下。
fill- (int or tuple) 填充的值是什么(仅当填充模式为constant时有用)。int时,各通道均填充该值,当长度为3的tuple时,表示RGB通道需要填充的值。

4.transforms.Normalize((0.5, 0.5 ,0.5), (0.5, 0.5, 0.5))
归一化操作,这里有两个参数一个是均值,一个是方差,由于是RGB型的所以一个参有三个值,这里面的参数的大小是可调的,调节的公式是:
class torchvision.transforms.Normalize(mean, std),
Normalized_image=(image-mean)/std
channel=(channel-mean)/std(因为transforms.ToTensor()已经把数据处理成[0,1],那么(x-0.5)/0.5就是[-1.0, 1.0])这样一来,我们的数据中的每个值就变成了[-1,1]的数了。

最后是训练集和测试集的下载及加载操作:

1. 训练集的下载

trainset = tv.datasets.CIFAR10(
root='/home/cy/data/',#设置数据集的根目录
    train=True,#训练集所以是True
    download=True,#因为本地没有,需要进行下载
    transform=transform#一个是原来的transform,一个是新定义的变量transform
)

测试集的下载

testset = tv.datasets.CIFAR10(
'/home/cy/data/',
train=False,#因为是测试集而不是训练集所以不是Ture
download=True,
transform=transform#用了之前定义的transform
)

2.训练集数据的加载

trainloader = t.utils.data.DataLoader(
    trainset,#训练集的调用
    batch_size=4,#设置每个batch有4个样本数据
    shuffle=True,#设置对每个epoch将数据集打乱,就是进行一个洗牌操作,防止过拟合现象的产生
    num_workers=2#设置使用2个子进程用来加载数据,这样可以更快的加载完数据
)

测试集数据的加载

  testloader = t.utils.data.DataLoader(
    testset,#前面测试集调用
    batch_size=4,
    shuffle=False,#**由于是测试集,所以不用进行洗牌操作**
    num_workers=2)

2网络模型的设置主要是网络结构的设置:

本次我们使用的是一个类网络的简单设置
卷积层、池化层、卷积层、全连接层、前后向传播的顺序

#定义网络
import torch.nn as nn
import torch.nn.functional as F
#构建自己的网络就是定义torch.nn.Module的一个子类,这时需要重写初始化函数__init__()和前向过程forward()。
#__init__()函数中需要调用父类的初始化函数;forward()函数用于构建网络从输入到输出的过程。
class Net(nn.Module):
    def __init__(self):
         #nn.Module子类的函数必须在构造函数中执行父类的构造函数
        super(Net, self).__init__()#调用父类的初始化函数 #super是比较两个类谁更高,谁是父类,然后执行父类的函数
        self.conv1 = nn.Conv2d(3, 6, 5) #卷积层3表示输入图片为RGB型,6表示输出通道数,5表示卷积核为5*5
        self.pool = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 16, 5)    
        self.fc1 = nn.Linear(16*5*5, 120) # 全连接层输入是16*5*5的,输出是120的
        self.fc2 = nn.Linear(120, 84)  
        self.fc3 = nn.Linear(84, 10) # 定义输出为10,因为10各类    
    def forward(self, x):
      	 x =self.pool(F.relu(self.conv1(x)))  # 输入x经过卷积conv1之后,经过激活函数ReLU,然后更新到x。
      	 x =self.pool(F.relu(self.conv2(x))) 
      	 x = x.view(-1, 16*5*5) #用来将x展平成16 * 5 * 5,然后就可以进行下面的全连接层操作
      	 x = F.relu(self.fc1(x))# 输入x经过全连接1,再经过ReLU激活函数,然后更新x
      	 x = F.relu(self.fc2(x))# 输入x经过全连接2,再经过ReLU激活函数,然后更新x
      	 x =self.fc3(x) # 输入x经过全连接3,然后更新x

    return x

net = Net()  

这块最主要的就是定义网络的参数和正向传播的顺序,这个怎么设置以后有机会再进行详细的描述,这里就不多介绍了
我的另一个博客进行了介绍https://blog.csdn.net/sinat_42239797/article/details/90646935

self.conv1 = nn.Conv2d(3, 6, 5)

****问题一:为什么是cove2d?
cove1d:用于文本数据,只对宽度进行卷积,对高度不进行卷积
cove2d:用于图像数据,对宽度和高度都进行卷积
****问题二:为什么卷积核大小5x5写一个5?
Conv2d(输入通道数, 输出通道数, kernel_size(长和宽)),当卷积核为方形时,只写一个就可以
卷积核不是方形时,长和宽都要写:

self.conv1 = nn.Conv2d(3, 6, (5,3))

下面附上cove2d的源代码部分作为参考

classtorch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)[source]


Parameters:

in_channels (int) – Number of channels in the input image
out_channels (int) – Number of channels produced by the convolution
kernel_size (int or tuple) – Size of the convolving kernel
stride (int or tuple, optional) – Stride of the convolution. Default: 1
padding (int or tuple, optional) – Zero-padding added to both sides of the input. Default: 0
dilation (int or tuple, optional) – Spacing between kernel elements. Default: 1
groups (int, optional) – Number of blocked connections from input channels to output channels. Default: 1
bias (bool, optional) – If True, adds a learnable bias to the output. Default: True

3.定义损失函数及优化器

对我们的网络设置它的损失函数和优化函数
#定义损失函数和优化器(loss和optimizer)
#如果想要在训练过程中修改学习率,需要使用新的参数构建新的optimizer,我们用的很简单并没有进行学习率的更改,关于不断在训练中更改学习率的操作,以后学习再写

from torch import optim
criterion = nn.CrossEntropyLoss()#交叉熵损失函数
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)#SGD(螺旋)随机梯度下降法,

torch.optim中实现了深度学习中大多数的优化方法
我们这里采用的是交叉熵损失函数和SGD梯度下降法,直接进行代用即可

关于交叉熵损失函数,我看的是http://blog.csdn.net/jasonzzj/article/details/52017438大家可以作为参考
关于SGD随机梯度下降法,大家可以参考https://blog.csdn.net/woxincd/article/details/7040944

4.用训练集训练网络

1.设置训练的次数
2.训练操作的执行顺序

from torch.autograd import Variable
for epoch in range(10):#设置训练的迭代次数
running_loss = 0.0
# 在测试集中迭代数据
for i, data in enumerate(trainloader, 0):#enumerate枚举数据并从下标0开始
    
    #输入数据
     # 读取数据的数据内容和标签
    inputs, labels = data
    inputs, labels = Variable(inputs), Variable(labels)
    
    #梯度清零,也就是把loss关于weight的导数变成0.
    optimizer.zero_grad()
    
    #forward+backward
     # 得到网络的输出
    outputs = net(inputs)
   

    # 计算损失值,将输出的outputs和原来导入的labels作为loss函数的输入就可以得到损失了:
    loss = criterion(outputs, labels)# output 和 labels的交叉熵损失
   # 计算得到loss后就要回传损失。
   
    
    loss.backward()
    #loss.backward(),有时候,我们并不想求所有Variable的梯度。那就要考虑如何在Backward过程中排除子图(ie.排除没必要的梯度计算)。 
    #可以通过Variable的两个参数(requires_grad和volatile)与电路的连接有关啊这样记住吧哈哈哈
    
        #更新参数
   
    #回传损失过程中会计算梯度,然后需要根据这些梯度更新参数,optimizer.step()就是用来更新参数的。optimizer.step()后,
    #你就可以从optimizer.param_groups[0][‘params’]里面看到各个层的梯度和权值信息。
    optimizer.step()#利用计算的得到的梯度对参数进行更新
    
    #打印log信息
    running_loss += loss.item()# #用于从tensor中获取python数字
    if i % 2000 == 1999:#每2000个batch打印一次训练状态
        print('[%d, %5d] loss: %.3f' \
             % (epoch+1, i+1, running_loss / 2000))
      
        running_loss = 0.0
print('Finished Training')
   #此处仅仅训练了两个epoch(遍历完一遍数据集),我们来看看网络有没有效果   

下面对我认为重要的进行简单的介绍

   1. for epoch in range(10):#设置训练的迭代次数

这里我们设置的迭代次数是10

2. running_loss = 0.0

初始化的损失是0,也就是每个epoch都要梯度清零

3. for i, data in enumerate(trainloader, 0):#enumerate枚举数据并从下标0开始

这里的i表示的是batch的个数,从下标0开始进行

4.  inputs, labels = data
    inputs, labels = Variable(inputs), Variable(labels)

首先进行数据的加载,由于前面在数据加载模块是我们已经知道我们的所有的数据集数据来自于torchvision包的CIFA10,它的data中包含了图片的数据及标签所以这里我们将data赋值给两个变量
其次为何要进行变量的转换?
因为Pytorch的Variable型自带自动求导机制,它能根据输入和前向传播自动构建计算图,并进行反向传播。不需要进行反向传播计算过程的设置

5.optimizer.zero_grad()

为何这里要进行梯度清零操作?
由于我们这里是对一个batch进行的操作,每个batch的操作应该是互不影响的
在进行训练时,根据每个batch累积的梯度,神经网络的权重是可以调整的,在每个新的batch内梯度必须重新设置为零

6. outputs = net(inputs)

将图片的信息输入到网络中,网络模型会预测出预测值(预测出的标签)

7. loss = criterion(outputs, labels)

将预测值和实际值(实际的标签)作为损失函数的输入得到损失

8. loss.backward()
    optimizer.step()#利用计算的得到的梯度对参数进行更新 

对损失执行反向传播,自动求出所有参数的梯度
根据传播的梯度调用optimizer.step()来更新参数

9. if i % 2000 == 1999:#每2000个batch打印一次训练状态

这里第一个损失的打印是从2000开始的

[1,  2000] loss: 2.195
[1,  4000] loss: 1.858
[1,  6000] loss: 1.693
[1,  8000] loss: 1.601
[1, 10000] loss: 1.508
[1, 12000] loss: 1.479
[2,  2000] loss: 1.410
[2,  4000] loss: 1.387
[2,  6000] loss: 1.374
[2,  8000] loss: 1.323
[2, 10000] loss: 1.324
[2, 12000] loss: 1.281
[3,  2000] loss: 1.223
[3,  4000] loss: 1.222
[3,  6000] loss: 1.207
[3,  8000] loss: 1.208
[3, 10000] loss: 1.190
[3, 12000] loss: 1.173
[4,  2000] loss: 1.105
[4,  4000] loss: 1.123
[4,  6000] loss: 1.116
[4,  8000] loss: 1.111
[4, 10000] loss: 1.108
[4, 12000] loss: 1.092
[5,  2000] loss: 1.026
[5,  4000] loss: 1.053
[5,  6000] loss: 1.029
[5,  8000] loss: 1.063
[5, 10000] loss: 1.067
[5, 12000] loss: 1.035
[6,  2000] loss: 0.968
[6,  4000] loss: 0.967
[6,  6000] loss: 0.993
[6,  8000] loss: 0.996
[6, 10000] loss: 1.010
[6, 12000] loss: 1.004
[7,  2000] loss: 0.901
[7,  4000] loss: 0.944
[7,  6000] loss: 0.942
[7,  8000] loss: 0.935
[7, 10000] loss: 0.962
[7, 12000] loss: 0.960
[8,  2000] loss: 0.856
[8,  4000] loss: 0.876
[8,  6000] loss: 0.894
[8,  8000] loss: 0.903
[8, 10000] loss: 0.910
[8, 12000] loss: 0.924
[9,  2000] loss: 0.820
[9,  4000] loss: 0.864
[9,  6000] loss: 0.871
[9,  8000] loss: 0.879
[9, 10000] loss: 0.875
[9, 12000] loss: 0.885
[10,  2000] loss: 0.785
[10,  4000] loss: 0.793
[10,  6000] loss: 0.829
[10,  8000] loss: 0.849
[10, 10000] loss: 0.859
[10, 12000] loss: 0.866
Finished Training

可以看到梯度在下降

5.用测试集测试网络

分三步进行测试
第一是进行局部测试查看测试的效果
第二是进行整个数据集的准确率的测试
第三是测试每一类的准确率
**

- 第一:进行局部测试

images, labels =testloader
print('GroundTruth: ', ' '.join('%5s' % classes[labels[j]] for j in range(4)))

我们在测试集中随机取一个batch输出其真实的图片及标签

GroundTruth:cat   ship   ship  plane 

这是真实图片标签的输出

outputs = net(images)

以上面一个batch的图片的数据作为网络的输入,得到网络模型的预测类的结果

_, predicted = t.max(outputs.data, 1)
print('预测结果: ', ' '.join('%5s' % classes[predicted[j]]for j in range(4)))

将得到的网络的预测结果输入到torch.max函数中,取得预测出的最大结果

预测结果:cat ship ship ship

我们可以看到网络学到了东西,预测结果为75%

这里的max()函数简单的介绍一下:

   max(a)#用于一维数据,求出最大值
   max(a,0)#计算出数据中一列的最大值,并输出最大值所在的列号
   max(a,1)#计算数据中一行的最大值,并输出最大值所在的行号

. 第二:测试整个网络的准确率

correct = 0#预测正确的图片数
total = 0#总共的图片数

初始化一些变量,作为计数器

with t.no_grad(): 

由于在测试时,我们不进行梯度计算,所以这里定义不进行梯度计算,节约空间

 for data in testloader:
        #读取数据的数据内容及标签
        images, labels = data
        #得到网络的输出
        outputs = net(images)
   # 得到预测值
        _, predicted = t.max(outputs.data, 1)

这里和前面的局部测试一样,这里就不进行描述了

total += labels.size(0)
    # 累积计算预测正确的数据集的大小
        correct += (predicted == labels).sum() # 两个一维张量逐行对比,相同的行记为1,不同的行记为0,再利用sum(),求总和,得到相同的个数。
print('Accuracy of the network on the 10000 test images: %d %%' % (
    100 * correct / total))

累计计算正确数据的大小,返回准确率

Accuracy of the network on the 10000 test images: 62 %

可以看到准确率为62%,比我们随机猜测的准确率10%(10类)要好得多,证明我们学到了东西
. 第三:测试整个数据集每一类的准确率

class_correct =list(0.for i in range(10))#10是类别的个数
class_total =list(0.for i in range(10))
with t.no_grad(): #设置在进行forward时不计算梯度
    for data in testloader:
        images, labels = data
        outputs = net(images)
        _, predicted = t.max(outputs, 1)
        c = (predicted == labels).squeeze()#每一个batch的(predicted==labels)
        for i in range(4):#4是每一个batch的个数
            label = labels[i]
            class_correct[label] += c[i].item()
            class_total[label] +=1
    for i in range(10):
        print('Accuracy of %5s : %2d %%'% (classes[i], 100* class_correct[i] / class_total[i]))

这里的

class_correct =list(0.for i in range(10))#10是类别的个数
class_total =list(0.for i in range(10))

这里的初始化是一个列表,因为我们要测试的是每一类的准确率,每一类都要进行计算

本人纯属小白,如有不足之处望批评指正!!!

你可能感兴趣的:(pytorch)