(pytorch-深度学习)门控循环单元(GRU)
門控循環(huán)單元(GRU)
循環(huán)神經網絡中的梯度計算
- 當時間步數較大或者時間步較小時,循環(huán)神經網絡的梯度較容易出現衰減或爆炸。
- 雖然裁剪梯度可以應對梯度爆炸,但無法解決梯度衰減的問題。
- 通常由于這個原因,循環(huán)神經網絡在實際中較難捕捉時間序列中時間步距離較大的依賴關系。
門控循環(huán)神經網絡(gated recurrent neural network)的提出,正是為了更好地捕捉時間序列中時間步距離較大的依賴關系。它通過可以學習的門來控制信息的流動。其中,門控循環(huán)單元(gated recurrent unit,GRU)是一種常用的門控循環(huán)神經網絡。
門控循環(huán)單元
門控循環(huán)單元引入了重置門(reset gate)和更新門(update gate)的概念,從而修改了循環(huán)神經網絡中隱藏狀態(tài)的計算方式。
門控循環(huán)單元中的重置門和更新門的
- 輸入均為當前時間步輸入Xt\boldsymbol{X}_tXt?與上一時間步隱藏狀態(tài)Ht?1\boldsymbol{H}_{t-1}Ht?1?
- 輸出由激活函數為sigmoid函數的全連接層計算得到。
假設
- 隱藏單元個數為hhh
- 給定時間步ttt的小批量輸入Xt∈Rn×d\boldsymbol{X}_t \in \mathbb{R}^{n \times d}Xt?∈Rn×d(樣本數為nnn,輸入個數為ddd)
- 和上一時間步隱藏狀態(tài)Ht?1∈Rn×h\boldsymbol{H}_{t-1} \in \mathbb{R}^{n \times h}Ht?1?∈Rn×h。
重置門Rt∈Rn×h\boldsymbol{R}_t \in \mathbb{R}^{n \times h}Rt?∈Rn×h和更新門Zt∈Rn×h\boldsymbol{Z}_t \in \mathbb{R}^{n \times h}Zt?∈Rn×h的計算如下:
Rt=σ(XtWxr+Ht?1Whr+br),Zt=σ(XtWxz+Ht?1Whz+bz),\begin{aligned} \boldsymbol{R}_t = \sigma(\boldsymbol{X}_t \boldsymbol{W}_{xr} + \boldsymbol{H}_{t-1} \boldsymbol{W}_{hr} + \boldsymbol{b}_r),\\ \boldsymbol{Z}_t = \sigma(\boldsymbol{X}_t \boldsymbol{W}_{xz} + \boldsymbol{H}_{t-1} \boldsymbol{W}_{hz} + \boldsymbol{b}_z), \end{aligned} Rt?=σ(Xt?Wxr?+Ht?1?Whr?+br?),Zt?=σ(Xt?Wxz?+Ht?1?Whz?+bz?),?
其中
- Wxr,Wxz∈Rd×h\boldsymbol{W}_{xr}, \boldsymbol{W}_{xz} \in \mathbb{R}^{d \times h}Wxr?,Wxz?∈Rd×h和Whr,Whz∈Rh×h\boldsymbol{W}_{hr}, \boldsymbol{W}{hz} \in \mathbb{R}^{h \times h}Whr?,Whz∈Rh×h是權重參數
- br,bz∈R1×h\boldsymbol{b}_r, \boldsymbol{b}_z \in \mathbb{R}^{1 \times h}br?,bz?∈R1×h是偏差參數。
- sigmoid函數可以將元素的值變換到0和1之間。因此,重置門Rt\boldsymbol{R}_tRt?和更新門Zt\boldsymbol{Z}_tZt?中每個元素的值域都是[0,1][0, 1][0,1]。
候選隱藏狀態(tài)
門控循環(huán)單元將計算候選隱藏狀態(tài)來輔助之后的隱藏狀態(tài)計算。
- 我們將當前時間步重置門的輸出與上一時間步隱藏狀態(tài)做按元素乘法(符號為⊙\odot⊙)。
- 如果重置門中元素值接近0,那么意味著重置對應隱藏狀態(tài)元素為0,即丟棄上一時間步的隱藏狀態(tài)。
- 如果元素值接近1,那么表示保留上一時間步的隱藏狀態(tài)。
- 然后,將按元素乘法的結果與當前時間步的輸入連結,再通過含激活函數tanh的全連接層計算出候選隱藏狀態(tài),其所有元素的值域為[?1,1][-1, 1][?1,1]。
時間步ttt的候選隱藏狀態(tài)H~t∈Rn×h\tilde{\boldsymbol{H}}_t \in \mathbb{R}^{n \times h}H~t?∈Rn×h的計算為
H~t=tanh(XtWxh+(Rt⊙Ht?1)Whh+bh),\tilde{\boldsymbol{H}}_t = \text{tanh}(\boldsymbol{X}_t \boldsymbol{W}_{xh} + \left(\boldsymbol{R}_t \odot \boldsymbol{H}_{t-1}\right) \boldsymbol{W}_{hh} + \boldsymbol{b}_h),H~t?=tanh(Xt?Wxh?+(Rt?⊙Ht?1?)Whh?+bh?),
其中
- Wxh∈Rd×h\boldsymbol{W}_{xh} \in \mathbb{R}^{d \times h}Wxh?∈Rd×h和Whh∈Rh×h\boldsymbol{W}_{hh} \in \mathbb{R}^{h \times h}Whh?∈Rh×h是權重參數
- bh∈R1×h\boldsymbol{b}_h \in \mathbb{R}^{1 \times h}bh?∈R1×h是偏差參數。
從上面這個公式可以看出,重置門控制了上一時間步的隱藏狀態(tài)如何流入當前時間步的候選隱藏狀態(tài)。而上一時間步的隱藏狀態(tài)可能包含了時間序列截至上一時間步的全部歷史信息。因此,重置門可以用來丟棄與預測無關的歷史信息。
隱藏狀態(tài)
時間步ttt的隱藏狀態(tài)Ht∈Rn×h\boldsymbol{H}_t \in \mathbb{R}^{n \times h}Ht?∈Rn×h的計算使用當前時間步的更新門Zt\boldsymbol{Z}_tZt?來對上一時間步的隱藏狀態(tài)Ht?1\boldsymbol{H}_{t-1}Ht?1?和當前時間步的候選隱藏狀態(tài)H~t\tilde{\boldsymbol{H}}_tH~t?做組合:
Ht=Zt⊙Ht?1+(1?Zt)⊙H~t.\boldsymbol{H}_t = \boldsymbol{Z}_t \odot \boldsymbol{H}_{t-1} + (1 - \boldsymbol{Z}_t) \odot \tilde{\boldsymbol{H}}_t.Ht?=Zt?⊙Ht?1?+(1?Zt?)⊙H~t?.
更新門可以控制隱藏狀態(tài)應該如何被包含當前時間步信息的候選隱藏狀態(tài)所更新,如圖所示。
- 假設更新門在時間步t′t't′到ttt(t′<tt' < tt′<t)之間一直近似1。那么,在時間步t′t't′到ttt之間的輸入信息幾乎沒有流入時間步ttt的隱藏狀態(tài)Ht\boldsymbol{H}tHt。
- 實際上,這可以看作是較早時刻的隱藏狀態(tài)Ht′?1\boldsymbol{H}{t'-1}Ht′?1一直通過時間保存并傳遞至當前時間步ttt。
- 這個設計可以應對循環(huán)神經網絡中的梯度衰減問題,并更好地捕捉時間序列中時間步距離較大的依賴關系。
對門控循環(huán)單元的設計作總結:
- 重置門有助于捕捉時間序列里短期的依賴關系;
- 更新門有助于捕捉時間序列里長期的依賴關系。
實現GRU網絡
讀取數據集
import numpy as np import torch from torch import nn, optim import torch.nn.functional as Fimport sys sys.path.append("..") device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')def load_data_jay_lyrics():"""加載周杰倫歌詞數據集"""with zipfile.ZipFile('../data/jaychou_lyrics.txt.zip') as zin:with zin.open('jaychou_lyrics.txt') as f:corpus_chars = f.read().decode('utf-8')corpus_chars = corpus_chars.replace('\n', ' ').replace('\r', ' ')corpus_chars = corpus_chars[0:10000]idx_to_char = list(set(corpus_chars))char_to_idx = dict([(char, i) for i, char in enumerate(idx_to_char)])vocab_size = len(char_to_idx)corpus_indices = [char_to_idx[char] for char in corpus_chars]return corpus_indices, char_to_idx, idx_to_char, vocab_size(corpus_indices, char_to_idx, idx_to_char, vocab_size) = load_data_jay_lyrics()初始化模型參數
num_inputs, num_hiddens, num_outputs = vocab_size, 256, vocab_sizedef 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_xz, W_hz, b_z = _three() # 更新門參數W_xr, W_hr, b_r = _three() # 重置門參數W_xh, W_hh, b_h = _three() # 候選隱藏狀態(tài)參數# 輸出層參數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_xz, W_hz, b_z, W_xr, W_hr, b_r, W_xh, W_hh, b_h, W_hq, b_q])定義模型
def init_gru_state(batch_size, num_hiddens, device):return (torch.zeros((batch_size, num_hiddens), device=device), )#形狀為(批量大小, 隱藏單元個數)的值為0的Tensor組成的元組。 def gru(inputs, state, params):W_xz, W_hz, b_z, W_xr, W_hr, b_r, W_xh, W_hh, b_h, W_hq, b_q = paramsH, = stateoutputs = []for X in inputs:Z = torch.sigmoid(torch.matmul(X, W_xz) + torch.matmul(H, W_hz) + b_z)R = torch.sigmoid(torch.matmul(X, W_xr) + torch.matmul(H, W_hr) + b_r)H_tilda = torch.tanh(torch.matmul(X, W_xh) + torch.matmul(R * H, W_hh) + b_h)H = Z * H + (1 - Z) * H_tildaY = torch.matmul(H, W_hq) + b_qoutputs.append(Y)return outputs, (H,)訓練模型并創(chuàng)作歌詞
num_epochs, num_steps, batch_size, lr, clipping_theta = 160, 35, 32, 1e2, 1e-2 pred_period, pred_len, prefixes = 40, 50, ['分開', '不分開']def train_and_predict_rnn(rnn, get_params, init_rnn_state, num_hiddens,vocab_size, device, corpus_indices, idx_to_char,char_to_idx, is_random_iter, num_epochs, num_steps,lr, clipping_theta, batch_size, pred_period,pred_len, prefixes):if is_random_iter:data_iter_fn = data_iter_randomelse:data_iter_fn = data_iter_consecutiveparams = get_params()loss = nn.CrossEntropyLoss()for epoch in range(num_epochs):if not is_random_iter: # 如使用相鄰采樣,在epoch開始時初始化隱藏狀態(tài)state = init_rnn_state(batch_size, num_hiddens, device)l_sum, n, start = 0.0, 0, time.time()data_iter = data_iter_fn(corpus_indices, batch_size, num_steps, device)for X, Y in data_iter:if is_random_iter: # 如使用隨機采樣,在每個小批量更新前初始化隱藏狀態(tài)state = init_rnn_state(batch_size, num_hiddens, device)else: # 否則需要使用detach函數從計算圖分離隱藏狀態(tài), 這是為了# 使模型參數的梯度計算只依賴一次迭代讀取的小批量序列(防止梯度計算開銷太大)for s in state:s.detach_()inputs = to_onehot(X, vocab_size)# outputs有num_steps個形狀為(batch_size, vocab_size)的矩陣(outputs, state) = rnn(inputs, state, params)# 拼接之后形狀為(num_steps * batch_size, vocab_size)outputs = torch.cat(outputs, dim=0)# Y的形狀是(batch_size, num_steps),轉置后再變成長度為# batch * num_steps 的向量,這樣跟輸出的行一一對應y = torch.transpose(Y, 0, 1).contiguous().view(-1)# 使用交叉熵損失計算平均分類誤差l = loss(outputs, y.long())# 梯度清0if params[0].grad is not None:for param in params:param.grad.data.zero_()l.backward()grad_clipping(params, clipping_theta, device) # 裁剪梯度sgd(params, lr, 1) # 因為誤差已經取過均值,梯度不用再做平均l_sum += l.item() * y.shape[0]n += y.shape[0]if (epoch + 1) % pred_period == 0:print('epoch %d, perplexity %f, time %.2f sec' % (epoch + 1, math.exp(l_sum / n), time.time() - start))for prefix in prefixes:print(' -', predict_rnn(prefix, pred_len, rnn, params, init_rnn_state,num_hiddens, vocab_size, device, idx_to_char, char_to_idx))train_and_predict_rnn(gru, get_params, init_gru_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)總結
以上是生活随笔為你收集整理的(pytorch-深度学习)门控循环单元(GRU)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 入门 | 一文看懂卷积神经网络
- 下一篇: 再看那个用代码把人类送上月球的女人——M