循环神经网络RNN 之 LSTM

本篇文章主要是记录RNN和LSTM的网络结构,以及RNN 到 LSTM的发展历程,文末给出一个能跑的demo示例

一. RNN 循环神经网络

1.1 初识单层神经网络

单层的网络结构:输入是x,经过变换Wx+b和激活函数f,得到输出y

 

 在平常的应用中,输入不是一个x序列,二是一排x序列,如 x1,x2,x3,x4....

他们都是有一定的先后顺序的如:

1.语音处理。此时,x1、x2、x3……是每帧的声音信号

 2.nlp问题,文本的输入,x1是第一个word,x2是第二个word,以此类推

3.时间序列问题。例如每天的股票价格,楼市的房价等,有一定先后顺序的

1.2 传统的神经网络

 输入层:可以包含多个神经元,可以接收多维度的信号,特征信息

 隐藏层:可以包含多个神经网络层,每一层可以包含多个神经元

 输出层:可以包含多个神经元,输出多维信息

每一层的神经元和上一层,下一层神经元链接,用于信号传递,传递过程中使用线性变化,输出多维度信息,最后使用激活函数,激活输出的多维度信息,再传递到下一层神经元,至于为什么需要使用激活函数,请大家自行百度哈,这里不准备介绍,也可以参考大神们的解释,https://www.zhihu.com/question/22334626/answer/103835591

总结:对于传统的神经网络,影响效果的参数有,神经元个数,神经元层数,连接方式,还有激活函数。

缺点

从上文中看出,信号流从输入层到输出层依次经过。每一层神经元之间 不会相互传递信息,每一个神经元只与自己本身的输入有关,不会记录输入的序列的先后顺序,比如说一段文本,一句话,“我从小在国外长大,我的英语很好。” 由于从小在国外长大,英语很好,有先后顺序。对于类似的一些任务,有先后顺序的序列,传统的神经网络就无能为力。因此,我们需要构建的神经网络,具有记忆能力,记住历史信息,记住上下文信息。循环神经网络 RNN ,具有这种能力,能记住历史信息,能够有效的处理具有先后顺序的任务,如时间序列等任务。接下来,介绍 RNN 和 他的进阶篇 LSTM

1.2 RNN的结构

RNN 是一种特殊的神经网络结构,其本身包括循环网络,也就是说神经元之间可以相互传递,如下图所示:

 

 输入信号有先后顺序的,和时间序列有关,X0,X1, X2, X3 ... Xt 作为输入的信号特征,X0时刻输入的信号特征信息,输出为h0,且输出的h0 也作为x1的输入参数。计算的公式如下(U、W、b均为模型参数):

\large h_{1}= f(UX_{2} + Wh_{0} + b)      

RNN计算过程中,参数,U、W、b 都是相同的,也就是说每一个步骤中的参数都是共享的

1.3 RNN的弊端

RNN使用神经网络的循环递归 来保留了时间序列的上下文信息,可以使用过去的历史信息来推测对当前信号的理解,并且理论上 RNN 可以保留历史时刻的任何信息,但是实际上结果不是这样的。

例子1:仅仅依据当前信息的前一个时刻或前两个时刻信息来预测 当前的信息,如:我们使用一个语言模型来预测下一个词语。如果我们试着预测“the clouds are in the sky”,中的  sky 。我们不需要借助其他额外的信息来预测当前的 word  是 sky 。语言模型能学习到 当前语义信息,不需要借助前面 N 个单词来推测这个 单词应该是 sky。此前场景中,相关的信息和预测的词位置之间的间隔是非常小的,RNN可以学会使用先前的信息。

例子2:复杂的场景,假设我们试着去预测“I grew up in France...I speak fluent French” 中的最后一个单词  French,当前的信息建议下一个词可能是一种语言的名字,但是如果我们需要弄清楚是什么语言,我们是需要先前提到的离当前位置很远的France的上下文的。这说明相关信息和当前预测位置之间的间隔就肯定变得相当的大。

二. LSTM网络

2.1 初步认识 LSTM网络

LSTM网络是一种RNN特殊的类型,可以学习长期依赖信息。LSTM网络和基线RNN并没有特别大的结构不同,但是它们用了不同的函数来计算隐状态。LSTM的“记忆”我们叫做细胞-cells,可以直接把它们当做黑盒子,这个黑盒的输入为前状态和当前输入。这些 cells 会决定哪些之前的信息和状态需要保留 或者 被抹去。实际的应用中,这种方式可以有效地保存很长时间之前的关联信息。


cell state 表示细胞状态-Ct,用来保存当前LSTM的状态信息传递到下一个时刻的LSTM,上一个时刻的  Ct-1  和当前时刻的LSTM输入共同作用产生 为 当前的 LSTM细胞状态 Ct

2.2  LSTM网络结构讲解

