Recurrent Neural Networks(RNN) 循環神經網絡初探

1. 針對機器學習/深度神經網絡「記憶能力」的討論

0x1:數據規律的本質是能表明此類數據的通用模式 - 數據挖掘的本質是在進行模式提取

數據的本質是存儲信息的介質,而模式(pattern)是信息的一種表現形式。在一個數據集中,模式有不少不一樣的表現形式,無論是在傳統的機器學習訓練的過程,仍是是深度學習的訓練過程,本質上都是在進行模式提取。php

而從信息論的角度來看,模式提取也能夠理解爲一種信息壓縮過程,經過將信息從一種形式壓縮爲另外一種形式。壓縮的過程不可避免會形成信息丟失。html

筆者這裏列舉幾種典型的體現模式提取思想的算法。git

1. 矢量圖表示法

1)像素圖表示法 - 最原始的信息記錄方法

像素這個概念咱們都很是熟悉,像素是表示每一個圖像的基本單位。github

傳統的bmp位圖亦稱爲點陣圖像繪製圖像,是由稱做像素(圖片元素)的單個點組成的。這些點能夠進行不一樣的排列和染色以構成圖樣。web

當放大位圖時,能夠看見賴以構成整個圖像的無數單個方塊。擴大位圖尺寸的效果是擴大單個像素,從而使線條和形狀顯得良莠不齊。算法

同時,縮小位圖尺寸也會使原圖變形,由於此舉是經過減小像素來使整個圖像變小的。shell

一樣,因爲位圖圖像是以排列的像素集合體形式建立的,因此不能單獨操做(如移動)局部位圖。能夠想象一下,位圖的移動相似數組的平移,成本很是高。編程

2)矢量圖表示法 - 記錄信息不如記錄生成原理

矢量圖,也稱爲面向對象的圖像繪圖圖像,在數學上定義爲一系列由線鏈接的點。數組

矢量文件中的圖形元素稱爲對象。每一個對象都是一個自成一體的實體,它具備顏色、形狀、輪廓、大小和屏幕位置等屬性。安全

矢量圖使用直線和曲線來描述圖形,這些圖形的元素是一些點、線、矩形、多邊形、圓和弧線等等,它們都是經過數學公式計算得到的。

例如一幅花的矢量圖形其實是由線段造成外框輪廓,由外框的顏色以及外框所封閉的顏色決定花顯示出的顏色。

矢量圖最大的好處是「存儲成本小」,由於矢量圖並不須要存儲原始文件的全部像素信息,而只要存儲有限的用於生成原始像素圖像的生成算法便可(很有機器學習模型參數的感受),所以,矢量圖能夠無限放大而不會增長額外的存儲成本。

2. K-mean聚類

1)聚類前的數據集(原始信息)是怎麼樣的?

2)聚類後獲得的壓縮後信息是怎麼樣的?

原始數據集通過K-mean以後,獲得的壓縮後信息爲 N 個聚類中心,N 由算法操做者決定。

3)Kmeans壓縮後信息如何表明原始的信息?

聚類獲得的 N 個聚類中心就是 K-means模型的模型參數。

從某種程度上來講,這個 N 個聚類中心就能夠表明原始的樣本信息。

訓練獲得的Kmeans模型能夠用於新樣本的標註(預測),當輸入待檢測樣本的時候,Kmeans根據必定的搜索算法,搜索已知的聚類中心,將待檢測樣本打標爲最靠近的那個類別。

這樣,Kmeans經過 N 個聚類中心,實現了原始信息的模式提取,或者說核心信息壓縮。

3. 線性迴歸模型學習

一個典型的例子

在一元線性迴歸的場景中,線性迴歸模型經過訓練獲得:Y = ax + b。

經過2個參數【a,b】,對本來龐大的樣本點集進行了描述。

從上面3個例子中,能夠看到一個共通點,即「信息壓縮」,它們的本質都是抽象出了一種形式化表達,而這種表達就表明了一種模式,這種模式能夠表明本來的海量樣本集中的某種形式的規律。

至於如何提取這種模式規律,以及提以後的模型規律如何被運用進行後續的新的預測,就是不一樣機器學習算法的變化所在了。

0x2:神經網絡是如何記憶和存儲數據中的模式規律的?

機器學習的神經是參考人腦神經網絡的構造而創造的,那人腦神經網絡又是如何識別、存儲、記憶天天看到的咱們所謂的有用的知識的呢?

這塊內容筆者只是查詢了網上的一些討論資料,徹底跨專業了,也只是看科普看了一些大概,目前業內彷佛並無一個準確的定義,不少的討論彷佛是在實驗觀測和理論假設猜想之上。

可是我看下來,有幾個觀點不少業內學者提到:

1. 記憶並非一個直接經過bit方式存儲在大腦內,而是經過一些的神經細胞的結構來存儲,即結構及記憶知識,換言之,大腦並非直接存儲知識自己,而是存儲知識的概念結構,也就是所謂的模式;
2. 腦內反映某外界客觀物體,是由被該外界刺激激活的全部皮層細胞組成的,這些同時被激活的神經元稱做「細胞集合」,假如這些細胞相互鏈接,細胞集合內的鏈接持續激活,對外界客觀物體的內部反應就能做爲短時程記憶始終保存,若是細胞集合能持續激活很長一段時間,那麼細胞間相互鏈接更有效的神經元就會鏈接在一塊兒,更緊密的鏈接就會使細胞集合再次興奮,記憶的鞏固就可能發生。換句話說,要記憶某種信息模式,就必須生成相應的神經網結構,而且經過不斷的刺激使之固定下來;
3. 僅僅集團內的一部分細胞的破壞並不能消除記憶,記憶的痕跡普遍分佈於細胞集合的細胞鏈接內。 

Relevant Link: 

https://arxiv.org/pdf/1706.05394.pdf
http://www.360doc.com/content/17/0622/19/16619343_665595088.shtml 
https://www.zhihu.com/question/20264424 

 

2. 循環神經網絡RNN(Recurrent Neural Network)介紹

循環神經網絡(recurrent neural network)或 RNN 是一類用於處理了序列數據的神經網絡。咱們這個章節來針對RNN的一些基本概念展開討論。

0x1:共享參數思想

咱們先從參數共享機制提及,這是RNN循環神經網絡的一個核心特色,也是RNN可以擁有某些強大性能的緣由之一。

參數共享機制使得神經網絡對序列數據中的模式具有了必定的平移不變泛化能力,以及模式記憶能力。

1. 從傳統全鏈接前饋網絡的特徵表徵提及 - 不包含參數共享機制

不單是對DNN,傳統的機器學習模型,也全都要求輸入的特徵向量是一個定長的vector。
對這種定長的 feature vector,不一樣的特徵維度之間是正交獨立的,即打亂順序是不會影響最後的檢測結果。因此開發者在進行特徵工程的時候,並不須要考慮特徵之間的序列關係,只要把特徵「堆」到一塊兒便可。
傳統的全鏈接前饋網絡會給每一個輸入特徵分配一個單獨的參數,不一樣特徵對應的參數是單獨調整的。

須要注意的是,在NLP場景中,傳統機器學習算法常常和詞袋編碼結合使用。詞袋編碼雖然不具有共享參數能力,可是由於詞袋編碼自己就就丟棄原始文本中的時序信息,即詞袋特徵對原始文本中的序列順序變化並不敏感,所以從某種程度上來講,詞袋編碼具有必定的對時序文本中特徵平移的泛化能力。

2. 卷積網絡的共享參數 - 感知域平移參數共享

一種捕獲文本中相同單詞在不一樣位置的特徵的方法是,1 維時間序列上使用卷積

這種卷積方法是時延神經網絡的基礎 (Lang and Hinton, 1988; Waibel et al., 1989; Lang et al., 1990)

卷積操做容許網絡跨時間共享參數,可是淺層的。卷積的輸出是一個序列,其中輸出中的每一項是相鄰幾項輸入的函數。

參數共享的概念體如今每一個時間步中使用的相同卷積核。

3. RNN中使用參數共享機制實現特徵位置平移不變性

從多層網絡到循環網絡,循環網絡吸取了20世紀80年代機器學習和統計模型早期思想的優勢:在模型的不一樣部分共享參數。參數共享使得RNN模型可以擴展到不一樣長度的序列樣本並進行泛化。

