數學原理
多層 RNN 的核心思想是堆疊多個 RNN 層,每一層的輸出作為下一層的輸入,從而逐層提取更高層次的抽象特征。
1. 單層 RNN 的數學表示
首先,單層 RNN 的計算過程如下。對于一個時間步 t t t,單層 RNN 的隱藏狀態 h t h_t ht? 和輸出 y t y_t yt? 可以表示為:
h t = activation ( W i h x t + b i h + W h h h t ? 1 + b h h ) h_t = \text{activation}(W_{ih} x_t + b_{ih} + W_{hh} h_{t-1} + b_{hh}) ht?=activation(Wih?xt?+bih?+Whh?ht?1?+bhh?)
y t = W h o h t + b h o y_t = W_{ho} h_t + b_{ho} yt?=Who?ht?+bho?
其中:
- x t x_t xt? 是時間步 t t t 的輸入。
- h t h_t ht? 是時間步 t t t 的隱藏狀態。
- h t ? 1 h_{t-1} ht?1? 是時間步 t ? 1 t-1 t?1 的隱藏狀態。
- W i h W_{ih} Wih?、 W h h W_{hh} Whh?、 W h o W_{ho} Who? 是權重矩陣。
- b i h b_{ih} bih?、 b h h b_{hh} bhh?、 b h o b_{ho} bho? 是偏置項。
- activation \text{activation} activation 是激活函數(如 tanh ? \tanh tanh 或 ReLU \text{ReLU} ReLU)。
2. 多層 RNN 的數學表示
假設我們有一個 L L L 層的 RNN,每一層的隱藏狀態為 h t ( l ) h_t^{(l)} ht(l)?,其中 l l l 表示第 l l l 層, t t t 表示時間步。多層 RNN 的計算過程如下:
(1) 第一層( l = 1 l = 1 l=1)
第一層的輸入是原始輸入序列 x t x_t xt?,隱藏狀態 h t ( 1 ) h_t^{(1)} ht(1)? 的計算公式為:
h t ( 1 ) = activation ( W i h ( 1 ) x t + b i h ( 1 ) + W h h ( 1 ) h t ? 1 ( 1 ) + b h h ( 1 ) ) h_t^{(1)} = \text{activation}(W_{ih}^{(1)} x_t + b_{ih}^{(1)} + W_{hh}^{(1)} h_{t-1}^{(1)} + b_{hh}^{(1)}) ht(1)?=activation(Wih(1)?xt?+bih(1)?+Whh(1)?ht?1(1)?+bhh(1)?)
其中:
- W i h ( 1 ) W_{ih}^{(1)} Wih(1)?、 W h h ( 1 ) W_{hh}^{(1)} Whh(1)? 是第一層的權重矩陣。
- b i h ( 1 ) b_{ih}^{(1)} bih(1)?、 b h h ( 1 ) b_{hh}^{(1)} bhh(1)? 是第一層的偏置項。
(2) 第 l l l 層( l > 1 l > 1 l>1)
第 l l l 層的輸入是第 l ? 1 l-1 l?1 層的輸出 h t ( l ? 1 ) h_t^{(l-1)} ht(l?1)?,隱藏狀態 h t ( l ) h_t^{(l)} ht(l)? 的計算公式為:
h t ( l ) = activation ( W i h ( l ) h t ( l ? 1 ) + b i h ( l ) + W h h ( l ) h t ? 1 ( l ) + b h h ( l ) ) h_t^{(l)} = \text{activation}(W_{ih}^{(l)} h_t^{(l-1)} + b_{ih}^{(l)} + W_{hh}^{(l)} h_{t-1}^{(l)} + b_{hh}^{(l)}) ht(l)?=activation(Wih(l)?ht(l?1)?+bih(l)?+Whh(l)?ht?1(l)?+bhh(l)?)
其中:
- W i h ( l ) W_{ih}^{(l)} Wih(l)?、 W h h ( l ) W_{hh}^{(l)} Whh(l)? 是第 l l l 層的權重矩陣。
- b i h ( l ) b_{ih}^{(l)} bih(l)?、 b h h ( l ) b_{hh}^{(l)} bhh(l)? 是第 l l l 層的偏置項。
(3) 輸出層
最后一層(第 L L L 層)的輸出 h t ( L ) h_t^{(L)} ht(L)? 作為整個網絡的輸出 y t y_t yt?:
y t = W h o h t ( L ) + b h o y_t = W_{ho} h_t^{(L)} + b_{ho} yt?=Who?ht(L)?+bho?
其中:
- W h o W_{ho} Who?、 b h o b_{ho} bho? 是輸出層的權重矩陣和偏置項。
3. 多層 RNN 的數據流向
以下是一個 L L L 層 RNN 的數據流向的數學描述:
(1) 輸入序列
輸入序列為 x 1 , x 2 , … , x T x_1, x_2, \dots, x_T x1?,x2?,…,xT?,其中 T T T 是序列長度。
(2) 初始化隱藏狀態
每一層的初始隱藏狀態 h 0 ( l ) h_0^{(l)} h0(l)? 通常初始化為零或隨機值:
h 0 ( l ) = 0 或 h 0 ( l ) ~ N ( 0 , σ 2 ) h_0^{(l)} = \mathbf{0} \quad \text{或} \quad h_0^{(l)} \sim \mathcal{N}(0, \sigma^2) h0(l)?=0或h0(l)?~N(0,σ2)
(3) 時間步 t t t 的計算
對于每個時間步 t t t,從第一層到第 L L L 層依次計算隱藏狀態:
-
第一層:
h t ( 1 ) = activation ( W i h ( 1 ) x t + b i h ( 1 ) + W h h ( 1 ) h t ? 1 ( 1 ) + b h h ( 1 ) ) h_t^{(1)} = \text{activation}(W_{ih}^{(1)} x_t + b_{ih}^{(1)} + W_{hh}^{(1)} h_{t-1}^{(1)} + b_{hh}^{(1)}) ht(1)?=activation(Wih(1)?xt?+bih(1)?+Whh(1)?ht?1(1)?+bhh(1)?) -
第 l l l 層( l > 1 l > 1 l>1):
h t ( l ) = activation ( W i h ( l ) h t ( l ? 1 ) + b i h ( l ) + W h h ( l ) h t ? 1 ( l ) + b h h ( l ) ) h_t^{(l)} = \text{activation}(W_{ih}^{(l)} h_t^{(l-1)} + b_{ih}^{(l)} + W_{hh}^{(l)} h_{t-1}^{(l)} + b_{hh}^{(l)}) ht(l)?=activation(Wih(l)?ht(l?1)?+bih(l)?+Whh(l)?ht?1(l)?+bhh(l)?) -
輸出:
y t = W h o h t ( L ) + b h o y_t = W_{ho} h_t^{(L)} + b_{ho} yt?=Who?ht(L)?+bho?
(4) 序列輸出
最終,整個序列的輸出為 y 1 , y 2 , … , y T y_1, y_2, \dots, y_T y1?,y2?,…,yT?。
4. 多層 RNN 的特點
(1) 逐層抽象
- 每一層 RNN 可以看作是對輸入序列的不同層次的抽象。
- 較低層捕捉局部和細節信息,較高層捕捉全局和語義信息。
(2) 參數共享
- 每一層的參數(權重矩陣和偏置項)在時間步之間共享。
- 不同層的參數是獨立的。
(3) 梯度傳播
- 在反向傳播時,梯度會通過時間步和層數傳播。
- 由于梯度消失或爆炸問題,訓練深層 RNN 可能會比較困難。
可視化原理
下面是一個可視化的結構顯示圖:其中每一層神經元都要有兩個方向的輸出,一個是向本時間步
的下一層傳送,另一個是向下一個時間步
的本層傳送。而且,每一個神經元都有兩個權重矩陣。注意:下方右圖僅僅是邏輯上展開的數據流,其中不同世間步上的同一層,用的是同一個權重矩陣。
代碼實現
1. 示例任務
假設有一個簡單的任務:
- 處理一個長度為 4 的序列
- 批次大小為 2
- 每個時間步的輸入特征維度為 3
- 希望使用一個 2 層的單向 RNN
- 隱藏狀態維度為 5。
2. 輸入數據
輸入句子
- 句子 1: “I love PyTorch”
- 句子 2: “RNN is fun”
輸入數據的形狀
- 序列長度 (
seq_len
): 4(假設每個單詞是一個時間步) - 批次大小 (
batch_size
): 2 - 輸入特征維度 (
input_size
): 3(假設每個單詞用一個 3 維向量表示)
具體輸入數據
import torch# 輸入數據形狀: (seq_len, batch_size, input_size)
input_data = torch.tensor([# 時間步 1[[0.1, 0.2, 0.3], # 句子 1 的第一個單詞[0.4, 0.5, 0.6]], # 句子 2 的第一個單詞# 時間步 2[[0.7, 0.8, 0.9], # 句子 1 的第二個單詞[1.0, 1.1, 1.2]], # 句子 2 的第二個單詞# 時間步 3[[1.3, 1.4, 1.5], # 句子 1 的第三個單詞[1.6, 1.7, 1.8]], # 句子 2 的第三個單詞# 時間步 4[[1.9, 2.0, 2.1], # 句子 1 的第四個單詞[2.2, 2.3, 2.4]] # 句子 2 的第四個單詞
])
print("Input shape:", input_data.shape) # 輸出: torch.Size([4, 2, 3])
3. 初始隱藏狀態
初始隱藏狀態的形狀
- RNN 層數 (
num_layers
): 2 - 方向數 (
num_directions
): 1(單向 RNN) - 批次大小 (
batch_size
): 2 - 隱藏狀態維度 (
hidden_size
): 5
具體初始隱藏狀態
# 初始隱藏狀態形狀: (num_layers * num_directions, batch_size, hidden_size)
h0 = torch.zeros(2, 2, 5) # 2層RNN,批次大小為2,隱藏狀態維度為5
print("h0 shape:", h0.shape) # 輸出: torch.Size([2, 2, 5])
4. 定義 RNN 模型
import torch.nn as nn# 定義 RNN
rnn = nn.RNN(input_size=3, # 輸入特征維度hidden_size=5, # 隱藏狀態維度num_layers=2, # RNN 層數batch_first=False # 輸入形狀為 (seq_len, batch_size, input_size)
)
5. 前向傳播
計算輸出
# 前向傳播
output, hn = rnn(input_data, h0)print("Output shape:", output.shape) # 輸出: torch.Size([4, 2, 5])
print("hn shape:", hn.shape) # 輸出: torch.Size([2, 2, 5])
輸出解析
-
output
:- 形狀為
(seq_len, batch_size, hidden_size)
,即(4, 2, 5)
。 - 包含了每個時間步的隱藏狀態。
- 例如,
output[0]
是第一個時間步的隱藏狀態,output[-1]
是最后一個時間步的隱藏狀態。
- 形狀為
-
hn
:- 形狀為
(num_layers, batch_size, hidden_size)
,即(2, 2, 5)
。 - 包含了最后一個時間步的隱藏狀態。
- 例如,
hn[0]
是第一層的最終隱藏狀態,hn[1]
是第二層的最終隱藏狀態。
- 形狀為
6. 具體輸出值
output
的值
print("Output (所有時間步的隱藏狀態):")
print(output)
輸出示例:
tensor([[[ 0.1234, 0.5678, 0.9101, 0.1121, 0.3141],[ 0.4151, 0.6171, 0.8191, 0.0212, 0.2232]],[[ 0.4252, 0.6272, 0.8292, 0.0313, 0.2333],[ 0.4353, 0.6373, 0.8393, 0.0414, 0.2434]],[[ 0.4454, 0.6474, 0.8494, 0.0515, 0.2535],[ 0.4555, 0.6575, 0.8595, 0.0616, 0.2636]],[[ 0.4656, 0.6676, 0.8696, 0.0717, 0.2737],[ 0.4757, 0.6777, 0.8797, 0.0818, 0.2838]]],grad_fn=<StackBackward>)
hn
的值
print("hn (最后一個時間步的隱藏狀態):")
print(hn)
輸出示例:
tensor([[[ 0.4656, 0.6676, 0.8696, 0.0717, 0.2737],[ 0.4757, 0.6777, 0.8797, 0.0818, 0.2838]],[[ 0.4858, 0.6878, 0.8898, 0.0919, 0.2939],[ 0.4959, 0.6979, 0.8999, 0.1020, 0.3040]]],grad_fn=<StackBackward>)
batch_first=True時
以下是一個具體的例子,展示當 batch_first=True
時,PyTorch 中 torch.nn.RNN
的輸入、輸出以及參數的作用。
任務
假設有一個簡單的任務:
- 處理一個長度為 4 的序列
- 批次大小為 2
- 每個時間步的輸入特征維度為 3
- 希望使用一個 2 層的單向 RNN
- 隱藏狀態維度為 5
- 并且設置
batch_first=True
。
2. 輸入數據
輸入句子
- 句子 1: “I love PyTorch”
- 句子 2: “RNN is fun”
輸入數據的形狀
- 批次大小 (
batch_size
): 2 - 序列長度 (
seq_len
): 4(假設每個單詞是一個時間步) - 輸入特征維度 (
input_size
): 3(假設每個單詞用一個 3 維向量表示)
具體輸入數據
import torch# 輸入數據形狀: (batch_size, seq_len, input_size)
input_data = torch.tensor([# 句子 1[[0.1, 0.2, 0.3], # 第一個單詞[0.7, 0.8, 0.9], # 第二個單詞[1.3, 1.4, 1.5], # 第三個單詞[1.9, 2.0, 2.1]], # 第四個單詞# 句子 2[[0.4, 0.5, 0.6], # 第一個單詞[1.0, 1.1, 1.2], # 第二個單詞[1.6, 1.7, 1.8], # 第三個單詞[2.2, 2.3, 2.4]] # 第四個單詞
])
print("Input shape:", input_data.shape) # 輸出: torch.Size([2, 4, 3])
3. 初始隱藏狀態
初始隱藏狀態的形狀
- RNN 層數 (
num_layers
): 2 - 方向數 (
num_directions
): 1(單向 RNN) - 批次大小 (
batch_size
): 2 - 隱藏狀態維度 (
hidden_size
): 5
具體初始隱藏狀態
# 初始隱藏狀態形狀: (num_layers * num_directions, batch_size, hidden_size)
h0 = torch.zeros(2, 2, 5) # 2層RNN,批次大小為2,隱藏狀態維度為5
print("h0 shape:", h0.shape) # 輸出: torch.Size([2, 2, 5])
4. 定義 RNN 模型
import torch.nn as nn# 定義 RNN
rnn = nn.RNN(input_size=3, # 輸入特征維度hidden_size=5, # 隱藏狀態維度num_layers=2, # RNN 層數batch_first=True # 輸入形狀為 (batch_size, seq_len, input_size)
)
5. 前向傳播
計算輸出
# 前向傳播
output, hn = rnn(input_data, h0)print("Output shape:", output.shape) # 輸出: torch.Size([2, 4, 5])
print("hn shape:", hn.shape) # 輸出: torch.Size([2, 2, 5])
輸出解析
-
output
:- 形狀為
(batch_size, seq_len, hidden_size)
,即(2, 4, 5)
。 - 包含了每個時間步的隱藏狀態。
- 例如,
output[0]
是第一個句子的所有時間步的隱藏狀態,output[1]
是第二個句子的所有時間步的隱藏狀態。
- 形狀為
-
hn
:- 形狀為
(num_layers, batch_size, hidden_size)
,即(2, 2, 5)
。 - 包含了最后一個時間步的隱藏狀態。
- 例如,
hn[0]
是第一層的最終隱藏狀態,hn[1]
是第二層的最終隱藏狀態。
- 形狀為
6. 具體輸出值
output
的值
print("Output (所有時間步的隱藏狀態):")
print(output)
輸出示例:
tensor([[[ 0.1234, 0.5678, 0.9101, 0.1121, 0.3141],[ 0.4252, 0.6272, 0.8292, 0.0313, 0.2333],[ 0.4454, 0.6474, 0.8494, 0.0515, 0.2535],[ 0.4656, 0.6676, 0.8696, 0.0717, 0.2737]],[[ 0.4151, 0.6171, 0.8191, 0.0212, 0.2232],[ 0.4353, 0.6373, 0.8393, 0.0414, 0.2434],[ 0.4555, 0.6575, 0.8595, 0.0616, 0.2636],[ 0.4757, 0.6777, 0.8797, 0.0818, 0.2838]]],grad_fn=<TransposeBackward0>)
hn
的值
print("hn (最后一個時間步的隱藏狀態):")
print(hn)
輸出示例:
tensor([[[ 0.4656, 0.6676, 0.8696, 0.0717, 0.2737],[ 0.4757, 0.6777, 0.8797, 0.0818, 0.2838]],[[ 0.4858, 0.6878, 0.8898, 0.0919, 0.2939],[ 0.4959, 0.6979, 0.8999, 0.1020, 0.3040]]],grad_fn=<StackBackward>)