zoukankan      html  css  js  c++  java
  • RNN 实现

    导入数据并设定随机数种子

    import torch
    import torch.nn as nn
    import numpy as np
    import random
    import pandas as pd
    import matplotlib.pyplot as plt
    from d2l import torch as d2l
    import torch.nn.functional as F
    import math
    
    def setup_seed(seed):
         torch.manual_seed(seed)
         torch.cuda.manual_seed_all(seed)
         np.random.seed(seed)
         random.seed(seed)
         torch.backends.cudnn.deterministic = True
    # 设置随机数种子
    setup_seed(916)
    
    data_iter, vocab = d2l.load_data_time_machine(64, 35)
    indim = 28
    hidim = 512
    outdim = 28
    epochs = 500
    batch_size = 64
    

    随机数种子设置是一个好习惯,因为我的生日就在916,所以设置916作为以后我训练的随机数种子。

    搭建RNN模型

    class RNN(nn.Module):
        def __init__(self, indim, hidim, outdim):
            super(RNN, self).__init__()
            self.rnn = nn.RNN(len(vocab), hidim)
            self.linear = nn.Linear(hidim, len(vocab))
            
        def get_begin_state(self, batch_size, hidim):
            self.begin_state = torch.zeros((1, batch_size, hidim), dtype=torch.float32)
            self.begin_state =self.begin_state.cuda()
            
        def forward(self, input, state):
            input = input.type(torch.float32)
            input = input.cuda()
            self.rnn.cuda()
            self.linear.cuda()
            # 这里的输入input 是(时间步,batchsize,feature维度)
            y, state = self.rnn(input, state) # state 是起始的state
            output = self.linear(y.reshape(-1, y.shape[-1]))
            return output, state
    
    

    rnn 的输入input 的shape是时间步,batchsize,特征维度)

    torch 中的 rnn 是不会直接就输出 label 预测的,rnn输出的y只是一连串隐变量,并且shape为(时间步, batch_size,hiddim),state 的shape是(层数, batch_size, hiddim)

    这里有点绕,并且要reshape,所以应当格外小心。

    训练

    def train():
        animator = d2l.Animator(xlabel='epoch',ylabel='perplexity', xlim = [10, epochs], 
                                legend=['train'])
        for epoch in range(epochs):
            metrics = d2l.Accumulator(2)
            for x, y in data_iter:
                x = x.T
                y = y.T
                y = y.reshape(-1).type(torch.int64) # 时间步 * batch_size
                y = y.cuda()
                x = F.one_hot(x, len(vocab))
                y_hat,_ = rnn(x, rnn.get_begin_state(batch_size, hidim))
                l = loss(y_hat, y)
                optimizer.zero_grad()
                l.backward()
                nn.utils.clip_grad.clip_grad_norm_(rnn.parameters(), 1)
                optimizer.step()
                metrics.add(l.item() * len(y), len(y))
            perplexity = math.exp(metrics[0]/metrics[1])
            if epoch % 50 == 0:
                print("perplexity : %.3f"% perplexity)
            #animator.add(epoch+1, perplexity)
            #plt.show()
        print("perplexity : %.3f"% perplexity)
    

    注意这里的真实值 y我做了一个转置并 reshape,这是因为我的预测值输出的(时间步*batchsize, 特征维度),而真实值y导入时的shape是(batch_size ,时间步)因为y是索引,没有特征维度这个说法,将其reshape(-1)是因为label要用在交叉熵计算上,而交叉熵输入label只接受一个一维向量。

    这里也是很绕,需要格外小心并且注意。

    预测

    def predict():
        prefix = 'time machine ' #长度为13
        state = rnn.get_begin_state(1, hidim)
        outputs = [vocab[prefix[0]]]
        get_input = lambda: torch.tensor([outputs[-1]]).reshape((1,1))
        for y in prefix[1:]:
            _, state = rnn(F.one_hot(get_input(),len(vocab)), state)# warm 操作,想用prefix做得到一个state,这里拆开了一个词一个词计算,其实吧,直接导入,相当于,13个时间步,一个batchsize,最后得到的输出还是
            outputs.append(vocab[y])
        for _ in range(100):
            y, state = rnn(F.one_hot(get_input(),len(vocab)), state)#用上一个预测值去得到下一个预测值
            outputs.append(int(y.argmax(dim=1).reshape(1)))
        
        print(''.join([vocab.idx_to_token[i] for i in outputs]))
    

    这里需要注意的是,预测输入的词实际上是上一步预测得到的词,所以在这里要一步一步的进行预测。


    在刚开始,我是直接手撸一个RNN,各种权重参数自己设定 ,发现结果并不好,困惑度一直维持在6左右,而用torch提供的RNN困惑度可以降到1.4。看了torch的rnn实现源码后发现可能是没有加tanh激活函数的原因,或者是初始化方式有关。可以看到torch对于rnn所有的权重参数常用相同的初始化方式。

    def reset_parameters(self) -> None:
      stdv = 1.0 / math.sqrt(self.hidden_size)
      for weight in self.parameters():
          init.uniform_(weight, -stdv, stdv)
    

    自己手写了一个RNN:

    class RNN(nn.Module):
        def __init__(self, indim, hidim, outdim):
            super(RNN, self).__init__()
            self.hidim = hidim
            self.W_hh = nn.Parameter(torch.FloatTensor(hidim, hidim))
            self.W_hx = nn.Parameter(torch.FloatTensor(indim, hidim))
            self.W_hy = nn.Parameter(torch.FloatTensor(hidim, outdim))
            self.b_h  = nn.Parameter(torch.FloatTensor(hidim))
            self.b_y = nn.Parameter(torch.FloatTensor(outdim))
            self.reset()
            
        def reset(self):
            stdv = 1.0 / math.sqrt(self.hidim)
            for param in self.parameters():
                nn.init.uniform_(param, -stdv, stdv)
                
        def get_begin_state(self, batch_size, hidim):
            self.begin_state = torch.zeros((batch_size, hidim), dtype=torch.float32)
            return self.begin_state
            
        def forward(self, input, state):
            input = input.type(torch.float32)
            input = input.cuda()
            h = state
            h = h.cuda()
            Y = []
            for x in input: # 每个时间步
                h = F.tanh(x @ self.W_hx + h @ self.W_hh + self.b_h)
                y = h @ self.W_hy + self.b_y
                Y.append(y)
            
            return torch.concat(Y, dim=0), h
    

    最终,我的复杂度完美达到了用torch 提供的rnn实现的效果。问题就出在初始化和激活函数上面。

  • 相关阅读:
    asp.net 发送邮件
    效控制C#中label输出文字的长度,自动换行
    无法连接到WMI 提供程序 请注意,你只能使用SQL Server 配置管理器来管理SQL Server 2005服务器。找不到指定的模块。[0x8007007e]
    查询区分大小写
    ASP.NET母版页引用外部css和js文件的写法
    VS2008 Debugging Breakpoint 补丁
    firefox下获得焦点
    IE打开出现windows找不到文件'(null)'解决方法Vinzipblog文之巴博客
    邪恶的web上下键焦点移动
    jQuery对下拉框Select操作总结
  • 原文地址:https://www.cnblogs.com/kalicener/p/15535805.html
Copyright © 2011-2022 走看看