pytorch--迴圈神經網路

shelley__huang發表於2020-12-22

import torch
torch.__version__

'1.2.0'

2.5 迴圈神經網路

2.5.1 RNN簡介

我們的大腦區別於機器的一個最大的特徵就是我們有記憶,並且能夠根據自己的記憶對未知的事務進行推導,我們的思想擁有永續性的。但是本教程目前所介紹的神經網路結構各個元素之間是相互獨立的,輸入與輸出是獨立的。

RNN的起因

現實世界中,很多元素都是相互連線的,比如室外的溫度是隨著氣候的變化而週期性的變化的、我們的語言也需要通過上下文的關係來確認所表達的含義。但是機器要做到這一步就相當得難了。因此,就有了現在的迴圈神經網路,他的本質是:擁有記憶的能力,並且會根據這些記憶的內容來進行推斷。因此,他的輸出就依賴於當前的輸入和記憶。

為什麼需要RNN

RNN背後的想法是利用順序的資訊。 在傳統的神經網路中,我們假設所有輸入(和輸出)彼此獨立。 如果你想預測句子中的下一個單詞,你就要知道它前面有哪些單詞,甚至要看到後面的單詞才能夠給出正確的答案。 RNN之所以稱為迴圈,就是因為它們對序列的每個元素都會執行相同的任務,所有的輸出都取決於先前的計算。 從另一個角度講RNN的它是有“記憶”的,可以捕獲到目前為止計算的資訊。 理論上,RNN可以在任意長的序列中使用資訊,但實際上它們僅限於回顧幾個步驟。 迴圈神經網路的提出便是基於記憶模型的想法,期望網路能夠記住前面出現的特徵.並依據特徵推斷後面的結果,而且整體的網路結構不斷迴圈,因為得名迴圈神經網路。

RNN都能做什麼

RNN在許多NLP任務中取得了巨大成功。 在這一點上,我應該提到最常用的RNN型別是LSTM,它在捕獲長期依賴性方面要比RNN好得多。 但不要擔心,LSTM與我們將在本教程中開發的RNN基本相同,它們只是採用不同的方式來計算隱藏狀態。 我們將在後面更詳細地介紹LSTM。 以下是RNN在NLP中的一些示例: 語言建模與生成文字

我們通過語言的建模,可以通過給定的單詞生成人類可以理解的以假亂真的文字

機器翻譯

機器翻譯類似於語言建模,我們的輸入源語言中的一系列單詞,通過模型的計算可以輸出目標語言與之對應的內容。

語音識別

給定來自聲波的聲學訊號的輸入序列,我們可以預測一系列語音片段及其概率,並把語音轉化成文字

生成影像描述

與卷積神經網路一起,RNN可以生成未標記影像的描述。

2.5.2 RNN的網路結構及原理

RNN

迴圈神經網路的基本結構特別簡單,就是將網路的輸出儲存在一個記憶單元中,這個記憶單元和下一次的輸入一起進入神經網路中。我們可以看到網路在輸入的時候會聯合記憶單元一起作為輸入,網路不僅輸出結果,還會將結果儲存到記憶單元中,下圖就是一個最簡單的迴圈神經網路在輸入時的結構示意圖.圖片來源

RNN 可以被看做是同一神經網路的多次賦值,每個神經網路模組會把訊息傳遞給下一個,我們將這個圖的結構展開

網路中具有迴圈結構,這也是迴圈神經網路名字的由來,同時根據迴圈神經網路的結構也可以看出它在處理序列型別的資料上具有天然的優勢.因為網路本身就是 一個序列結構,這也是所有迴圈神經網路最本質的結構。

迴圈神經網路具有特別好的記憶特性,能夠將記憶內容應用到當前情景下,但是網路的記憶能力並沒有想象的那麼有效。記憶最大的問題在於它有遺忘性,我們總是更加清楚地記得最近發生的事情而遺忘很久之前發生的事情,迴圈神經網路同樣有這樣的問題。

pytorch 中使用 nn.RNN 類來搭建基於序列的迴圈神經網路,它的建構函式有以下幾個引數:

  • nput_size:輸入資料X的特徵值的數目。
  • hidden_size:隱藏層的神經元數量,也就是隱藏層的特徵數量。
  • num_layers:迴圈神經網路的層數,預設值是 1。
  • bias:預設為 True,如果為 false 則表示神經元不使用 bias 偏移引數。
  • batch_first:如果設定為 True,則輸入資料的維度中第一個維度就 是 batch 值,預設為 False。預設情況下第一個維度是序列的長度, 第二個維度才是 - - batch,第三個維度是特徵數目。
  • dropout:如果不為空,則表示最後跟一個 dropout 層拋棄部分資料,拋棄資料的比例由該引數指定。

