• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

长短期记忆LSTM

武飞扬头像
tt丫
帮助1

入门小菜鸟,希望像做笔记记录自己学的东西,也希望能帮助到同样入门的人,更希望大佬们帮忙纠错啦~侵权立删。

✨完整代码在我的github上,有需要的朋友可以康康✨

https://github.com/tt-s-t/Deep-Learning.git

目录

一、背景

二、原理

1、前向传播

(1)输入门、遗忘门和输出门

 (2)候选记忆细胞

 (3)记忆细胞

 (4)隐藏状态

 (5)输出

2、反向传播

(1)输出层参数

(2)过渡

(3)候选记忆细胞的参数

(4)输出门的参数

(5)遗忘门的参数

(6)输入门的参数

(7)上一隐藏状态and记忆细胞

三、总结

四、LSTM的优缺点

1、优点

2、缺点

五、LSTM代码实现

1、numpy实现LSTM类

(1)前期准备

(2)初始化参数

(3)前向传播

(4)反向传播

(5)预测

2、调用我们实现的LSTM进行训练与预测

3、结果

一、背景

       当时间步数(T)较大或时间步(t)较小的时候,RNN的梯度较容易出现衰减或爆炸。虽然裁剪梯度可以应对梯度爆炸,但是无法解决梯度衰减的问题。这个原因使得RNN在实际中难以捕捉时间序列中时间步(t)距离较大的依赖关系。因此LSTM应运而生。


二、原理

1、前向传播

输入:当前时间步的输入学新通与上一时间步隐藏状态学新通

(1)输入门、遗忘门和输出门

输入门:学新通

遗忘门:学新通

输出门:学新通

他们都在后面起到一个比例调节的作用。

其中,学新通学新通

学新通为激活函数(sigmoid函数),故取值范围为:[0,1]

n为样本数,d为输入的特征数,h为隐藏大小。

学新通

 (2)候选记忆细胞

   学新通

学新通

 (3)记忆细胞

        当前时间步记忆细胞的计算组合了上一时间步记忆细胞和当前时间步候选记忆细胞的信息。

       学新通

       遗忘门学新通控制上一时间步的记忆细胞学新通中的信息是否传递到当前时间步的记忆细胞,而输出门学新通则控制当前时间步的输入学新通通过候选记忆细胞的学新通如何流入当前时间步的记忆细胞。

       如果遗忘门一直近似为1且输入门一直近似为0,则说明:过去的记忆细胞将一直通过时间保存并传递到当前时间步,而当前输入学新通则被屏蔽掉。

       这个设计可以应对循环神经网络中的梯度衰减问题(可以有选择地对前面的信息进行保留,不至于直接出现指数项),并更好地捕捉时间序列中时间步距离较大的依赖关系(存在学新通中)。 

学新通

 (4)隐藏状态

       我们通过输出门学新通来控制从记忆细胞到隐藏状态学新通的信息流动。

      学新通

       当输出门学新通近似为1时,记忆细胞信息将传递到隐藏状态供输出层使用;近似为0时,记忆细胞信息只自己保留。

学新通

 (5)输出

学新通

2、反向传播

已知学新通(注:*是矩阵乘法,•是矩阵上对应元素相乘)

(1)输出层参数

Note:这里的学新通指的是上一次(即t 1时间步)计算得到的学新通

学新通学新通学新通

(2)过渡

对于链式法则涉及到记忆细胞的,我们设为

学新通

Note:同样的,这里的学新通指的是上一次(即t 1时间步)计算得到的学新通

对于链式法则涉及到候选记忆细胞的,我们设为学新通

对于链式法则涉及到输出门的,我们设为学新通

对于链式法则涉及到遗忘门的,我们设为学新通

对于链式法则涉及到输入门的,我们设为学新通

(3)候选记忆细胞的参数

学新通学新通学新通

(4)输出门的参数

学新通学新通学新通

(5)遗忘门的参数

学新通学新通学新通

(6)输入门的参数

学新通学新通学新通

(7)上一隐藏状态and记忆细胞

学新通

学新通


三、总结

       LSTM 的核心概念在于细胞状态以及“门”结构。细胞状态相当于信息传输的路径,让信息能在序列连中传递下去——即网络的“记忆”。理论上讲,细胞状态能够将序列处理过程中的相关信息一直传递下去。

      因此,即使是较早时间步的信息也能携带到较后时间步的细胞中来,这克服了短时记忆的影响(RNN可能会因为指数项的累积,变得越来越小或大到“面目全非”,LSTM将累积下来的影响由指数运算转化为了加法运算与参数学习控制去留)。信息的添加和移除我们通过“门”结构来实现,“门”结构在训练过程中会去学习该保存或遗忘哪些信息。