以单个LSTM神经元为例子,着重讲解 输入门,遗忘门,输出门

 图中的符号说明:

黄色方块(Neural Network Layer):表示一个神经网络层

粉色圆圈(Pointwise Operation) : 表示按位操作或逐点操作

单箭头(Vector Transfer): 表示向量的传递,信号传递

合流箭头(Concatenate) :表示信号的连接(向量拼接)

分流箭头(Copy): 表示信号被复制后,传递到其他输入

forget gate - 遗忘门:

有选择性的 忘记一些信息,决定了cell state 中的哪些信息被遗忘

遗忘门 主要是选择性的遗忘某些信息,采用的是sigmoid神经网络层和按照位操作 .sigmoid网络层可以将输入信号转换为0 或者1 之间的数值,决定哪些信号被保留 哪些被去掉。

上面输入 h\large _{t-1} 和 x\large _{t} 输入 得到 f\large _{t}  就是遗忘门了

(1).黄色方块是sigmodi神经网络层(参数为wf  和 bf)

(2).ft 是输出信号,t时刻的输入 xt  和上一个时刻的输出ht-1 进行拼接后 经过sigmoid 神经网络层,得到输出ft,ft的取值范围是[0,1]之间

(3).ft与细胞状态ct-1 进行相乘 ,得到哪些信息保留哪些信息被去掉

举个例子:

ct-1 = [0.5,0.6,0.4]   细胞状态信息 cell state ht-1 = [0.3,0.8,0.69] ,xt = [0.2,1.3,0.7]

[ht-1,ct-1] = [0.3,0.8,0.69,0.2,1.3,0.7] 输出ft = [0.5,0.1,0.8]得到的是和cell state相同维度的输出向量,最终是ft和 Ct-1相乘对细胞进行更新 ,决定上一个cell的信息 哪些被保留,哪些被去掉

memory gate - 记忆门(输入门) :

记忆门的作用与遗忘门相反,它将决定新输入的信息Xt 和 ht-1 哪些被保留

从上图得到,记忆门包括两个部分,一个是黄色方块的sigmoid神经网络层,参数是 Wi 和 bi ,另一个是tanh层,参数为Wc和 bc

Sigmoid网络层,和遗忘门一样,接受Xt 和 ht-1 作为输入,然后输出一个0到1之间的数值 i\large _{t} 来决定哪些信息被保留 哪些被去掉

Tanh网络层,接受Xt 和 ht-1 作为输入,然后通过tanh层来创建一个新的状态\large C_{t}\widetilde{},其数值为[-1,1]之间

遗忘门和记忆门 最终的输出(细胞的状态更新): 

遗忘门的输出 ft 与上一个时刻细胞状态Ct-1 相乘来选择遗忘和保留一些信息,将记忆门的输出和遗忘门的输出信息相加得到新的系统状态 Ct,此时刻细胞状态 Ct 已经包含了此时需要丢弃的t-1时刻的信息,和 t 时刻记忆门得到信息\large i_{t}\large C\widetilde{}_{t}。 新的细胞状态 Ct 将继续传递到下一个时刻 t+1

out gate - 输出门 :

 

下图方便大家理解:

三.双向LSTM网络

RNN和LSTM都只能依据之前时刻的时序信息来预测下一时刻的输出,但在有些问题中,当前时刻的输出不仅和之前的状态有关,还可能和未来的状态有关系

比如预测一句话中缺失的单词不仅需要根据前文来判断,还需要考虑它后面的内容,真正做到基于上下文判断。BRNN有两个RNN上下叠加在一起组成的,输出由这两个LSTM 的状态共同决定。

(1). 前向的LSTM网络依次 输入 “我”、“爱” 、“你” 得到三个向量{\large h_{L0}, \large h_{L1}, \large h_{L2}}

(2). 后向的LSTM网络依次 输入 “你”、“爱” 、“我” 得到三个向量{\large h_{R0}, \large h_{R1}, \large h_{R2}}

(3).将前向和后向进行拼接,得到{ [\large h_{L0},\large h_{R2}] , [\large h_{L1},\large h_{R1}], [\large h_{L2},\large h_{R0}]}, 也就是最后的 { \large h_{0}\large h_{1}, \large h_{2}}

 (4).对于一般的分类任务来说,一般都是获取最后一个token的语义表达,也就是   [\large h_{L2},\large h_{R2}],因为其中已经包含了整个句子的前后所有的信息

 

四.代码例子

4.1  LSTM 例子

import torch
import torch.nn as nn
import torch.optim as optim
import numpy as  np
import pdb
#### batch = 3 seq_len =2,dim = len(word_list)

hidden_size = 256

def  make_batch():
    
    input_batch, target_batch = [],[]
    for sen in sentences:
        word = sen.split()
        input = [ word_dict[item] for item in word[:-1]]
        input_batch.append(np.eye(voc_size)[input])
        target_batch.append(word_dict[word[-1]])

    return input_batch,target_batch


