# 6.8 長短期記憶(LSTM)
本節將介紹另一種常用的門控循環神經網絡:長短期記憶(long short-term memory,LSTM)[1]。它比門控循環單元的結構稍微復雜一點。
## 6.8.1 長短期記憶
LSTM 中引入了3個門,即輸入門(input gate)、遺忘門(forget gate)和輸出門(output gate),以及與隱藏狀態形狀相同的記憶細胞(某些文獻把記憶細胞當成一種特殊的隱藏狀態),從而記錄額外的信息。
### 6.8.1.1 輸入門、遺忘門和輸出門
與門控循環單元中的重置門和更新門一樣,如圖6.7所示,長短期記憶的門的輸入均為當前時間步輸入`$ \boldsymbol{X}_t $` 與上一時間步隱藏狀態`$ \boldsymbol{H}_{t-1} $`,輸出由激活函數為sigmoid函數的全連接層計算得到。如此一來,這3個門元素的值域均為`$ [0,1] $`。
:-: 
<div align=center>圖6.7 長短期記憶中輸入門、遺忘門和輸出門的計算</div>
具體來說,假設隱藏單元個數為`$ h $`,給定時間步`$ t $`的小批量輸入`$ \boldsymbol{X}_t \in \mathbb{R}^{n \times d} $`(樣本數為`$ n $`,輸入個數為`$ d $`)和上一時間步隱藏狀態`$ \boldsymbol{H}_{t-1} \in \mathbb{R}^{n \times h} $` 。
時間步`$ t $`的輸入門`$ \boldsymbol{I}_t \in \mathbb{R}^{n \times h} $`、遺忘門`$ \boldsymbol{F}_t \in \mathbb{R}^{n \times h} $`和輸出門`$ \boldsymbol{O}_t \in \mathbb{R}^{n \times h} $`分別計算如下:
```[tex]
\begin{aligned}
\boldsymbol{I}_t &= \sigma(\boldsymbol{X}_t \boldsymbol{W}_{xi} + \boldsymbol{H}_{t-1} \boldsymbol{W}_{hi} + \boldsymbol{b}_i),\\
\boldsymbol{F}_t &= \sigma(\boldsymbol{X}_t \boldsymbol{W}_{xf} + \boldsymbol{H}_{t-1} \boldsymbol{W}_{hf} + \boldsymbol{b}_f),\\
\boldsymbol{O}_t &= \sigma(\boldsymbol{X}_t \boldsymbol{W}_{xo} + \boldsymbol{H}_{t-1} \boldsymbol{W}_{ho} + \boldsymbol{b}_o),
\end{aligned}
```
其中的`$ \boldsymbol{W}_{xi}, \boldsymbol{W}_{xf}, \boldsymbol{W}_{xo} \in \mathbb{R}^{d \times h} $`和`$ \boldsymbol{W}_{hi}, \boldsymbol{W}_{hf}, \boldsymbol{W}_{ho} \in \mathbb{R}^{h \times h} $`是權重參數,`$ \boldsymbol{b}_i, \boldsymbol{b}_f, \boldsymbol{b}_o \in \mathbb{R}^{1 \times h} $`是偏差參數。
### 6.8.1.2 候選記憶細胞
接下來,長短期記憶需要計算候選記憶細胞`$ \tilde{\boldsymbol{C}}_t $` 。它的計算與上面介紹的3個門類似,但使用了值域在`$ [-1, 1] $`的tanh函數作為激活函數,如圖6.8所示。
:-: 
<div align=center>圖6.8 長短期記憶中候選記憶細胞的計算</div>
具體來說,時間步`$ t $`的候選記憶細胞`$ \tilde{\boldsymbol{C}}_t \in \mathbb{R}^{n \times h} $` 的計算為
```[tex]
\tilde{\boldsymbol{C}}_t = \text{tanh}(\boldsymbol{X}_t \boldsymbol{W}_{xc} + \boldsymbol{H}_{t-1} \boldsymbol{W}_{hc} + \boldsymbol{b}_c),
```
其中`$ \boldsymbol{W}_{xc} \in \mathbb{R}^{d \times h} $`和`$ \boldsymbol{W}_{hc} \in \mathbb{R}^{h \times h} $`是權重參數,`$ \boldsymbol{b}_c \in \mathbb{R}^{1 \times h} $`是偏差參數。
### 6.8.1.3 記憶細胞
我們可以通過元素值域在`$ [0, 1] $`的輸入門、遺忘門和輸出門來控制隱藏狀態中信息的流動,這一般也是通過使用按元素乘法(符號為`$ \odot $`)來實現的。當前時間步記憶細胞`$ \boldsymbol{C}_t \in \mathbb{R}^{n \times h} $`的計算組合了上一時間步記憶細胞和當前時間步候選記憶細胞的信息,并通過遺忘門和輸入門來控制信息的流動:
```[tex]
\boldsymbol{C}_t = \boldsymbol{F}_t \odot \boldsymbol{C}_{t-1} + \boldsymbol{I}_t \odot \tilde{\boldsymbol{C}}_t.
```
如圖6.9所示,遺忘門控制上一時間步的記憶細胞`$ \boldsymbol{C}_{t-1} $`中的信息是否傳遞到當前時間步,而輸入門則控制當前時間步的輸入`$ \boldsymbol{X}_t $`通過候選記憶細胞`$ \tilde{\boldsymbol{C}}_t $`如何流入當前時間步的記憶細胞。如果遺忘門一直近似1且輸入門一直近似0,過去的記憶細胞將一直通過時間保存并傳遞至當前時間步。這個設計可以應對循環神經網絡中的梯度衰減問題,并更好地捕捉時間序列中時間步距離較大的依賴關系。
:-: 
<div align=center>圖6.9 長短期記憶中記憶細胞的計算</div>
### 6.8.1.4 隱藏狀態
有了記憶細胞以后,接下來我們還可以通過輸出門來控制從記憶細胞到隱藏狀態`$ \boldsymbol{H}_t \in \mathbb{R}^{n \times h} $`的信息的流動:
```[tex]
\boldsymbol{H}_t = \boldsymbol{O}_t \odot \text{tanh}(\boldsymbol{C}_t).
```
這里的tanh函數確保隱藏狀態元素值在-1到1之間。需要注意的是,當輸出門近似1時,記憶細胞信息將傳遞到隱藏狀態供輸出層使用;當輸出門近似0時,記憶細胞信息只自己保留。圖6.10展示了長短期記憶中隱藏狀態的計算。
:-: 
<div align=center>圖6.10 長短期記憶中隱藏狀態的計算</div>
## 6.8.2 讀取數據集
下面我們開始實現并展示長短期記憶。和前幾節中的實驗一樣,這里依然使用周杰倫歌詞數據集來訓練模型作詞。
``` python
import numpy as np
import torch
from torch import nn, optim
import torch.nn.functional as F
import sys
sys.path.append("..")
import d2lzh_pytorch as d2l
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
(corpus_indices, char_to_idx, idx_to_char, vocab_size) = d2l.load_data_jay_lyrics()
```
## 6.8.3 從零開始實現
我們先介紹如何從零開始實現長短期記憶。
### 6.8.3.1 初始化模型參數
下面的代碼對模型參數進行初始化。超參數`num_hiddens`定義了隱藏單元的個數。
``` python
num_inputs, num_hiddens, num_outputs = vocab_size, 256, vocab_size
print('will use', device)
def get_params():
def _one(shape):
ts = torch.tensor(np.random.normal(0, 0.01, size=shape), device=device, dtype=torch.float32)
return torch.nn.Parameter(ts, requires_grad=True)
def _three():
return (_one((num_inputs, num_hiddens)),
_one((num_hiddens, num_hiddens)),
torch.nn.Parameter(torch.zeros(num_hiddens, device=device, dtype=torch.float32), requires_grad=True))
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 = torch.nn.Parameter(torch.zeros(num_outputs, device=device, dtype=torch.float32), requires_grad=True)
return nn.ParameterList([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])
```
## 6.8.4 定義模型
在初始化函數中,長短期記憶的隱藏狀態需要返回額外的形狀為(批量大小, 隱藏單元個數)的值為0的記憶細胞。
``` python
def init_lstm_state(batch_size, num_hiddens, device):
return (torch.zeros((batch_size, num_hiddens), device=device),
torch.zeros((batch_size, num_hiddens), device=device))
```
下面根據長短期記憶的計算表達式定義模型。需要注意的是,只有隱藏狀態會傳遞到輸出層,而記憶細胞不參與輸出層的計算。
``` python
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 = torch.sigmoid(torch.matmul(X, W_xi) + torch.matmul(H, W_hi) + b_i)
F = torch.sigmoid(torch.matmul(X, W_xf) + torch.matmul(H, W_hf) + b_f)
O = torch.sigmoid(torch.matmul(X, W_xo) + torch.matmul(H, W_ho) + b_o)
C_tilda = torch.tanh(torch.matmul(X, W_xc) + torch.matmul(H, W_hc) + b_c)
C = F * C + I * C_tilda
H = O * C.tanh()
Y = torch.matmul(H, W_hq) + b_q
outputs.append(Y)
return outputs, (H, C)
```
### 6.8.4.1 訓練模型并創作歌詞
同上一節一樣,我們在訓練模型時只使用相鄰采樣。設置好超參數后,我們將訓練模型并根據前綴“分開”和“不分開”分別創作長度為50個字符的一段歌詞。
``` python
num_epochs, num_steps, batch_size, lr, clipping_theta = 160, 35, 32, 1e2, 1e-2
pred_period, pred_len, prefixes = 40, 50, ['分開', '不分開']
```
我們每過40個迭代周期便根據當前訓練的模型創作一段歌詞。
``` python
d2l.train_and_predict_rnn(lstm, get_params, init_lstm_state, num_hiddens,
vocab_size, device, corpus_indices, idx_to_char,
char_to_idx, False, num_epochs, num_steps, lr,
clipping_theta, batch_size, pred_period, pred_len,
prefixes)
```
輸出:
```
epoch 40, perplexity 211.416571, time 1.37 sec
- 分開 我不的我 我不的我 我不的我 我不的我 我不的我 我不的我 我不的我 我不的我 我不的我 我不的我
- 不分開 我不的我 我不的我 我不的我 我不的我 我不的我 我不的我 我不的我 我不的我 我不的我 我不的我
epoch 80, perplexity 67.048346, time 1.35 sec
- 分開 我想你你 我不要再想 我不要這我 我不要這我 我不要這我 我不要這我 我不要這我 我不要這我 我不
- 不分開 我想你你想你 我不要這不樣 我不要這我 我不要這我 我不要這我 我不要這我 我不要這我 我不要這我
epoch 120, perplexity 15.552743, time 1.36 sec
- 分開 我想帶你的微笑 像這在 你想我 我想你 說你我 說你了 說給怎么么 有你在空 你在在空 在你的空
- 不分開 我想要你已經堡 一樣樣 說你了 我想就這樣著你 不知不覺 你已了離開活 后知后覺 我該了這生活 我
epoch 160, perplexity 4.274031, time 1.35 sec
- 分開 我想帶你 你不一外在半空 我只能夠遠遠著她 這些我 你想我難難頭 一話看人對落我一望望我 我不那這
- 不分開 我想你這生堡 我知好煩 你不的節我 后知后覺 我該了這節奏 后知后覺 又過了一個秋 后知后覺 我該
```
## 6.8.5 簡潔實現
在Gluon中我們可以直接調用`rnn`模塊中的`LSTM`類。
``` python
lr = 1e-2 # 注意調整學習率
lstm_layer = nn.LSTM(input_size=vocab_size, hidden_size=num_hiddens)
model = d2l.RNNModel(lstm_layer, vocab_size)
d2l.train_and_predict_rnn_pytorch(model, num_hiddens, vocab_size, device,
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 1.020401, time 1.54 sec
- 分開始想擔 媽跟我 一定是我媽在 因為分手前那句抱歉 在感動 穿梭時間的畫面的鐘 從反方向開始移動 回到
- 不分開始想像 媽跟我 我將我的寂寞封閉 然后在這里 不限日期 然后將過去 慢慢溫習 讓我愛上你 那場悲劇
epoch 80, perplexity 1.011164, time 1.34 sec
- 分開始想擔 你的 從前的可愛女人 溫柔的讓我心疼的可愛女人 透明的讓我感動的可愛女人 壞壞的讓我瘋狂的可
- 不分開 我滿了 讓我瘋狂的可愛女人 漂亮的讓我面紅的可愛女人 溫柔的讓我心疼的可愛女人 透明的讓我感動的可
epoch 120, perplexity 1.025348, time 1.39 sec
- 分開始共渡每一天 手牽手 一步兩步三步四步望著天 看星星 一顆兩顆三顆四顆 連成線背著背默默許下心愿 看
- 不分開 我不懂 說了沒用 他的笑容 有何不同 在你心中 我不再受寵 我的天空 是雨是風 還是彩虹 你在操縱
epoch 160, perplexity 1.017492, time 1.42 sec
- 分開始鄉相信命運 感謝地心引力 讓我碰到你 漂亮的讓我面紅的可愛女人 溫柔的讓我心疼的可愛女人 透明的讓
- 不分開 我不能再想 我不 我不 我不能 愛情走的太快就像龍卷風 不能承受我已無處可躲 我不要再想 我不要再
```
## 小結
* 長短期記憶的隱藏層輸出包括隱藏狀態和記憶細胞。只有隱藏狀態會傳遞到輸出層。
* 長短期記憶的輸入門、遺忘門和輸出門可以控制信息的流動。
* 長短期記憶可以應對循環神經網絡中的梯度衰減問題,并更好地捕捉時間序列中時間步距離較大的依賴關系。
## 參考文獻
[1] Hochreiter, S., & Schmidhuber, J. (1997). Long short-term memory. Neural computation, 9(8), 1735-1780.
-----------
> 注:除代碼外本節與原書此節基本相同,[原書傳送門](https://zh.d2l.ai/chapter_recurrent-neural-networks/lstm.html)
- Home
- Introduce
- 1.深度學習簡介
- 深度學習簡介
- 2.預備知識
- 2.1環境配置
- 2.2數據操作
- 2.3自動求梯度
- 3.深度學習基礎
- 3.1 線性回歸
- 3.2 線性回歸的從零開始實現
- 3.3 線性回歸的簡潔實現
- 3.4 softmax回歸
- 3.5 圖像分類數據集(Fashion-MINST)
- 3.6 softmax回歸的從零開始實現
- 3.7 softmax回歸的簡潔實現
- 3.8 多層感知機
- 3.9 多層感知機的從零開始實現
- 3.10 多層感知機的簡潔實現
- 3.11 模型選擇、反向傳播和計算圖
- 3.12 權重衰減
- 3.13 丟棄法
- 3.14 正向傳播、反向傳播和計算圖
- 3.15 數值穩定性和模型初始化
- 3.16 實戰kaggle比賽:房價預測
- 4 深度學習計算
- 4.1 模型構造
- 4.2 模型參數的訪問、初始化和共享
- 4.3 模型參數的延后初始化
- 4.4 自定義層
- 4.5 讀取和存儲
- 4.6 GPU計算
- 5 卷積神經網絡
- 5.1 二維卷積層
- 5.2 填充和步幅
- 5.3 多輸入通道和多輸出通道
- 5.4 池化層
- 5.5 卷積神經網絡(LeNet)
- 5.6 深度卷積神經網絡(AlexNet)
- 5.7 使用重復元素的網絡(VGG)
- 5.8 網絡中的網絡(NiN)
- 5.9 含并行連結的網絡(GoogLeNet)
- 5.10 批量歸一化
- 5.11 殘差網絡(ResNet)
- 5.12 稠密連接網絡(DenseNet)
- 6 循環神經網絡
- 6.1 語言模型
- 6.2 循環神經網絡
- 6.3 語言模型數據集(周杰倫專輯歌詞)
- 6.4 循環神經網絡的從零開始實現
- 6.5 循環神經網絡的簡單實現
- 6.6 通過時間反向傳播
- 6.7 門控循環單元(GRU)
- 6.8 長短期記憶(LSTM)
- 6.9 深度循環神經網絡
- 6.10 雙向循環神經網絡
- 7 優化算法
- 7.1 優化與深度學習
- 7.2 梯度下降和隨機梯度下降
- 7.3 小批量隨機梯度下降
- 7.4 動量法
- 7.5 AdaGrad算法
- 7.6 RMSProp算法
- 7.7 AdaDelta
- 7.8 Adam算法
- 8 計算性能
- 8.1 命令式和符號式混合編程
- 8.2 異步計算
- 8.3 自動并行計算
- 8.4 多GPU計算
- 9 計算機視覺
- 9.1 圖像增廣
- 9.2 微調
- 9.3 目標檢測和邊界框
- 9.4 錨框
- 10 自然語言處理
- 10.1 詞嵌入(word2vec)
- 10.2 近似訓練
- 10.3 word2vec實現
- 10.4 子詞嵌入(fastText)
- 10.5 全局向量的詞嵌入(Glove)
- 10.6 求近義詞和類比詞
- 10.7 文本情感分類:使用循環神經網絡
- 10.8 文本情感分類:使用卷積網絡
- 10.9 編碼器--解碼器(seq2seq)
- 10.10 束搜索
- 10.11 注意力機制
- 10.12 機器翻譯