科研训练第十七周——关于RNN\LSTM的盘复

LSTM&RNN——关于理论和coding的story

本周的任务主要是盘复这个学期接触到的比较基础的NLP的模型~🐸(#)
第一周任务汇总如下:

  • RNN的理论以及手撕代码
  • LSTM的理论以及手撕代码

1、RNN

1.1 RNN简介

单输出的RNN结构如图所示

在这里插入图片描述

此时输出层的神经元公式为:
y = f ( U x 3 + W h 3 + b ) y=f(Ux_3+Wh_3+b) y=f(Ux3+Wh3+b)

h i = f ( U x i + W h i − 1 + b ) h_i=f(Ux_i+Wh_{i-1}+b) hi=f(Uxi+Whi1+b)

RNN的特点在于整个结构共享1组(U,W,b),f是同一个激活函数,每一个神经元h的计算公式都是由当前的输入x与上一个隐层神经元的输出组成。

它的一些变式:

1、单输入,多输出:通常用于输入一个图像,输出描述该图像的一个文本

2、多输入,多输出

  • 如果输入输出等长(应用范围较小,一般是作诗~好闲😆)
  • 输入输出不等长的话,Seq2Seq!

1.2 Seq2Seq

两种结构:
在这里插入图片描述

第一种格式:

第二种格式:

Seq2Seq的小瑕疵:解码器decoder的输入都是译码器encoder的同一个输出,也就是说无论输入的语句是什么,编码器encoder都会将它转化成同一个中间语意h’.

因为一句话是有重点的,因此注意力机制应运而生。

Seq2Seq的改进

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4e3sVkWf-1641645593005)(E:\NLP_LEARNING\notebook_pic\image-20220104233138898.png)]

上图为基于注意力机制的Seq2Seq:注意力机制下,输入不是直接的序列输入,而是经过编码器encoder转换的中间语意C,而这些输入C也不尽相同,每一个C都是由权重w和译码器的隐藏层输出h加权组成

举个栗子:下图如果C1的重点在‘中’这个字,那么中间语意可以表示为
C 1 = 0.6 h 1 + 0.2 h 2 + 0.3 h 3 + 0.1 h 4 C_1=0.6h_1+0.2h_2+0.3h_3+0.1h_4 C1=0.6h1+0.2h2+0.3h3+0.1h4
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UpHAVdzt-1641645593006)(E:\NLP_LEARNING\notebook_pic\image-20220104234151151.png)]

求中间语意C的权值w的表征可以看作是一个分类的问题,输出的是一个概率值,与hi越接近,输出的概率就越大

在这里插入图片描述

1.3 RNN的代码🍣

麻了,0.65是什么黑洞吖😅,怎么换成asgd还是这个局部最优解😓
max_test_acc_overall: 0.65
max_f1_overall: 0.26262626262626265在这里插入图片描述

class MyRNN(nn.Module):
    def __init__(self,embedding_matrix,opt):
        super(MyRNN,self).__init__()
        self.embed=nn.Embedding.from_pretrained(torch.tensor(embedding_matrix,dtype=torch.float))
        '''RNN的调用'''
        self.rnn=myrnn(opt.embed_dim,opt.hidden_dim)
        self.dense=nn.Linear(opt.hidden_dim,opt.polarities_dim)

    def forward(self,inputs):
        text=inputs[0]
        x=self.embed(text)
        h,y=self.rnn(x)
        #print(y.shape)
        out=self.dense(y)
        #print(out.shape)
        return out

