Long Short-Term Memory

LSTM(Long Short-Term Memory)

LSTM出现背景:由于RNN存在梯度消失的问题,很难处理长序列的数据。为了解决RNN存在问题,后续人们对RNN做了改进,得到了RNN的特例LSTM,它可以避免常规RNN的梯度消失,因此在工业界得到了广泛的应用。
LSTM模型是RNN的变体,它能够学习长期依赖,允许信息长期存在。

举个例子来讲:比如人们读文章的时候,人们会根据已经阅读过的内容来对后面的内容进行理解,不会把之前的东西都丢掉从头进行思考,对内容的理解是贯穿的。
传统的神经网络即RNN做不到这一点,LSTM是具有循环的网络,解决了信息无法长期存在的问题,在工业界普遍使用有良好的效果。

带循环的递归神经网络如下
带循环的递归神经网络

RNN与LSTM之间联系

RNN具有如下的结构,每个序列索引位置t都有一个隐藏状态h(t)。
在这里插入图片描述
如果略去每层都有的o(t),L(t),y(t),则RNN的模型可以简化成如下图的形式:
在这里插入图片描述
可以看出h(t)由x(t)和h(t−1)得到。
得到h(t)后一方面用于当前层的模型损失计算,另一方面用于计算下一层的h(t+1)。

为了避免RNN的梯度消失,LSTM将tanh激活函数转为更为复杂的结构
LSTM的结构如下图:在这里插入图片描述
在下图中,每一行都带有一个向量,该向量从一个节点输出到其他节点的输入。 粉红色圆圈表示点向运算,如向量加法、点乘,而黄色框是学习神经网络层。 线的合并表示连接,而线的交叉表示其内容正在复制,副本将转到不同的位置。

LSTM模型结构

细胞状态

我们可以看出每个序列位置t时刻除了跟RNN一样的隐藏状态h(t),还多了一个穿过图的顶部的长横线
长直线称之为细胞状态(Cell State),记为C(t)。如下图
在这里插入图片描述

遗忘门

遗忘门(forget gate)决定我们会从细胞状态中丢弃什么信息
在LSTM中即以一定的概率控制是否遗忘上一层的隐藏细胞状态。遗忘门子结构如下图所示:
在这里插入图片描述
图中输入的有上一序列的隐藏状态h(t−1)和本序列数据x(t),通过一个激活函数,一般是sigmoid,得到遗忘门的输出f(t)。由于sigmoid的输出f(t)在[0,1]之间,因此这里的输出ft代表了遗忘上一层隐藏细胞状态的概率。用数学表达式即为:
在这里插入图片描述
其中Wf,Uf,bf为线性关系的系数和偏倚,和RNN中的类似。σ为sigmoid激活函数

输入门

输入门它的作用是处理哪部分应该被添加到细胞状态中,也就是为什么被称为部分存储。它的子结构如下图:
在这里插入图片描述
从图中可以看到输入门由两部分组成,第一部分使用了sigmoid激活函数,输出为i(t),第二部分使用了tanh激活函数,输出为a(t), 两者的结果后面会相乘再去更新细胞状态。用数学表达式即为:
在这里插入图片描述
其中Wi,Ui,bi,Wa,Ua,ba,为线性关系的系数和偏倚,和RNN中的类似。σ为sigmoid激活函数。

更新细胞状态

根据遗忘门得到的概率值,丢弃掉一部分之前的细胞状态加上根据输入门得到的新的一部分细胞状态相加,得到此时刻的细胞状态
在这里插入图片描述

输出门

输出门(output gate)由 o(t) 控制,在这一时刻的输出 h(t)和 y(t) 就是由输出门控制的
h(t)的更新由两部分组成,第一部分是o(t), 它由上一序列的隐藏状态h(t−1)和本序列数据x(t),以及激活函数sigmoid得到,第二部分由隐藏状态C(t)和tanh激活函数组成
在这里插入图片描述
简要来说,LSTM 单元能够学习到识别重要输入(输入门作用),存储进长时状态,并保存必要的时间(遗忘门功能),并学会提取当前输出所需要的记忆。

这也解释了 LSTM 单元能够在提取长时序列,长文本,录音等数据中的长期模式的惊人成功的原因。

前向传播算法

一个时刻的前向传播过程如下,对比RNN多了12个参数
在这里插入图片描述
在这里插入图片描述

反向传播算法

反向传播通过梯度下降法迭代更新所有的参数
在这里插入图片描述
如图上的5个不同的阶段反向传播误差

先介绍下各个变量的维度,htht 的维度是黄框里隐藏层神经元的个数,记为d,即矩阵W∗W∗ 的行数(因为WjiWji是输入层的第ii个神经元指向隐藏层第jj个神经元的参数),xtxt的维度记为n,则[ht−1xt][ht−1xt]的维度是d+nd+n,矩阵的维度都是d∗(d+n)d∗(d+n),其他的向量维度都是d∗1d∗1。(为了表示、更新方便,我们将bias放到矩阵里)
以Wf举例:
在这里插入图片描述
同理:
在这里插入图片描述
合并为一个矩阵就是:
在这里插入图片描述
⊙是element-wise乘,即按元素乘。其他的为正常的矩阵乘
用δztδzt表示EtEt对ztzt的偏导
⊗ 表示外积,即列向量乘以行向量

结论

LSTM对比RNN简单来讲就是复杂了每层的计算方式,解决梯度消失,可以用于处理处理长序列的数据,所以在工业界得到了广泛的应用

代码实例

使用keras实现LSTM 情感分析

