PyTorch 笔记(15)— 分别使用 tensor、autograd、torch.nn 搭建简易神经网络

1. 使用 tensor 搭建神经网络

搭建神经网络模型的具体代码如下,这里会将完整的代码分成几部分进行详细介绍,以便于理解。

import torch as t

batch_n = 100
input_data = 10000
hidden_layer = 100
output_data = 10
  • batch_n 是在一个批次中输入数据的数量,值是 100,这意味着我们在一个批次中输入 100个数据;
  • input_data 表示 batch_n 包含的数据特征个数,该值是 1000,所以每个数据的数据特征就是 1000 个;
  • hidden_layer 用于定义经过隐藏层后保留的数据特征的个数,这里有 100 个,因为我们的模型只考虑一层隐藏层,所以在代码中仅定义了一个隐藏层的参数;
  • output_data 是输出的数据,值是 10,我们可以将输出的数据看作一个分类结果值的数量,个数 10 表示我们最后要得到 10 个分类结果值;

一个批次的数据从输入到输出的完整过程是:先输入100 个具有1000 个特征的数据,经过隐藏层后变成 100 个具有 100 个特征的数据,再经过输出层后输出 100 个具有 10 个分类结果值的数据,在得到输出结果之后计算损失并进行后向传播,这样一次模型的训练就完成了,然后循环这个流程就可以完成指定次数的训练,并达到优化模型参数的目的。

下面看看如何完成从输入层到隐藏层、从隐藏层到输出层的权重初始化定义工作,代码如下:

x = t.randn(batch_n, input_data)
y = t.randn(batch_n, output_data)

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

在以上代码中定义的从输入层到隐藏层、从隐藏层到输出层对应的权重参数,同在之前说到的过程中使用的参数维度是一致的。

由于我们现在并没有好的权重参数的初始化方法,所以选择通过torch.randn 来生成指定维度的随机参数作为其初始化参数,尽管这并不是一个好主意。可以看到,在代码中定义的输入层维度为(100,1000),输出层维度为(100,10),同时,从输入层到隐藏层的权重参数维度为(1000,100),从隐藏层到输出层的权重参数维度为(100,10),这里我们可能会好奇权重参数的维度是怎么定义下来的,其实,只要我们把整个过程看作矩阵连续的乘法运算,就自然能够很快明白了。

在代码中我们的真实值 y 也是通过随机的方式生成的,所以一开始在使用损失函数计算损失值时得到的结果会较大。

在定义好输入、输出和权重参数之后,就可以开始训练模型和优化权重参数了,在此之前,我们还需要明确训练的总次数和学习速率,代码如下:

epoch_n = 20
learning_rate = 1e-6

因为接下来会使用梯度下降的方法来优化神经网络的参数,所以必须定义后向传播的次数和梯度下降使用的学习速率。

在以上代码中使用了 epoch_n 定义训练的次数,epoch_n 的值为 20,所以我们需要通过循环的方式让程序进行 20 次训练,来完成对初始化权重参数的优化和调整。在优化的过程中使用的学习速率 learning_rate 的值为1e-6,即 0.000001。接下来对模型进行正式训练并对参数进行优化,代码如下:

