《深度学习之pytorch实战计算机视觉》第6章 PyTorch基础(代码可跑通)

上一篇文章《深度学习之pytorch实战计算机视觉》第5章 Python基础讲了Python基础。接下来看看第6章 PyTorch基础。

目录

6.1 PyTorch中的Tensor

6.1.1 Tensor的数据类型

6.1.2 Tensor的运算

6.1.3 搭建一个简易神经网络

6.2 自动梯度

6.2.1 torch.autograd和Variable

6.2.2 自定义传播函数

6.3 模型搭建和参数优化

6.3.1 PyTorch之torch.nn

6.3.2 PyTorch之torch.optim

6.4 实战手写数字识别

6.4.1 torch和torchvision

6.4.2 PyTorch之torch.transforms

6.4.3 数据预览和数据装载

6.4.4 模型搭建和参数优化

PyTorch 是美国互联网巨头Facebook在深度学习框架 Torch 的基础上使用 Python 重写的一个全新的深度学习框架,它更像 NumPy 的替代产物,不仅继承了 NumPy 的众多优点,还支持GPUs计算,在计算效率上要比NumPy有更明显的优势;不仅如此,PyTorch还有许多高级功能,比如拥有丰富的API,可以快速完成深度神经网络模型的搭建和训练。所以PyTorch一经发布,便受到了众多开发人员和科研人员的追捧与喜爱,成为AI从业者的重要工具之一。

6.1 PyTorch中的Tensor

6.1.1 Tensor的数据类型

  • torch.FloatTensor:生成数据类型为浮点数的张量,入参为列表或者维度值
  • torch.IntTensor:生成数据类型为整型的张量,入参为列表或者维度值
  • torch.rand:随机生成数据类型为浮点型的张量,浮点数据在0-1区间均匀分布,入参为维度值
  • torch.randn:随机生成数据类型为浮点型的张量,浮点数据满足N(0,1)的正态分布,入参为维度值
  • torch.range:生成数据类型为浮点数且自定义起始范围和结束范围的张量,入参为起始点、结束点和步长
  • torch.zeros:生成浮点型且维度指定的张量,元素值全部为0
import torch

a = torch.range(1,20,1)
a.size()  #1 2 ... 19 20

输出:

torch.Size([20])

6.1.2 Tensor的运算

  • torch.abs() 求绝对值,入参是张量数据类型的变量
  • torch.add() 求和,入参都是张量,或者一个张量一个标量
  • torch.clamp() 裁剪,入参为张量、裁剪的上边界和下边界。每个元素都要和上边界和下边界进行比较,如果元素值小于下边界的值,则该元素被重写成下边界的值;元素值大于上边界同理。
  • torch.div() 求商,入参都是张量,或者一个张量一个标量
  • torch.mul() 求积,入参都是张量,或者一个张量一个标量
  • torch.pow() 求幂,入参都是张量,或者一个张量一个标量
  • torch.mm() 矩阵的乘法,入参的维度要满足矩阵乘法
  • torch.mv() 矩阵和向量的乘法,第一个参数是矩阵,第二个参数是向量

6.1.3 搭建一个简易神经网络

#导包
import torch

batch_n = 100   #一个批次输入的数据数量
hidden_layer = 100   #隐藏层之后保留的数据特征个数
input_data = 1000  #每个数据的数据特征
output_data = 10   #分类结果值

#从输入层到隐藏层、从隐藏层到输出层的权重初始化
x = torch.randn(batch_n,input_data)
y = torch.randn(batch_n,output_data)

w1 = torch.randn(input_data,hidden_layer)
w2 = torch.randn(hidden_layer,output_data)

epoch_n = 20
learning_rate = 1e-6

#对参数优化,能够显示的写出求导公式
for epoch in range(epoch_n):
    h1 = x.mm(w1)  #100*1000
    h1 = h1.clamp(min=0)   #将小于0的值全部赋值为0,就相当于加了一个relu激活函数
    y_pred = h1.mm(w2)  #100*10
    
    loss = (y_pred - y).pow(2).sum()
    print("Epoch:{},Loss:{:.4f}".format(epoch,loss))
    
    grad_y_pred = 2*(y_pred - y)
    grad_w2 = h1.t().mm(grad_y_pred)
    
    grad_h = grad_y_pred.clone()
    grad_h = grad_h.mm(w2.t())
    grad_h.clamp(min=0)
    grad_w1 = x.t().mm(grad_h)
    
    w1 -= learning_rate*grad_w1
    w2 -= learning_rate*grad_w2

输出:

