以一个简单的RNN为例梳理神经网络的训练过程

栏目: 数据库 · 发布时间: 6年前

内容简介:我们构建一个RNN类其中函数init用于搭建神经网络的结构,网络的输入维度,输出维度,隐含层维度和数量,过程中需要用到的模型等等,都在init中定义。

我们构建一个RNN类

class simpleRNN(nn.Module):
    def __init():
        ...
    def forword():
        ...
    def initHidden():
        ...
复制代码

其中函数 initHidden 的作用是初始化隐含层向量

def initHidden(self):
    # 对隐含单元的初始化
    # 注意尺寸是: layer_size, batch_size, hidden_size
    return Variable(torch.zeros(self.num_layers, 1, self.hidden_size))
复制代码

使用init函数

init用于搭建神经网络的结构,网络的输入维度,输出维度,隐含层维度和数量,过程中需要用到的模型等等,都在init中定义。

其中 nn 是直接pytorch自带的模块,里面包含了内置的 Embedding ,RNN, Linear, logSoftmax 等模型,可以直接使用。

# 引入pytorch 中的 nn(模型模块)
import torch.nn as nn
def __init__(self, input_size, hidden_size, output_size, num_layers = 1):
        # 定义
        super(SimpleRNN, self).__init__()
        self.hidden_size = hidden_size
        self.num_layers = num_layers
        # 一个embedding层
        self.embedding = nn.Embedding(input_size, hidden_size)
        # PyTorch的RNN模型,batch_first标志可以让输入的张量的第一个维度表示batch指标
        self.rnn = nn.RNN(hidden_size, hidden_size, num_layers, batch_first = True)
        # 输出的全链接层
        self.linear = nn.Linear(hidden_size, output_size)
        # 最后的logsoftmax层
        self.softmax = nn.LogSoftmax()

复制代码

使用forward函数作为神经网络的运算过程

运算过程也很好理解,就是将输入一步一步地走过嵌入层,rnn层,linear层,和softmax层

  • embedding(嵌入层):用于输入层到隐含层的嵌入。过程大致是把输入向量先转化为one-hot编码,再编码为一个hidden_size维的向量
  • RNN层:经过一层RNN模型
  • linear层(全链接层):将隐含层向量的所有维度一一映射到输出上,可以理解为共享信息
  • softmax:将数据归一化处理
# 运算过程
def forward(self, input, hidden):
        # size of input:[batch_size, num_step, data_dim]
        
        # embedding层:
        # 从输入到隐含层的计算
        output = self.embedding(input, hidden)
        # size of output:[batch_size, num_step, hidden_size]
        
        output, hidden = self.rnn(output, hidden)
        # size of output:[batch_size, num_step, hidden_size]
      
        # 从输出output中取出最后一个时间步的数值,注意output输出包含了所有时间步的结果
        output = output[:,-1,:]
        # size of output:[batch_size, hidden_size]
        
        # 全链接层
        output = self.linear(output)
        # output尺寸为:batch_size, output_size
        
        # softmax层,归一化处理
        output = self.softmax(output)
         # size of output:batch_size, output_size
        return output, hidden
复制代码

对RNN的训练结果中间有一个特别的操作

output = output[:, -1 ,:]
复制代码

output尺寸为[batch_size, step, hidden_size], 这一步是把第二维时间步的数据只保留最后一个数。因为RNN的特征就是记忆,最后一步数据包含了之前所有步数的信息。所以这里只需要取最后一个数即可

使用这个init和forword

initforward 都是 pythonclass 中内置的两个函数。

  • 如果你定义了 __init__ ,那么在实例化类的时候就会自动运行 init 函数体,而且实例化的参数就是 init 函数的参数
  • 如果你定义了 forward , 那么你在执行这个类的时候,就自动执行 forward 函数
# 实例化类simpleRNN,此时执行__init__函数
rnn = simpleRNN(input_size = 4, hidden_size = 1, output_size = 3, num_layers = 1)

# 使用类simpleRNN
output, hidden = rnn(input, hidden)
复制代码