keras提供一个LSTM层,用它来构造和训练一个多对一的RNN。我们的网络吸收一个序列(词序列)并输出一个情感分析值(正或负)。
训练集源自于kaggle上情感分类竞赛,包含7000个短句 UMICH SI650
每个句子有一个值为1或0的分别用来代替正负情感的标签,这个标签就是我们将要学习预测的。

导入所需库

1
2
3
4
5
6
7
8
9
10
11
from keras.layers.core import Activation, Dense, Dropout, SpatialDropout1D
from keras.layers.embeddings import Embedding
from keras.layers.recurrent import LSTM
from keras.models import Sequential
from keras.preprocessing import sequence
from sklearn.model_selection import train_test_split
import collections
import matplotlib.pyplot as plt
import nltk
import numpy as np
import os

探索性分析

特别地想知道语料中有多少个独立的词以及每个句子包含多少个词:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# Read training data and generate vocabulary
maxlen = 0
word_freqs = collections.Counter()
num_recs = 0
ftrain = open(os.path.join(DATA_DIR, "umich-sentiment-train.txt"), 'rb')
for line in ftrain:
label, sentence = line.strip().split("\t")
words = nltk.word_tokenize(sentence.decode("ascii", "ignore").lower())
if len(words) > maxlen:
maxlen = len(words)
for word in words:
word_freqs[word] += 1
num_recs += 1
ftrain.close()

通过上述代码,我们可以得到语料的值
maxlen: 42
len(word_freqs): 2313
我们将单词总数量设为固定值,并把所有其他词看作字典外的词,这些词全部用伪词unk(unknown)替换,预测时候将未见的词进行替换
句子包含的单词数(maxlen)让我们可以设置一个固定的序列长度,并且用0进行补足短句,把更长的句子截短至合适的长度。
把VOCABULARY_SIZE设置为2002,即源于字典的2000个词,加上伪词UNK和填充词PAD(用来补足句子到固定长度的词)
这里把句子最大长度MAX_SENTENCE_LENGTH定为40

1
2
MAX_FEATURES = 2000
MAX_SENTENCE_LENGTH = 40

下一步我们需要两个查询表,RNN的每一个输入行都是一个词序列索引,索引按训练集中词的使用频度从高到低排序。这两张查询表允许我们通过给定的词来查找索引以及通过给定的索引来查找词。

1
2
3
4
5
6
7
8
# 1 is UNK, 0 is PAD
# We take MAX_FEATURES-1 featurs to accound for PAD
vocab_size = min(MAX_FEATURES, len(word_freqs)) + 2
word2index = {x[0]: i+2 for i, x in
enumerate(word_freqs.most_common(MAX_FEATURES))}
word2index["PAD"] = 0
word2index["UNK"] = 1
index2word = {v:k for k, v in word2index.items()}

接着我们将序列转换成词索引序列

补足MAX_SENTENCE_LENGTH定义的词的长度

因为我们的输出标签是二分类(正负情感)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# convert sentences to sequences
X = np.empty((num_recs, ), dtype=list)
y = np.zeros((num_recs, ))
i = 0
ftrain = open(os.path.join(DATA_DIR, "umich-sentiment-train.txt"), 'rb')
for line in ftrain:
label, sentence = line.strip().split("\t")
words = nltk.word_tokenize(sentence.decode("ascii", "ignore").lower())
seqs = []
for word in words:
if word2index.has_key(word):
seqs.append(word2index[word])
else:
seqs.append(word2index["UNK"])
X[i] = seqs
y[i] = int(label)
i += 1
ftrain.close()

# Pad the sequences (left padded with zeros)
X = sequence.pad_sequences(X, maxlen=MAX_SENTENCE_LENGTH)

划分测试集与训练集

1
2
3
4
# Split input into training and test
Xtrain, Xtest, ytrain, ytest = train_test_split(X, y, test_size=0.2,
random_state=42)
print(Xtrain.shape, Xtest.shape, ytrain.shape, ytest.shape)

训练模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
EMBEDDING_SIZE = 128
HIDDEN_LAYER_SIZE = 64
# 美伦批大小32
BATCH_SIZE = 32
# 网络训练10轮
NUM_EPOCHS = 10

# Build model
model = Sequential()
model.add(Embedding(vocab_size, EMBEDDING_SIZE,
input_length=MAX_SENTENCE_LENGTH))
model.add(SpatialDropout1D(Dropout(0.2)))
model.add(LSTM(HIDDEN_LAYER_SIZE, dropout=0.2, recurrent_dropout=0.2))
model.add(Dense(1))
model.add(Activation("sigmoid"))

model.compile(loss="binary_crossentropy", optimizer="adam",
metrics=["accuracy"])

history = model.fit(Xtrain, ytrain, batch_size=BATCH_SIZE,
epochs=NUM_EPOCHS,
validation_data=(Xtest, ytest))

最后我们在测试集上评估模型并打印出评分和准确率

1
2
3
4
5
6
7
8
9
10
11
# evaluate
score, acc = model.evaluate(Xtest, ytest, batch_size=BATCH_SIZE)
print("Test score: %.3f, accuracy: %.3f" % (score, acc))

for i in range(5):
idx = np.random.randint(len(Xtest))
xtest = Xtest[idx].reshape(1,40)
ylabel = ytest[idx]
ypred = model.predict(xtest)[0][0]
sent = " ".join([index2word[x] for x in xtest[0].tolist() if x != 0])
print("%.0f\t%d\t%s" % (ypred, ylabel, sent))

至此我们使用keras实现lstm的情感分析实例
在此实例中可学习到keras框架的使用、lstm模型搭建、短语句处理方式