若是咱們在每一個時間點都有一個單獨的參數,不但不能泛化到訓練時沒有見過的序列長度,也不能在時間上共享不一樣序列長度和不一樣位置的統計強度。當信息的特定部分會在序列內多個位置出現時,這樣的共享尤其重要。

例如,考慮這兩句話:「I went to Nepal in 2009’’ 「In 2009, I went to Nepal.」。

若是咱們讓一個機器學習模型讀取這兩個句子,並提取敘述者去Nepal的年份,不管 「2009’’ 是做爲句子的第六個單詞仍是第二個單詞出現,咱們都但願模型能認出 「2009’’ 做爲相關資料片斷。

相比傳統DNN網絡,循環神經網絡在幾個時間步內共享相同的權重,不須要分別學習句子每一個位置的全部語言規則。

相比於CNN卷積網絡,循環神經網絡以不一樣的方式共享參數。輸出的每一項是前一項的函數。輸出的每一項對先前的輸出應用相同的更新規則(參數共享)而產生。這種循環方式致使參數經過很深的計算圖共享。

此外, 須要注意的是,所謂的時間序列沒必要是字面上現實世界中流逝的時間。有時,它僅表示序列中的位置。RNN 也能夠應用於跨越兩個維度的空間數據(如圖像)

當應用於涉及時間的數據, 而且將整個序列提供給網絡以前就能觀察到整個序列時,該網絡可具備關於時間向後的鏈接。

筆者思考:RNN和CNN這種神經網絡具有參數共享機制,能夠對特徵的位置平移實現泛化能力,那是否是就意味着RNN全面優於傳統機器學習算法呢?筆者認爲答案是否認的!

由於並非全部的數學建模場景中,特徵都呈現時序關係的。整體上來講,特徵工程獲得的特徵分爲兩大類:1)正交獨立的特徵集,例如說描述一個桌子的一組物理參數;2)時序特徵,例如某人發出的一段聲音的聲紋信號。

對於正交獨立的特徵集,時序模型就不必定能發揮其自己的做用,甚至還會起反效果。正交獨立的特徵集更適合使用傳統機器學習模型進行機率分佈/模型參數的訓練評估。

在具體的AI項目中,咱們會遇到各類各樣的具體問題,首先要思考的是,從哪些角度入手進行特徵工程,若是是正交獨立特徵集,選擇哪些特徵?若是是時序特徵,選取什麼數據抽取時序特徵?

4. 參數共享的假設前提

在循環網絡中使用的參數共享的前提是相同參數可用於不一樣時間步的假設。也 就是說,假設給定時刻 t 的變量後,時刻 t + 1 變量的條件機率分佈是 平穩的 (stationary),這意味着以前的時間步與下個時間步之間的關係並不依賴於 t

0x2:RNN網絡具有的時序記憶能力

在不少項目場景中,針對當前樣本的判斷不僅僅僅限於當前樣本,而須要結合歷史的樣本進行時序依賴判斷。

以突顯識別舉例來講(實際上RNN並不侷限於圖像時序數據):

若是咱們看到一個沙灘的場景,咱們應該在接下來的幀數中加強沙灘活動:若是圖像中的人在海水中,那麼這個圖像可能會被標記爲「游泳」;若是圖像中的人閉着眼睛躺在沙灘上,那麼這個圖像可能會被標記爲「日光浴」。

若是若是咱們可以記得Bob剛剛抵達一家超市的話,那麼即便沒有任何特別的超市特徵,Bob手拿一塊培根的圖像均可能會被標記爲「購物」而不是「烹飪」。

所以,咱們但願讓咱們的模型可以跟蹤事物的各類狀態:

  • 在檢測完每一個圖像後,模型會輸出一個標籤,這個標籤對應該圖像的識別結果(即RNN每一個時間步輸出的 y 值)。同時模型對世界的認識也會有所更新(更新隱狀態)。例如,模型可能會學習自主地去發現並跟蹤相關的信息,如位置信息(場景發生的地點是在家中仍是在沙灘上?)、時間(若是場景中包含月亮的圖像,模型應該記住該場景發生在晚上)和電影進度(這個圖像是第一幀仍是第100幀?)

  • 在向模型輸入新的圖像時,模型應該結合它收集到的歷史信息,對當前的輸入圖片進行更合理的綜合判斷。

循環神經網絡(RNN),它不只可以完成簡單地圖像輸入和事件輸出行爲,還能保持對世界的記憶(給不一樣信息分配的權重),以幫助改進本身的分類功能。

0x3: RNN的圖靈完備性

"循環"兩個字,表達了RNN的核心特徵, 即系統的輸出會保留在網絡裏和系統下一刻的輸入一塊兒共同決定下一刻的輸出。這就把動力學的本質體現了出來, 循環正對應動力學系統的反饋概念,能夠刻畫複雜的歷史依賴。

另外一個角度看也符合著名的圖靈機原理。 即此刻的狀態包含上一刻的歷史,又是下一刻變化的依據。

這其實包含了可編程神經網絡的核心概念,即當你有一個未知的過程,但你能夠測量到輸入和輸出, 你假設當這個過程經過RNN的時候,它是能夠本身學會這樣的輸入輸出規律的, 並且所以具備預測能力。 在這點上說, RNN是圖靈完備的。

下面列舉了一些可能的圖靈操做規則:

1. 圖1即CNN的架構
2. 圖2是把單一輸入轉化爲序列輸出,例如把圖像轉化成一行文字
3. 圖三是把序列輸入轉化爲單個輸出,好比情感測試,測量一段話正面或負面的情緒
4. 圖四是把序列轉化爲序列,最典型的是機器翻譯 
5. 圖5是無時差(注意輸入和輸出的"時差")的序列到序列轉化, 好比給一個錄像中的每一幀貼標籤(每個中間狀態都輸出一個output)

0x4:長期依賴的挑戰

學習循環網絡長期依賴的數學挑戰在於「梯度消失」和「梯度爆炸」。根本問題是,通過許多階段傳播後的梯度傾向於消失(大部分狀況)或爆炸(不多,但對優化過程影響很大)。

即便咱們假設循環網絡是參數穩定的(可存儲記憶,且梯度不爆炸),但長期依賴的困難來自比短時間相互做用指數小的權重(涉及許多 Jacobian 相乘)。

循環網絡涉及相同函數的屢次組合,每一個時間步一次。這些組合能夠致使極端非線性行爲,以下圖所示:

當組合許多非線性函數(如這裏所示的線性 tanh 層)時,獲得的結果是高度非線性的。
在大多數狀況下,導數不是過大,就是太小,以及在增長和減少之間的屢次交替。
此處,咱們繪製從 100 維隱藏狀態降到單個維度的線性投影,繪製於 y 軸上。x 軸是 100 維空間中沿着隨機方向的初始狀態的座標。所以,咱們能夠將該圖視爲高維函數的線性截面。曲線顯示每一個時間步以後的函數,或者等價地,轉換函數被組合必定次數以後。

特別地,循環神經網絡所使用的函數組合有點像矩陣乘法。咱們能夠認爲,循環聯繫:

是一個很是簡單的、缺乏非線性激活函數和輸入 x 的循環神經網絡。這種遞推關係本質上描述了冪法。它能夠被簡化爲:

而當 W 符合下列形式的特徵分:

其中 Q 正交,循環性可進一步簡化爲

特徵值提高到 t 次後,致使幅值不到一的特徵值衰減到零,而幅值大於一的特徵值就會激增。任何不與最大特徵向量對齊的 h(0) 的部分將最終被丟棄。

RNN 梯度消失和爆炸問題是由不一樣研究人員獨立發現 (Hochreiter, 1991a; Bengio et al., 1993, 1994b)。有人可能會但願經過簡單地停留在梯度不消失或爆炸的參數空間來避免這個問題。不幸的是,爲了儲存記憶並對小擾動具備魯棒性,RNN 必須進入參數空間中的梯度消失區域。
具體來講,每當模型可以表示長期依賴時,長期相互做用的梯度幅值就會變得指數小,這將直接致使長期依賴的權重調整速度極度放緩,這也是RNN很難學到長序列特徵的緣由。
同時,因爲長期依賴關係的信號很容易被短時間相關性產生的最小波動隱藏,於是學習長期依賴可能須要很長的時間。
實踐中實驗代表,當咱們增長了須要捕獲的依賴關係的跨度, 基於梯度的優化變得愈來愈困難,SGD 在長度僅爲 10 或 20 的序列上成功訓練傳統 RNN 的機率迅速變爲 0。
咱們這裏討論幾種緩解Long Term依賴問題的方法。