class myrnn(nn.Module):
    def __init__(self,input_size,hidden_size):
        super(myrnn,self).__init__()
        self.input_size=input_size
        self.hidden_size=hidden_size
        # RNN参数
        self.U=nn.Parameter(torch.Tensor(input_size,hidden_size))
        self.W=nn.Parameter(torch.Tensor(hidden_size,hidden_size))
        self.b=nn.Parameter(torch.Tensor(hidden_size))
        self.init_weights()
        # 这个函数捏,可以省略,但是收敛应该会差一些
    def init_weights(self):
        stdv = 1.0 / math.sqrt(self.hidden_size)
        for weight in self.parameters():
            weight.data.uniform_(-stdv, stdv)

    # 前向传播的过程
    def forward(self,inputs,init_state=None):
        batch_size,seq_size,_=inputs.size()
        # 初始化
        if init_state is None:
            h_t, c_t = (
                torch.zeros(batch_size, self.hidden_size).to(inputs.device),
                torch.zeros(batch_size, self.hidden_size).to(inputs.device)
            )
        else:
            h_t, c_t = init_state

        # 对于序列的时间步进行RNN step
        for t in range(seq_size):
            x_t = inputs[:, t, :]
            #print(x_t.shape,self.U.shape,h_t.shape)
            h_t=torch.sigmoid(x_t@self.U+h_t@self.W+self.b)
            y_t = torch.sigmoid(x_t@self.U + h_t@self.W + self.b)



        return h_t,y_t

2、LSTM

LSTM提出的契机: LSTM是基于RNN的缺陷提出的一种方法,因为RNN是共享一组(U,W,b)的结构,在(U,W,b)不变的情况下,梯度在反向传播的过程中,不断连乘,数值不是越来越大就是越来越小,这样就导致__梯度消失__或者__梯度爆炸__的情况

1.1 LSTM的总体结构

在这里插入图片描述

与RNN相比,LSTM的神经元加入了输入门i、遗忘门f、输出门o和内部记忆单元c。

遗忘门f:控制输入x和上一层隐藏层输出h被遗忘的程度大小
f t = s i g m o i d ( W f x t + U f h t − 1 + b f ) f_t=sigmoid(W_fx_t+U_fh_{t-1}+b_f) ft=sigmoid(Wfxt+Ufht1+bf)
输入门i:控制输入x和当前计算的状态更新到记忆单元程度的大小
i t = s i g m o i d ( W i x t + U i h t − 1 + b i ) i_t=sigmoid(W_ix_t+U_ih_{t-1}+b_i) it=sigmoid(Wixt+Uiht1+bi)
内部记忆单元:利用激活函数得到当前内部记忆单元表征c’,再结合前一个结点的内部记忆单元最终表征c得到当前记忆单元的最终表征
c t ′ = T a n h ( W c x t + U c h t − 1 ) c'_t=Tanh(W_cx_t+U_ch_{t-1}) ct=Tanh(Wcxt+Ucht1)

c t = f t c t − 1 + i t c t ′ c_t=f_tc_{t-1}+i_tc'_t ct=ftct1+itct

输出门o:控制输入x和当前输出取决于记忆单元的程度大小
o t = s i g m o i d ( W o x t + U o h t − 1 + b o ) o_t=sigmoid(W_ox_t+U_oh_{t-1}+b_o) ot=sigmoid(Woxt+Uoht1+bo)

h t = o t T a n h ( c t ) h_t=o_tTanh(c_t) ht=otTanh(ct)

**注意:**LSTM的激活函数可以自己调节

1.2 一个利用LSTM实现情感预测的Demo

好叭,其实是论文复现工作的回炉~
Effective LSTMs for Target-Dependent Sentiment Classification

  • 之前这个任务上用的用的是nn.LSTM版本~虽说暑假自己裸着写过莎士比亚诗词生成的LSTM的Demo,但是当时用的是NoteBook(😐加上期末的时候NoteBook环境被玩坏了一直没修,所以相当于重新修练了哇)
  • 改了两天一直收敛不起来,后来调epoch从20到30就修好了,对比试验用nn.LSTM做的,确实20epoch可以收敛(基本上1-5就可以收敛比较完美了),但是换上自己的,可能初始化部分写的还是有问题叭,总之epoch在1-2的时候有loss下降,后来2-20都是在震荡(输出中间结果看了觉得逻辑没有问题🙄,但是损失一直不降)
  • 离谱的是20之后就很快收敛了🤪

model.py