每个门起到的作用顾名思义:

     遗忘门:决定什么信息需要从记忆细胞中删除——0:将学新通(过去的记忆)的值删除,1:保留学新通的值;

     输入门:决定输入的哪些新信息(输入信息通过候选记忆细胞传入)需要增加至记忆细胞中;

     输出门:决定从记忆细胞中选出哪些信息进行输出。


四、LSTM的优缺点

1、优点

       继承了大部分RNN模型的特性,同时解决了梯度反传过程由于逐步缩减而产生的梯度衰减问题,使用门控与记忆细胞来学习如何取舍过去的信息,如何提取当前的输入信息。

       具体改进点:RNN这部分的取舍过去信息和提取当前输入信息都是由参数学习得到的(权重参数),结构过于简单,原理也上也有所欠缺(一般我们判断过去信息对现在重不重要也是需要根据过去信息以及当前状态同时决定的,而非直接由一个U和W权重矩阵决定)

2、缺点

(1)并行处理上存在劣势,难以落地;

(2)RNN的梯度问题在LSTM及其变种里面得到了一定程度的解决,但还是不够。它可以处理100个量级的序列,而对于1000个量级,或者更长的序列则依然会很棘手;

(3)模型结构复杂,计算费时。每一个LSTM的cell里面都意味着有4个全连接层(MLP),如果LSTM的时间跨度很大,并且网络又很深,这个计算量会很大,很耗时。


五、LSTM代码实现

       这里只展示我用numpy搭建的LSTM网络,并且实现对“abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz”序列数据的预测。详细地可以在我的github的LSTM文件夹上看,包括用pytorch实现的LSTM实现文本生成,以及这个numpy搭建的LSTM实现对序列数据预测的完整版本。

http://https://github.com/tt-s-t/Deep-Learning.git

首先我们定义一个LSTM类。

1、numpy实现LSTM类

(1)前期准备

  1.  
    import numpy as np
  2.  
     
  3.  
    def sigmoid(x):
  4.  
    x_ravel = x.ravel() # 将numpy数组展平
  5.  
    length = len(x_ravel)
  6.  
    y = []
  7.  
    for index in range(length):
  8.  
    if x_ravel[index] >= 0:
  9.  
    y.append(1.0 / (1 np.exp(-x_ravel[index])))
  10.  
    else:
  11.  
    y.append(np.exp(x_ravel[index]) / (np.exp(x_ravel[index]) 1))
  12.  
    return np.array(y).reshape(x.shape)
  13.  
     
  14.  
    def tanh(x):
  15.  
    result = (np.exp(x)-np.exp(-x))/(np.exp(x) np.exp(-x))
  16.  
    return result
学新通

(2)初始化参数

  1.  
    class LSTM(object):
  2.  
    def __init__(self, input_size, hidden_size):
  3.  
    self.input_size = input_size
  4.  
    self.hidden_size = hidden_size
  5.  
    #输入门
  6.  
    self.Wxi = np.random.randn(input_size, hidden_size)
  7.  
    self.Whi = np.random.randn(hidden_size, hidden_size)
  8.  
    self.B_i = np.zeros((1, hidden_size))
  9.  
    #遗忘门
  10.  
    self.Wxf = np.random.randn(input_size, hidden_size)
  11.  
    self.Whf = np.random.randn(hidden_size, hidden_size)
  12.  
    self.B_f = np.zeros((1, hidden_size))
  13.  
    #输出门
  14.  
    self.Wxo = np.random.randn(input_size, hidden_size)
  15.  
    self.Who = np.random.randn(hidden_size, hidden_size)
  16.  
    self.B_o = np.zeros((1, hidden_size))
  17.  
    #候选记忆细胞
  18.  
    self.Wxc = np.random.randn(input_size, hidden_size)
  19.  
    self.Whc = np.random.randn(hidden_size, hidden_size)
  20.  
    self.B_c = np.zeros((1, hidden_size))
  21.  
    #输出
  22.  
    self.W_hd = np.random.randn(hidden_size, input_size)
  23.  
    self.B_d = np.zeros((1, input_size))
学新通

