rnn - cccbook/py2gpt GitHub Wiki

RNN 循環神經網路

學過《數位電路/數位邏輯》的人,應該還記得《循序電路 (Sequential Logic) / 組合電路(Combinatorial Logic)》 這樣的區分。

其中的循序電路就是具有循環的電路,有了循環之後,我們就可以做出 Latch 去記憶一個位元。

但是沒有循環的《組合電路》,是沒有記憶的,因此只要輸入相同,輸出必然相同。(例如加法器,同樣的 a,b 必然會輸出同樣的 a+b)

傳統的神經網路模型,像是 MLP / CNN 等,就像《組合電路》一樣,對相同的輸入,會有相同的輸出,而不會記住你前面到底輸入了些甚麼?

而循環神經網路 RNN,就像《循序電路》一樣,會在《內部狀態》記住你之前輸入了甚麼,於是即使有同樣的輸入,未必有同樣的輸出,因為《內部狀態》的記憶可能不同。

RNN (Recurrent Neural Network) 循環神經網路透過加入循環,讓 RNN 可以記住過去的資訊在隱藏層 h 中 (如下圖左半部)

這種有循環的隱藏層,會有記憶效應,根據不同的內部狀態,就會有不同的輸出,即使輸入相同,輸出也有可能不同。

如果我們用 PyTorch 實作一個 RNN 循環神經網路,用來預測文章中的下一個詞,那麼就可以採用下列程式來實作。

class RNNLM(nn.Module):
    def __init__(self, method, vocab_size, embed_size, hidden_size, num_layers):
        super(RNNLM, self).__init__()
        method = method.upper()
        self.embed = nn.Embedding(vocab_size, embed_size)
        self.rnn = nn.RNN(embed_size, hidden_size, num_layers, batch_first=True) # RNN 也可以改為 GRU
        self.linear = nn.Linear(hidden_size, vocab_size)
        
    def forward(self, x, h):
        # Embed word ids to vectors
        x = self.embed(x)
        
        # Forward propagate 
        out, h = self.rnn(x, h)
        
        # Reshape output to (batch_size*seq_length, hidden_size)
        out = out.reshape(out.size(0)*out.size(1), out.size(2))
        
        # Decode hidden states of all time steps
        out = self.linear(out)
        return out, h

上述程式中的 h 就是隱藏層的狀態 ...

RNN 神經網路出現後,很多人拿來訓練語言模型,讓 RNN 學習如何產生一篇文章。(通常是像文字接龍一樣,如果你先給定幾句話,RNN 就會開始接龍)

MinGPT / MicroGrad 的作者 Karpathy 曾經寫過下列文章,展示了 RNN 的神奇效用

Karpathy 用 RNN 做了一些實驗,發現若讓 RNN 學《莎士比亞小說》,然後 RNN 寫出來就會很像《莎士比亞小說》,同樣的,學 LaTex 論文後,就會寫出看來很漂亮的論文,學 Linux 程式碼之後,RNN 就會寫很漂亮的 C 語言了。

但是 RNN 的記憶,常常衰退的很快,因此後來發展出了像 LSTM/GRU 等 RNN 的變形,於是 RNN 的記憶就變得更好了。