日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

RNN的手動推導與代碼逐行實現

作者:MoxiMoses 更新時間: 2022-09-26 編程語言

文章目錄

  • 前言
  • 一、RNN的手推過程
  • 二、RNN代碼實現
    • 1、PyTorch API實現
    • 2、代碼逐行實現RNN
  • 總結


前言

RNN的特點是擁有儲存上一次節點的輸出結果的能力,因此就算是同樣的輸入集合,只要改變其輸入序列,輸出結果就會完全不一樣。在本次學習中,主要展示了RNN的正向和反向手動推導過程,用代碼逐行實現單向和雙向RNN,并與PyTorch API輸出的結果進行驗證正確。


一、RNN的手推過程

1. 如下圖所示,這是我們要手推RNN模型的內部結構示意圖。圖中的t下標指當前時間節點,St由Whh矩陣和Whx矩陣豎著拼接,乘上橫著拼接的ht-1和Xt得到;ht由tanh作為激活函數,St作為參數得到,在這里每一輪產生的ht將會更新到下一輪使用;Ot由Wyh乘上ht得到;最后用softmax得到概率分布。
在這里插入圖片描述
2. 如下圖所示,這是我們制定的任務,訓練數據用one-hot vector表示。圖中的最下面是RNN的展開,因為我們只需要最后一次的輸出,所以就無需計算出第一次和第二次的輸出。
在這里插入圖片描述
3. 如下圖所示,初始化參數。
在這里插入圖片描述
4. 正向運算
1)我們設定初始的h0為0,于是計算出t=1時的s1、h1,t=2時s2、h2。
在這里插入圖片描述
2)當t=3時,計算出s3、h3、O,最后把O放入softmax中得到概率分布。
在這里插入圖片描述
5. 反向運算
反向運算中比較復雜的是loss對Whh求導和loss對Whx求導,原因是Whh和Whx對Loss的影響有3種情況,一種是h3直接影響,另一種是h2、h3影響,最后一種是h1、h2與h3影響。
在這里插入圖片描述
6. 通過反向運算得到的gradient,更新參數。
在這里插入圖片描述

二、RNN代碼實現

1、PyTorch API實現

1)實現單向、單層RNN
首先實例化對象single_rnn,然后調用正態分布隨機函數torch.randn生成輸入,最后將生成的輸入作為single_rnn的輸入,并且得到輸出和最后時刻狀態。

single_rnn = nn.RNN(4, 3, 1, batch_first=True)
input = torch.randn(1, 2, 4)  # bs * sl * fs
output, h_n = single_rnn(input)
print(output)
print(h_n)

查看輸出結果。
在這里插入圖片描述
2)實現雙向、單層RNN
首先實例化對象bidirectional_rnn,在這里需要加上bidirectional=True即可實現雙向RNN,然后給bidirectional_rnn輸入,得到輸出和最后時刻狀態。

bidirectional_rnn = nn.RNN(4, 3, 1, batch_first=True, bidirectional=True)
bi_output, bi_h_n = bidirectional_rnn(input)
print(bi_output)
print(bi_h_n)

查看輸出結果。
在這里插入圖片描述
3)比較單向、單層RNN和雙向、單層RNN

print(output.shape)
print(bi_output.shape)
print(h_n.shape)
print(bi_h_n.shape)

從輸出上來看,單向的RNN維度是1 * 2 * 3,而雙向的RNN維度是1 * 2 * 6,原因是雙向RNN把forward和backward的結果拼在一起;從最后時刻狀態來看,單向的RNN維度是1 * 1 * 3,而雙向的RNN維度是2 * 1 * 3,原因是雙向RNN在最后時刻有兩個層,而單向RNN在最后時刻有一個層。
在這里插入圖片描述

2、代碼逐行實現RNN

1)首先初始一些張量,然后調用正態分布隨機函數隨機初始化一個輸入特征序列,以及初始隱含狀態(設為0)。

import torch
import torch.nn as nn

bs, T = 2, 3  # 批大小,輸入序列長度
input_size, hidden_size = 2, 3  # 輸入特征大小,隱含層特征大小
input = torch.randn(bs, T, input_size)  # 隨機初始化一個輸入特征序列
h_prev = torch.zeros(bs, hidden_size)  # 初始隱含狀態

2)手寫一個rnn_forward函數,手動地模擬單向RNN的運算過程,并且與PyTorch RNN API進行比較,驗證輸出結果。

# step1 調用PyTorch RNN API
rnn = nn.RNN(input_size, hidden_size, batch_first=True)
rnn_output, state_final = rnn(input, h_prev.unsqueeze(0))

print("PyTorch API output:")
print(rnn_output)
print(state_final)