RNN 中最主要的引數是 input_size 和 hidden_size,這兩個引數務必要搞清楚。其餘的引數通常不用設定,採用預設值就可以了。

rnn = torch.nn.RNN(20,50,2)
input = torch.randn(100 , 32 , 20)
h_0 =torch.randn(2 , 32 , 50)
output,hn=rnn(input ,h_0) 
print(output.size(),hn.size())
torch.Size([100, 32, 50]) torch.Size([2, 32, 50])

LSTM

LSTM 是 Long Short Term Memory Networks 的縮寫,按字面翻譯就是長的短時記憶網路。LSTM 的網路結構是 1997 年由 Hochreiter 和 Schmidhuber 提出的,隨後這種網路結構變得非常流行,。 LSTM雖然只解決了短期依賴的問題,並且它通過刻意的設計來避免長期依賴問題,這樣的做法在實際應用中被證明還是十分有效的,有很多人跟進相關的工作解決了很多實際的問題,所以現在LSTM 仍然被廣泛地使用。圖片來源

標準的迴圈神經網路內部只有一個簡單的層結構,而 LSTM 內部有 4 個層結構:

第一層是個忘記層:決定狀態中丟棄什麼資訊

第二層tanh層用來產生更新值的候選項,說明狀態在某些維度上需要加強,在某些維度上需要減弱

第三層sigmoid層(輸入門層),它的輸出值要乘到tanh層的輸出上,起到一個縮放的作用,極端情況下sigmoid輸出0說明相應維度上的狀態不需要更新

最後一層 決定輸出什麼,輸出值跟狀態有關。候選項中的哪些部分最終會被輸出由一個sigmoid層來決定。

pytorch 中使用 nn.LSTM 類來搭建基於序列的迴圈神經網路,他的引數基本與RNN類似,這裡就不列出了。

lstm = torch.nn.LSTM(10, 20,2)
input = torch.randn(5, 3, 10)
h0 =torch.randn(2, 3, 20)
c0 = torch.randn(2, 3, 20)
output, hn = lstm(input, (h0, c0))
print(output.size(),hn[0].size(),hn[1].size())
torch.Size([5, 3, 20]) torch.Size([2, 3, 20]) torch.Size([2, 3, 20])

GRU

GRU 是 gated recurrent units 的縮寫,由 Cho在 2014 年提出GRU 和 LSTM 最 的不同在於 GRU 將遺忘門和輸入門合成了一個"更新門",同時網路不再額外給出記憶狀態,而是將輸出結果作為記憶狀態不斷向後迴圈傳遞,網路的輸人和輸出都變得特別簡單。

rnn = torch.nn.GRU(10, 20, 2)
input = torch.randn(5, 3, 10)
h_0= torch.randn(2, 3, 20)
output, hn = rnn(input, h0)
print(output.size(),h0.size())
torch.Size([5, 3, 20]) torch.Size([2, 3, 20])

2.5.3 迴圈網路的向後傳播(BPTT)

在向前傳播的情況下,RNN的輸入隨著每一個時間步前進。在反向傳播的情況下,我們“回到過去”改變權重,因此我們叫它通過時間的反向傳播(BPTT)。

我們通常把整個序列(單詞)看作一個訓練樣本,所以總的誤差是每個時間步(字元)中誤差的和。權重在每一個時間步長是相同的(所以可以計算總誤差後一起更新)。

  1. 使用預測輸出和實際輸出計算交叉熵誤差
  2. 網路按照時間步完全展開
  3. 對於展開的網路,對於每一個實踐步計算權重的梯度
  4. 因為對於所有時間步來說,權重都一樣,所以對於所有的時間步,可以一起得到梯度(而不是像神經網路一樣對不同的隱藏層得到不同的梯度)
  5. 隨後對迴圈神經元的權重進行升級

RNN展開的網路看起來像一個普通的神經網路。反向傳播也類似於普通的神經網路,只不過我們一次得到所有時間步的梯度。如果有100個時間步,那麼網路展開後將變得非常巨大,所以為了解決這個問題才會出現LSTM和GRU這樣的結構。

迴圈神經網路目前在自然語言處理中應用最為火熱,所以後面的內容將介紹一下迴圈神經網路在處理NLP的時候需要用到的一些其他的知識

2.5.4 詞嵌入(word embedding)