class TextLSTM(nn.Module):
    def __init__(self) -> None:
        super(TextLSTM,self).__init__()

        self.lstm = nn.LSTM(input_size = voc_size,hidden_size = hidden_size)
        self.W = nn.Linear(hidden_size,n_class,bias=False)
        self.b = nn.Parameter(torch.ones(n_class))

    def forward(self,X):
        X = X.transpose(0,1) ### 一开始X shape = [batch,seq_len,dim]  transpose = [seq_len,batch,dim]
        out,(h_n,c_n) = self.lstm(X)  ### out输出的shape = [seq_len,batch,hidden_size]
        # pdb.set_trace()

        outputs = out[-1] ### 然后得到的shape是 [batch,hidden_size]
        
        model = self.W(outputs) + self.b
        return model

if __name__ == '__main__':

    sentences = ["i like dog", "i love coffee", "i hate milk"]
    word_list = ' '.join(sentences).split()
    word_list = list(set(word_list))

    word_dict = {w:i for i,w in enumerate(word_list)}
    name_dict = {i:w for i,w in enumerate(word_list)}
    voc_size = len(word_list)
    n_class = len(word_list)

    input_batch,target_batch = make_batch()

    input_batch = torch.FloatTensor(input_batch)
    target_batch = torch.LongTensor(target_batch)

    model = TextLSTM()

    loss_fuction = nn.CrossEntropyLoss() ##定义损失函数
    optimizer = optim.Adam(model.parameters(), lr=0.001)
    
    for epoch in range(5000):
        optimizer.zero_grad()

        output = model(input_batch)
        loss = loss_fuction(output,target_batch)

        if (epoch + 1)% 1000 == 0:
            print(" Epoch =",'%4d'% (epoch +1),' loss = ','{:4f}'.format(loss))
        loss.backward()
        optimizer.step()
    input = [sen.split()[:2] for sen in sentences]

    predict = model(input_batch).data.max(1, keepdim=True)[1]
    print([sen.split()[:2] for sen in sentences], '->', [name_dict[n.item()] for n in predict.squeeze()])

4.2  BiLSTM 例子

import torch
import torch.nn as nn
import torch.optim as optim
import numpy as  np
#### batch = 3 seq_len =2,dim = len(word_list)

hidden_size = 256

def  make_batch():
    input_batch, target_batch = [],[]
    for sen in sentences:
        word = sen.split()
        input = [ word_dict[item] for item in word[:-1]]
        input_batch.append(np.eye(voc_size)[input])
        target_batch.append(word_dict[word[-1]])

    return input_batch,target_batch


class BiLSTM(nn.Module):
    def __init__(self) -> None:
        super(BiLSTM,self).__init__()
        ###输入是:  input = [seq_len,batch_size,dim]
        ###输出是:  out = [seq_len,batch_size,hidden_size * 2] 
        self.lstm = nn.LSTM(input_size = voc_size,hidden_size = hidden_size,bidirectional=True)
        self.W = nn.Linear(hidden_size * 2,n_class,bias=False)
        self.b = nn.Parameter(torch.ones(n_class))

    def forward(self,X):
        X = X.transpose(0,1) ### 一开始X shape = [batch,seq_len,dim]  transpose = [seq_len,batch,dim]
        out,(_,_) = self.lstm(X)  ### out输出的shape = [seq_len,batch,hidden_size]

        outputs = out[-1] ### 然后得到的shape是 [batch,hidden_size]

        model = self.W(outputs) + self.b
        return model


if __name__ == '__main__':

    sentences = ["i like dog", "i love coffee", "i hate milk"]
    word_list = ' '.join(sentences).split()
    word_list = list(set(word_list))

    word_dict = {w:i for i,w in enumerate(word_list)}
    name_dict = {i:w for i,w in enumerate(word_list)}
    voc_size = len(word_list)
    n_class = len(word_list)

    input_batch,target_batch = make_batch()

    input_batch = torch.FloatTensor(input_batch)
    target_batch = torch.LongTensor(target_batch)

    model = BiLSTM()
    
    loss_fuction = nn.CrossEntropyLoss() ##定义损失函数
    optimizer = optim.Adam(model.parameters(), lr=0.001)
    
    for epoch in range(5000):
        optimizer.zero_grad()

        output = model(input_batch)
        loss = loss_fuction(output,target_batch)

        if (epoch + 1)% 1000 == 0:
            print(" Epoch =",'%4d'% (epoch +1),' loss = ','{:4f}'.format(loss))
        loss.backward()
        optimizer.step()
    input = [sen.split()[:2] for sen in sentences]

    predict = model(input_batch).data.max(1, keepdim=True)[1]
    print([sen.split()[:2] for sen in sentences], '->', [name_dict[n.item()] for n in predict.squeeze()])
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值