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的分別用來代替正負情感的標籤,這個標籤就是我們將要學習預測的。
導入所需庫
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
探索性分析
特別地想知道語料中有多少個獨立的詞以及每個句子包含多少個詞:
# 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
MAX_FEATURES = 2000
MAX_SENTENCE_LENGTH = 40
下一步我們需要兩個查詢表,RNN的每一個輸入行都是一個詞序列索引,索引按訓練集中詞的使用頻度從高到低排序。這兩張查詢表允許我們通過給定的詞來查找索引以及通過給定的索引來查找詞。
# 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定義的詞的長度
因爲我們的輸出標籤是二分類(正負情感)
# 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)
劃分測試集與訓練集
# 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)
訓練模型
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))
最後我們在測試集上評估模型並打印出評分和準確率
# 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模型搭建、短語句處理方式