Epoch:0,Loss:61389692.0000
Epoch:1,Loss:160758480.0000
Epoch:2,Loss:545299456.0000
Epoch:3,Loss:589745280.0000
Epoch:4,Loss:5771187.5000
Epoch:5,Loss:3852597.2500
Epoch:6,Loss:2813506.2500
Epoch:7,Loss:2202821.2500
Epoch:8,Loss:1816049.7500
Epoch:9,Loss:1553005.6250
Epoch:10,Loss:1361722.5000
Epoch:11,Loss:1215317.7500
Epoch:12,Loss:1097719.7500
Epoch:13,Loss:999968.8750
Epoch:14,Loss:916233.5625
Epoch:15,Loss:843229.7500
Epoch:16,Loss:778861.1875
Epoch:17,Loss:721594.6875
Epoch:18,Loss:670533.8125
Epoch:19,Loss:624570.6250

6.2 自动梯度

通过torch.autograd包,使模型参数自动计算在优化过程中需要的梯度值,降低了后向传播代码的复杂度。

6.2.1 torch.autograd和Variable

功能是完成神经网络后向传播中的链式求导。自动梯度功能的过程大致为:先通过输入的Tensor 数据类型的变量在神经网络的前向传播过程中生成一张计算图,然后根据这个计算图和输出结果准确计算出每个参数需要更新的梯度,并通过完成后向传播完成对参数的梯度更新。

#自动梯度例子
# import torch
from torch.autograd import Variable
batch_n = 100 
hidden_layer = 100   #隐藏层之后保留的数据特征个数
input_data = 1000  #每个数据的数据特征
output_data = 10   #分类结果值

#x和y不保留梯度值,因为这两个变量不是我们模型需要优化的参数
x = Variable(torch.randn(batch_n,input_data),requires_grad = False) 
y = Variable(torch.randn(batch_n,output_data),requires_grad = False)

w1 = Variable(torch.randn(input_data,hidden_layer),requires_grad = True)
w2 = Variable(torch.randn(hidden_layer,output_data),requires_grad = True)

epoch_n = 20
learning_rate = 1e-6

for epoch in range(epoch_n):
    y_pred = x.mm(w1).clamp(min=0).mm(w2)
    loss = (y_pred - y).pow(2).sum()
    print("Epoch:{},Loss:{:.4f}".format(epoch,loss.item())) #loss.data[0]报错
    
    loss.backward()
    
    w1.data -= learning_rate*w1.grad.data
    w2.data -= learning_rate*w2.grad.data
    
    w1.grad.data.zero_()
    w2.grad.data.zero_()

输出:

Epoch:0,Loss:38478596.0000
Epoch:1,Loss:64376108.0000
Epoch:2,Loss:226000160.0000
Epoch:3,Loss:615954176.0000
Epoch:4,Loss:260315120.0000
Epoch:5,Loss:11254304.0000
Epoch:6,Loss:6599379.5000
Epoch:7,Loss:4310198.0000
Epoch:8,Loss:3030261.0000
Epoch:9,Loss:2257996.2500
Epoch:10,Loss:1765981.0000
Epoch:11,Loss:1437985.2500
Epoch:12,Loss:1210078.5000
Epoch:13,Loss:1045272.9375
Epoch:14,Loss:921474.1875
Epoch:15,Loss:824932.1250
Epoch:16,Loss:747264.5625
Epoch:17,Loss:682944.3125
Epoch:18,Loss:628346.6875
Epoch:19,Loss:581212.0625

6.2.2 自定义传播函数

除了使用自动梯度函数,还可以通过构建一个继承了torch.nn.Module的类,来完成前向传播和后向传播函数的重写。

# import torch
from torch.autograd import Variable
batch_n = 100 
hidden_layer = 100   #隐藏层之后保留的数据特征个数
input_data = 1000  #每个数据的数据特征
output_data = 10   #分类结果值

#定义类
class Model(torch.nn.Module):
    def __init__(self):
        super(Model,self).__init__()
        
    def forward(self,input,w1,w2):
        x = torch.mm(input,w1)
        x = torch.clamp(x,min=0)
        x = torch.mm(x,w2)
        return x
    
    def backward(self):
        pass
    
#调用类
model = Model()

#模型的训练和参数优化
x = Variable(torch.randn(batch_n,input_data),requires_grad = False) 
y = Variable(torch.randn(batch_n,output_data),requires_grad = False)

w1 = Variable(torch.randn(input_data,hidden_layer),requires_grad = True)
w2 = Variable(torch.randn(hidden_layer,output_data),requires_grad = True)

epoch_n = 20
learning_rate = 1e-6

for epoch in range(epoch_n):
    y_pred = model(x,w1,w2)
    loss = (y_pred - y).pow(2).sum()
    print("Epoch:{},Loss:{:.4f}".format(epoch,loss.item())) #loss.data[0]报错
    
    loss.backward()
    
    w1.data -= learning_rate*w1.grad.data
    w2.data -= learning_rate*w2.grad.data
    
    w1.grad.data.zero_()
    w2.grad.data.zero_()

输出:

Epoch:0,Loss:43075812.0000
Epoch:1,Loss:97484256.0000
Epoch:2,Loss:410335744.0000
Epoch:3,Loss:923601728.0000
Epoch:4,Loss:78289096.0000
Epoch:5,Loss:24612662.0000
Epoch:6,Loss:14282344.0000
Epoch:7,Loss:9647261.0000
Epoch:8,Loss:7035086.0000
Epoch:9,Loss:5384709.5000
Epoch:10,Loss:4264662.5000
Epoch:11,Loss:3462480.7500
Epoch:12,Loss:2866421.7500
Epoch:13,Loss:2410418.7500
Epoch:14,Loss:2053546.5000
Epoch:15,Loss:1768931.8750
Epoch:16,Loss:1538703.2500
Epoch:17,Loss:1349771.8750
Epoch:18,Loss:1193112.2500
Epoch:19,Loss:1062226.8750

6.3 模型搭建和参数优化

接下来看搭建复杂的神经网络模型,同时让参数的优化方法趋于高效。

如同使用PyTorch中的自动梯度方法一样,在搭建复杂的神经网络模型的时候,我们也可以使用PyTorch 中己定义的类和方法,这些类和方法覆盖了神经网络中的线性变换激活函数卷积层全连接层池化层等常用神经网络结构的实现

6.3.1 PyTorch之torch.nn

PyTorch 中的 torch.nn 包提供了很多与实现神经网络中的具体功能相关的类,这些类涵盖了深度神经网络模型在搭建和参数优化过程中的常用内容 ,比如神经网络中的卷积层、池化层、全连接层这类层次构造的方法、防止过拟合的参数归一化方法、 Dropout方法,还有激活函数部分的线性激活函数、非线性激活函数相关的方法,等等。在学会使用PyTorch的torch.nn进行神经网络模型的搭建和参数优化后,我们就会发现实现一个神经网络应用并没有我们想象中那么难。

下面使用torch.nn包来简化之前的代码:

# import torch
from torch.autograd import Variable
batch_n = 100 
hidden_layer = 100   #隐藏层之后保留的数据特征个数
input_data = 1000  #每个数据的数据特征
output_data = 10   #分类结果值

x = Variable(torch.randn(batch_n,input_data),requires_grad = False) 
y = Variable(torch.randn(batch_n,output_data),requires_grad = False)

models = torch.nn.Sequential(
    torch.nn.Linear(input_data,hidden_layer),
    torch.nn.ReLU(),
    torch.nn.Linear(hidden_layer,output_data)
)

具体看看torch.nn中常用的类。

(1) torch.nn.Sequential: torch.nn.Sequential类是torch.nn 中的一种序列容器,通过在容器中嵌套各种实现神经网络中具体功能相关的类,来完成对神经网络模型的搭建,最主要的是参数会按照定义好的序列自动传递下去。我们可以将嵌套在容器中的各个部分看作各种不同的模块,这些模块可以自由组合。

模块的加入一般有两种方式:一种是在以上代码中使用的直接嵌套,另一种是以 orderdict 有序字典的方式进行传入。这两种方式的唯一区别是,使用后者搭建的模型的每个模块都有我们自定义的名字,而前者默认使用从零开始的数字序列作为每个模块的名字。下面通过示例来直观地看一下使用这两种方式搭建的模型之间的区别。

方法一,直接使用嵌套搭建模型:

#直接使用嵌套搭建的模型代码:
hidden_layer = 100   #隐藏层之后保留的数据特征个数
input_data = 1000  #每个数据的数据特征
output_data = 10   #分类结果值

models = torch.nn.Sequential(
    torch.nn.Linear(input_data,hidden_layer),
    torch.nn.ReLU(),
    torch.nn.Linear(hidden_layer,output_data)
)
print(models)

打印输出:

Sequential(
  (0): Linear(in_features=1000, out_features=100, bias=True)
  (1): ReLU()
  (2): Linear(in_features=100, out_features=10, bias=True)
)

方法二,使用orderdict有序字典搭建模型:

#使用orderdict有序字典
hidden_layer = 100   #隐藏层之后保留的数据特征个数
input_data = 1000  #每个数据的数据特征
output_data = 10   #分类结果值

from collections import OrderedDict
models2 = torch.nn.Sequential(OrderedDict([
    ("Line1",torch.nn.Linear(input_data,hidden_layer)),
    ("ReLU1",torch.nn.ReLU()),
    ("Line2",torch.nn.Linear(hidden_layer,output_data))])
)
print(models2)

输出:

Sequential(
  (Line1): Linear(in_features=1000, out_features=100, bias=True)
  (ReLU1): ReLU()
  (Line2): Linear(in_features=100, out_features=10, bias=True)
)

对两种方法进行比较,发现对模块使用自定义的名称可以更便捷地找到响应的模块进行操作。

