循環神經⽹絡是為更好地處理時序信息而設計的。它引⼊狀態變量來存儲過去的信息,並⽤其與當前的輸⼊共同決定當前的輸出。
循環神經⽹絡常⽤於處理序列數據,如⼀段⽂字或聲⾳、購物或觀影的順序,甚⾄是圖像中的⼀⾏或⼀列像素。因此,循環神經⽹絡有着極為⼴泛的實際應⽤,如語⾔模型、⽂本分類、機器翻譯、語⾳識別、圖像分析、⼿寫識別和推薦系統。
引入
對於2句話,都有Taipei這個詞,但是一個是目的地,一個是出發地
如果神經網絡有記憶力,能夠根據上下文對同樣的input詞匯產生不同的輸出,我們就能解決這個問題
像下面兩句話,同樣輸入Taipei,一個輸出“目的地”,一個輸出“出發地”
arrive Taipei on November 2 leave Taipei on November 2
RNN
Recurrent Neural Network(RNN)就是一個有記憶的神經網絡,在隱藏層中的每個隱藏單元產生Output的時候,都會存到Memory中
下一次,當有input的時候,這個隱藏單元不僅會考慮input的值,還會考慮存在memory中的值
例子
下面舉一個例子,假設圖中的RNN的所有weight都是1,沒有bias,激活函數都是linear
我們的input是一個下圖中的sequence
在我們使用練RNN的時候,我需要先要給memory初始值,比如這里的 0 0
現在輸入[1, 1],隱藏單元還會考慮內存中的0,0,因為我們weight都是1,所以隱藏層神經元的都是 0 + 0 + 1 + 1 =2
由於權重都是1,所以這時候網絡的輸出是[4, 4]
另外,剛剛隱藏層的輸出[2,2]還會被存到memory中去
這時候如果我們的輸入是[1,1],隱藏層的輸出就變為了[6, 6]。因為memory中存着[2,2],輸入是[1,1],權重都為1,所以每一個隱藏層神經元的輸出為1 + 1 + 2 + 2 = 6
這時候網絡的輸出是[12, 12]。可以看到如果memory中的值不一樣,我們同樣的輸入會產生不同的輸出。
當我們的輸入是[2,2]的時候,memory中的值是[6,6],跟前面的步驟一樣,我們可以得到輸出是[32, 32]。我們輸入sequence的輸出結果如下
調換輸入sequence中的順序,我的輸出也會不一樣,即RNN考慮了輸入sequence的順序
例子
讓我們回到一開始講的兩句話,看看RNN是怎么識別Taipei的不同含義(目的地or出發地)
arrive Taipei on November 2 leave Taipei on November 2
首先,網絡接收第一個單詞(arrive)的輸入,經過網絡得到一個輸出,並保存隱藏層的輸出。
然后接收第二個單詞(Taipei)的輸入,經過網絡得到輸出,……,用同樣的網絡結構不斷重復這個行為
所以當兩句不同的話輸入的時候,一個Taipei前面是leave,一個是arrive,而這兩個的vector是不一樣的,所以存在memory中的值不同,這樣就會得到不同的輸出
更一般地,我們的網絡可以不止有一個隱藏層,而是有許多個隱藏層,每個單詞輸入的時候,各個隱藏層都會考慮之前存在memory中的值
兩種RNN
我們前面看到的RNN是隱藏層的輸出存起來,當下一個sequence輸入的時候,再讀取來,這個是Elman Network
還有另一種RNN——Jordan Network,它把網絡的整個輸出存到memory中,下一個sequence輸入時再讀出來
隱藏層是沒有target,而output有,所以Jordan Network一般會有比較好的表現
雙向RNN(bidirectional RNN)
我們的RNN可以是雙向的,我們輸入一個句子的時候,它會從句首讀到句尾,但讀取的方向可以反過來,從句尾讀到句首,如上圖中第一個結構。
我們可以同時訓練一個正向的RNN和一個逆向的RNN,把這兩個RNN的隱藏層輸出拿出來,接給一個輸出層
用雙向RNN的好處是網絡可以學習到更多的上下文信息,正向或者反向RNN能學習到的內容有限,只能學習到一半的信息
LSTM
原理
Long Short-Term Memory(LSTM),長短期記憶網絡。
前面提到的Memory是最單純的,我們隨時可以把值存到Memory中去,也可以隨時把值從中讀出來。
但LSTM不是這樣,它有3個gate:
- 1)input gate
- 2)ouput gate
- 3)forget gate
當網絡中的其他部分的output想要寫入memory中的時候,需要通過一個input gate,input gate打開的時候才能夠把值寫到里面,如果Input gate是關閉的,那么就不能把值寫到里面。這個input gate是打開還是關閉是由網絡自己學習的,學習什么時候打開input gate,什么時候關閉。
輸出的地方也有一個output gate,這個這個output gate決定外界能否從memory中讀取值。跟input gate一樣,output gate的開關也由網絡自己學習。
forget gate決定什么時候memory cell要把memory中的內容忘掉,這個也由網絡自己學習
整個網絡的結構如下圖所示,我們可以把LSTM看成有4個輸入,1個輸出的特殊神經網絡。這4個值分別是
- 想存進memory的值
- 操控input gate的信號
- 操控output gate的信號
- 操控forget gate的信號
下面是LSTM更詳細的結構
其中
- $z$ 是要存到memory中的輸入
- $z_{i}$ 是操控input gate的信號,是一個數值
- $z_{f}$ 是操控forget gate的信號,是一個數值
- $z_{o}$ 是操控output gate的信號,是一個數值
- $a$ 是輸出
- memory cell中的初始值是$c$
4個輸入經過gate的時候常用的激活函數是 sigmoid,因為它的值在0到1之間,1代表gate打開,0代表關閉。
下面我們看看網絡是怎么輸入輸出的,首先 $z_{i}$ 和 $z$ 經過神經元得到$g(z)$和$f(z_{i})$,將兩者相乘
$z_{f}$ 經過神經元得到 $f(z_{f})$ ,取出memory中的值$c$,相乘得到$cf(z_{f})$
然后將上面得到的兩個相乘的結果相加,得到$c'$。$c'$就是新的要存入memory中的值
從上面的式子可以看到,$f(z_{i})$是控制$g(z)$能否輸入的關卡,$f(z_{f})$是控制是否取出memory中的值
如果$f(z_{i})$為0,那么它們相乘為0,跟沒有輸入是一樣的,如果$f(z_{i})$是1,那么相乘會得到$g(z)$本身
$f(z_{f})$是也是同樣的工作原理,如果$f(z_{f})$是1,那么$c$會通過,表示之前存的值還是記得。如果$f(z_{f})$是0,那么相乘得到0,相當於之前的值不記得
可以看出,forget gate打開代表記得,關閉代表忘記
接着$c'$通過$h$,得到$h(c')$,讓$f(z_{o})$和$h(c')$相乘,得到輸出
原理也是一樣的,$f(z_{o})$是1,等於$h(c')$可以通過output gate,否則為0,存在memory中的值無法讀取出來
例子
假設我們的網絡有以下性質:
現在的網絡只有一個memory cell,我們的input都是3維的vector,輸出都是1維的vector。
當$x_{2}$中的值為1時,$x_{1}$中的值就會被寫到memory中
當$x_{2}$中的值為-1時,memory中的值就會被遺忘
當$x_{3}$中的值為1時,我們的output gate才會輸出
memory中的初始值為0
那么根據網絡的性質,我們可以得到的輸出和memory中的值如下圖所示,其中藍色方塊代表memory中的值
具體地來看,假設我們網絡的weight如下圖,4個輸入都由我們輸入的向量乘上weight再加上bias得到。
假設網絡中$g$和$h$現在是linear激活函數,我們輸入[3,1,0]時,根據我們剛剛介紹的過程,網絡的輸入輸出過程如下圖所示
輸入[4,1,0]和[2,0,0]時,結果如下圖所示,網絡依然沒有輸出
當輸入[1,0,1]的時候,output gate 的值是1,所以memory中的值會取出。輸入是[3,-1,0]的時候,forget gate的值是0,memory中的內容會被忘記
LSTM結構
我們上面談到的LSTM,其實可以變成下面這種形式
但是通常LSTM並不是這樣的,它還會把輸出作為下一個輸入的一部分,還會加一個“peephole”,它會把存在memory中的值也作為下一個輸入的一部分。
LSTM通常也不會有一層,可能會有多層
下面我們看看這種LSTM結構是如何計算的,它可以簡化成下面這種形式,它有3個輸入,分別是$x^{t}$、$c^{t-1}$、$h^{t-1}$。
這時候我們的3個輸入分別乘以各自的權重,其中$c^{t-1}$的權重矩陣是對角矩陣,這是為了減少訓練的參數。
像前面介紹的LSTM一樣,我們其他門的輸入如下圖所示,各部分的的權重矩陣是不一樣的,
memory cell 中要存入的內容如下,$\odot $是 element wise 乘法(對應元素相乘)。
輸出的$h^{t}$和$y^{t}$如下
最終我們得到的 LSTM 的一個 block 就像下面的樣子
GRU
表現上看,GRU會輸入一個$h^{t-1}$,輸出一個$h^{t}$,輸入一個$x^{t}$,輸出一個$y^{t}$。看起來很像RNN,其實它更像LSTM。GRU里面這個$h^{t}$的作用更像是LSTM中$c^{t}$的作用。GRU中我們可以看作是 forget gate 和 input gate 是聯動的LSTM,一個開另一個就要關。
GRU(gated recurrent unit)門控循環單元,它引入了reset gate(重置門)和update gate(更新門)的概念,類似LSTM。
相較於LSTM,它只有2個gate,所以它的參數量比較少,它的思想是:舊的不去,新的不來。要把 memory 中的值清掉,才能放進新的值。
重置門的輸入包括當前時間的輸入$X_{t}$和 memory 中的值$H_{t-1}$,經過 sigmoid 激活函數得到輸出$R_{t}$和$Z_{t}$。
重置門和更新門的計算公式如下,其中$W$是weight
然后我們讓重置門的輸出$R_{t}$ 與 memory 中的值$H_{t-1}$相乘,如果結果是0,則將 memory 中的值重置為0。
跟LSTM中一樣,輸入$X_{t}$跟權重$W_{xh}$相乘,然后把這個結果跟上面相乘的結果相加。
得到一個候選值$\tilde{H_{t}}$,其中內容是:
從上⾯這個公式可以看出,重置門控制了上⼀時間步的 memory 如何流⼊當前時間步的候選值。
上⼀時間步的 memory 可能包含了時間序列截⾄上⼀時間步的全部歷史信息。因此,重置⻔可以⽤來丟棄與預測⽆關的歷史信息。
然后用當前更新門$Z_{t}$來對上⼀時間步的 memory $H_{t-1}$和當前時間步的候選值$\tilde{H_{t}}$做組合,得到新的要存入 memory 中的值:
更新門$Z_{t}$取值為0的時候,新的 memory 只跟候選值有關,而候選值又是經過激活函數$tanh$輸出的,所以在-1和1之間。
當更新門$Z_{t}$為1的時候,新 memory 同等於上一時間點的 memory ,即不執行更新操作。
如果更新門$Z_{t}$在$t'$和$t$($t'$ < $t$)之間一直是1,那么$t'$到$t$之間輸入的信息幾乎沒有流入 memory 中,一直保持較早的memory。
RNN網絡學習過程
損失函數
我們訓練網絡的時候,我們需要定義一個損失函數。在RNN中,我們需要怎么定義損失函數?
假設我們現在要做slot filling,我們會有訓練數據,數據是一些sentence,然后需要給sentence一些label,告訴我們不同的詞屬於不同的slot
然后把單詞輸入到網絡中,RNN會得到一個輸出$y^{1}$,接下來這個$y^{1}$會和一個 reference vector 算它的 cross entropy 。
當我們輸入是單詞arrive的時候,$y^{1}$應該跟“other”對應的reference vector距離越近越好,這個reference vector的長度就是我們slot的數目,后面輸入的單詞同理。
我們損失函數就是:每一個時間點的RNN的output跟 reference vector 的 cross entropy ,這就是我們要優化的函數。
訓練過程
訓練也是使用梯度下降來更新參數,我們已經有了損失函數,我們要更新網絡中某個參數$w$的公式如下
但是為了計算方便,開發了一套算法,這套算法是BackPropagation(反向傳播)的進階版,叫做BackPropagation through time(BPTT)
它跟BackPropagation是很類似的,只是RNN在time sequence上做運算,所以需要考慮時間的信息
RNN的訓練是比較困難的,一般我們都希望我們的學習曲線像下圖中的藍色曲線一樣,但我們訓練RNN的時候,有時候會看到綠色這條線,訓練曲線劇烈抖動。
這是因為RNN的error surface(total loss對參數的變化)對參數的變化是非常崎嶇的,error surface有些地方很平坦,有些地方很陡峭。
如下圖我們,假設我們的起始位置是左下方的橙色點,第一次更新參數讓我的 loss 上升,來到右邊的橙色點
第二次更新參數的時候,我們的學習率剛好讓我們來到了左上方的橙色點,這時候loss就突然增加
可以用Clipping解決這一問題,當梯度大於某個閾值的時候,就不要讓梯度等於閾值
在RNN中,激活函數使用ReLU的表現通常比sigmoid差
那為什會這樣?我們可以用一個直觀的例子來看梯度的大小是怎么樣的:就是我們把某一個參數做小小的變化,看網絡的輸出變化有多大。就能測出這個參數梯度的大小。
比如下面這個例子:
我們有一個簡單的RNN,輸入輸出的weight都是1,memory接到隱層單元的weight是$w$,隱層單元激活函數是linear,假設我們輸入[1,0,...,0],那我們的輸出是$w^{999}$。
我們改變$w$的值,對網絡的輸出有多大影響,假設此時$w$為1,那么$w$增加0.1,網絡的輸出會增加特別多,所以$w$在1的地方特別大的梯度
如果我們的學習率設小,可以彌補這種缺陷嗎?我們來看一下$w$為0.99和0.1的情況,可以發現輸出都是0,說明在0.99的地方梯度變得很小。
所以學習率的設置不能改變這種局面,因為我們的梯度時大時小。
RNN訓練的問題在於:RNN把同樣的東西在 transition 的時候反復使用,即memory接到隱藏層的權重$w$。
有什么技巧可以幫助我們解決RNN的這一問題?
- LSTM可以解決梯度消失的問題,但不能解決梯度爆炸的問題。所以我們可以把學習率設置小一點。
- 跟普通 RNN 相比,LSTM 在處理 memory 中的操作是不一樣的。RNN中每個時間點都會更新memory中的值。LSTM中,它將 memory 乘上一個值再和輸入相加的放到memory cell中。
- RNN中如果memory中的值清零了,那weight的影響就不存在了。weight 如果影響 LSTM中 memory 的值的話,這個影響會一直持續到 memory 中的內容被忘掉。一般我們需要給 forget gate 一個較大的 bias,讓它在少數情況下才會清零。
RNN的輸入和輸出形式
One to One:輸入和輸出都是一個vector。
One to Many:輸入是vector,輸出是vector sequence
Many to One:輸入是一個vector sequence,輸出是一個vector。比如情感分析,輸出哪些文章是正類,哪些是負類;比如文檔關鍵詞提取;
Many to Many:輸入輸出都是vector sequence。輸出sequence比輸入sequence少的例子:比如語音識別任務,輸入是聲音信號(每隔一小段時間用vector表示),輸出是character sequence。通常是好多個vector對應到一個character。
Sqe2Seq簡介
要理解word sequence的含義,單詞在句子的位置信息是不能忽略的。下面兩句話有同樣的單詞,但單詞的位置不同,含義也完全相反
但是如果用 bag-of-word 來描述的話,它們是完全一樣的。
我們可以用Seq2Seq Auto-encoder 來考慮 word sequence 有順序的情況,把一個 document 變成一個 vector
我們輸入一個word sequence,通過一個RNN,把它變成一個embedding vector,再把這個 embedding vector 當做 Decoder 的輸入,讓 Decoder 造回一個一模一樣的句子。
如果RNN可以做到這一件事,那么 encoder 的這個 vector 就代表 input sequence 里面重要的信息。所以 Decoder 才能根據 Encoder 的 vector,把這個序列 decode 回來。
訓練Seq2Seq我們不需要labeled data,只需要有大量的文章,然后輸入到網絡中。
它也可以是 hierarchy 結構,我們先得到每個句子的vector,然后把這些vector加起來,變成一整個 document higher-level 的 vector。再用這個higher-level vector產生一串 sentence vector,再根據每一個 sentence vector解回 word sequence。