长短期记忆(Long Short Term Memory)跟上篇的循环神经网络(RNN)之门控循环单元(GRU),两者比较的话,LSTM稍微更复杂一点,但很类似,对整个隐藏单元来讲可以说增加了一个记忆细胞的新概念,这样可以记录更多的额外信息,除了应对RNN中梯度衰减的问题,还能够更好地捕捉时间序列中时间步距离较大的依赖关系。
        长短期记忆顾名思义就是将长期和短期的特征都要捕捉到,在时间序列中,那就是长的时间步的一些有用的信息我要保留下来,短的时间步筛选掉一些无关无用的信息,其中记忆就是类似人的大脑记忆,抽象成记忆细胞进行筛选然后进行输出。

继续画图来看下长短期记忆的计算图:

Python长短时记忆神经网路程序 长短期记忆神经网络_时间序列

 

我们可以看出,在这个单元里面的记忆细胞最后做一个tanh激活(值域在[-1,1]),然后跟输出门按元素乘法,作为最后的输出,这个记忆细胞没有参与到输出层的计算。
遗忘门:控制上一时间步的记忆细胞的信息是否传递到当前时间步
输入门:控制当前时间步的输入,通过候选记忆细胞如何流入当前时间步的记忆细胞
如果遗忘门一直近似1且输入门一直近似0,过去的记忆细胞将一直通过时间保存并传递至当前时间步。

代码实现:

import d2lzh as d2l
from mxnet import nd
from mxnet.gluon import rnn

(corpus_indices,char_to_idx,idx_to_char,vocab_size)=d2l.load_data_jay_lyrics()
num_inputs,num_hiddens,num_outputs=vocab_size,256,vocab_size
ctx=d2l.try_gpu()
#ctx=None

def get_params():
    def _one(shape):
        return nd.random.normal(scale=0.01,shape=shape,ctx=ctx)
    def _three():
        return (_one((num_inputs,num_hiddens)),_one((num_hiddens,num_hiddens)),nd.zeros(num_hiddens,ctx=ctx))
    
    W_xi,W_hi,b_i=_three()#输入门参数
    W_xf,W_hf,b_f=_three()#遗忘门参数
    W_xo,W_ho,b_o=_three()#输出门参数
    W_xc,W_hc,b_c=_three()#候选记忆细胞参数
    #输出层参数
    W_hq=_one((num_hiddens,num_outputs))
    b_q=nd.zeros(num_outputs,ctx=ctx)
    #附上梯度
    params=[W_xi,W_hi,b_i,W_xf,W_hf,b_f,W_xo,W_ho,b_o,W_xc,W_hc,b_c,W_hq,b_q]
    for p in params:
        p.attach_grad()
    return params

#定义模型
#隐藏状态初始化函数,需要返回记忆细胞,比较门控RNN多一个
def init_lstm_state(batch_size,num_hiddens,ctx):
    return (nd.zeros(shape=(batch_size,num_hiddens),ctx=ctx),
            nd.zeros(shape=(batch_size,num_hiddens),ctx=ctx))

def lstm(inputs,state,params):
    [W_xi,W_hi,b_i,W_xf,W_hf,b_f,W_xo,W_ho,b_o,W_xc,W_hc,b_c,W_hq,b_q]=params
    (H,C)=state #上一时间步的隐藏状态和记忆细胞
    outputs=[]
    for X in inputs:
        I=nd.sigmoid(nd.dot(X,W_xi)+nd.dot(H,W_hi)+b_i)
        F=nd.sigmoid(nd.dot(X,W_xf)+nd.dot(H,W_hf)+b_f)
        O=nd.sigmoid(nd.dot(X,W_xo)+nd.dot(H,W_ho)+b_o)
        C_tilda=nd.tanh(nd.dot(X,W_xc)+nd.dot(H,W_hc)+b_c)
        C=F*C+I*C_tilda#成了当前时间步的记忆细胞
        H=O*C.tanh()
        Y=nd.dot(H,W_hq)+b_q
        outputs.append(Y)
    return outputs,(H,C)

#训练模型(相邻采样)
num_epochs,num_steps,batch_size,lr,clipping_theta=160,35,32,1e2,1e2-2
pred_period,pred_len,prefixes=40,50,['分开','不分开']
#d2l.train_and_predict_rnn(lstm,get_params,init_lstm_state,num_hiddens,vocab_size,ctx,corpus_indices,idx_to_char,char_to_idx,False,num_epochs,num_steps,lr,clipping_theta,batch_size,pred_period,pred_len,prefixes)


#简洁实现
lstm_layer=rnn.LSTM(num_hiddens)
model=d2l.RNNModel(lstm_layer,vocab_size)
d2l.train_and_predict_rnn_gluon(model,num_hiddens,vocab_size,ctx,corpus_indices,idx_to_char,char_to_idx,num_epochs,num_steps,lr,clipping_theta,batch_size,pred_period,pred_len,prefixes)

epoch 40, perplexity 156.775626, time 0.10 sec
 - 分开 我想你的让我 我想你 我不不 我想你的让我 我想你你的让我 我想你你的让我 我想你你的让我 我想你
 - 不分开 我想你的让我 我想你 我不不 我想你的让我 我想你你的让我 我想你你的让我 我想你你的让我 我想你
epoch 80, perplexity 32.347565, time 0.10 sec
 - 分开 我想要这样的让我 想要你这样 我不要再想你 我不要再想你 我不要这样 我不要再想你 我不要这样 我
 - 不分开 你在我遇口你 让我 这样的微笑 你说 却想你的微笑 像思在美不口 让你在我遇多 你爱在美人人 让我
epoch 120, perplexity 4.793431, time 0.10 sec
 - 分开 我想要这样牵着你的手不放开 爱可不可以简简单 没有你在 其实我 别发  爱情的甜蜜 让我开始乡的怒
 - 不分开  没有你烦我有多烦 难道你的手 让我开红乡 你已经离 为过了一个秋 后知后觉 我该好好生活 我该好
epoch 160, perplexity 1.435368, time 0.10 sec
 - 分开 我感轻的生息 随悔着对不起 藤蔓植物 爬满了伯爵的坟墓蛇藏藏无远 看远 有AB血型的公老鼠 恍恍惚
 - 不分开恼  没有你烦我有多烦恼多难熬  穿过云层 我试著努力向你奔跑 爱才送到 你却已在别人怀抱 就是开不
epoch 200, perplexity 1.066441, time 0.10 sec
 - 分开 我想要这样 我想著你 说对去离开嘛妈妈    你的黑色幽默 嘴嘟嘟那可爱的模样 还有在你身上香香的
 - 不分开觉 你已经离开我 不知不觉 我跟了这节奏 后知后觉 后知后觉 迷迷蒙蒙 你给的梦 出现裂缝 隐隐作痛