1. 多時間尺度的策略

處理長期依賴的一種方法是設計工做在多個時間尺度的模型,使模型的某些部分在細粒度時間尺度上操做並能處理小細節;而其餘部分在粗時間尺度上操做,並能把遙遠過去的信息更有效地傳遞過來。

這種循環網絡的依賴鏈是在時間單元間「跳躍的」,相似於咱們經常會將不一樣感知域的CNN Filter進行Stacking以得到綜合的效果,多時間尺度的目的也是同樣,但願同時兼顧短程依賴和長程依賴的序列特徵模式。

細粒度時間尺度不用特殊設計,就是原始的RNN結構。粗時間尺度須要特殊設計,目前存在多種同時構建粗細時間尺度的策略。

1)在時間軸增長跳躍鏈接

增長從遙遠過去的變量到目前變量的直接鏈接是獲得粗時間尺度的一種方法。使用這樣跳躍鏈接的想法能夠追溯到Lin et al. (1996),緊接是向前饋網絡引入延遲的想法 (Lang and Hinton, 1988)。但其實增長跳躍的本質就是引入延時,本該被傳入鄰接時間步的輸出被延遲傳入了以後 N 步時間步中。

在普通的循環網絡中,循環從時刻 t 的單元鏈接 到時刻 t + 1 單元。構造較長的延遲循環網絡是可能的。

對於梯度爆炸和梯度消失問題,引入了 d 延時的循環鏈接能夠減輕這個問題。由於引入 d 延時後,導數指數減少的速度變爲 τ/d相關,而不是 τ。

既然同時存在延遲鏈接單步鏈接,梯度仍可能成 t 指數爆炸,只是問題會有所緩解。

2)滲漏單元

得到導數乘積接近 1 的另外一方式是設置線性自鏈接單元,而且這些鏈接的權重接近 1。
咱們對某些 v 值應用更新累積一個滑動平均值 μ(t), 其中 α 是一個從 μ(t−1) 到 μ(t) 線性自鏈接的例子。

當 α 接近 1 時,滑動平均值能記住過去很長一段時間的信息,而當 α 接近 0,關於過去的信息被迅速丟棄。

線性自鏈接的隱藏單元能夠模擬滑動平均的行爲。這種隱藏單元稱爲滲漏單元(leaky unit)

d 時間步的跳躍鏈接能夠確保單元總能被 d 個時間步前的那個值影響。使用權重接近 1 的線性自鏈接是確保該單元能夠訪問過去值的不一樣方式。

線性自鏈接經過調節實值 α 更平滑靈活地調整這種效果,比整數的跳躍長度更「柔順」。

咱們能夠經過兩種基本策略設置滲漏單元使用的時間常數。

1. 一種策略是手動將其固定爲常數,例如在初始化時從某些分佈採樣它們的值;
2. 另外一種策略是使時間常數成爲自由變量,並學習出來;

3)刪除鏈接

處理長期依賴另外一種方法是在多個時間尺度組織 RNN 狀態的想法。

一個根本的理論是:信息在較慢的時間尺度上更容易長距離流動。這很容易理解,若是時間尺度很小,信息在每一個時間步要迅速的被決策是保留仍是捨棄,以及保留和捨棄的比例。時間尺度變慢,意味着決策的次數減小,信息保留的機率就增大了。

這個想法與以前討論的時間維度上的跳躍鏈接不一樣:

1. 該方法涉及主動刪除長度爲 1 的鏈接並用更長的鏈接替換它們。以這種方式修改的單元被迫在長時間尺度上運做;
2. 而經過時間跳躍鏈接是添加邊,收到這種新鏈接的單元,能夠學習在長時間尺度上運做,但也能夠選擇專一於本身其餘的短時間鏈接;

0x5:長短時間記憶和門控RNN

像滲漏單元同樣,門控 RNN 想法也是基於生成經過時間的路徑,其中導數既不消失也不發生爆炸。

滲漏單元經過手動選擇常量的鏈接權重或參數化的鏈接權重來達到這一目的。門控 RNN 將其推廣爲在每一個時間步均可能改變的鏈接權重。

滲漏單元容許網絡在較長持續時間內積累信息(諸如用於特定特徵或類的線索)。然而,一旦該信息被使用了,讓神經網絡遺忘舊的狀態多是有幫助的。

例如,若是一個序列是由子序列組成,咱們但願滲漏單元能在各子序列內積累線索,可是當進入新的子序列前能夠忘記舊的子序列的線索信息,咱們須要一種忘記舊狀態的機制。

咱們但願神經網絡學會決定什麼時候清除狀態,而不是手動決定。這就是門控 RNN 要作的事。

1. LSTM

引入自循環的巧妙構思,以產生梯度長時間持續流動的路徑是初始長短時間記憶 (long short-term memory, LSTM)模型的核心貢獻。

其中一個關鍵擴展是使自循環的權重視上下文而定,而不是固定的

咱們前面說過,RNN的核心思想就是參數共享,LSTM也一樣遵照這個核心思想,所不一樣的是,LSTM並非從頭至尾參數一直共享,而是在某個「時間區間」內進行共享,在整個時間步鏈路上權重會動態調整。

在這種狀況下,即便是具備固定參數的 LSTM,累積的時間尺度也能夠因輸入序列而改變,由於時間常數是模型自己的輸出。

2. LSTM的核心思想

1. 容許網絡動態地控制時間尺度,即信息在時間步鏈條上的存活時間是動態控制的;
2. 容許網絡動態控制不一樣單元的遺忘行爲;

Relevant Link:

https://blog.csdn.net/cf2suds8x8f0v/article/details/79244587
https://blog.csdn.net/qq_36279445/article/details/72724649

 

3. 循環神經網絡計算圖(Computational Graph)

本章咱們將計算圖的思想擴展到包括循環。

這些週期表明瞭變量自身的值在將來某一時間步會對自身值的影響。這樣的計算圖容許咱們定義循環神經網絡。

0x1:計算圖定義

咱們使用圖中的每個節點來表示一個變量。

變量能夠是標量、向量、矩陣、張量、或者甚至是另外一類型的變量。

爲了形式化咱們的圖形,咱們還需引入操做(operation)這一律念。操做是指一個或多個變量的簡單函數。

咱們的圖形語言伴隨着一組被容許的操做。咱們能夠經過將多個操做複合在一塊兒來描述更爲複雜的函數。

若是變量 y 是變量 x 經過一個操做計算獲得的,那麼咱們畫一條從 x 到 y 的有向邊。咱們有時用操做的名稱來註釋輸出的節點,當上下文很明確時,有時也會省略這個標註。

1. 不一樣操做對應的計算圖舉例

使用 × 操做計算 z = xy 的圖

用於邏輯迴歸預測 yˆ = σ(x⊤w + b) 的圖。一些中間表達式在代數表達式中沒有名稱,但在圖形中卻須要。咱們簡單地將 第 i 個這樣的變量命名爲 u(i)

表達式 H = max{0, XW + b} 的計算圖,在給定包含小批量輸入數據的設計矩陣 X 時,它計算整流線性單元激活的設計矩陣 H。

對變量實施多個操做也是可能的。該計算圖對線性迴歸模型的權重 w 實施多個操做。這個權重不只用於預測 yˆ,也用於權重衰減罰項 λ∑ w2。這就是所謂的結構化風險評估。

0x2:展開RNN計算圖

計算圖是形式化一組計算結構的方式,如那些涉及將輸入和參數映射到輸出和損失的計算。

咱們對展開(unfolding)遞歸或循環計算獲得的重複結構進行解釋,這些重複結構一般對應於一個事件鏈展開(unfolding)這個計算圖將更好地可視化深度網絡結構中的參數共享。

1. 動態系統的經典形式計算圖

例如,考慮下式:

,其中,稱爲系統的狀態。

s 在時刻 t 的定義須要參考時刻 t-1 時一樣的定義,所以上式是循環的

對有限時間步 τ, τ - 1 次應用這個定義能夠展開這個圖。例如 τ = 3,咱們對上式進行展開,能夠獲得:

以這種方式重複應用定義,展開等式,就能獲得不涉及循環的表達式。

如今咱們可使用傳統的有向無環圖(和HMM同樣都是有向無環圖機率圖模型)呈現上式的表達。