(2) torch.nn.Linear: torch.nn.Linear 类用于定义模型的线性层 ,即完成前面提到的不同的层之间的线性变换。torch.nn.Linear 类接收的参数有3个,分别是输入特征数、输出特征数和是否使用偏置,设置是否使用偏置的参数是一个布尔值,默认为 True ,即使用偏置。在实际使用的过程中,我们只需将输入的特征数和输出的特征数传递给torch.nn.Linear类,就会自动生成对应维度的权重参数和偏置,对于自动生成的权重参数和偏置,我们的模型默认使用了一种比之前的简单随机方式更好的参数初始化方法。

根据我们搭建模型的输入、输出和层次结构需求,它的输入是在一个批次中包含100个特征数为1000的数据,最后得到100个特征数为10输出数据,中间需要经过两次线性变换,所以要使用两个线性层,两个线性层的代码分别是 torch.nn.Linear(input_data,hidden_layer)和 torch.nn.Linear(hidden_layer, output_data)。替代了之前使用矩阵乘法方式的实现,代码更精炼、简洁。

(3) torch.nn.ReLU: torch.nn.ReLU 类属于非线性激活分类,在定义时默认不需要传入参数。当然,在 torch.nn 中还有许多非线性激活函数类可供选择,比如之前讲到的PReLULeakyRuLUTanhSigmoidSoftmax

在掌握 torch.nn.Sequential、torch.nn.Linear和torch.nn.ReLU的使用方法后,快速搭建更复杂的多层神经网络模型变为可能,而且在整个模型的搭建过程中不需要对在模型中使用到的权重参数和偏置进行任何定义和初始化说明, 因为参数已经完成了自动生成。

#接下来对已经搭建好的模型进行训练并对参数进行优化,代码如下:
epoch_n = 10000   #增加训练次数
learning_rate = 1e-4  #增加学习速率
loss_fn = torch.nn.MSELoss()

torch.nn中的损失函数

(1) torch.nn.MSELoss: torch.nn.MSELoss 类使用均方误差函数对损失值进行计算,在定义类的对象时不用传入任何参数,但在使用实例时需要输入两个维度一样的参数方可进行计算。示例如下:

# import torch
# from torch.autograd import Variable
loss_f1 = torch.nn.MSELoss()
x1 = Variable(torch.randn(100,100))  #随机生成维度是(100,100)的参数
y1 = Variable(torch.randn(100,100))
loss = loss_f1(x1,y1)
print(loss.data)

输出:

tensor(2.0312)

(2) torch.nn.L1Loss: torch.nn.L1Loss类使用平均绝对误差函数对损失值进行计算,同样,在定义类的对象时不用传入任何参数,但在使用实例时需要输入两个维度一样的参数进行计算。示例如下:

# import torch
# from torch.autograd import Variable
loss_f2 = torch.nn.L1Loss()
x2 = Variable(torch.randn(100,100))  #随机生成维度是(100,100)的参数
y2 = Variable(torch.randn(100,100))
loss = loss_f2(x2,y2)
print(loss.data)

输出:

tensor(1.1243)

(3)torch.nn.CrossEntropyLoss: torch.nn.CrossEntropyLoss 类用于计算交叉熵,在定义类的对象时不用传入任何参数,在使用实例时需要输入两个满足交叉熵的计算条件的参数,代码如下:

# import torch
# from torch.autograd import Variable
loss_f3 = torch.nn.CrossEntropyLoss()
x3 = Variable(torch.randn(3,5))   #随机生成维度是(3,5)的参数
y3 = Variable(torch.LongTensor(3).random_(5))  #3个范围是0-4的数字
loss = loss_f3(x3,y3)
print(loss.data)

输出:

tensor(3.0554)

看具体情况:

print(x3,y3)

输出:

tensor([[ 1.5224, -1.7120,  2.7553,  0.0077,  0.0938],
        [-0.2122,  1.3795, -1.6359, -1.2175, -1.2968],
        [-0.8271, -0.7797, -0.1743, -0.0701,  0.8936]]) 
tensor([1, 0, 1])

模型训练和参数优化:

#模型训练和参数优化
x4 = Variable(torch.randn(100,1000))  #随机生成维度是(100,100)的参数
y4 = Variable(torch.randn(100,10))
for epoch in range(epoch_n):
    y_pred = models2(x4)
    loss = loss_f(y_pred,y4)
    #if epoch%1000 == 0:
    print("Epoch:{},Loss:{:.4f}".format(epoch,loss.item()))
    models2.zero_grad()
    
    loss.backward()
    
    for param in models.parameters():
        param.grad -= param.grad*learning_rate
#         param.data -= param.grad.data*learning_rate

减少训练时间,训练10个epochs的结果如下:

Epoch:0,Loss:1.0703
Epoch:1,Loss:1.0703
Epoch:2,Loss:1.0703
Epoch:3,Loss:1.0703
Epoch:4,Loss:1.0703
Epoch:5,Loss:1.0703
Epoch:6,Loss:1.0703
Epoch:7,Loss:1.0703
Epoch:8,Loss:1.0703
Epoch:9,Loss:1.0703

6.3.2 PyTorch之torch.optim

到目前为止,代码中的神经网络权重的参数优化和更新还没有实现自动化,并且目前使用的优化方法都有固定的学习速率,所以优化函数相对简单,如果我们自己实现一些高级的参数优化算法,则优化函数部分的代码会变得较为复杂。在PyTorch的torch.optim包中提供了非常多的可实现参数自动优化的类,比如 SGD、AdaGrad、RMSProp和Adam 等,这些类都可被直接调用,使用起来也非常方便。我们使用自动化的优化函数实现方法对之前的代码进行替换,新的代码如下:

# import torch
# from torch.autograd import Variable
batch_n = 100 
hidden_layer = 100   #隐藏层之后保留的数据特征个数
input_data = 1000  #每个数据的数据特征
output_data = 10   #分类结果值

x = Variable(torch.randn(batch_n,input_data),requires_grad = False) 
y = Variable(torch.randn(batch_n,output_data),requires_grad = False)

models = torch.nn.Sequential(
    torch.nn.Linear(input_data,hidden_layer),
    torch.nn.ReLU(),
    torch.nn.Linear(hidden_layer,output_data)
)

epoch_n = 10000   #增加训练次数
learning_rate = 1e-4  #增加学习速率
loss_fn = torch.nn.MSELoss()

optimzer = torch.optim.Adam(models.parameters(),lr = learning_rate) #Adam类入参是被优化的参数和学习速率的初始值

for epoch in range(epoch_n):
    y_pred = models(x)
    loss = loss_fn(y_pred,y)
    print("Epoch:{},Loss:{:.4f}".format(epoch,loss.item()))
    optimzer.zero_grad()
    
    loss.backward()
    optimzer.step( )

 输出:

Epoch:0,Loss:1.0195
Epoch:1,Loss:0.9984
Epoch:2,Loss:0.9777
Epoch:3,Loss:0.9575
Epoch:4,Loss:0.9377
Epoch:5,Loss:0.9185
Epoch:6,Loss:0.8998
Epoch:7,Loss:0.8817
Epoch:8,Loss:0.8640
Epoch:9,Loss:0.8468
Epoch:10,Loss:0.8299
Epoch:11,Loss:0.8134
Epoch:12,Loss:0.7972
Epoch:13,Loss:0.7815
Epoch:14,Loss:0.7662
Epoch:15,Loss:0.7513
Epoch:16,Loss:0.7367
Epoch:17,Loss:0.7224
Epoch:18,Loss:0.7085
Epoch:19,Loss:0.6948
Epoch:20,Loss:0.6814

6.4 实战手写数字识别

我们现在已经学会基于 PyTorch 深度学习框架高效、快捷地搭建一个神经网络,并对模型进行训练和对参数进行优化的方法,接下来让我们小试牛刀,基于PyTorch框架使用神经网络来解决一个关于手写数字识别的计算机视觉问题,评价我们搭建的模型的标准是它能否准确地对手写数字图片进行识别

其具体过程是:

  1. 先使用己经提供的训练数据对搭建好的神经网络模型进行训练并完成参数优化
  2. 然后使用优化好的模型对测试数据进行预测
  3. 对比预测值和真实值之间的损失值,同时计算出结果预测的准确率

在将要搭建的模型中会用到卷积神经网络模型,下面让我们开始吧。

6.4.1 torch和torchvision

在PyTorch中有两个核心的包,分别是torch和torchvision。之前已经接触了torch包的一部分内容,比如使用了torch.nn 中的线性层加激活函数配合torch.optim完成了神经网络模型的搭建和模型参数的优化,并使用了torch.autograd实现自动梯度的功能,接下来会介绍如何使用torch.nn中的类来搭建卷积神经网络。torchvision包的主要功能是实现数据的处理、导入和预览等,所以如果需要对计算机视觉的相关问题进行处理,就可以借用在torchvision包中提供的大量的类来完成相应的工作。 代码中的开始部分如下:

import torch
from torchvision import datasets,transforms
from torch.autograd import Variable

#实现下载的代码是torchvision.datasets.MNIST。
#其他的数据集如COCO、ImageNet、CIFCAR等都可以通过这个方法快速下载和载入。
data_train = datasets.MNIST(root="./data/",transform = transform,train = True,download = False)
data_test = datasets.MNIST(root="./data/",transform = transform,train = False)