那么执行一次forward就相当于一个训练过程:输入 -> 输出

2. 可以开始训练了

首先是构造 损失函数优化器

强大的pytorch自带了通用的损失函数以及优化器模型。一句命令就搞定了一切。

criterion = torch.nn.NLLLoss()
optimizer = torch.optim.Adam(rnn.parameters(), lr = 0.001)
复制代码

损失函数criterion : 用于记录训练损失,所有权重都会根据每一步的损失值来调整。这里使用的是 NLLLoss 损失函数,是一种比较简单的损失计算,计算真实值和预测值的绝对差值

# output是预测值,y是真实值
loss = criterion(output, y)
复制代码

优化器optimizer : 训练过程的迭代操作。包括梯度反传和梯度清空。传入的参数为神经网络的参数 rnn.parameters() 以及学习率 lr

# 梯度反传,调整权重
optimizer.zero_grad()
# 梯度清空
optimizer.step()
复制代码

训练过程

训练的思路是:

  1. 准备训练数据,校验数据和测试数据(每个数据集的一组数据都是一个数字序列)
  2. 循环数数字序列,当前数字作为输入,下一个数字作为标签(即真实结果)
  3. 每次循环都经过一个rnn网络
  4. 计算每一组的损失t_loss并记录
  5. 优化器优化参数
  6. 重复1~5的训练步骤n次,n自定义

训练数据的准备不在本次的讨论范围内,所以这里直接给出处理好的结果如下。

train_set = [[3, 0, 0, 1, 1, 2],
            [3, 0, 1, 2],
            [3, 0, 0, 0, 1, 1, 1, 2],
            [3, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2]
            ...]
复制代码

开始进行训练

# 重复进行50次试验
num_epoch = 50
loss_list = []
for epoch in range(num_epoch):
    train_loss = 0
    # 对train_set中的数据进行随机洗牌,以保证每个epoch得到的训练顺序都不一样。
    np.random.shuffle(train_set)
    # 对train_set中的数据进行循环
    for i, seq in enumerate(train_set):
        loss = 0
        # 对每一个序列的所有字符进行循环
        for t in range(len(seq) - 1):
            #当前字符作为输入
            x = Variable(torch.LongTensor([seq[t]]).unsqueeze(0))
            # x尺寸:batch_size = 1, time_steps = 1, data_dimension = 1
            # 下一个字符作为标签
            y = Variable(torch.LongTensor([seq[t + 1]]))
            # y尺寸:batch_size = 1, data_dimension = 1
            output, hidden = rnn(x, hidden) #RNN输出
            # output尺寸:batch_size, output_size = 3
            # hidden尺寸:layer_size =1, batch_size=1, hidden_size
            loss += criterion(output, y) #计算损失函数
        loss = 1.0 * loss / len(seq) #计算每字符的损失数值
        optimizer.zero_grad() # 梯度清空
        loss.backward() #反向传播
        optimizer.step() #一步梯度下降
        train_loss += loss #累积损失函数值
        # 把结果打印出来
        if i > 0 and i % 500 == 0:
            print('第{}轮, 第{}个,训练Loss:{:.2f}'.format(epoch, i, train_loss.data.numpy()[0] / i))
    loss_list.appand(train_loss)
            
复制代码

这里的 loss 是对每一个训练循环(epoch)的损失,事实上无论训练的如何,这里的loss都会下降,因为神经网络就是会让最后的结果尽可能地靠近真实数据,所以训练集的loss其实并不能用来评价一个模型的训练好坏。

在实际的训练过程中,我们会在每一轮训练后,把得到的模型放入 校验集 去计算loss, 这样的结果更为客观。

校验集loss的计算和训练集完全一致,只不过把 train_set 替换成了 valid_set ,而且也不需要去根据结果优化参数,这在训练步骤中已经做了,校验集的作用就是看模型的训练效果:

for epoch in range(num_epoch):
    # 训练步骤
    ...
    valid_loss = 0
    for i, seq in enumerate(valid_set):
        # 对每一个valid_set中的字符串做循环
        loss = 0
        outstring = ''
        targets = ''
        hidden = rnn.initHidden() #初始化隐含层神经元
        for t in range(len(seq) - 1):
            # 对每一个字符做循环
            x = Variable(torch.LongTensor([seq[t]]).unsqueeze(0))
            # x尺寸:batch_size = 1, time_steps = 1, data_dimension = 1
            y = Variable(torch.LongTensor([seq[t + 1]]))
            # y尺寸:batch_size = 1, data_dimension = 1
            output, hidden = rnn(x, hidden)
            # output尺寸:batch_size, output_size = 3
            # hidden尺寸:layer_size =1, batch_size=1, hidden_size               
            loss += criterion(output, y) #计算损失函数
        loss = 1.0 * loss / len(seq)
        valid_loss += loss #累积损失函数值
#     # 打印结果
    print('第%d轮, 训练Loss:%f, 校验Loss:%f, 错误率:%f'%(epoch, train_loss.data.numpy() / len(train_set),valid_loss.data.numpy() / len(valid_set),1.0 * errors / len(valid_set)))
复制代码

根据校验集的loss输出,我们可以绘制出最终的loss变化。

3. 测试模型预测效果

构造数据,测试模型是否能猜出当前数字的下一个数。成功率有多高 首先是构造数据,构造长度分别为0~20的数字序列

for n in range(20):
    inputs = [0] * n + [1] * n
复制代码

然后对每一个序列进行测试

for n in range(20):
    inputs = [0] * n + [1] * n
    
    outstring = ''
    targets = ''
    diff = 0
    hiddens = []
    hidden = rnn.initHidden()
    for t in range(len(inputs) - 1):
        x = Variable(torch.LongTensor([inputs[t]]).unsqueeze(0))
        # x尺寸:batch_size = 1, time_steps = 1, data_dimension = 1
        y = Variable(torch.LongTensor([inputs[t + 1]]))
        # y尺寸:batch_size = 1, data_dimension = 1
        output, hidden = rnn(x, hidden)
        # output尺寸:batch_size, output_size = 3
        # hidden尺寸:layer_size =1, batch_size=1, hidden_size
        hiddens.append(hidden.data.numpy()[0][0])
        #mm = torch.multinomial(output.view(-1).exp())
        mm = torch.max(output, 1)[1][0]
        outstring += str(mm.data.numpy()[0])
        targets += str(y.data.numpy()[0])
         # 计算模型输出字符串与目标字符串之间差异的字符数量
        diff += 1 - mm.eq(y)
    # 打印出每一个生成的字符串和目标字符串
    print(outstring)
    print(targets)
    print('Diff:{}'.format(diff.data.numpy()[0]))
复制代码

最终输出的结果为

[0, 1, 2]
[0, 1, 2]
Diff: 0
[0, 0, 1, 1, 2]
[0, 0, 1, 1, 2]
Diff: 0
[0, 0, 0, 1, 1, 1, 2]
[0, 0, 0, 1, 1, 1, 2]
Diff: 0
...
# 结果不一一列出,大家可以自行尝试
复制代码

总结

神经网络可以理解为让计算机使用各种数学手段从一堆数据中找规律的过程。我们可以通过解剖一些简单任务来理解神经网络的内部机制。当面对复杂任务的时候,只需要把数据交给模型,它就能尽其所能地给你一个好的结果。

本文是学习完 集智学园《PyTorch入门课程:火炬上的深度学习——自然语言处理(NLP)》 系列课之后的梳理。课程中还有关于 lstm , 翻译任务实操 等基础而且丰富的知识点,我还会再回来的


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Little MLer

The Little MLer

Matthias Felleisen、Daniel P. Friedman、Duane Bibby、Robin Milner / The MIT Press / 1998-2-19 / USD 34.00

The book, written in the style of The Little Schemer, introduces instructors, students, and practicioners to type-directed functional programming. It covers basic types, quickly moves into datatypes, ......一起来看看 《The Little MLer》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

SHA 加密
SHA 加密

SHA 加密工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具