(3)前向传播

  1.  
    def forward(self,X,Ht_1,Ct_1): #前向传播
  2.  
    #存储
  3.  
    self.it_stack = {} #输入门存储
  4.  
    self.ft_stack = {} #遗忘门存储
  5.  
    self.ot_stack = {} #输出门存储
  6.  
    self.cc_stack = {} #候选记忆细胞存储
  7.  
    self.c_stack = {} #记忆细胞存储
  8.  
    self.X_stack = {} #X存储
  9.  
    self.Ht_stack = {} #隐藏状态存储
  10.  
    self.Y_stack = {} #输出存储
  11.  
     
  12.  
    self.Ht_stack[-1] = Ht_1
  13.  
    self.c_stack[-1] = Ct_1
  14.  
    self.T = X.shape[0]
  15.  
     
  16.  
    for t in range(self.T):
  17.  
    self.X_stack[t] = X[t].reshape(-1,1).T
  18.  
    #输入门
  19.  
    net_i = np.matmul(self.X_stack[t], self.Wxi) np.matmul(self.Ht_stack[t-1], self.Whi) self.B_i
  20.  
    it = sigmoid(net_i)
  21.  
    self.it_stack[t] = it
  22.  
    #遗忘门
  23.  
    net_f = np.matmul(self.X_stack[t], self.Wxf) np.matmul(self.Ht_stack[t-1], self.Whf) self.B_f
  24.  
    ft = sigmoid(net_f)
  25.  
    self.ft_stack[t] = ft
  26.  
    #输出门
  27.  
    net_o = np.matmul(self.X_stack[t], self.Wxo) np.matmul(self.Ht_stack[t-1], self.Who) self.B_o
  28.  
    ot = sigmoid(net_o)
  29.  
    self.ot_stack[t] = ot
  30.  
    #候选记忆细胞
  31.  
    net_cc = np.matmul(self.X_stack[t], self.Wxc) np.matmul(self.Ht_stack[t-1], self.Whc) self.B_c
  32.  
    cct = tanh(net_cc)
  33.  
    self.cc_stack[t] = cct
  34.  
    #记忆细胞
  35.  
    Ct = ft*self.c_stack[t-1] it*cct
  36.  
    self.c_stack[t] = Ct
  37.  
    #隐藏状态
  38.  
    Ht = ot*tanh(Ct)
  39.  
    self.Ht_stack[t] = Ht
  40.  
    #输出
  41.  
    y = np.matmul(Ht, self.W_hd) self.B_d
  42.  
    Yt = np.exp(y) / np.sum(np.exp(y)) #softmax
  43.  
    self.Y_stack[t] = Yt
学新通