在我們人類交流過程中表徵詞彙是直接使用英文單詞來進行表徵的,但是對於計算機來說,是無法直接認識單詞的。為了讓計算機能夠能更好地理解我們的語言,建立更好的語言模型,我們需要將詞彙進行表徵。

在影像分類問題會使用 one-hot 編碼.比如LeNet中一共有10個數字0-9,如果這個數字是2的話類,它的 編碼就是 (0,0,1,0, 0,0 ,0,0,0,0),對於分類問題這樣表示十分的清楚,但是在自然語言處理中,因為單詞的數目過多比如有 10000 個不同的詞,那麼使用 one-hot 這樣的方式來定義,效率就特別低,每個單詞都是 10000 維的向量.其中只有一位是 1 , 其餘都是 0,特別佔用記憶體,而且也不能體現單詞的詞性,因為每一個單詞都是 one-hot,雖然有些單詞在語義上會更加接近.但是 one-hot 沒辦法體現這個特點,所以 必須使用另外一種方式定義每一個單詞。

用不同的特徵來對各個詞彙進行表徵,相對與不同的特徵,不同的單詞均有不同的值這就是詞嵌入。下圖還是來自吳恩達老師的課程截圖

詞嵌入不僅對不同單詞實現了特徵化的表示,還能通過計算詞與詞之間的相似度,實際上是在多維空間中,尋找詞向量之間各個維度的距離相似度,我們就可以實現類比推理,比如說夏天和熱,冬天和冷,都是有關聯關係的。

在 PyTorch 中我們用 nn.Embedding 層來做嵌入詞袋模型,Embedding層第一個輸入表示我們有多少個詞,第二個輸入表示每一個詞使用多少個向量表示。

# an Embedding module containing 10 tensors of size 3
embedding = torch.nn.Embedding(10, 3)
# a batch of 2 samples of 4 indices each
input = torch.LongTensor([[1,2,4,5],[4,3,2,9]])
output=embedding(input)
print(output.size())
print(output)
torch.Size([2, 4, 3])
tensor([[[-0.3589,  1.5131,  0.4178],
         [ 0.1826,  1.7582, -0.3257],
         [ 0.8926,  0.8610, -0.8967],
         [ 0.3332, -0.1560,  0.3492]],

        [[ 0.8926,  0.8610, -0.8967],
         [-1.1215,  0.3267,  0.5100],
         [ 0.1826,  1.7582, -0.3257],
         [-0.1752,  0.9962,  0.6333]]], grad_fn=<EmbeddingBackward>)

2.5.5 其他重要概念

在生成第一個詞的分佈後,可以使用貪心搜尋會根據我們的條件語言模型挑選出最有可能輸出的第一個詞語,但是對於貪心搜尋演算法來說,我們的單詞庫中有成百到千萬的詞彙,去計算每一種單詞的組合的可能性是不可行的。所以我們使用近似的搜尋辦法,使得條件概率最大化或者近似最大化的句子,而不是通過單詞去實現。

Beam Search(集束搜尋)是一種啟發式圖搜尋演算法,通常用在圖的解空間比較大的情況下,為了減少搜尋所佔用的空間和時間,在每一步深度擴充套件的時候,剪掉一些質量比較差的結點,保留下一些質量較高的結點。雖然Beam Search演算法是不完全的,但是用於瞭解空間較大的系統中,可以減少空間佔用和時間。

beam search可以看做是做了約束優化的廣度優先搜尋,首先使用廣度優先策略建立搜尋樹,樹的每層,按照啟發代價對節點進行排序,然後僅留下預先確定的個數(Beam width-集束寬度)的節點,僅這些節點在下一層次繼續擴充套件,其他節點被剪下掉。

  1. 將初始節點插入到list中
  2. 將給節點出堆,如果該節點是目標節點,則演算法結束;
  3. 否則擴充套件該節點,取集束寬度的節點入堆。然後到第二步繼續迴圈。
  4. 演算法結束的條件是找到最優解或者堆為空。

在使用上,集束寬度可以是預先約定的,也可以是變化的,具體可以根據實際場景調整設定。

注意力模型

對於使用編碼和解碼的RNN模型,我們能夠實現較為準確度機器翻譯結果。對於短句子來說,其效能是十分良好的,但是如果是很長的句子,翻譯的結果就會變差。 我們人類進行人工翻譯的時候,都是一部分一部分地進行翻譯,引入的注意力機制,和人類的翻譯過程非常相似,其也是一部分一部分地進行長句子的翻譯。

具體的內容在這裡就不詳細介紹了

 

 

相關文章