# step2 手寫一個rnn_forward函數,實現單向RNN的計算原理
def rnn_forward(input, weight_ih, weight_hh, bias_ih, bias_hh, h_prev):
    bs, T, input_size = input.shape
    h_dim = weight_ih.shape[0]
    h_out = torch.zeros(bs, T, h_dim)  # 初始化一個輸出(狀態)矩陣

    for t in range(T):
        x = input[:, t, :].unsqueeze(2)  # 獲取當前時刻輸入特征,bs * input_size
        w_ih_batch = weight_ih.unsqueeze(0).tile(bs, 1, 1)  # bs * h_dim * input_size
        w_hh_batch = weight_hh.unsqueeze(0).tile(bs, 1, 1)  # bs * h_dim * h_dim

        w_times_x = torch.bmm(w_ih_batch, x).squeeze(-1)  # bs * h_dim
        w_times_h = torch.bmm(w_hh_batch, h_prev.unsqueeze(2)).squeeze(-1)  # bs * h_dim
        h_prev = torch.tanh(w_times_x + bias_ih + w_times_h + bias_hh)

        h_out[:, t, :] = h_prev

    return h_out, h_prev.unsqueeze(0)


# 驗證rnn_forward的正確性
# for k, v in rnn.named_parameters():
#     print(k, v)

custom_rnn_output, custom_state_final = rnn_forward(input, rnn.weight_ih_l0, rnn.weight_hh_l0,
                                                    rnn.bias_ih_l0, rnn.bias_hh_l0, h_prev)

print("rnn_forward function output:")
print(custom_rnn_output)
print(custom_state_final)

查看輸出結果以及并用torch.allclose驗證最后時刻的結果

print(torch.allclose(state_final, custom_state_final))

在這里插入圖片描述
3)手寫一個bidirectional_rnn_forward函數,手動地模擬雙向RNN的運算過程,并且與PyTorch RNN API進行比較,驗證輸出結果。

# step3 手寫一個bidirectional_rnn_forward函數,實現雙向RNN的計算原理
def bidirectional_rnn_forward(input, weight_ih, weight_hh, bias_ih, bias_hh, h_prev,
                              weight_ih_reverse, weight_hh_reverse, bias_ih_reverse, bias_hh_reverse, h_prev_reverse):
    bs, T, input_size = input.shape
    h_dim = weight_ih.shape[0]
    h_out = torch.zeros(bs, T, h_dim * 2)  # 初始化一個輸出(狀態)矩陣,注意雙向是兩倍的特征大小

    forward_output = rnn_forward(input, weight_ih, weight_hh, bias_ih, bias_hh, h_prev)[0]  # forward layer
    backward_output = rnn_forward(torch.flip(input, [1]), weight_ih_reverse, weight_hh_reverse, bias_ih_reverse,
                                  bias_hh_reverse, h_prev_reverse)[0]  # backward layer

    h_out[:, :, :h_dim] = forward_output
    h_out[:, :, h_dim:] = backward_output

    return h_out, h_out[:, -1, :].reshape((bs, 2, h_dim)).transpose(0, 1)


# 驗證bidirectional_rnn_forward的正確性
bi_rnn = nn.RNN(input_size, hidden_size, batch_first=True, bidirectional=True)
h_prev = torch.zeros(2, bs, hidden_size)
bi_rnn_output, bi_state_final = bi_rnn(input, h_prev)
# for k, v in bi_rnn.named_parameters():
#     print(k, v)

custom_bi_rnn_output, custom_bi_state_final = bidirectional_rnn_forward(input, bi_rnn.weight_ih_l0, bi_rnn.weight_hh_l0,
                                                                        bi_rnn.bias_ih_l0, bi_rnn.bias_hh_l0,
                                                                        h_prev[0], bi_rnn.weight_ih_l0_reverse,
                                                                        bi_rnn.weight_hh_l0_reverse,
                                                                        bi_rnn.bias_ih_l0_reverse,
                                                                        bi_rnn.bias_hh_l0_reverse, h_prev[1])
print("PyTorch API output:")
print(bi_rnn_output)
print(bi_state_final)

print("bidirectional_rnn_forward function output:")
print(custom_bi_rnn_output)
print(custom_bi_state_final)

查看輸出結果以及并用torch.allclose驗證最后時刻的結果

print(torch.allclose(bi_state_final, custom_bi_state_final))

在這里插入圖片描述


總結

在本次的學習中,通過對RNN的手動推導與代碼逐行實現,加深了自己對RNN的理解與推導。RNN其實就是給模型一個記憶的功能,讓之后每一步的輸出對于前面的輸入有關,因此隨著時間變化的數據,使用RNN會比較好,但RNN也存在一些問題,隨著時序長度變長,RNN的深度也會變深,這就會導致出現梯度爆炸和梯度消失的問題,于是出現了對RNN的改進,因此在下次的學習中我將繼續學習LSTM。

原文鏈接:https://blog.csdn.net/peaunt1/article/details/126334471

欄目分類
最近更新