如何基于PyTorch深度学习框架用简单快捷的方式搭建出复杂的神经网络模型,同时让模型参数的优化方法趋于高效。如同使用PyTorch中的自动梯度方法一样,在搭建复杂的神经网络模型的时候,我们也可以使用PyTorch中已定义的类和方法,这些类和方法覆盖了神经网络中的线性变换、激活函数、卷积层、全连接层、池化层等常用神经网络结构的实现。在完成模型的搭建之后,我们还可以使用PyTorch提供的类型丰富的优化函数来完成对模型参数的优化,除此之外,还有很多防止模型在模型训练过程中发生过拟合的类。
PyTorch中的 torch.nn包提供了很多与实现神经网络中的具体功能相关的类,这些类涵盖了深度神经网络模型在搭建和参数优化过程中的常用内容,比如神经网络中的卷积层、池化层、全连接层这类层次构造的方法、防止过拟合的参数归一化方法、Dropout 方法,还有激活函数部分的线性激活函数、非线性激活函数相关的方法,等等。在学会使用PyTorch的 torch.nn进行神经网络模型的搭建和参数优化后,我们就会发,现实现一个神经网络应用并没有我们想象中那么难。
(1)torch.nn.Sequential:torch.nn.Sequential类是torch.nn中的一种序列容器,通过在容器中嵌套各种实现神经网络中具体功能相关的类, 来完成对神经网络模型的搭建,最主要的是,参数会按照我们定义好的 序列自动传递下去。我们可以将嵌套在容器中的各个部分看作各种不同 的模块,这些模块可以自由组合。模块的加入一般有两种方式,一种是 在以上代码中使用的直接嵌套,另一种是以orderdict有序字典的方式进 行传入,这两种方式的唯一区别是,使用后者搭建的模型的每个模块都 有我们自定义的名字,而前者默认使用从零开始的数字序列作为每个模 块的名字。下面通过示例来直观地看一下使用这两种方式搭建的模型之间的区别.
首先,使用直接嵌套搭建的模型代码如下:
import torch
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有序字典进行传入来搭建的模型代码如下:
import torch
from collections import OrderedDict
hidden_layer = 100
input_data = 1000
output_data = 10
models = 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 (models)
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类接收的参 数有三个,分别是输入特征数、输出特征数和是否使用偏置,设置是否 使用偏置的参数是一个布尔值,默认为True,即使用偏置。在实际使用 的过程中,我们只需将输入的特征数和输出的特征数传递给 torch.nn.Linear类,就会自动生成对应维度的权重参数和偏置,对于生成 的权重参数和偏置,我们的模型默认使用了一种比之前的简单随机方式 更好的参数初始化方法。
(3)torch.nn.ReLU:torch.nn.ReLU类属于非线性激活分类,在定 义时默认不需要传入参数。当然,在 torch.nn包中还有许多非线性激活 函数类可供选择,比如之前讲到的PReLU、LeakyReLU、Tanh、 Sigmoid、Softmax等。
在掌握torch.nn.Sequential、torch.nn.Linear和torch.nn.RelU的使用方法后,快速搭建更复杂的多层神经网络模型变为可能,而且在整个模型 的搭建过程中不需要对在模型中使用到的权重参数和偏置进行任何定义 和初始化说明,因为参数已经完成了自动生成。
下面简单介绍在torch.nn包中常用的损失函数的具体用法,如下所述。
(1)torch.nn.MSELoss:torch.nn.MSELoss类使用均方误差函数对损失值进行计算,在定义类的对象时不用传入任何参数,但在使用实例时需要输入两个维度一样的参数方可进行计算。示例如下:
loss_fn = torch.nn.MSELoss()
loss = loss_fn(torch.rand(1),torch.rand(1))
print (loss.data)
tensor(0.5333)
(2)torch.nn.L1Loss:torch.nn.L1Loss类使用平均绝对误差函数对损失值进行计算,同样,在定义类的对象时不用传入任何参数,但在使用实例时需要输入两个维度一样的参数进行计算。示例如下:
loss_fn = torch.nn.L1Loss()
loss = loss_fn(torch.rand(1),torch.rand(1))
print (loss.data)
tensor(0.0366)
(3)torch.nn.CrossEntropyLoss:torch.nn.CrossEntropyLoss类用 于计算交叉熵,在定义类的对象时不用传入任何参数,在使用实例时需 要输入两个满足交叉熵的计算条件的参数,代码如下:
loss_fn = torch.nn.CrossEntropyLoss()
x = Variable(torch.randn(3,5))
print (x)
y = Variable(torch.LongTensor(3).random_(5))
print (y)
loss = loss_fn(x,y)
print (loss.data)
这里生成的第1组参数是一个随机参数,维度为(3,5);第2组参 数是3个范围为0~4的随机数字。计算这两组参数的损失值,打印输出 的结果如下:
tensor([[-0.7255, 0.1866, -2.6075, -1.2567, -0.3385],
[ 1.1799, 0.5256, 1.0063, 1.0577, 0.9446],
[-0.1277, -1.4513, 0.0989, -1.6757, -0.9638]])
tensor([1, 3, 4])
tensor(1.4450)
下面使用PyTorch的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)
)
epoch_n = 10000
learning_rate = 1e-4
loss_fn1 = torch.nn.MSELoss()
for epoch in range(epoch_n):
y_pred = models(x)
loss = loss_fn1(y_pred, y)
if (epoch + 1) % 1000 == 0:
print('Epoch:{},Loss:{}'.format(epoch + 1, loss.data))
models.zero_grad()
loss.backward()
for param in models.parameters():
param.data -= param.grad.data * learning_rate
以上代码中的绝大部分和之前训练和优化部分的代码是一样的,但是参数梯度更新的方式发生了改变。因为使用了不同的模型搭建方法,所以访问模型中的全部参数是通过对“models.parameters()”进行遍历完成的,然后才对每个遍历的参数进行梯度更新。其打印输入结果的方式是每完成1000次训练,就打印输出当前的loss值,最后输出的结果如下:
Epoch:1000,Loss:0.9827698469161987
Epoch:2000,Loss:0.9132287502288818
Epoch:3000,Loss:0.8532592058181763
Epoch:4000,Loss:0.7999617457389832
Epoch:5000,Loss:0.7520259618759155
Epoch:6000,Loss:0.7082270979881287
Epoch:7000,Loss:0.6677832007408142
Epoch:8000,Loss:0.6301513314247131
Epoch:9000,Loss:0.5949526429176331
Epoch:10000,Loss:0.5619678497314453
在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 = 20
learning_rate = 1e-4
loss_fn1 = torch.nn.MSELoss()
optimzer = torch.optim.Adam(models.parameters(), lr=learning_rate)
for epoch in range(epoch_n):
y_pred = models(x)
loss = loss_fn1(y_pred, y)
print('Epoch:{},Loss:{}'.format(epoch + 1, loss.data))
optimzer.zero_grad()
loss.backward()
optimzer.step()
在以上代码中有几处代码和之前的训练代码不同,这是因为我们引入了优化算法,所以通过直接调用optimzer.zero_grad来完成对模型参数梯度的归零;并且在以上代码中增加了optimzer.step,它的主要功能是使用计算得到的梯度值对各个节点的参数进行梯度更新。这里只进行20次训练并打印每轮训练的loss值,结果如下:
Epoch:1,Loss:1.1183404922485352
Epoch:2,Loss:1.0961917638778687
Epoch:3,Loss:1.0746650695800781
Epoch:4,Loss:1.0536885261535645
Epoch:5,Loss:1.033348560333252
Epoch:6,Loss:1.0135233402252197
Epoch:7,Loss:0.9941656589508057
Epoch:8,Loss:0.9753313660621643
Epoch:9,Loss:0.9568551778793335
Epoch:10,Loss:0.9388622641563416
Epoch:11,Loss:0.921436607837677
Epoch:12,Loss:0.9045109748840332
Epoch:13,Loss:0.8878739476203918
Epoch:14,Loss:0.8715552687644958
Epoch:15,Loss:0.8556023836135864
Epoch:16,Loss:0.8400096893310547
Epoch:17,Loss:0.8247792720794678
Epoch:18,Loss:0.8098956346511841
Epoch:19,Loss:0.795305609703064
Epoch:20,Loss:0.7809619307518005
使用torch.optim.Adam类进 行参数优化后仅仅进行了20次训练,得到的loss值就已经远远低于之前进行10=5000次优化训练的结果。所以,如果对torch.optim中的优化算法类使用得当,就更能帮助我们优化好模型中的参数。