56門控循環單元(GRU
)
我們討論了如何在循環神經網絡中計算梯度, 以及矩陣連續乘積可以導致梯度消失或梯度爆炸的問題。 下面我們簡單思考一下這種梯度異常在實踐中的意義:
- 我們可能會遇到這樣的情況:早期觀測值對預測所有未來觀測值具有非常重要的意義。 考慮一個極端情況,其中第一個觀測值包含一個校驗和, 目標是在序列的末尾辨別校驗和是否正確。 在這種情況下,第一個詞元的影響至關重要。 我們希望有某些機制能夠在一個記憶元里存儲重要的早期信息。 如果沒有這樣的機制,我們將不得不給這個觀測值指定一個非常大的梯度, 因為它會影響所有后續的觀測值。
- 我們可能會遇到這樣的情況:一些詞元沒有相關的觀測值。 例如,在對網頁內容進行情感分析時, 可能有一些輔助HTML代碼與網頁傳達的情緒無關。 我們希望有一些機制來跳過隱狀態表示中的此類詞元。
- 我們可能會遇到這樣的情況:序列的各個部分之間存在邏輯中斷。 例如,書的章節之間可能會有過渡存在, 或者證券的熊市和牛市之間可能會有過渡存在。 在這種情況下,最好有一種方法來重置我們的內部狀態表示。
門控循環單元與普通的循環神經網絡之間的關鍵區別在于: 前者支持隱狀態的門控。 這意味著模型有專門的機制來確定應該何時更新隱狀態, 以及應該何時重置隱狀態。 這些機制是可學習的,并且能夠解決了上面列出的問題。 例如,如果第一個詞元非常重要, 模型將學會在第一次觀測之后不更新隱狀態。 同樣,模型也可以學會跳過不相關的臨時觀測。 最后,模型還將學會在需要的時候重置隱狀態。
1.重置門和更新門
- 重置門有助于捕獲序列中的短期依賴關系。
- 更新門有助于捕獲序列中的長期依賴關系。
2.候選隱狀態
3.隱狀態
4.從零開始實現
import torch
from torch import nn
from d2l import torch as d2l
import matplotlib.pyplot as plt# 定義批量大小和時間步數
batch_size, num_steps = 32, 35# 使用d2l庫的load_data_time_machine函數加載數據集
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)def get_params(vocab_size, num_hiddens, device):"""初始化GRU模型的參數。參數:vocab_size (int): 詞匯表的大小。num_hiddens (int): 隱藏單元的數量。device (torch.device): 張量所在的設備。返回:list of torch.Tensor: 包含所有參數的列表。"""num_inputs = num_outputs = vocab_size # 輸入和輸出的數量都等于詞匯表大小def normal(shape):"""使用均值為0,標準差為0.01的正態分布初始化張量。參數: shape (tuple): 張量的形狀。返回:torch.Tensor: 初始化后的張量。"""return torch.randn(size=shape, device=device) * 0.01def three():"""初始化GRU門的參數。返回:tuple of torch.Tensor: 包含門的權重和偏置的元組。"""return (normal((num_inputs, num_hiddens)),normal((num_hiddens, num_hiddens)),torch.zeros(num_hiddens, device=device))W_xz, W_hz, b_z = three() # 更新門參數W_xr, W_hr, b_r = three() # 重置門參數W_xh, W_hh, b_h = three() # 候選隱藏狀態參數# 輸出層參數W_hq = normal((num_hiddens, num_outputs))b_q = torch.zeros(num_outputs, device=device)# 將所有參數收集到一個列表中params = [W_xz, W_hz, b_z, W_xr, W_hr, b_r, W_xh, W_hh, b_h, W_hq, b_q]for param in params: # 啟用所有參數的梯度計算param.requires_grad_(True)return paramsdef init_gru_state(batch_size, num_hiddens, device):"""初始化GRU的隱藏狀態。參數:batch_size (int): 批量大小。num_hiddens (int): 隱藏單元的數量。device (torch.device): 張量所在的設備。返回:tuple of torch.Tensor: 初始隱藏狀態。"""return (torch.zeros((batch_size, num_hiddens), device=device), )def gru(inputs, state, params):"""定義GRU的前向傳播。參數:inputs (torch.Tensor): 輸入數據。state (tuple of torch.Tensor): 隱藏狀態。params (list of torch.Tensor): GRU的參數。返回:torch.Tensor: GRU的輸出。tuple of torch.Tensor: 更新后的隱藏狀態。"""W_xz, W_hz, b_z, W_xr, W_hr, b_r, W_xh, W_hh, b_h, W_hq, b_q = paramsH, = state # 獲取隱藏狀態outputs = [] # 存儲輸出的列表for X in inputs: # 遍歷每一個輸入時間步# 計算更新門ZZ = torch.sigmoid((X @ W_xz) + (H @ W_hz) + b_z)# 計算重置門RR = torch.sigmoid((X @ W_xr) + (H @ W_hr) + b_r)# 計算候選隱藏狀態H_tildaH_tilda = torch.tanh((X @ W_xh) + ((R * H) @ W_hh) + b_h)# 更新隱藏狀態HH = Z * H + (1 - Z) * H_tilda# 計算輸出YY = H @ W_hq + b_qoutputs.append(Y) # 將輸出添加到列表中return torch.cat(outputs, dim=0), (H,) # 返回連接后的輸出和更新后的隱藏狀態# 獲取詞匯表大小、隱藏單元數量和設備
vocab_size, num_hiddens, device = len(vocab), 256, d2l.try_gpu()
# 定義訓練的輪數和學習率
num_epochs, lr = 500, 1
# 初始化GRU模型
model = d2l.RNNModelScratch(len(vocab), num_hiddens, device, get_params, init_gru_state, gru)
# 使用d2l庫的train_ch8函數訓練模型
d2l.train_ch8(model, train_iter, vocab, lr, num_epochs, device)
plt.show()
# perplexity 1.1, 38557.3 tokens/sec on cuda:0
# time traveller for so it will be convenient to speak of himwas e
5.簡潔實現
import torch
from torch import nn
from d2l import torch as d2l
import matplotlib.pyplot as plt# 定義批量大小和時間步數
batch_size, num_steps = 32, 35
# 使用d2l庫的load_data_time_machine函數加載數據集
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)num_epochs, lr = 500, 1
# # 獲取詞匯表大小、隱藏單元數量和設備
vocab_size, num_hiddens, device = len(vocab), 256, d2l.try_gpu()
num_inputs = vocab_size
gru_layer = nn.GRU(num_inputs, num_hiddens) # 定義一個GRU層,輸入大小為num_inputs,隱藏單元數量為num_hiddens
model = d2l.RNNModel(gru_layer, len(vocab)) # 使用GRU層和詞匯表大小創建一個RNN模型
model = model.to(device)
# 該函數需要模型、訓練數據迭代器、詞匯表、學習率、訓練輪數和設備作為參數
d2l.train_ch8(model, train_iter, vocab, lr, num_epochs, device)
plt.show()
# perplexity 1.0, 248342.8 tokens/sec on cuda:0
# time travelleryou can show black is white by argument said filby