1 应用pytorch搭建一个简易的神经网络模型此次将通过应用第一节已学过的知识plus一些神经网络的知识使用Pytorch搭建一个简易神经网络。
1.1 导入包
在代码最开始是导入相关包
import torch
batch_n = 100
input_data = 1000
hidden_layer = 100
output_data = 10
首先通过import torch导入相关包,然后定义相关变量。batch_n是在一个批次中输入数据的个数;其中每个数据包含的特征维度为input_data个(特征数),此处说明数据的特征是1000个;hidden_layer用于定义经过一个隐层后保留的数据特征的个数,此处是100此处设置隐层个数为1;output_data是输出的数据,可将输出的数据看作分类结果的数目,此处为10.说明最后要得到10个分类结果。
在机器学习和,尤其是深度学习中,一般不会将数据全量送入进行训练,因为数据量都非常大,且最终的优化函数一般情况下都非凸,很容易陷入局部最优点,此外全量数据对于硬件条件的要求过高,一般难以达到。所以训练过程一般都会选择按batch_size大小进行训练。
此处一个批次(batch)的数据从输入到输出的完整过程:先输入100个每个有1000维特征的数据,经过hidden_layer后输出100个每个有100维特征的数据,然后输入到输出层,得到100个具有10个分类结果值的数据。然后与实际的标签进行比对,算出误差,然后进行反向传播,完成模型的一次训练。然后按此流程完成指定epoch的训练,对参数进行调整,从而优化模型。
1.2 权重、训练次数及学习率的初始化
x = torch.randn(batch_n,input_data)
y = torch.randn(batch_n,hidden_layer)
w1 = torch.randn(input_data,hidden_layer)
w2 = torch.randn(hidden_layer,output_data)
epoch_n = 20
learning_rate = 1e-6
上述代码定义了从输入层到隐藏层,从隐藏层到输出层对应的权重参数。可看到在代码中定义了输入层维度为(100,1000),输出层维度为(100,10),同时定义了从输入层到隐藏层的权重参数维度为(1000,100),从隐藏层到输出层的权重参数维度为(100,10)。权重参数维度的定义类似矩阵的连续乘法。其中真实的y是通过随机产生的。
1.3 通过梯度下降优化神经网络参数
接下来通过训练保证模型可训练epoch_n次。通过内部循环完成前向传递与反向传播。通过梯度进行参数的优化和更新。
for epoch in range(epoch_n):
h1 = x.mm(w1) #100*1000
h1 = hi.clamp(min=0) #relu的实现
y_pred= h1.mm(w2) #100*10
loss = (y_pred - y).pow(2).sum() #求loss,是一个tensor
loss = loss.item() #转化为数字
print("Epoch:{},Loss:{:.4f}.format(epoch,loss))
grad_y_pred = 2(y_pred-y)
grad_w2 = h1.t().mm(grad_y_pred) #.t在pytorch中表示转置 transpose
grad_h = grad_y_pred.clone() #克隆一个tensor
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
以上代码通过最外层的一个大循环来保证我们的模型可进行20次训练。在计算中通过clamp方法进行剪裁,将小于0的值全部更新为0,类似于relu的效果。
前向传播得到的预测结果通过 y_pred来表示,在得到了预测值后就可以使用预测值和真实值来计算误差值了。我们用loss来表示误差值,对误差值的计算使用了均方误差函数。之后的代码部分就是通过实现后向传播来对权重参数进行优化了,为了计算方便,我们的代码实现使用的是每个节点的链式求导结果,在通过计算之后,就能够得到每个权重参数对应的梯度分别是grad_w1和grad_w2。在得到参数的梯度值之后,按照之前定义好的学习速率对w1和w2的权重参数进行更新。
2 自动梯度
在上述通过pytorch搭建简易神经网络模型过程中,通过在代码中使用前向传递和反向传播实现了对该训练模型的权重参数的优化。pytorch提供了非常简便的方法,可帮助我们实现对模型中反向传播梯度的自动计算,从而简化了模型代码实现的复杂度,通过导入torch.autograd包,可使模型参数自动计算在优化过程中需用到的梯度值。
2.1 torch.autograd和Variable
torch.autograd包的主要功能是完成神经网络反向传播的链式求导,手动实现链式求导的代码会给我们造成很多麻烦,而torch.autograd中包含了丰富的类,可轻松解决。
实现自动梯度功能的过程大概分为以下三步:
- 通过输入的Tensor数据类型的变量在神经网络中的前向传播过程中生成一张计算图
- 根据1中生成的计算图和输出结果,准确计算每个参数需更新的梯度
- 通过完成反向传播对参数梯度进行更新
完成自动梯度需用到torch.autograd包中的Variable类,对我们定义的Tensor数据类型变量进行封装,完成封装后,计算图中每个节点都是一个variable对象,则可自动梯度的功能。autograd package提供了Tensors上所有运算的自动求导功能。并且它是按运行定义的框架,所以在进行反向传播时,依据代码情况定义,每一个单次的迭代都可能不同。
autograd.Variable是这个package的中心类,它打包了一个Tensor,并支持所有的运算,完成运算后,调用.package()方法,所有梯度都可自动计算 。
下面通过一个自动剃度的实例来看看如何使用torch.autograd.Variable类和torch.autograd包,我们同样搭建一个二层结构的神经网络模型,这有利于我们之前搭建的简易神经网络模型的训练和优化过程进行对比,重新实现。
2.2 导入包
import torch
from trh.autograd import Variable
#批量输入的数据量
batch_n = 100
#通过隐藏层后输出的特征数
hidden_layer = 100
#输入数`的特征个数
input_data = 1000
#最后输出的分类结果数(但隐层)
output_data = 10
2.3 初始化权重
x = Variable(torch.randn(batch_n,
input_data),requires_grad = False)
y = Variable(torch.randn(batch_n,
hidden_layer),requires_grad = False)
w1 = Variable(torch.randn(input_data,
hidden_layer),requires_grad = True)
X = Variable(torch.randn(hidden_layer,
output_data),requires_grad = True)
"Variable(torch.randn(batch_n, input_data),requires_grad = False) 这段代码就是之前用Variable类对Tensor数据类型变量进行封装的操作,在代码中使用了requires_grad参数,该参数的赋值类型是布尔型,True/False。设为True时,在自动计算梯度的过程中会保留梯度值。False时,自动计算梯度的过程中不会保留梯度值。一般对于模型要优化的参数,计算其梯度时,需要保留梯度值,所以此处将权重w1和w2的requires_grad参数设为True。
2.4 定义训练迭代次数与学习率
在定义好输入,输出和权重后,在开始进行训练模型和优化参数前,需明确训练迭代的总次数,即训练何时结束;以及学习效率,即参数更新搜索的快慢。
epoch_n = 20
learning_rate =1e-6
2.5 模型训练和参数优化
for epoch in range(epoch_n):
y_pred = x.mm(w1).clamp(min=0).mm(w2) #w1*x经过relu后,在乘w2
loss = (y_pred-y).pow(2).sum()
print("Epoch:{},Loss:{:.4f}.format(epoch,loss))
loss.backward() #将loss里面所有参数求导,通过计算图得到
#learning_rate * w1.grad.data,也是一张计算图,在pytorch中,所有tensor的计算,都是计算图
#为防止计算图占内存,写with torch.no_grad。将不会在记下w1与w2的grad
with torch.no_grad:
w1.data-=learning_rate * w1.grad.data
w2.data-=learning_rate * w2.grad.data
w1.grad.data.zero_() #将grad这个tensor清0,防止叠加
w2.grad.data.zero_()
在代码的最后要将本次计算得到的各个参数节点的梯度值通过grad.data.zero_()全部置零,如果不置零,则计算的梯度值会被一直累加,这样就会影响到后续的计算。