for i in range(epoch_n):
    h1 = x.mm(w1)
    h1 = h1.clamp(min=0)
    
    y_pred = h1.mm(w2)
    
    loss = (y_pred - y).pow(2).sum()
    print("i is {}, loss is {}".format(i, 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

以上代码通过最外层的一个大循环来保证我们的模型可以进行 20 次训练,循环内的是神经网络模型具体的前向传播和后向传播代码,参数的优化和更新使用梯度下降来完成。

在这个神经网络的前向传播中,通过两个连续的矩阵乘法计算出预测结果,在计算的过程中还对矩阵乘积的结果使用 clamp 方法进行裁剪,将小于零的值全部重新赋值为 0,这就像加上了一个 ReLU 激活函数的功能。

前向传播得到的预测结果通过 y_pred 来表示,在得到了预测值后就可以使用预测值和真实值来计算误差值了。

我们用 loss 来表示误差值,对误差值的计算使用了均方误差函数。之后的代码部分就是通过实现后向传播来对权重参数进行优化了,为了计算方便,我们的代码实现使用的是每个节点的链式求导结果,在通过计算之后,就能够得到每个权重参数对应的梯度分别是 grad_w1grad_w2

在得到参数的梯度值之后,按照之前定义好的学习速率对 w1w2 的权重参数进行更新,在代码中每次训练时,我们都会对 loss 的值进行打印输出,以方便看到整个优化过程的效果,所以最后会有 20 个 loss 值被打印显示,打印输出的结果如下:

i is 0, loss is 506614656.0
i is 1, loss is 76480659456.0
i is 2, loss is 976.175537109375
i is 3, loss is 976.175537109375
i is 4, loss is 976.175537109375
i is 5, loss is 976.175537109375
i is 6, loss is 976.175537109375
i is 7, loss is 976.175537109375
i is 8, loss is 976.175537109375
i is 9, loss is 976.175537109375
i is 10, loss is 976.175537109375
i is 11, loss is 976.175537109375
i is 12, loss is 976.175537109375
i is 13, loss is 976.175537109375
i is 14, loss is 976.175537109375
i is 15, loss is 976.175537109375
i is 16, loss is 976.175537109375
i is 17, loss is 976.175537109375
i is 18, loss is 976.175537109375
i is 19, loss is 976.175537109375

可以看到,loss 值从之前的巨大误差逐渐缩减,这说明我们的模型经过 20次训练和权重参数优化之后,得到的预测的值和真实值之间的差距越来越小了。

2. 使用 autograd 搭建神经网络

torch.autograd 包的主要功能是完成神经网络后向传播中的链式求导,手动实现链式求导的代码会给我们带来很大的困扰,而 torch.autograd 包中丰富的类减少了这些不必要的麻烦。

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

import torch as t

batch_n = 100
input_data = 10000
hidden_layer = 100
output_data = 10

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

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

epoch_n = 20
learning_rate = 1e-6

for i in range(epoch_n):
    h1 = x.mm(w1)
    h1 = h1.clamp(min=0)
    y_pred = h1.mm(w2)
    
    loss = (y_pred - y).pow(2).sum()
    print("i is {}, loss is {}".format(i, loss.data))
    
    loss.backward()
    
    w1.data -= learning_rate * w1.grad.data
    w2.data -= learning_rate * w2.grad.data
    
    w1.grad.data.zero_()
    w2.grad.data.zero_()

代码中使用了 requires_grad 参数,这个参数的赋值类型是布尔型,如果 requires_grad 的值是 False ,那么表示该变量在进行自动梯度计算的过程中不会保留梯度值。

我们将输入的数据 x 和输出的数据 yrequires_grad 参数均设置为 False ,这是因为这两个变量并不是我们的模型需要优化的参数,而两个权重 w1w2requires_grad 参数的值为 True

和之前的代码相比,当前的代码更简洁了,之前代码中的后向传播计算部分变成了新代码中的 loss.backward(),这个函数的功能在于让模型根据计算图自动计算每个节点的梯度值并根据需求进行保留,有了这一步,我们的权重参数 w1.dataw2.data 就可以直接使用在自动梯度过程中求得的梯度值w1.data.gradw2.data.grad ,并结合学习速率来对现有的参数进行更新、优化了。

在代码的最后还要将本次计算得到的各个参数节点的梯度值通过 grad.data.zero_() 全部置零,如果不置零,则计算的梯度值会被一直累加,这样就会影响到后续的计算。

3. 使用 torch.nn 搭建神经网络

其实除了可以采用自动梯度方法,我们还可以通过构建一个继承了 torch.nn.Module 的新类,来完成对前向传播函数和后向传播函数的重写。

在这个新类中,我们使用 forward 作为前向传播函数的关键字,使用 backward 作为后向传播函数的关键字。

下面看看新的代码部分是如何定义我们的前向传播 forward 函数和后向传播 backward 函数的:

class Model(nn.Module):
    def __init__(self):
        super(Model, self).__init__()
    
    def forward(self, input, w1, w2):
        x = t.mm(input, w1)
        x = t.clamp(x, min=0)
        x = t.mm(x, w2)
        return x
    
    def backward(self):
        pass

以上代码展示了一个比较常用的 Python 类的构造方式:

  • 首先通过 class Model(torch.nn.Module) 完成了类继承的操作,之后分别是类的初始化,以及 forward 函数和backward 函数。
  • forward 函数实现了模型的前向传播中的矩阵运算,
  • backward 实现了模型的后向传播中的自动梯度计算,后向传播如果没有特别的需求,则在一般情况下不用进行调整。

在定义好类之后,我们就可以对其进行调用了,代码如下:

model = Model()

整体代码如下:

import torch as t
from torch import nn

batch_n = 100
input_data = 10000
hidden_layer = 100
output_data = 10


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

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

epoch_n = 20
learning_rate = 1e-6

class Model(nn.Module):
    def __init__(self):
        super(Model, self).__init__()
    
    def forward(self, input, w1, w2):
        x = t.mm(input, w1)
        x = t.clamp(x, min=0)
        x = t.mm(x, w2)
        return x
    
    def backward(self):
        pass

model = Model()

for i in range(epoch_n):

    y_pred = model(x, w1, w2)
    
    loss = (y_pred - y).pow(2).sum()
    print("i is {}, loss is {}".format(i, loss.data))
    
    loss.backward()
    
    w1.data -=(learning_rate * w1.grad.data)
    w2.data -=(learning_rate * w2.grad.data)
    
    w1.grad.data.zero_()
    w2.grad.data.zero_()

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值