搭建复杂神经网络同时优化参数

前面已经搭建了一个简易神经网络,并且使用自动梯度搭建一个二层结构的神经网络模型,现在我们基于 PyTorch 深度学习框架用简单快捷的方式搭建出复杂的神经网络模型,同时让模型参数的优化方法趋于高效。

1 PyTorch 之 torch.nn

PyTorch 中的 torch.nn 包提供了很多与实现神经网络中的具体功能相关的类,这些类涵盖了深度神经网络模型在搭建和参数优化过程中的常用内容。

'''使用 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)  #100*1000
y = Variable(torch.randn(batch_n, output_data), requires_grad = False)  #100*10

这里仅定义了输入和输出的变量,没有定义权重w1和w2,这和我们之后在代码中使用的 torch.nn 包中的类有关,因为这个类能够帮助我们自动生成和初始化对应维度的权重参数。

'''模型搭建'''
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)
)
  • 介绍torch.nn.Sequential类的内容在此链接中。
  • orch.nn.Linear用于线性变换(torch.nn.Linear 类接收的参数有三个,分别是输入特征数、输出特征数和是否使用偏置,设置是否使用偏置的参数是一个布尔值,默认为 True,即使用偏置),我们只需将输入的特征数和输出的特征数传递给torch.nn.Linear 类,就会自动生成对应维度的权重参数和偏置,对于生成的权重参数和偏置,我们的模型默认使用了一种比之前的简单随机方式更好的参数初始化方法。
  • orch.nn.ReLU 类属于非线性激活分类,在定义时默认不需要传入参数。

上面models的括号内的内容分别是:

  • 完成从输入层到隐藏层的线性变换
  • 激活函数
  • 完成从隐藏层到输出层的线性变换

下面是模型训练和参数优化部分,关于torch.nn包中的损失函数见此链接。访问模型中的全部参数是通过对“models.parameters()”进行遍历完成的,然后才对每个遍历的参数进行梯度更新。

'''对已经搭建好的模型进行训练并对参数进行优化'''
epoch_n = 10000 
learning_rate = 1e-4 
loss_fn = torch.nn.MSELoss()  # torch.nn 包中已经定义好的均方误差函数类

for epoch in range(epoch_n):
    y_pred = models(x)
    loss = loss_fn(y_pred, y)
    if epoch % 1000 == 0:  #每隔1000个数字打印一次
        print("Epoch:{}, Loss:{:.4f}".format(epoch, loss))
    models.zero_grad()
    
    loss.backward()
    
    for param in models.parameters():
        param.data -= param.grad.data*learning_rate
Epoch:0, Loss:1.0301
Epoch:1000, Loss:0.9579
Epoch:2000, Loss:0.8952
Epoch:3000, Loss:0.8396
Epoch:4000, Loss:0.7894
Epoch:5000, Loss:0.7437
Epoch:6000, Loss:0.7016
Epoch:7000, Loss:0.6626
Epoch:8000, Loss:0.6261
Epoch:9000, Loss:0.5918

可以看出,参数的优化效果比较理想,loss 值被控制在相对较小的范围之内,这和我们增加了训练次数有很大关系。

2 PyTorch 之 torch.optim

上面的权重参数的优化和更新还没有自动化,且learning_rate固定。 一旦想尝试高级的算法,代码就会很复杂。

因此,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)  #100*1000
y = Variable(torch.randn(batch_n, output_data), requires_grad = False)  #100*10

'''模型搭建'''
models = torch.nn.Sequential(
    torch.nn.Linear(input_data, hidden_layer),  
    torch.nn.ReLU(),  
    torch.nn.Linear(hidden_layer, output_data)  
)
print(models)

epoch_n = 20
learning_rate = 1e-4 
loss_fn = torch.nn.MSELoss()

optimzer = torch.optim.Adam(models.parameters(), lr = learning_rate)  
Sequential(
  (0): Linear(in_features=1000, out_features=100, bias=True)
  (1): ReLU()
  (2): Linear(in_features=100, out_features=10, bias=True)
)

Adam 优化函数:

  • torch.potim.Adam类输入的是被优化的参数和学习速率的初始值。如果没有输入学习速率的初始值,那么默认使用 0.001 这个值。
  • 可以对梯度更新使用到的学习速率进行自适应调节,所以最后得到的结果自然会比之前的代码更理想。
'''模型训练'''
for epoch in range(epoch_n):
    y_pred = models(x)
    loss = loss_fn(y_pred, y)
    print("Epoch:{}, Loss:{:.4f}".format(epoch, loss))
    optimzer.zero_grad()  #直接调用 optimzer.zero_grad 来完成对模型参数梯度的归零
    
    loss.backward()
    
    optimzer.step()  #使用计算得到的梯度值对各个节点的参数进行梯度更新
Epoch:0, Loss:1.0300
Epoch:1, Loss:1.0091
Epoch:2, Loss:0.9887
Epoch:3, Loss:0.9686
Epoch:4, Loss:0.9490
Epoch:5, Loss:0.9299
Epoch:6, Loss:0.9112
Epoch:7, Loss:0.8929
Epoch:8, Loss:0.8751
Epoch:9, Loss:0.8577
Epoch:10, Loss:0.8406
Epoch:11, Loss:0.8239
Epoch:12, Loss:0.8076
Epoch:13, Loss:0.7917
Epoch:14, Loss:0.7762
Epoch:15, Loss:0.7610
Epoch:16, Loss:0.7462
Epoch:17, Loss:0.7316
Epoch:18, Loss:0.7174
Epoch:19, Loss:0.7035

可以看出,使用 torch.optim.Adam 类进行参数优化后仅仅进行了 20 次训练,得到的 loss 值就已经远远低于之前进行 10000 次优化训练的结果。所以,如果对 torch.optim 中的优化算法类使用得当,就更能帮助我们优化好模型中的参数。