import torch
import torch.nn as nn
import math
class MyLSTM(nn.Module):
    def __init__(self,embedding_matrix,opt):
        super(MyLSTM,self).__init__()
        self.embed=nn.Embedding.from_pretrained(torch.tensor(embedding_matrix,dtype=torch.float))

        '''LSTM的调用'''

        self.lstm=myLstm(opt.embed_dim,opt.hidden_dim)

        self.dense=nn.Linear(opt.hidden_dim,opt.polarities_dim)

    def forward(self,inputs):
        text = inputs[0]
        x = self.embed(text)

        _, (h_n, _) = self.lstm(x)
        
        #print("?",h_n.shape)
        out = self.dense(h_n)
        
        return out
class myLstm(nn.Module):
    def __init__(self, input_sz, hidden_sz):
        super(myLstm,self).__init__()
        self.input_size = input_sz
        self.hidden_size = hidden_sz
        self.U_i = nn.Parameter(torch.Tensor(input_sz, hidden_sz))
        self.V_i = nn.Parameter(torch.Tensor(hidden_sz, hidden_sz))
        self.b_i = nn.Parameter(torch.Tensor(hidden_sz))

        # f_t遗忘门
        self.U_f = nn.Parameter(torch.Tensor(input_sz, hidden_sz))
        self.V_f = nn.Parameter(torch.Tensor(hidden_sz, hidden_sz))
        self.b_f = nn.Parameter(torch.Tensor(hidden_sz))

        # c_t记忆门
        self.U_c = nn.Parameter(torch.Tensor(input_sz, hidden_sz))
        self.V_c = nn.Parameter(torch.Tensor(hidden_sz, hidden_sz))
        self.b_c = nn.Parameter(torch.Tensor(hidden_sz))

        # o_t 输出门
        self.U_o = nn.Parameter(torch.Tensor(input_sz, hidden_sz))
        self.V_o = nn.Parameter(torch.Tensor(hidden_sz, hidden_sz))
        self.b_o = nn.Parameter(torch.Tensor(hidden_sz))

        self.init_weights()
    # 这个函数捏,可以省略,但是收敛应该会差一些
    def init_weights(self):
        stdv = 1.0 / math.sqrt(self.hidden_size)
        for weight in self.parameters():
            weight.data.uniform_(-stdv, stdv)


    def forward(self, x, init_states=None):
        bs, seq_sz, _ = x.size()
        # 64 80 300
        hidden_seq = []

        if init_states is None:
            h_t, c_t = (
                torch.zeros(bs, self.hidden_size).to(x.device),
                torch.zeros(bs, self.hidden_size).to(x.device)
            )
        else:
            h_t, c_t = init_states
        for t in range(seq_sz):
            x_t = x[:, t, :]

            i_t = torch.sigmoid(x_t @ self.U_i + h_t @ self.V_i + self.b_i)
            f_t = torch.sigmoid(x_t @ self.U_f + h_t @ self.V_f + self.b_f)
            g_t = torch.tanh(x_t @ self.U_c + h_t @ self.V_c + self.b_c)
            o_t = torch.sigmoid(x_t @ self.U_o + h_t @ self.V_o + self.b_o)
            c_t = f_t * c_t + i_t * g_t
            h_t = o_t * torch.tanh(c_t)

            hidden_seq.append(h_t.unsqueeze(0))
        hidden_seq = torch.cat(hidden_seq, dim=0)
        hidden_seq = hidden_seq.transpose(0, 1).contiguous()
        return hidden_seq, (h_t, c_t)

搞心态的结果展示:
在这里插入图片描述

看结果的时候一度以为是跑错了模型,就2个epoch的差别太大了哇~🐸

在这里插入图片描述

batch_loss:很明显的断层哇哇哇,没见过这种的!!!😅
在这里插入图片描述

参考

1、阿力阿哩哩模型讲解视频

2、关于LSTM与RNN的一些知识点

3、关于手动造轮子的故事

可以忽视的碎碎念

暑假因为疫情阴差阳错困在了学校大半年,结果刚结束期末考试就飞扑回家啦😃~
然后默默吐槽一下自己一回家就是晚睡晚起的”社畜“状态,放假快一周的内心os居然是想快点回学校恢复正常作息🤣

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值