每一個節點表示在某個時刻 t 的狀態,而且函數 f t 處的狀態映射到 t + 1 處的狀態。全部時間步都使用相同的參數(用於參數化 f 的相同 θ 值)

2. 存在外部驅動信號的動態系統的計算圖

做爲另外一個例子,讓咱們考慮由外部信號驅動的動態系統:

從公式上看,當前狀態包含了整個過去序列的信息。可是這個歷史信息是有損的

當訓練循環網絡根據過去預測將來時,網絡一般要學會使用 h(t) 做爲過去序列的有損摘要。

此摘要通常而言必定是有損的,由於其映射任意長度的序列到一固定長度的向量 h(t)。

根據不一樣的訓練準則,摘要可能選擇性地精確保留過去序列的某些方面

例如,若是在統計語言建模中使用RNN,一般給定前一個詞預測下一個詞,可能沒有必要存儲時刻 t 前輸入序列中的全部信息,而僅僅存儲足夠預測句子其他部分的信息(相似HMM)。

最苛刻的狀況是咱們要求 h(t) 足夠豐富,並能大體恢復輸入序列,如自編碼器框架。

上面公式能夠用兩種不一樣的方式繪製,以下圖:

1)迴路圖表示法

一種方法是爲可能在模型的物理實現中存在的部分賦予一個節點,如生物神經網絡。在這個觀點下,網絡定義了實時操做的迴路,如上圖左側,其當前狀態能夠影響其將來的狀態。

咱們使用 迴路圖的黑色方塊代表在時刻 t 的狀態到時刻 t + 1 的狀態單個時刻延遲中的相互做用。

2)展開表示法

另外一個繪製 RNN 的方法是展開的計算圖,其中每個組件由許多不一樣的變量表示,每一個時間步一個變量,表示在該時間點組件的狀態。每一個時間步的每一個變量繪製爲計算圖的一個獨立節點,如上圖右側。

咱們所說的展開是將左圖中的迴路映射爲右圖中包含重複組件的計算圖的操做。目前,展開圖的大小取決於序列長度。

3. 展開計算圖的優勢

咱們能夠用一個函數 g(t) 表明經 t 步展開後的循環:

函數 g(t) 將所有的過去序列做爲輸入來生成當前狀態,可是展開的循環架構容許咱們將 g(t) 分解爲函數 f 的重複應用。所以,展開過程引入兩個主要優勢:

1. 不管序列的長度,學成的模型始終具備相同的輸入大小,由於它指定的是從一種狀態到另外一種狀態的轉移,而不是在可變長度的歷史狀態上操做
2. 咱們能夠在每一個時間步使用相同參數的相同轉移函數 f

這兩個因素使得學習在全部時間步全部序列長度上操做單一的模型 f 是可能的,而不須要在全部可能時間步學習獨立的模型 g(t)。

學習單一的共享模型容許泛化到訓練集中未出現的序列長度,而且估計模型所需的訓練樣本遠遠少於不帶參數共享的模型。

循環神經網絡能夠經過許多不一樣的方式創建。就像幾乎全部函數均可以被認爲是前饋網絡,本質上任何涉及循環的函數均可以被認爲是一個循環神經網絡

Relevant Link:

《深度學習》花書

 

4. 循環神經網絡邏輯圖結構

基於以前討論的圖展開參數共享的思想,能夠設計各類循環神經網絡。

讀者朋友須要注意的是,循環神經網絡不是特指必定具體的算法實現,循環神經網絡是特指一整類具有某些特性的神經網絡結構,注意要和TensorFlow/theano中的RNN實現類區分開來。

循環神經網絡從大的分類來講能夠分爲如下幾種:

1. 從序列到序列的神經網絡:即每一個時間步都有輸出;
3. 從序列到向量的神經網絡:即讀取整個序列後產生單個輸出,即整個循環網絡能夠壓縮爲一個擁有惟一輸出的循環遞歸函數;
3. 從向量到序列的神經網絡:輸入單個向量,在每一個時間步都有輸出;

在遵循以上設計模型的原則之下,RNN能夠進行各類結構上的變種,使之具有相應新的能力和性能。

咱們學習RNN,就是要重點理解不一樣結構之間的區別和原理,理解不一樣的網絡拓樸結構是如何影響信息流的傳遞和依賴。至於具體網絡內部的激活函數是用tang仍是relu,其實倒還不是那麼重要了。

0x1:經典RNN結構 - 時間步之間存在「隱藏神經元」循環鏈接

1. 邏輯流程圖 - 將輸入序列映射到等長的輸出序列

下圖是該循環神經網絡的邏輯流程圖:

計算循環網絡(將 x 值的輸入序列映射到輸出值 o 的對應序列) 訓練損失的計算圖;
RNN輸入到隱藏的鏈接由權重矩陣 U 參數化;
隱藏到隱藏的循環鏈接由權重矩陣 W 參數化以及隱藏到輸出的鏈接由權重矩陣 V 參數化;
其中每一個節點如今與一個特定的時間實例相關聯

任何圖靈可計算的函數均可以經過這樣一個有限維的循環網絡計算,在這個意義上公式表明的循環神經網絡是萬能的。

RNN 通過若干時間步後讀取輸出,這與由圖靈機所用的時間步是漸近線性的,與輸入長度也是漸近線性的。

RNN 做爲圖靈機使用時,須要一個二進制序列做爲輸入,其輸出必須離散化以提供二進制輸出。利用單個有限大小的特定 RNN 計算全部函數是可能的。

RNN 能夠經過激活和權重(由無限精度的有理數表示)來模擬無限堆棧。 

2. 一個具體的網絡結構 - 指定特定的激活函數

再次強調,循環神經網絡的結構和具體的激活函數和損失函數是不存在強關聯的,網絡能夠選擇任何激活函數

爲了可以更好地公式化地描述上圖網絡結構,咱們指定特定的模型參數。

激活函數:雙曲正切激活函數;
輸出形式:假定輸出是離散的,如用於預測詞或字符的RNN。表示離散變量的常規方式是把輸出 o 做爲每一個離散變量可能值的非標準化對數機率;
損失函數。應用 softmax 函數後續處理後,得到標準化後機率的輸出向量 yˆ;

RNN從特定的初始狀態 h(0) 開始前向傳播。從 t = 1 到 t = τ 的每一個時間步,咱們應用如下更新方程:

這個循環網絡將一個輸入序列映射到相同長度的輸出序列。

與 x 序列配對的 y 的總損失就是全部時間步的損失之和。例如,L(t) 爲給定的 x(1),...,x(t) 後y(t) 的負對數似然,則

其中,須要讀取模型輸出向量 yˆ(t) 中對應於 y(t) 的項。

0x2:導師驅動過程循環網絡 - 時間步之間存在」目標值單元「和」隱藏神經元」鏈接的循環鏈接

1. 邏輯流程圖 - 將輸入序列映射到等長的輸出序列

導師驅動過程循環神經網絡,它僅在一個時間步的目標單元值下一個時間步的隱藏單元間存在循環鏈接。邏輯流程以下:

從本質上理解,這種導師驅動的循環神經網絡,就是將多個單神經元感知機按照序列的方式串聯起來,相鄰神經元感知機之間存在2階的依賴關係。

2. 導師驅動循環網絡的優缺點

1)缺點

由於缺少隱藏到隱藏的循環鏈接,因此它不能模擬通用圖靈機。本質緣由在於,隱藏神經元中保存和傳遞的是高階維度特徵,隱藏神經元之間循環鏈接使得這種高階維度特徵得以傳播和記憶。

而目標值單元和隱藏神經元相連的網絡結構,它要求目標值單元捕捉用於預測將來的關於過去的全部信息。

可是由於目標值單元(輸出單元)明確地訓練成匹配訓練集的目標,它們不太能捕獲關於過去輸入歷史的必要信息,除非用戶知道如何描述系統的所有狀態,並將它做爲訓練目標的一部分。

2)優勢

反過來講,消除隱藏到隱藏循環的優勢在於,任何基於比較時刻 t 的預測和時刻 t 的訓練目標的損失函數中的全部時間步都解耦了。所以訓練能夠並行化,即在各時刻 t 分別計算梯度。由於訓練集提供輸出的理想值,因此沒有必要先計算前一時刻的輸出。

3. 導師驅動過程訓練(teacher forcing)

由輸出反饋到模型而產生循環鏈接的模型可用導師驅動過程(teacher forcing) 進行訓練。
訓練模型時,導師驅動過程在時刻 t + 1 接收真實值 y(t) 做爲輸入。咱們能夠經過檢查兩個時間步的序列得知這一點:

在這個例子中,同時給定迄今爲止的 x 序列和來自訓練集的前一 y 值,咱們可 以看到在時刻 t = 2 時,模型被訓練爲最大化 y(2) 的條件機率。

所以最大似然在訓練時指定正確反饋,而不是將本身的輸出反饋到模型。

咱們使用導師驅動過程的最初動機是爲了在缺少隱藏神經元到隱藏神經元鏈接的模型中避免經過時間反向傳播

0x3:時間步之間存在「隱藏神經元」循環鏈接,且網絡只有惟一的單向量輸出

1. 邏輯流程圖 - 將輸入序列映射爲固定大小的向量

關於時間展開的循環神經網絡,在序列結束時具備單個輸出。

這樣的網絡能夠用於歸納序列產生用於進一步處理的固定大小的表示。在結束處可能存在目標(如此處所示),或者經過更下游模塊的反向傳播來得到輸出 o(t) 上的梯度。

0x4:基於上下文的RNN建模 - 隱藏神經元之間存在循環鏈接,且」目標值單元「和」隱藏神經元「之間存在鏈接

通常狀況下,RNN 容許將圖模型的觀點擴展到不只表明 y 變量的聯合分佈也能表示給定 x 後 y 條件分佈。

須要重點理解的一點是,輸入序列 x 的方式的不一樣,會形成RNN的效果和性能的很大差異。某種程度上甚至能夠說,RNN網絡中結構的一個調整,可能就是一個徹底不一樣的新算法了,這也是深度神經網絡強大而複雜的一面了。

1. 在每一個時間步將完整的 x序列 輸入網絡中 - 輸入序列 x 和輸出序列 y 不必定要等長 - 將固定大小的向量映射成一個序列

網絡結構以下圖所示:

輸入 x 和每一個隱藏單元向量 h(t) 之間 的相互做用是經過新引入的權重矩陣 R 參數化的。乘積 x⊤R 在每一個時間步做爲隱藏單元的一個額外輸入。

咱們能夠認爲 x 的選擇(肯定 x⊤R 的值),是有效地用於每一個隱藏單元的一個新偏置參數。權重與輸入保持獨立。

將固定長度的向量 x 映射到序列 Y 上每一個時間步的 RNN上。這類 RNN 適用於不少任務如圖像標註, 其中單個圖像做爲模型的輸入,而後產生描述圖像的詞序列。觀察到的輸出序列的每一個元素 y(t) 同時用做輸入(對於當前時間步)和訓練期間的目標(對於前一時間步)

筆者思考:這種結構能用於圖注任務的原理很是的直觀,圖注的註解序列的每個詞都應該和整張圖片有關,因此須要在每一個時間步都輸入完整的 x 序列,同時,圖注註解序列的單詞之間也存在依賴推導關係,由於 y(t) 須要傳入下一個時間步

2. 將 x序列 依次輸每一個時間步中 - 輸入序列 x 和輸出序列 y 等長 - 將輸入序列映射爲等長的輸出序列

RNN 能夠接收向量序列 x(t) 做爲輸入,而不是僅接收單個向量 x 做爲輸入。

接受向量序列的RNN的條件機率分佈公式爲:

結構圖以下:
將可變長度的 x 值序列映射到相同長度的 y 值序列上分佈的條件循環神經網絡。此 RNN 包含從前一個輸出到當前狀態的鏈接。這些鏈接容許此RNN對給定 x 的序列後 相同長度的 y 序列上的任意分佈建模

0x5:雙向RNN

傳統的前饋RNN網絡都有一個 ‘‘因果’’ 結構,意味着在時刻 t 的狀態只能從過去的序列x(1),...,x(t−1) 以及當前的輸入x(t) 捕獲信息。

然而,在許多應用中,咱們要輸出的 y(t) 的預測可能依賴於整個輸入序列。

例如,在語音識別中,因爲協同發音,當前聲音做爲音素的正確解釋可能取決於將來幾個音素,甚至潛在的可能取決於將來的幾個詞,由於詞與附近的詞之間的存在語義依賴,若是當前的詞有兩種聲學上合理的解釋,咱們可能要在更遠的將來(和過去)尋找信息區分它們。這在手寫識別和許多其餘序列到序列學習的任務中也是如此。

雙向循環神經網絡(或雙向 RNN)爲知足這種須要而被髮明。他們在須要雙向信息的應用中很是成功,如手寫識別,,語音識別以及生物信息學。
顧名思義,雙向RNN結合時間上從序列起點開始移動的RNN和另外一個時間上從序列末尾開始移動的RNN。下圖展現了典型的雙向 RNN

1. 邏輯流程圖 - 將輸入序列映射到等長的輸出序列

其中 h(t) 表明經過時間向前移動的子 RNN 的狀態,g(t) 表明經過時間向後移動的子 RNN 的狀態。所以在每一個點 t,輸出單元 o(t) 能夠受益於輸入 h(t) 中關於過去的相關概要以及輸入 g(t) 中關於將來的相關概要

這容許輸出單元 o(t) 可以計算同時依賴於過去和將來且對時刻 t 的輸入值最敏感的表示,而沒必要指定 t 周圍固定大小的窗口。

0x6:基於編碼 - 解碼的序列到序列結構

這一小節,咱們將討論RNN如何將一個輸入序列映射到不等長的輸出序列。這在許多場景中都有應用,如語音識別、機器翻譯或問答,其中訓練集的輸入和輸出序列的長度一般不相同。

咱們常常將RNN的輸入稱爲「上下文」。咱們但願產生此上下文的表示C。這個上下文C多是一個歸納輸入序列 X = (x(1) , . . . , x(nx ) ) 的向量或者向量序列,即神經網絡的隱層高維度向量。

實際上,輸入長度和輸出長度不一致的神經網絡並不罕見,DNN和CNN中這種狀況都很是常見(例如將圖像輸入獲得手寫數字輸出),實現這一能力的核心思想就是增長 1 個及以上的隱層,對於RNN也是同樣的,隱層起到信息壓縮和解壓縮的承上啓下做用。

這種架構稱爲編碼-解碼序列到序列架構。以下圖所示:

這個結構其實是由兩個RNN結構拼接組成的。

(1) 編碼器(encoder)或讀取器(reader)或輸入 (input) RNN 處理輸入序列。編碼器輸出上下文C(一般是最終隱藏狀態的簡單函數),C表示輸入序列的語義概要;

(2) 解碼器(decoder)或寫入器 (writer)或輸出 (output) RNN 則以固定長度的向量(即上下文C)爲條件產生輸出序列 Y = (y(1),...,y(ny))。

在序列到序列的架構中,兩個 RNN 共同訓練以最大化 logP(y(1),...,y(ny) | x(1),...,x(nx))(訓練集中全部 x 和 y 對的損失)。

編碼器 RNN 的最後一個狀態 hnx 一般被看成輸入的表示 C 並做爲解碼器 RNN 的輸入。 

0x7:LSTM

LSTM邏輯圖以下所示:

LSTM 循環網絡除了外部的 RNN 循環外,還具備內部的 「LSTM 細胞’’ 循環(自環),所以 LSTM 不是簡單地向輸入和循環單元的仿射變換以後施加一個逐元素的非線性。

與普通的循環網絡相似,每一個單元有相同的輸入和輸出,但也有更多的參數和控制信息流動的門控單元系統

最重要的組成部分是狀態單元 s(t),與以前討論的滲漏單元有相似的線性自環。然而,此處自環的權重(或相關聯的時間常數)由遺忘門 (forget gate) f(t) 控制。而滲漏單元須要設計者實現手工決定。一個是數據驅動,一個是經驗驅動。

遺忘門函數 f(t) 由 sigmoid 單元將權重設置爲 0 和 1 之間的值:

其中 x(t) 是當前輸入向量,ht 是當前隱藏層向量,ht 包含全部 LSTM 細胞的輸出。 bf , Uf , Wf 分別是偏置、輸入權重和遺忘門的循環權重。

LSTM 細胞內部狀態以以下方式更新:

其中 b, U, W 分別是 LSTM 細胞中的偏置、輸入權重和遺忘門的循環權重。外部輸入門 (external input gate) 單元 g(t) 以相似遺忘門(使用sigmoid得到一個 0 和 1 之
間的值)的方式更新,但有自身的參數:

LSTM 細胞的輸出 h(t) 也能夠由輸出門 (output gate) q(t) 關閉(使用sigmoid單元做爲門控):

其中 bo, Uo, Wo 分別是偏置、輸入權重和遺忘門的循環權重。

LSTM 網絡比簡單的循環架構更易於學習長期依賴

筆者思考:LSTM從數學公式上並無什麼特別的地方,這是相比於原始的RNN公式加入了一些額外的函數,使得聯合優化過程更復雜了,固然也帶來的額外的好處。LSTM的核心思想就是在隱狀態的循環傳遞中插入了一個「門控函數」,該門控函數具有「放行」和「阻斷」這兩種能力,而具體是否要放行以及放行多少由輸入和輸出進行BP聯合訓練。
進一步擴展,咱們甚至能夠將門控函數改成一個「信號放大函數」,使其成爲一個具有新能力的RNN網絡,全部的功能背後都是數學公式以及該公式具有的線性和非線性能力

0x8:GRU(門控循環單元)

GRU 與 LSTM 的主要區別是,單個門控單元同時控制遺忘因子和更新狀態單元的決定。更新公式以下:

其中 u 表明 」更新門」,r 表示 「復位門「。它們的值定義以下:

復位和更新門能獨立地 ‘‘忽略’’ 狀態向量的一部分。

1. 更新門像條件滲漏累積器同樣,能夠線性門控任意維度,從而選擇將它複製(在 sigmoid 的一個極端)或徹底由新的 ‘‘目標狀態’’ 值(朝向滲漏累積器的收斂方向)替換並徹底忽略它(在另外一個極端)。
2. 復位門控制當前狀態中哪些部分用於計算下一個目標狀態,在過去狀態和將來狀態之間引入了附加的非線性效應。

圍繞這一主題能夠設計更多的變種。例如復位門(或遺忘門)的輸出能夠在多個隱藏單元間共享。或者,全局門的乘積(覆蓋一整組的單元,例如整一層)和一個局部門(每單元)可用於結合全局控制和局部控制。但無論怎樣,讀者朋友須要明白的是,這些本質上都是數學公式上的增長和變化,在覈心架構上,GRU和咱們以前討論的RNN單元公式是相似的。

 

5. 循環神經網絡機率圖結構

從機率模型的角度來看,咱們能夠將深度神經網絡的輸出解釋爲一個機率分佈,而且咱們一般使用與分佈相關聯的交叉熵來定義損失。

須要注意的是,是否將上一時間步的某種形式輸出做爲當前時間步的輸入(便是否存在序列依賴),以及取多少步的歷史時間步輸出做爲當前時間步的輸入,會對網絡模型的性能和效果形成很是大的變化,咱們這個小節來嘗試討論下這個話題。

0x1:序列時間步之間輸入輸出獨立

將整個序列 y 的聯合分佈分解爲一系列單步的機率預測是捕獲關於整個序列完整聯合分佈的一種方法。

當咱們不把過去時間步的 y 值反饋給下一步做爲預測的條件時,那麼該有向圖模型模型不包含任何從過去 y(i) 到當前 y(t) 的邊。在這種狀況下,輸出 y 與給定的 x 序列是條件獨立的。

樸素貝葉斯NB算法中的樸素貝葉斯假設本質上就屬於這種狀況。

0x2:序列時間步之間有限步(階)輸入輸出依賴

許多機率圖模型的目標是省略不存在強相互做用的邊以實現統計和計算的效率

例如經典的Markov假設, 即圖模型應該只包含從 {y(t−k), . . . , y(t−1)} 到 y(t) 的邊(k階馬爾科夫),而不是包含整個過去歷史的邊。

然而,在一些狀況下,咱們認爲整個過去的輸入會對序列的下一個元素有必定影響。當咱們認爲 y(t) 的分佈可能取決於遙遠過去 (在某種程度) 的 y(i) 的值,且 沒法經過 y(t−1) 捕獲 y(i) 的影響時,RNN 將會頗有用。

筆者思考:在實際項目中,咱們對序列依賴的長度的需求是須要仔細思考的,並非全部狀況下都須要針對超長序列提取模式記憶。例如在webshell檢測場景中,咱們每每更關注」短程語法句式模式「,由於惡意代碼的主題功能每每在5步以內就會完成,咱們須要捕獲的也就是這些短程的序列模式。

0x3:歷史時間步長序列輸入輸出依賴

RNN 被訓練爲可以根據以前的歷史輸入估計下一個序列元素 y(t) 的條件分佈,條件機率公式以下:

能夠看到,RNN遵循的是一種長序列依賴假設。

舉一個簡單的例子,讓咱們考慮對標量隨機變量序列 Y = {y(1),...,y(τ)} 建模的 RNN,也沒有額外的輸入 x(實際大多數狀況是存在輸入 x 的)。在時間步 t 的輸入僅僅是時間步 t − 1 的輸出:

這個例子中的 RNN 定義了關於 y 變量的有向圖模型。咱們使用鏈式法則參數化這些觀察值的聯合分佈:

其中當 t = 1 時豎槓右側顯然爲空。所以,根據這樣一個模型,一組值 {y(1),...,y(τ)} 的負對數似然爲:

,其中,

」該RNN中每一時間步都參考了歷史上全部歷史時間步的輸出「,這句話有點抽象很差理解,爲了更好地討論這句話的概念,咱們將計算圖展開爲徹底圖:

咱們將RNN視爲定義一個結構爲徹底圖的圖模型,且可以表示任何一對 y 值之間的直接聯繫。即每個時間步之間都存在某種聯繫。這預示着 RNN 能對觀測的聯合分佈提供很是有效的參數,以下圖:

序列 y(1), y(2), . . . , y(t), . . . 的全鏈接圖模型。給定先前的值,每一個過去的觀察值 y(i) 可 以影響一些 y(t)(t > i) 的條件分佈。

當序列中每一個元素的輸入和參數的數目愈來愈多,根據此圖直接參數化圖模型多是很是低效的。RNN 能夠經過高效的參數化(參數共享機制)得到相同的全鏈接。

可是全鏈接帶來一個嚴重的問題,參數膨脹

假設咱們用表格表示法來表示離散值上任意的聯合分佈,即對每一個值可能的賦值分配一個單獨條目的數組,該條目表示發生該賦值的機率。若是 y 能夠取 k 個不一樣的 值,表格表示法將有 O(kτ ) 個參數。

可是 RNN 因爲使用參數共享機制,RNN 的參數數目爲 O(1) 且是序列長度的函數。咱們能夠調節 RNN 的參數數量來控制模型容量,但不用被迫與序列長度成比例。

下式展現了所述 RNN 經過循環應用相同的函數,以及在每一個時間步的相同參數 θ,有效地參數化的變量之間的長期聯繫:

同時在 RNN 圖模型中引入狀態變量,儘管它是輸入的肯定性函數,但它有助於咱們得到很是高效的參數化。

序列中的每一個階段(對於 h(t) 和 y(t) )使用相同的結構(每一個節點具備相同數量的輸入),而且能夠與其餘階段共享相同的參數。

在圖模型中結合 h(t) 節點能夠用做過去和將來之間的中間量,從而將它們解耦。遙遠過去的變量 y(i) 能夠經過其對 h 的影響來影響變量 y(t)。

 

6. 循環神經網絡的梯度計算

循環神經網絡中,關於各個參數計算這個損失函數的梯度是計算成本很高的操做。

經過將RNN的計算圖展開後能夠清楚地看到,梯度計算涉及執行一次前向傳播,接着是由右到左的反向傳播。運行時間是 O(τ),而且不能經過並行化來下降,由於前向傳播圖是固有循序的,每一個時間步只能一前一後地計算。前向傳播中的各個狀態必須保存,直到它們反向傳播中被再次使用,所以內存代價也是 O(τ)。

應用於展開圖且代價爲 O(τ) 的反向傳播算法稱爲經過時間反向傳播(back-propagation through time, BPTT),隱藏單元之間存在循環的網絡很是強大但訓練代價也很大。

0x1:舉例說明BPTT計算過程

以文章以前討論的例子爲例計算梯度:

計算圖的節點包括參數 U, V, W, b 和 c,以及以 t 爲索引的節點序列 x(t), h(t), o(t) 和 L(t)。

對於每個節點 N,咱們須要基於 N 後面的節點的梯度,遞歸地計算梯度 ∇NL。咱們從緊接着最終損失的節點開始往回遞歸:

在這個導數中,咱們假設輸出 o(t) 做爲 softmax 函數的參數,咱們能夠從 softmax函數能夠得到關於輸出機率的向量 yˆ。咱們也假設損失是迄今爲止給定了輸入後的真實目標 y(t) 的負對數似然。對於全部 i, t,關於時間步 t 輸出的梯度 ∇o(t) L 以下:

咱們從序列的末尾開始,反向進行計算。在最後的時間步 τ, h(τ) 只有 o(τ) 做爲後續節點,所以這個梯度很簡單:

而後,咱們能夠從時刻 t = τ − 1 到 t = 1 反向迭代,經過時間反向傳播梯度,注意h(t)(t < τ) 同時具備 o(t) 和 h(t+1) 兩個後續節點。所以,它的梯度由下式計算:

其中 diag 1−(h(t+1))2 表示包含元素 1−(h(t+1))2 的對角矩陣。這是關於時刻 t+1 與隱藏單元 i 關聯的雙曲正切的Jacobian。

一旦得到了計算圖內部節點的梯度,咱們就能夠獲得關於參數節點的梯度。

由於參數在許多時間步共享,咱們必須在表示這些變量的微積分操做時謹慎對待。咱們但願使用 bprop 方法計算計算圖中單一邊對梯度的貢獻。然而微積分中的 ∇Wf 算子,計算 W 對於 f 的貢獻時將計算圖中的全部邊都考慮進去了。爲了消除這種歧義,咱們定義只在 t 時刻使用的虛擬變量 W(t) 做爲 W 的副本。而後,咱們可使用 ∇W(t) 表示權重在時間步 t 對梯度的貢獻。

使用這個表示,計算節點內部參數的梯度能夠由下式給出:

由於計算圖中定義的損失的任何參數都不是訓練數據 x(t) 的父節點,因此咱們不須要計算關於它的梯度。

 

7. 循環神經網絡的具體應用

0x1:基於RNN+LSTM的模型自動編寫古詩

1. 語料數據

一共四萬多首古詩,每行一首詩。

2. 樣本預處理

這裏咱們採用one-hot的形式,基於當前的詩句文件統計出一個字典,這樣詩句中的每一個字都能用向量來表示。固然,也能夠採用emberding方式進行詞向量嵌入。

# *-* coding:utf-8 *-*


puncs = [']', '[', '', '', '{', '}', '', '', '']


def preprocess_file(Config):
    # 語料文本內容
    files_content = ''
    with open(Config.poetry_file, 'r', encoding='utf-8') as f:
        for line in f:
            # 每行的末尾加上"]"符號表明一首詩結束
            for char in puncs:
                line = line.replace(char, "")
            files_content += line.strip() + "]"  

    # 統計整個預料的詞頻
    words = sorted(list(files_content))
    words.remove(']')
    counted_words = {}
    for word in words:
        if word in counted_words:
            counted_words[word] += 1
        else:
            counted_words[word] = 1

    # 去掉低頻的字
    erase = []
    for key in counted_words:
        if counted_words[key] <= 2:
            erase.append(key)
    for key in erase:
        del counted_words[key]
    del counted_words[']']
    wordPairs = sorted(counted_words.items(), key=lambda x: -x[1])

    words, _ = zip(*wordPairs)
    # word到id的映射
    word2num = dict((c, i + 1) for i, c in enumerate(words))
    num2word = dict((i, c) for i, c in enumerate(words))
    word2numF = lambda x: word2num.get(x, 0)
    return word2numF, num2word, words, files_content

3. 生成序列數據

RNN是序列到序列的有監督模型,所以咱們須要定義每一個時間步的輸入x,以及每一個時間步的輸出目標值y。

咱們給模型學習的方法是,給定前六個字,生成第七個字,因此在後面生成訓練數據的時候,會以6的跨度,1的步長截取文字,生成語料。

好比「我要吃香蕉」,如今以3的跨度生成訓練數據就是("我要吃", 「香」),("要吃香", "蕉")。跨度爲6的句子中,先後每一個字都是有關聯的。若是出現了]符號,說明]符號以前的語句和以後的語句是兩首詩裏面的內容,兩首詩之間是沒有關聯關係的,因此咱們後面會捨棄掉包含]符號的訓練數據。

def data_generator(self):
        '''生成器生成數據'''
        i = 0
        while 1:
            x = self.files_content[i: i + self.config.max_len]  # max_len跨度做爲x
            y = self.files_content[i + self.config.max_len]     # max_len+1 的那個跟隨詞做爲y

            puncs = [']', '[', '', '', '{', '}', '', '', '', ':']
            if len([j for j in puncs if j in x]) != 0:  # x中出現詩句中止符,丟棄該x
                i += 1
                continue
            if len([j for j in puncs if j in y]) != 0:  # y恰好是詩句中止符,丟棄該y
                i += 1
                continue

            y_vec = np.zeros(
                shape=(1, len(self.words)),
                dtype=np.bool
            )
            y_vec[0, self.word2numF(y)] = 1.0  # y是one-hot編碼,對應出現的那個詞爲true,其餘爲false

            x_vec = np.zeros(
                shape=(1, self.config.max_len),
                dtype=np.int32
            )

            for t, char in enumerate(x):
                x_vec[0, t] = self.word2numF(char)
            yield x_vec, y_vec
            i += 1 

x表示輸入,y表示輸出,輸入就是前六個字,輸出即爲第七個字。再將文字轉換成向量的形式。

4. 構建模型

def build_model(self):
        '''創建模型'''

        # 輸入的dimension
        input_tensor = Input(shape=(self.config.max_len,))
        embedd = Embedding(len(self.num2word) + 2, 300, input_length=self.config.max_len)(input_tensor)
        lstm = Bidirectional(GRU(128, return_sequences=True))(embedd)
        # dropout = Dropout(0.6)(lstm)
        # lstm = LSTM(256)(dropout)
        # dropout = Dropout(0.6)(lstm)
        flatten = Flatten()(lstm)
        dense = Dense(len(self.words), activation='softmax')(flatten)
        self.model = Model(inputs=input_tensor, outputs=dense)
        optimizer = Adam(lr=self.config.learning_rate)
        self.model.compile(loss='categorical_crossentropy', optimizer=optimizer, metrics=['accuracy'])

雙向lstm以後用flattern和DNN進行壓平和整合,最後softmax獲得單個向量的輸出。

5. 訓練模型

 def train(self):
        '''訓練模型'''
        number_of_epoch = len(self.words) // self.config.batch_size
 
        if not self.model:
            self.build_model()
 
        self.model.fit_generator(
            generator=self.data_generator(),
            verbose=True,
            steps_per_epoch=self.config.batch_size,
            epochs=number_of_epoch,
            callbacks=[
                keras.callbacks.ModelCheckpoint(self.config.weight_file, save_weights_only=False),
                LambdaCallback(on_epoch_end=self.generate_sample_result)
            ]
        )

6. 每輪epoch訓練結果,進行一次成果展現

 def generate_sample_result(self, epoch, logs):
        '''訓練過程當中,每一個epoch打印出當前的學習狀況'''
        # if epoch % 5 != 0:
        #     return
        print("\n==================Epoch {}=====================".format(epoch))
        for diversity in [0.5, 1.0, 1.5]:
            print("------------Diversity {}--------------".format(diversity))
            start_index = random.randint(0, len(self.files_content) - self.config.max_len - 1)
            generated = ''
            sentence = self.files_content[start_index: start_index + self.config.max_len]   # 隨機截取一段6詞序列做爲待預測x
            generated += sentence
            for i in range(20):     # 循環20次,即生成一句20個詞的詩句
                x_pred = np.zeros((1, self.config.max_len))
                for t, char in enumerate(sentence[-6:]):
                    x_pred[0, t] = self.word2numF(char)

                preds = self.model.predict(x_pred, verbose=0)[0]    # 獲得y預測結果
                print "preds: ", preds
                next_index = self.sample(preds, diversity)          # 從y中選擇機率最大的詞編碼
                next_char = self.num2word[next_index]   # 翻譯回可讀漢字

                generated += next_char
                sentence = sentence + next_char
            print(sentence)

下圖展現了訓練初期和訓練一段時間以後,RNN的詩句生成效果

訓練一段時間後:

Relevant Link:

https://www.ioiogoo.cn/2018/02/01/%E7%94%A8keras%E5%AE%9E%E7%8E%B0rnnlstm%E7%9A%84%E6%A8%A1%E5%9E%8B%E8%87%AA%E5%8A%A8%E7%BC%96%E5%86%99%E5%8F%A4%E8%AF%97/
https://github.com/LittleHann/poetry_generator_Keras

0x2:基於LSTM生成城市名稱

RNN具有記憶性,在通過大量訓練後能夠學習到時序數據的潛在規律,而且可使用這種規律隨機生成新的序列。

1. 如何給RNN輸入訓練樣本

RNN能夠學習到數據中的時序規律,可是做爲模型設計者,咱們須要明確地定義:該樣本集中時序規律的形式是什麼

例如筆者在項目中遇到的一些典型場景:

1. 你有一段時序向量數據,而且擁有對這個時序向量數據的一個 0/1 label,即二分類問題,這在安全攻防場景中很常見;
2. 你有一個預料庫,該語料庫中包含了各類句子。你但願讓RNN從中學習到隱藏的」句式、語法模式「。咱們知道,語言對話是由詞/句/短語/段落組成的,我麼能夠採起」滑動窗口「的方式,逐段地將整個句子分紅多個【X(可能長度爲7), Y(可能長度爲1)】的訓練樣本,經過讓RNN學習 X序列 和緊隨其後的 Y 字符的序列特徵,等效地讓RNN學會語料庫中的」句式、語法模式「;
3. 同理,基於圖像生成標註的道理也是相似的(同2);

2. 數據集

Abbeville
Abbotsford
Abbott
Abbottsburg
Abbottstown
Abbyville
Abell
Abercrombie
Aberdeen
Aberfoil
Abernant
Abernathy
Abeytas
Abie
Abilene
Abingdon
Abington
Abiquiu
Abita Springs
Abo
Aboite
Abraham
Abram
Abrams
Absarokee
Absecon
Academy
Accokeek
Accomac
Accord
Ace
Aceitunas
Acequia
Achille
Achilles
Ackerly
Ackerman
Ackley
Ackworth
Acme
Acomita Lake
Acra
Acree
Acton
Acworth
Acy
Ada
Adair
Adair Village
Adairsville
Adairville
Adams
Adams Center
Adams City
Adamstown
Adamsville
Adario
Addicks
Addie
Addieville
Addington
Addis
Addison
Addy
Addyston
Adel
Adelaide
Adelanto
Adelino
Adell
Adelphi
Adelphia
Aden
Adena
Adgateville
Adin
Adjuntas
Admire
Adna
Adona
Adrian
Advance
Adwolf
Ady
Aetna
Affton
Afton
Agar
Agate
Agate Beach
Agawam
Agency
Agnes
Agness
Agnew
Agnos
Agoura
Agra
Agricola
Agua Dulce

3. 訓練代碼

from __future__ import absolute_import, division

import os
from six import moves
import ssl

import tflearn
from tflearn.data_utils import *

path = "../data/US_Cities.txt"
maxlen = 20

file_lines = open(path, "r").read()
X, Y, char_idx = string_to_semi_redundant_sequences(file_lines, seq_maxlen=maxlen, redun_step=3)
print "X[0]", X[0]
print "len(X[0])", len(X[0])
print "Y[0]", Y[0]
print "char_idx", char_idx


g = tflearn.input_data(shape=[None, maxlen, len(char_idx)])
g = tflearn.lstm(g, 512, return_seq=True)
g = tflearn.dropout(g, 0.5)
g = tflearn.lstm(g, 512)
g = tflearn.dropout(g, 0.5)
g = tflearn.fully_connected(g, len(char_idx), activation='softmax')
g = tflearn.regression(g, optimizer='adam', loss='categorical_crossentropy',
                       learning_rate=0.001)

m = tflearn.SequenceGenerator(g, dictionary=char_idx,
                              seq_maxlen=maxlen,
                              clip_gradients=5.0,
                              checkpoint_path='model_us_cities')


for i in range(40):
    seed = random_sequence_from_string(file_lines, maxlen)
    m.fit(X, Y, validation_set=0.1, batch_size=128,
          n_epoch=1, run_id='us_cities')
    print("-- TESTING...")
    print("-- Test with temperature of 1.2 --")
    print(m.generate(30, temperature=1.2, seq_seed=seed))
    print("-- Test with temperature of 1.0 --")
    print(m.generate(30, temperature=1.0, seq_seed=seed))
    print("-- Test with temperature of 0.5 --")
    print(m.generate(30, temperature=0.5, seq_seed=seed))

4. 實驗結果

0x3:基於LSTM生成JSP WEBSHELL樣本

1. 樣本集

咱們收集了131個大小在4096bytes內的JSP webshell文件,這批樣本做爲訓練語料庫。

須要特別注意的一點是,每一個文件之間理論上應該是一個獨立的樣本集,最合理的作法是單獨從每一個文件中以ngram方式提取序列。

咱們這裏爲了簡單起見,把全部文件concat到一個總體的字符串中,進行向量化,讀者朋友在實際項目中要注意這點。

2. webshell詞法模式提取原理

採集滑動窗口進行詞模式提取,窗口越小,提取到的詞模式特定空間就越大,描述能力就越強,相對的,訓練難度也越大,舉例說明:

<?php
    eval($_POST['op']);
?>
採用step_size = 2的滑動窗口進行詞模式提取:
EOF< -> ?
<? -> p
?p -> h
... 
ev -> a
va -> l
al -> (
...
$_ -> P
..
?> -> EOF

3. 生成(預測)過程

RNN是一種sequence to sequence的神經網絡,所以咱們須要給模型提供一個種子seed字符,做爲啓動字符,選擇這個字符的原則也很簡單,選擇對應編程語言開頭的第一個字母。

這裏咱們簡述過程

1. step_1: 輸入 START<,prediect後進行softmax獲得"?"
2. step_2: 在上一步的基礎上,輸入"<?",prediect後進行softmax獲得"換行"或者"空格"
3. ...
4. 循環到直接網絡輸出EOF或者達到開發者設定的filesize
5. 最終獲得的序列就是一個目標webshell序列

4. 實驗代碼

from __future__ import absolute_import, division

import os
from six import moves
import ssl

import tflearn
from tflearn.data_utils import *

DataDir = "../data/jsp_hash"
maxlen = 20
shelllen = 4096
step_size = 2

file_lines = ""
rootDir = DataDir
for file in os.listdir(rootDir):
    if file == '.DS_Store':
        continue
    print file
    path = os.path.join(rootDir, file)
    file_content = open(path, "r").read()
    file_lines += file_content

X, Y, char_idx = string_to_semi_redundant_sequences(file_lines, seq_maxlen=maxlen, redun_step=step_size)
print "X[0]", X[0]
print "len(X[0])", len(X[0])
print "Y[0]", Y[0]
print "char_idx", char_idx


g = tflearn.input_data(shape=[None, maxlen, len(char_idx)])
g = tflearn.lstm(g, 512, return_seq=True)
g = tflearn.dropout(g, 0.5)
g = tflearn.lstm(g, 512)
g = tflearn.dropout(g, 0.5)
g = tflearn.fully_connected(g, len(char_idx), activation='softmax')
g = tflearn.regression(g, optimizer='adam', loss='categorical_crossentropy',
                       learning_rate=0.001)

m = tflearn.SequenceGenerator(g, dictionary=char_idx,
                              seq_maxlen=maxlen,
                              clip_gradients=5.0,
                              checkpoint_path='model_us_cities')


for i in range(40):
    seed = random_sequence_from_string(file_lines, maxlen)
    m.fit(X, Y, validation_set=0.2, batch_size=128,
          n_epoch=5, run_id='webshell generate')
    print("-- GENERATING...")
    print("-- Test with temperature of 1.2 --")
    print(m.generate(shelllen, temperature=1.2, seq_seed=seed))
    print("-- Test with temperature of 1.0 --")
    print(m.generate(shelllen, temperature=1.0, seq_seed=seed))
    print("-- Test with temperature of 0.5 --")
    print(m.generate(shelllen, temperature=0.5, seq_seed=seed))

3. 實驗結果

0x4:GENERATING IMAGE DESCRIPTIONS

Together with convolutional Neural Networks, RNNs have been used as part of a model to generate descriptions for unlabeled images. It’s quite amazing how well this seems to work. The combined model even aligns the generated words with features found in the images.

相關文章
相關標籤/搜索