(4)反向传播

  1.  
    def backward(self,target,lr):
  2.  
    #初始化
  3.  
    dH_1, dnet_ct_1 = np.zeros([1,self.hidden_size]), np.zeros([1,self.hidden_size])
  4.  
     
  5.  
    dWxi, dWhi, dBi = np.zeros_like(self.Wxi), np.zeros_like(self.Whi), np.zeros_like(self.B_i)
  6.  
    dWxf, dWhf, dBf = np.zeros_like(self.Wxf), np.zeros_like(self.Whf), np.zeros_like(self.B_f)
  7.  
    dWxo, dWho, dBo = np.zeros_like(self.Wxo), np.zeros_like(self.Who), np.zeros_like(self.B_o)
  8.  
    dWxc, dWhc, dBc = np.zeros_like(self.Wxc), np.zeros_like(self.Whc), np.zeros_like(self.B_c)
  9.  
    dWhd,dBd = np.zeros_like(self.W_hd),np.zeros_like(self.B_d)
  10.  
     
  11.  
    self.loss = 0
  12.  
     
  13.  
    for t in reversed(range(self.T)): #反过来开始,越往前面分支越多
  14.  
    dY = self.Y_stack[t] - target[t].reshape(-1,1).T
  15.  
    self.loss = -np.sum(np.log(self.Y_stack[t]) * target[t].reshape(-1,1).T)
  16.  
    #对输出的参数
  17.  
    dWhd = np.matmul(self.Ht_stack[t].T,dY)
  18.  
    dBd = dY
  19.  
     
  20.  
    dH = np.matmul(dY, self.W_hd.T) dH_1 #dH更新
  21.  
     
  22.  
    #对有关输入门,遗忘门,输出门,候选记忆细胞中参数的求导的共同点
  23.  
    temp = tanh(self.c_stack[t])
  24.  
    dnet_ct = dH * self.ot_stack[t] * (1-temp*temp) dnet_ct_1 #记忆细胞
  25.  
    dnet_cct = dnet_ct * self.it_stack[t] * (1 - self.cc_stack[t]*self.cc_stack[t]) #候选记忆细胞
  26.  
    dnet_o = dH * temp * self.ot_stack[t] * (1 - self.ot_stack[t]) #输出门
  27.  
    dnet_f = dnet_ct * self.c_stack[t-1] * self.ft_stack[t] * (1 - self.ft_stack[t]) #遗忘门
  28.  
    dnet_i = dnet_ct * self.cc_stack[t] * self.it_stack[t] * (1 - self.it_stack[t]) #输入门
  29.  
     
  30.  
    #候选记忆细胞中参数
  31.  
    dWxc = np.matmul(self.X_stack[t].T, dnet_cct)
  32.  
    dWhc = np.matmul(self.Ht_stack[t-1].T, dnet_cct)
  33.  
    dBc = dnet_cct
  34.  
     
  35.  
    #输出门
  36.  
    dWxo = np.matmul(self.X_stack[t].T, dnet_o)
  37.  
    dWho = np.matmul(self.Ht_stack[t-1].T, dnet_o)
  38.  
    dBo = dnet_o
  39.  
     
  40.  
    #遗忘门
  41.  
    dWxf = np.matmul(self.X_stack[t].T, dnet_f)
  42.  
    dWhf = np.matmul(self.Ht_stack[t-1].T, dnet_f)
  43.  
    dBf = dnet_f
  44.  
     
  45.  
    #输入门
  46.  
    dWxi = np.matmul(self.X_stack[t].T, dnet_i)
  47.  
    dWhi = np.matmul(self.Ht_stack[t-1].T, dnet_i)
  48.  
    dBi = dnet_i
  49.  
     
  50.  
    #Ht-1和Ct-1
  51.  
    dH_1 = np.matmul(dnet_cct, self.Whc) np.matmul(dnet_i, self.Whi) np.matmul(dnet_f, self.Whf) np.matmul(dnet_o, self.Who)
  52.  
    dnet_ct_1 = dnet_ct * self.ft_stack[t]
  53.  
     
  54.  
    #候选记忆细胞
  55.  
    self.Wxc = -lr * dWxc
  56.  
    self.Whc = -lr * dWhc
  57.  
    self.B_c = -lr * dBc
  58.  
    #输出门
  59.  
    self.Wxo = -lr * dWxo
  60.  
    self.Who = -lr * dWho
  61.  
    self.B_o = -lr * dBo
  62.  
    #遗忘门
  63.  
    self.Wxf = -lr * dWxf
  64.  
    self.Whf = -lr * dWhf
  65.  
    self.B_f = -lr * dBf
  66.  
    #输入门
  67.  
    self.Wxi = -lr * dWxi
  68.  
    self.Whi = -lr * dWhi
  69.  
    self.B_i = -lr * dBi
  70.  
     
  71.  
    return self.loss
学新通

(5)预测

  1.  
    def pre(self,input_onehot,h_prev,c_prev,next_len,vocab): #input_onehot为输入的一个词的onehot编码,next_len为需要生成的单词长度,vocab是"索引-词"的词典
  2.  
    xs, hs, cs = {}, {}, {} #字典形式存储
  3.  
    hs[-1] = np.copy(h_prev) #隐藏状态赋予
  4.  
    cs[-1] = np.copy(c_prev)
  5.  
    xs[0] = input_onehot
  6.  
    pre_vocab = []
  7.  
    for t in range(next_len):
  8.  
    #输入门
  9.  
    net_i = np.matmul(xs[t], self.Wxi) np.matmul(hs[t-1], self.Whi) self.B_i
  10.  
    it = sigmoid(net_i)
  11.  
    #遗忘门
  12.  
    net_f = np.matmul(xs[t], self.Wxf) np.matmul(hs[t-1], self.Whf) self.B_f
  13.  
    ft = sigmoid(net_f)
  14.  
    #输出门
  15.  
    net_o = np.matmul(xs[t], self.Wxo) np.matmul(hs[t-1], self.Who) self.B_o
  16.  
    ot = sigmoid(net_o)
  17.  
    #候选记忆细胞
  18.  
    net_cc = np.matmul(xs[t], self.Wxc) np.matmul(hs[t-1], self.Whc) self.B_c
  19.  
    cct = tanh(net_cc)
  20.  
    #记忆细胞
  21.  
    Ct = ft*cs[t-1] it*cct
  22.  
    cs[t] = Ct
  23.  
    #隐藏状态
  24.  
    Ht = ot*tanh(Ct)
  25.  
    hs[t] = Ht
  26.  
    #输出
  27.  
    Ot = np.matmul(Ht, self.W_hd) self.B_d
  28.  
    Yt = np.exp(Ot) / np.sum(np.exp(Ot)) #softmax
  29.  
    pre_vocab.append(vocab[np.argmax(Yt)])
  30.  
     
  31.  
    xs[t 1] = np.zeros((1, self.input_size)) # init
  32.  
    xs[t 1][0,np.argmax(Yt)] = 1
  33.  
    return pre_vocab
