循環神經網絡
引言
上幾節講了一些時間序列的基本概念, 大家總感覺不那么的'智能', 與現在的人工智能的總那么的... 不太搭邊. 先不管以上'感覺'對不對, 今天插隊與大家分享一個處理時間序列的'大殺器' — 循環神經網絡(Recurrent neural network, RNN), RNN就是為處理序列數據而生的( 時間序列數據是當是序列數據!!!).
首先,對於時間序列,讓大家感受一下我隨便訓練的結果(真的很隨便,大約只有100個循環,
+_+!):
怎么樣? 預測效果驚人的好!
RNN在當今的人工智能領域,或者說機器學習領域炙手可熱,大放異彩, 特別是在自然語言處理(natural language processing, NLP)方面(比如機器翻譯,語音識別,對話機器人等等)可謂獨孤求敗. 而且已有許多應用被大家開發出來,比如讓機器寫文章,寫古詩,譜曲填詞,寫代碼... 不勝枚舉, 這些無論怎么看都比時間序列(特指業務上的時間序列數據,一般有timestamp, value 等類似格式)復雜得多, 所以用RNN處理時間序列,大家是不是已經很有信心啦?
舉個例子, '曾經有一份真摯的感情,擺在我面前,我沒有去___.'
這句話如果應用傳統的語言模型,可選項有:'北京','學校','珍惜','上班',… 有好多選項.傳統的語言模型基於統計模型,可以利用橫線前面的信息非常有限, 目前最長用的傳統語言模型可能利用的前面信息是: '去….', '沒有去…','我沒有去…',再長就受不了.
顯然,就算是可利用的信息是'我沒有去…', 你認為選擇出''珍惜''的概率會比'北京'大嗎? 不過,RNN就會選擇''珍惜'',而且概率會比'北京','上班'等等大很多. 這是因為,RNN不但會利用'我沒有去…'這樣的信息,而且還會利用'擺在我面前',以及'曾經有一份真摯的感情'等信息. 當這些信息利用上以后,就會發現'上班','學校'等等詞匯放在橫線上意思就對不上了.
不僅如此,RNN 還會告訴你,接下來會說什么:' 等到失去了,才追悔莫及','這讓我很后悔','從而傷害了一位可愛的女孩',… 至於哪個,就看你的數據及訓練情況了,如果都還行,至尊寶的'謊言'RNN完全可以臉不紅心不跳的說出來.
Tip: 本文面向不同需求讀者(或為您的最佳閱讀體驗着想):
- 只想了解一下,或拓寬一下思路的讀者, 只需讀 **非 *** 部分即可;
- 如果想稍微深入一些,或知道一些細節則需閱讀所有小節包括 帶 * 部分.
循環神經網絡
那什么是神經網絡? 別急,先看下RNN擅長做什么? 前面已經說過就是序列數據(sequential data). 序列數據的特點是什么? 就是序列數據前后之間是有很強的關聯性的,專業一點的說法是不獨立的(nonindependent). 前面出現的數據(比如詞匯)對后面的數據有重大影響的, 甚至后面的數據對前的數據也是有重要影響的(雙向循環神經網絡,稍后介紹).
RNN就是為處理此等問題而生的,即RNN在處理當前信息時,會考慮前面出現的信息,理論上RNN可以包含當前信息前面的所有已知信息的.那循環是什么意思呢? 就是說之前處理過的信息還會一直被利用去幫助后面到來的信息,.
舉個例子: 把一串信息比作一群學生去體檢, 而RNN可以看成的一家醫院, 第一個人進去后, 體檢完成后,體檢單包含了此人體檢相關的所有信息, 作為輸出信息上存儲在系統中,並打印一份給這位學生,,而此學生出來后卻把體檢單交給了下一位同學(別問我為什么要給下一位同學,'醫院'就是這么規定的,沒辦法,學生只能按學校的規則來,否則,想不想畢業?).
這個''下一位''同學進去后,把體檢單交給醫生,醫生根據這個同學的體檢情況及上交的這份體檢單給這個同學重新寫一份體檢單,儲存,並打印一份交給這位同學,此同學拿着這份包含他和之前同學信息的體檢單出來后交給下一位同學.如此循環下去.
直到最后一位同學拿着包含他之前所有同學信息的體檢單進去並檢查完成后,他的體檢單(理論上包含所有同學的信息)被儲存,並打印一份交給他,這樣一隊人的'花式'體檢就結束了, RNN就是這樣的處理數據的.
循環結構*
規范化一點, 正如上面的例子一樣,當前的數據依賴於之前的信息, 設有一狀態序列數據\(\{s_t\}\)
要表示這一性質,典型的處理方式:
其中f() 為映射(在RNN中可以簡單的理解激活函數), \(\theta\) 為參數. 從上式可以看出, 1). 映射是與時間不相關的. 2). \(\theta\) 也是與時間無關的,這里體現了循環結構(在RNN中)的很重要性質: 參數(主要為權值參數)共享(parameter sharing).
上式可以用另一種形式(展開式)表示:
如果狀態序列中的每個數據不只受其前面信息的影響,還受外部信息的影響,那么循環結構可以表示成:
其中\(x_t\)為外部信息序列的第t個元素. 這個就是RNN(簡單的)使用的循環結構.
寫成帶權重的形式:
為簡潔,可以把偏置省略,可以將其看成是U中的(額外)第一維(元素都為 1),后面的BPTT推導將采用此種方式.
如果考慮輸出層:
RNN 結構*
一個標准的(簡單的)RNN單元包含三層: 輸入層,隱藏層和輸出層,用圖示有兩種方式:折疊式(圖1左)與展開式(圖1右):
圖1 RNN結構的兩種呈現方式,折疊式(左),展開式(右),上圖中青色(圓)區塊為輸入層,綠色(方)為隱藏層,而紫色的為輸出層.(引自: understanding LSTM Networks, colah'blog 2015 .)
如果理解了上面的循環結構,那對RNN的結構就可以比較明白.當然如果你對着圖看了好久也是正常的,因為這個思維還是需要轉換的,畢竟不那么直觀.
雙向循環神經網絡
雙向循環神經網絡(Bi-Directional Recurrent Neural Network,BDRNN) 則不止利用前面的信息,還會利用后面的信息.
比如: 我的家鄉是一千五里外的哈爾濱, ___回家,要三個多小時.
這句話, 如果只考慮前面的信息,后面真的有太多可能: ' 那里每年二百多天處於冬季', '不是上海線','回家不方便','吃燒烤','看電影','乘火車','乘飛機'…
但考慮后面的信息(如''回家'')前面大多滿足要求了,因為后面有''回家''二字,那么可以想到與交通工具可能有很大關系(但也其他可能),比如'走路','跑步','騎車','不想','很少','媽媽總希望我',…雖然還是有很多種可能性,但范圍是極大的縮小了.
如果把后面的'要三個多小時',那這個空白的部分就基本確定了: 乘飛機.(有些小伙伴,可能會說也可能是'坐飛機','打飛的'等等啊.對的,完全正確, 這就需要額外的信息了,比如語境等等, 但多數情況,選擇不只一種時,就會選取概率最大的選項了.)
上個圖*****:
圖2 雙向RNN (引自: understanding LSTM Networks, colah'blog 2015)
是不是一看到這張圖就懵了? 對的,第一次看到類似結構圖我也懵,懵得很徹底.不過目前我們只需要知道處理當前信息時,利用了未來信息(穿越,對的, BDRNN就是解決未來信息如何'穿越'回來而設計的~~)就可以了.目前的業務上的時間序列研究方式不傾向於BDRNN的研究方式(但要注意,也是可以應用的:比如某電商大促時間的前一段時間會對業務產生'反向'性抑制就是BDRNN的應用場景.什么,還太懂?'雙十一'前的幾天,不止商家囤貨,買家也會囤單吧,就是這個意思.)
深度循環神經網絡
前面提到的其是都只有RNN的一單元或者說RNN單元只有一層隱藏層,可以想象將多個RNN單元堆疊在一起,那就形成了深度循環神經網絡(deep RNN),或者說是多層RNN(multi-layers RNN).
圖3 多層RNN (圖中呈現的是二層RNN)(引自: Tensorflow for Intelligence, Sam Abrahams et al.)
穿越時間的反向傳播算法
建模可分為兩個過程,都是無比重要的,其一就是模型的構建,其二就是對模型參數的訓練了.模型構建之后, 框架就確定了,但在框架之內,你是搭建出垃圾廠,還是宮殿就要靠參數的訓練了. 更加直觀的例子: y = ax +b, 這個線性方程,幾乎所有人都懂, 可在這個模型確定之后, y = 2x + 3 與 y = -2x +3 可完全不相同啊.
那神經網絡用什么訓練呢? 用得即是梯度下降算法(gradient descent algorithm),這與一般的機器學習算法沒有什么不同, 但我們知道,神經網絡是,特別多層神經網絡,參數眾多,層與層之間的關系復雜,如果每一個都按傳統的方式計算梯度,那有太多無意義開銷(重復計算).因此在BP隨之而生,如許多驚人的貢獻一樣,最初(1970年)BP被提及時,並沒有引起人們的注意,而在十六年后才有人認識到其重要性,但真正(火熱地)用起來的當然是二十一世紀之后的事了.
反向傳播算法(back propagation algorithm, BP-algorithm)是深度學習的最重要的兩大基石(另一個是梯度下降算法),幾乎所有的深度學習算法底層都會調用BP算法,其本質就是對鏈式求導法則的應用而已. 而穿越時間的反向傳播算法(back propagation through time algorithm, BPTT-algorithm) 則是BP上的應用,其核心沒有改變,只不過在應用時,要注意一些穿越時間的特別之處.
反向傳播算法*
BP的目標是計算損失(Loss 或者叫'代價','成本'(cost)等等)函數對參數(主要是權重(weights)及偏置(biasses))求偏導.
在梯度下降法中, 對參數進行更新:
其中 w 即為待更新(訓練)參數,\(\eta\)為超參數(hyper parameter) 學習率, 而\(\frac{\partial L}{\partial w}\) 即為損失函數L對w的偏導,也可稱為w在梯度, 而反向傳播即是對這個梯度的求解.
學習反向傳播算法,要記住'一,二,三,四':
一. 一個乘積:
Hadamard 乘積: 是逐元素的(element-wise)乘積, 用符號\(\odot\) 表示.舉個例子:
a = [1,2,4], b = [2,3,5], 則\(a\odot b = [1*2,2* 3,4*5] = [2,6,20]\).
二. 二個假設:
- 損失函數可以表示成每個訓練樣本x上的代價函數的均值.這個假設很基本也很自然,從樣本的視角看損失函數,它一要是'一視同仁的'.
- 損失函數可以表示成神經網絡輸出的函數, 否則如何反向傳播?沒出門就停下了,怎么往下進行?
三: 三個步驟:
- 前向傳播,計算神經網絡的輸出;
- 根據誤差函數(或損失函數) 對所有所需訓練的參數求偏導(其間要先計算每個神經元的偏導數);
- 基於2步,計算每個參數的梯度.
四: 四個基本方程**:
設:
L為神經網絡的損失函數;
\(w_{jk}^l\) 表示從(l-1)層的第 k 個神經元到第 l 層的第 j 個神經元(很怪是吧,習慣就好了~哈,其實這樣表示有好處,因為公式表述更方便)的權值,請看下圖:
這里的\(w_{2,4}^3\) 表示從第二層的第四個神經元到第三層的第二個神經元的權值;
\(a^l\) 表示第 l 層的神經元激活值,(逐個使用sigmoid 函數,即element wise):
其中 \(\sigma\) 是激活函數數(activation function);
中間變量: \(\boldsymbol{z^l = w^l a^{l-1} + b^l}\) .
定義 l 層的第 j 個神經元上的誤差為\(\delta_j^l\):
OK, 准備結束,開始:
-
輸出層的誤差:
\[\delta_j^L = \frac{\partial L}{\partial a_j^L} \sigma'(z_j^L) \]寫成向量(矩陣形式):
\[\boldsymbol{(\delta^L)^T= \nabla_a L \odot \sigma'(z^L)} \]其中\(\nabla_a L\)的每一個分量即是 \(\frac{\partial L}{\partial a_j^L}\).(注: 其中角標L 為最后一層(L層,不同於損失L)的意思,另, 矩陣微積分這樣重要而常用的知識形式竟然沒有統,因此這也為矩陣的微積發增加了難度,這里應用'分子式排版',即形式優先考慮分子的形式,具體請看wikipedia)
-
使用下一層的誤差來\(\delta^{l+1}\)表示當前層的誤差\(\delta^l\):
\[\boldsymbol{(\delta^l)^T = (w^{l+1})^T\delta^{l+1}\odot\sigma'(z^l)} \]這個一下出結果不像上一個那么直觀,咱們來推導一下:
\[\begin{array} \\ \delta_j^l &= &\partial L / \partial z_j^l \\ & = & \sum_k \frac{\partial L}{\partial z_k^{l+1}}\frac{\partial z_{k}^{l+1}}{\partial z_j^l}\\ &=& \sum_k \frac{\partial L}{\partial z_j^{l+1}}\delta_k^{l+1}\\ & =& \sum_k w_{k,j}^{l+1}\delta_k^{l+1}\sigma'(z_j^l) \end{array} \]K 是(l+1)層的神經元的個數.這里用到了(8)式及下面的式子(13):
\[\begin{array} \\ \frac{\partial z_k^{l+1}}{\partial z_j^l} &=& \frac{\partial (\sum_j w_{k,j}^{l+1}\sigma(z_j^l)+b_k^{l+1})}{\partial z_j^l} \\ &=& w_{k,j}^{l+1}\sigma'(z_j^l) \end{array} \] -
對任一偏置的梯度:
\[\frac{\partial L}{\partial b_j^l } = \frac{\partial L}{\partial z_j^l}\frac{\partial z_j^l}{\partial b_j^l} = \delta_j^l \]即:
\[\boldsymbol{(\frac{\partial L}{\partial b^l})^T = (\delta^l)^T} \] -
對任一權重的梯度:
\[\begin{array} \\ \frac{\partial L}{\partial w_{j,k}^l } &=& \frac{\partial L}{\partial z_j^l}\frac{\partial z_j^l}{\partial w_{j,k}^l} \\ &= &\delta_j^l \frac{\partial (w^l a^{l-1} + b^l)}{w_{j,k}^l}\\ &=& \delta_j^l a^{l-1}_k \end{array} \]即:
\[(\boldsymbol{\frac{\partial L}{\partial w^l})^T = (\delta^l)^T (a^{l-1})^T} \]
式(10),(11),(15)(17)即為BP的四個方程,有了這四個方程,任一可訓練參數的梯度就可以計算了,進而可以更新,達到學習的目的.
BPTT**
有了上面的鋪墊, BPTT就簡單多了, BPTT 的誤差項沿兩個方向傳播,一個是從當前層傳上向一層,另一個是從當前時刻傳到上一時刻(在處理時域反向傳播時,最好把這個循環展開(unfold)這樣,結構就像一個層的全連接網絡了,只不過權值共享):
(PS: 下面均為矩陣運算)
根據上面:
即:
先考慮:
其中:
注意,(20,21)式本來均是Jacobian矩陣,但因其中(22)式是一對角陣\((diag(f'(z_{t-1}))\),簡化,寫成向量式(如(20)式中),可以應用hardamad 乘積,形式上更統一,簡潔.
最后T時刻的誤差\(\delta_k\):
寫成矩陣形式:
則第t 時刻的誤差(角標 l 代表層,這里的角標有些亂,因為時間,層際,元素三個在一起不好排列, 故注意描述./^_^!) \(\delta_t^l\):
第 l-1層的誤差:
對w_t 的梯度:
而對於w 的梯度則是上式在時間軸上的相加:
對u_t的梯度:
則對U的梯度:
上式(22)(23)(25)(27)即為BPTT的四個方程了.
總結
以上就是RNN的基本內容, 盡管RNN很強大,但其實也有些現實的問題,這里先不展開了(比如梯度消失,爆炸等等), 因為這些問題,出現了許多變種, 其中門限變種比LSTM,GRU 可謂如日中天.
參考方獻:
- Deep learning, 2015,Ian Goodfellow et al.
- Neural network and deep learning, 2016 Micheal Nielsen.
- Tensorflow for intelligence 2016, Sam Abrahams et al.
- Understanding LSTM Networks —colah's blog
- A critical Review of Recurrent Neural Network for sequence learning, 2015, Zachary C. Lipton
- Hands on Machine learning with scikit-learn and Tensorflow: Concept, Tools, and, Techniques for Building Intelligent Systems.
- The Unreasonable Effectiveness of Recurrent Neural Network, 2015, Andrej Karpathy blog.
- 零基礎入門深度學習(5)- 循環神經網絡,2017, hanbingtao.
- Matrix calculus,2017, Wikipedia.
- Matrix Differential Calculus with Applications in Statistics and Econometrics,2007, Jan R. Magnus, et al.