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

本文是学习完集智学园《PyTorch入门课程:火炬上的深度学习——天然语言处理(NLP)》系列课以后的梳理。python

本次任务为预测字符(数字),让神经网络找到下面数字的规律。bash

012
00112
0001112
000011112
00000111112
复制代码

当咱们给定一组数据(如0000001)的时候,让神经网络去预测后面的数字应该是什么网络

1. 创建神经网络架构

咱们构建一个RNN类架构

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

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

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

使用init函数

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

其中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都是python的class中内置的两个函数。

  • 若是你定义了__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, 翻译任务实操等基础并且丰富的知识点,我还会再回来的

相关文章
相关标签/搜索