学新通

2、调用我们实现的LSTM进行训练与预测

  1.  
    from lstm_model import LSTM
  2.  
    import numpy as np
  3.  
    import math
  4.  
     
  5.  
    class Dataset(object):
  6.  
    def __init__(self,txt_data, sequence_length):
  7.  
    self.txt_len = len(txt_data) #文本长度
  8.  
    vocab = list(set(txt_data)) #所有字符合集
  9.  
    self.n_vocab = len(vocab) #字典长度
  10.  
    self.sequence_length = sequence_length
  11.  
    self.vocab_to_index = dict((c, i) for i, c in enumerate(vocab)) #词-索引字典
  12.  
    self.index_to_vocab = dict((i, c) for i, c in enumerate(vocab)) #索引-词字典
  13.  
    self.txt_index = [self.vocab_to_index[i] for i in txt_data] #输入文本的索引表示
  14.  
     
  15.  
    def one_hot(self,input):
  16.  
    onehot_encoded = []
  17.  
    for i in input:
  18.  
    letter = [0 for _ in range(self.n_vocab)]
  19.  
    letter[i] = 1
  20.  
    onehot_encoded.append(letter)
  21.  
    onehot_encoded = np.array(onehot_encoded)
  22.  
    return onehot_encoded
  23.  
     
  24.  
    def __getitem__(self, index):
  25.  
    return (
  26.  
    self.txt_index[index:index self.sequence_length],
  27.  
    self.txt_index[index 1:index self.sequence_length 1]
  28.  
    )
  29.  
     
  30.  
    #输入的有规律的序列数据
  31.  
    #txt_data = "abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc"
  32.  
    txt_data = "abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz "
  33.  
     
  34.  
    #config
  35.  
    max_epoch = 5000
  36.  
    sequence_length = 28
  37.  
    dataset = Dataset(txt_data,sequence_length)
  38.  
    batch_num = math.ceil(dataset.txt_len /sequence_length) #向上取整
  39.  
    hidden_size = 32
  40.  
    lr = 1e-3
  41.  
     
  42.  
    model = LSTM(dataset.n_vocab,hidden_size)
  43.  
     
  44.  
    #训练
  45.  
    for epoch in range(max_epoch):
  46.  
    h_prev = np.zeros((1, hidden_size))
  47.  
    c_prev = np.zeros((1, hidden_size))
  48.  
    loss = 0
  49.  
    for b in range(batch_num):
  50.  
    (x,y) = dataset[b]
  51.  
    input = dataset.one_hot(x)
  52.  
    target = dataset.one_hot(y)
  53.  
    ps = model.forward(input,h_prev,c_prev) #注意:每个batch的h都是从0初始化开始,batch与batch间的隐藏状态没有关系
  54.  
    loss = model.backward(target,lr)
  55.  
    print("epoch: ",epoch)
  56.  
    print("loss: ",loss/batch_num)
  57.  
     
  58.  
    #预测
  59.  
    input_txt = 'a'
  60.  
    input_onehot = dataset.one_hot([dataset.vocab_to_index[input_txt]])
  61.  
    next_len = 50 #预测后几个word
  62.  
    h_prev = np.zeros((1, hidden_size))
  63.  
    c_prev = np.zeros((1, hidden_size))
  64.  
    pre_vocab = ['a']
  65.  
    pre_vocab1 = model.pre(input_onehot,h_prev,c_prev,next_len,dataset.index_to_vocab)
  66.  
    pre_vocab = pre_vocab pre_vocab1
  67.  
    print(''.join(pre_vocab))
学新通

3、结果

以a开头预测后续的50个字符。

学新通


欢迎大家在评论区批评指正,谢谢~

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgcecja
系列文章
更多 icon
同类精品
更多 icon
继续加载