• 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实现的效果。问题就出在初始化和激活函数上面。

  • 相关阅读:
    Android读取url图片保存及文件读取
    Adroid解析json
    接口设计的 11 种原则
    检查并创建目录mkdir
    python 替换windows换行符为unix格式
    python中 __name__及__main()__的使用
    python中的urlencode与urldecode
    CentOS Docker 安装
    CentOS基础命令大全
    ubuntu更换阿里源
  • 原文地址:https://www.cnblogs.com/kalicener/p/15535805.html
Copyright © 2020-2023  润新知