其中:

  • root 用于指定数据集在下载之后的存放路径,这里存放在根目录下的 data 文件夹中
  • transform 用于指定导入数据集时需要对数据进行哪种变换操作,在后面会介绍详细的变换操作类型,注意要提前定义这些变换操作
  • train 用于指定在数据集下载完成后需要载入哪部分数据,如果设置为True ,则说明载入的是该数据集的训练集部分;如果设置为False,则说明载入的是该数据集的测试集部分

6.4.2 PyTorch之torch.transforms

在torch.transforms 中有大量的数据变换类,其中有很大一部分可以用于实现数据增强( Data Argumentation)。若在我们需要解决的问题上能参与模型训练的图片非常有限,则要通过对有限的图片数据进行各种变换来生成新的训练集了,这些变换可以是缩小或者放大图片的大小、对图片进行水平或者垂直翻转等(数据增强十分重要)。不过在手写数字识别的问题上可以不使用数据增强的方法,因为可用于模型训练的数据已经足够了。对数据进行载入及有相应变化的代码如下:

transform = transforms.Compose([transforms.ToTensor(),
                                transforms.Lambda(lambda x: x.repeat(3,1,1)),
                               transforms.Normalize(mean = [0.5,0.5,0.5],std = [0.5,0.5,0.5])
])

我们可以将transforms.Compose看做一种容器,它能够同时对多种数据变换进行组合。传入的参数是一个列表,列表中的元素就是对载入的数据进行的各种变换操作。上述代码进行了类型的转换变换和标准化变换(变换之后数据符合均值为0,标准差为1的正态分布)

下面看看在 torchvision. transforms 中常用的数据变换操作

  • (1) torchvision.transforms.Resize:用于对载入的图片数据按我们需求的大小进行缩放。传递给这个类的参数可以是一个整型数据,也可以是一个类似于(h,w)的序列,其中h代表高度,w代表宽度,但是如果使用的是一个整型数据,那么表示缩放的宽度和高度都是这个整型数据的值。
  • (2) torchvision.transforms.Scale:用于对载入的图片数据按我们需求的大小进行缩放,用法和 torchvision transforms.Resize 类似。
  • (3) torchvision.transforms.CenterCrop:用于对载入的图片以图片中心为参考点,按我们需要的大小进行裁剪。传递给这个类的参数可以是一个整型数据 ,也可以是一个类似于(h,w)的序列。
  • (4) torchvision.transforms.RandomCrop:用于对载入的图片按我们需要的大小进行随机裁剪。传递给这个类的参数可以是一个整型数据,也可以是一个类似于(h,w)的序列。
  • (5) torchvision.transforms.RandomHorizontalFlip:用于对载入的图片按随机概率进行水平翻转。我们可以通过传递给这个类的参数自定义随机概率,如果没有定义,则使用默认的概率值0.5。
  • (6) torchvision.transforms.RandomVerticalFlip:用于对载入的图片按随机概率进行垂直翻转。 我们可以通过传递给这个类的参数自定义随机概率,如果没有定义,则使用默认的概率值0.5。
  • (7) torchvision.transforms.ToTensor:用于对载入的图片数据进行类型转换,将之前构成 PIL 图片的数据转换成 Tensor 数据类型的变量,让PyTorch能够对其进行计算和处理。
  • (8) torchvision.transforms.ToPILlmage:用于将Tensor变量的数据转换成 PIL 图片数据,主要是为了方便图片的展示。

6.4.3 数据预览和数据装载

在数据下载完成和载入之后,还需要对数据装载(DataLoader)。可以将数据的载入理解为对图片的处理,处理完成之后需要装载,就是将图片打包好送给模型进行训练。在装载时通过batch_size来确认每个包的大小,通过shuffle来确实是否打乱图片顺序。装载图片的代码如下:

data_loader_train = torch.utils.data.DataLoader(dataset = data_train,batch_size = 64,shuffle=True)
data_loader_test = torch.utils.data.DataLoader(dataset = data_test,batch_size = 64,shuffle=True)

在装载完成后,选取其中一个批次的数据进行预览,代码如下:

import torchvision
import matplotlib.pyplot as plt

images,labels = next(iter(data_loader_train))  #获取一个批次的图片和标签
img = torchvision.utils.make_grid(images) #将一个批次的图片构造成网格模式

img = img.numpy().transpose(1,2,0)
std = [0.5,0.5,0.5]
mean = [0.5,0.5,0.5]
img = img*std + mean
print([labels[i] for i in range(64)])  #打印这个批次数据的全部标签
plt.imshow(img)  #显示图片

输出结果:

[tensor(9), tensor(1), tensor(1), tensor(8), tensor(2), tensor(2), tensor(8), tensor(4), tensor(9), tensor(5), tensor(4), tensor(5), tensor(6), tensor(4), tensor(4), tensor(7), tensor(2), tensor(1), tensor(7), tensor(8), tensor(0), tensor(3), tensor(5), tensor(7), tensor(5), tensor(3), tensor(2), tensor(7), tensor(3), tensor(9), tensor(4), tensor(4), tensor(0), tensor(1), tensor(0), tensor(6), tensor(0), tensor(0), tensor(4), tensor(9), tensor(7), tensor(4), tensor(8), tensor(6), tensor(4), tensor(2), tensor(0), tensor(3), tensor(3), tensor(8), tensor(3), tensor(1), tensor(0), tensor(2), tensor(3), tensor(3), tensor(1), tensor(5), tensor(0), tensor(7), tensor(1), tensor(2), tensor(0), tensor(0)]

《深度学习之pytorch实战计算机视觉》第6章 PyTorch基础(代码可跑通)_第1张图片

img = torchvision.utils.make_grid(images)中使用 torchvision.utils的make_grid()类方法将一个批次的图片构造成网格模式。需要传递的参数就是一个批次的装载数据,每个批次的装载数据都是4维的,维度的构成从前往后分别为batch_size、channel、height、weight(注意这是pytorch框架的格式,tensorflow略有不同,为batch_size、height、weight、channel,分别对应一个批次中的数据个数、每张图片的色彩通道数、每张图片的高度和宽度。输出的图片维度变成了(channel、height、weight),这个批次的图片全部被整合到一起,在这个维度中对应值和之前不一样, 但是色彩通道数保持不变。

如果想使用Matplotlib显示正常的图片形式,使用的必须是(height、weight、channel)的数组,即色彩通道数在最后面。

6.4.4 模型搭建和参数优化

在顺利完成数据装载后,可以开始编写卷积神经网络模型搭建和参数优化的代码。这里搭建一个包含卷积层、激活函数、池化层、全连接层的卷积神经网络,各个部分的功能实现依然是通过torch.nn 中的类来完成的,比如卷积层使用torch.nn.Conv2d 类方法来搭建;激活层使用 torch.nn.ReLU 类方法来搭建;池化层使用 torch.nn.MaxPool2d 类方法;全连接层使用torch.nn.Linear 类方法来搭建。代码如下:

class Model(torch.nn.Module):
    def __init__(self):
        super(Model,self).__init__()
        self.conv1=torch.nn.Sequential(
        torch.nn.Conv2d(3,64,kernel_size=3,stride=1,padding=1),#1---3
        torch.nn.ReLU(),
        torch.nn.Conv2d(64,128,kernel_size=3,stride=1,padding=1),
        torch.nn.ReLU(),
        torch.nn.MaxPool2d(stride=2,kernel_size=2))
        
        self.dense=torch.nn.Sequential(
        torch.nn.Linear(14*14*128,1024),
        torch.nn.ReLU(),
        torch.nn.Dropout(p=0.5),
        torch.nn.Linear(1024,10))
        
    def forward(self,x):
        x = self.conv1(x)
        x = x.view(-1,14*14*128)
        x = self.dense(x)
        return x

对具体的使用方法补充说明:

  • (1) torch.nn.Conv2d:用于搭建卷积神经网络的卷积层,主要的输入参数有输入通道数、输出通道数、卷积核大小、卷积核移动步长和 Padding值。其中,输入通道数的数据类型是整型,用于确定输入数据的层数:输出通道数的数据类型也是整型,用于确定输出数据的层数;卷积核大小的数据类型是整型,用于确定卷积核的大小;卷积核移动步长的数据类型是整型,用于确定卷积核每次滑动的步长; Padding的数据类型是整型,为0表示不进行边界像素的填充,如果值大于0,那么增加数字所对应的边界像素层数。
  • (2) torch.nn.MaxPool2d:用于实现卷积神经网络中的最大池化层,主要的输入参数是池化窗口大小、池化窗口移动步长和 Padding值。同样,池化窗口大小的数据类型是整型 ,用于确定池化窗口的大小。池化窗口步长的数据类型也是整型,用于确定窗口每次移动的步长。Padding值的用法和意义同上。
  • (3) torch.nn.Dropout: torch.nn.Dropout 类用于防止卷积神经网络在训练的过程中发生过拟合,其工作原理简单来说就是在模型训练的过程中,以一定的随机概率将卷积神经网络模型的部分参数归零, 以达到减少相邻两层神经连接的目的。 正是因为选取方式的随机性,所以在模型的每轮训练中选择丢弃的神经连接也是不同的,这样做是为了让最后训练出来的模型对各部分的权重参数不过度依赖,从而防止过拟合。我们可以对随机概率值的大小进行设置,如果不做任何设置,就使用默认的概率值0.5。

最后看看代码中前向传播 forward 函数中的内容。首先经过 self.conv1卷积处理,然后进行 x.view (-1,14*14*128),对参数实现扁平化,因为之后紧接着的就是全连接,所以如果不进行扁平化,则全连接的实际输出的参数维度和其定义输入的维度将不匹配,程序会报错;最后,通过self.dense 定义的全连接进行最后的分类。

在编写完搭建卷积神经网络模型的代码后,我们就可以开始对模型进行训练和对参数进行优化了。首先,定义在训练之前使用哪种损失函数和优化函数:

model = Model()
cost = torch.nn.CrossEntropyLoss()  #交叉熵损失函数
optimizer = torch.optim.Adam(model.parameters())
print(model)  #查看模型的完整结构

输出结果:

Model(
  (conv1): Sequential(
    (0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (1): ReLU()
    (2): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (3): ReLU()
    (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (dense): Sequential(
    (0): Linear(in_features=25088, out_features=1024, bias=True)
    (1): ReLU()
    (2): Dropout(p=0.5, inplace=False)
    (3): Linear(in_features=1024, out_features=10, bias=True)
  )
)

最后,模型进行训练和参数优化的代码如下:

n_epochs = 5
for epoch in range(n_epochs):
    running_loss = 0.0
    running_correct = 0
    print("Epoch {}/{}".format(epoch,n_epochs))
    print("-"*10)
    for data in data_loader_train:
        X_train,y_train = data
        X_train,y_train = Variable(X_train),Variable(y_train)
        outputs = model(X_train)
        _,pred = torch.max(outputs.data,1)
        loss = cost(outputs,y_train)
        optimizer.zero_grad()
        
        loss.backward()
        optimizer.step()
        running_loss += loss.item()#loss.data[0]改成loss.item()
        running_correct += torch.sum(pred == y_train.data)  #输出是tensor!!!
        
    testing_correct = 0
    for data in data_loader_test:
        X_test,y_test = data
        X_test,y_test = Variable(X_test),Variable(y_test)
        outputs = model(X_test)
        _,pred = torch.max(outputs.data,1)
        testing_correct += torch.sum(pred == y_test.data)
    
#     print("running_loss:",running_loss)
#     print("len(data_train):",len(data_train))
    print("Loss is:{:.4f}, Train Accuracy is:{:.4f}%, Test Accuracy is:{:.4f}%".format(running_loss/len(data_train),100*running_correct.numpy()/len(data_train),100*testing_correct.numpy()/len(data_test)))

输出:

Epoch 0/5
----------
Loss is:0.0005, Train Accuracy is:99.0517%, Test Accuracy is:98.5300%
Epoch 1/5
----------
Loss is:0.0003, Train Accuracy is:99.3083%, Test Accuracy is:98.6800%
Epoch 2/5
----------
Loss is:0.0003, Train Accuracy is:99.4067%, Test Accuracy is:98.7600%
Epoch 3/5
----------
Loss is:0.0002, Train Accuracy is:99.4950%, Test Accuracy is:98.9000%
Epoch 4/5
----------
Loss is:0.0002, Train Accuracy is:99.6367%, Test Accuracy is:98.4600%

为了验证我们训练模型是不是真的己如结果显示的一样准确,则最好的方法就是随机选取一部分测试集中的图片,用训练好的模型进行预测,看看和真实值有多大的偏差,并对结果进行可视化。测试过程的代码如下:

data_loader_test1 = torch.utils.data.DataLoader(dataset = data_test,batch_size=4,shuffle=True)
X_test1,y_test1 = next(iter(data_loader_test1))
inputs1 = Variable(X_test1)
pred1 = model(inputs1)
#dim=1表示输出所在行的最大值,若改写成dim=0则输出所在列的最大值
_,pred1 = torch.max(pred1,1)  #返回具体的value,和value所在的index(_可用其他变量替换)

print("Predict Label is:",[i for i in pred1.data])
print("Real Label is:",[i for i in y_test1])

#该方法可以用来直接将数据结构[batch,channel.height,width]形式的图像转化为图像矩阵,便于将多张图像进行可视化。
img1 = torchvision.utils.make_grid(X_test1)
img1 = img1.numpy().transpose(1,2,0)

std = [0.5,0.5,0.5]
mean = [0.5,0.5,0.5]
img1 = img1*std + mean
plt.imshow(img1)

输出如下:

Predict Label is: [tensor(6), tensor(2), tensor(0), tensor(5)]
Real Label is: [tensor(6), tensor(2), tensor(0), tensor(5)]

《深度学习之pytorch实战计算机视觉》第6章 PyTorch基础(代码可跑通)_第2张图片

上述随机选取的测试结果表明模型识别正确。因为卷积神经网络在解决计算机视觉问题上有独特的优势,因此,采用简单的神经网络模型就能将识别的准确率达到很高的水平。不过,用来训练和测试模型的手写图片模型的特征非常明显,也很容易被模型捕获。

说明:记录学习笔记,如果错误欢迎指正!写文章不易,转载请联系我。

你可能感兴趣的:(AI,笔记,pytorch,深度学习,计算机视觉,机器学习,python)