AI => CNN-RNN(Ng)

前言

看Andrew Ng視頻,總結的學習心得。
雖然本篇文章可能不是那麼細緻入微,甚至可能有了解誤差。
可是,我喜歡用更直白的方式去理解知識。
上一篇文章傳送門: https://segmentfault.com/a/11...面試

端到端

首先聊一個面試經歷

我最開始接觸的是ML (但只限於Sklearn的簡單應用,工程化的內容當時一點都不瞭解。)
後來有幸瞭解到DL (這個瞭解比較多)
我面的是普通Python崗, 由於個人小項目中涉及到 (聊天機器人)。因此第二個面試官揪着這個聊了聊。
與面試官交談時,我也直接挑明瞭,模型是Github找的,當時本身爬了些問答對,處理後放入模型本身訓練的。
面試官一頓(特徵提取,語義)等各類 ML-NLP工程化的過程,把我直接問懵了。。segmentfault

怎麼提取特徵(問號臉,難道是TF-IDF,分詞之類的??)?????
我也不知道說啥,以僅有的能力,和他聊聊(LSTM、Embedding, Seq2Seq的 Encoder-Vector-Decoder)。。網絡

面試官說:「你說了這些, 那你特徵工程是怎麼作的???」
我感受已經沒有任何反駁的能力了。。。接下來的事情,我不說,你們也應該清楚了。ide

反思

我回來後也反思過, 作了什麼特徵工程??
我看視頻中 也是,數據簡單預處理下,而後分詞,詞頻過濾,構建詞典
而後,直接就是構建NN層(包括Embedding層)。函數

直到最後瞭解了"端到端這個概念" 與 傳統ML的區別。
才清楚, 當時面試的場景是怎麼個狀況。。。源碼分析

正式開篇端到端

傳統ML: 原數據 -> 數據特徵工程(各類複雜的人工處理) ---> 模型
端到端DL:原數據 -----------------------------------------------------> 模型 學習

端到端:(一步到位):優化

傳統的ML作的中間層人工"手動"特徵工程處理出來的特徵。  
這些特徵,端到端的NN均可能"自動學習"的到。 


這也多是當時爲何面試官一直追問我"特徵如何處理"的緣由吧。也肯能他另有目的QAQ...
或者咱們真的不在一個頻道上。。。可是交流的過程真的使我受益不淺,有了更廣闊的視野(3Q!)

強調一點:編碼

雖然端到端 模型很便捷。可是須要大量的數據,才能訓練出好的效果。

CNN (卷積神經網絡)

構成

卷積層(激活函數) + 池化層    + 全鏈接層
Convolution       + Pooling   + Dense

至於一些術語:

有人喜歡把: 卷積層 + 池化層   做爲一層網絡 (由於池化層無訓練訓練,後面會提到)
也有人喜歡把:  卷積層 和 池化層 各自單獨算一個層(也是沒問題的。Tensorflow的API就是這樣設計的)

卷積層(Convolution Layer)

卷積過程

卷積計算過程就不說了。沒有案例圖。
但你能夠理解爲: 兩個 正方體 的 對應位置的元素 (相乘再相加)的結果。。。 (互相關,,,)

卷積的輸出計算

輸出圖像大小計算公式:spa

h圖片輸出 = (h圖片輸入 - h卷積核 + 2padding) / strides + 1
w圖片輸出 = (w圖片輸入 - w卷積核 + 2padding) / strides + 1

首先聲明: 這個式子因爲不必定可以整除, 所以除不盡的狀況下,向下取整, 也叫地板除
由於有個原則: 卷積核滑動的時候(一般是,步長>1的狀況下) 若是越界了一部分。則捨棄掉

根據上面的公式,求一個輸出圖像大小的例子(此處,不作paddding, 而且步長爲1)

eg: 輸入8x8 的圖像 ,  並使用3x3的卷積核  
輸出圖像高度爲:  h圖片輸出 = (8-3 + 2x0) / 1 + 1 = 6
輸出圖像寬度爲:  w圖片輸出 = (8-3 + 2x0) / 1 + 1 = 6
因此輸出圖像爲:  6x6

很明顯: 卷積一次,圖像變小了。 若是再卷積幾回, 圖像就看不到了。。。

因此: 咱們須要解決這個問題
原則上: 增長 padding 能解決步長爲1時,卷積後的圖片縮放問題。

假如咱們但願輸出圖像的大小 等於 輸出圖像的大小,而咱們想要求padding須要設置爲多少。
通常這種場景適用於 步長strides = 1, 因此參考開始的公式,可寫出以下公式:

由於: w 和 h是同樣的公式,此處只寫出一個h,來推導:
    h圖片輸出 = (h圖片輸入 - h卷積核 + 2padding) / strides + 1
化簡:
    padding =( h圖片輸出 - h圖片輸入 + h卷積核 - 1 ) / 2
由於咱們但願的條件: h圖片輸出 等於 h圖片輸入, 因此可繼續化簡:
    padding =( h卷積核 - 1 ) / 2

因此步長爲1的狀況下卷積, 而且想讓圖片不變形,你的padding的取值,就須要受到 卷積核大小的影響。
如今經常使用的卷積核大多都是 1x一、 3x三、 5x3 。因此看上面化簡好的公式:

padding =( h卷積核 - 1 ) / 2     <=============   1x1, 3x3, 5x5
奇數-1總等於偶數。
因此不用擔憂除不盡的狀況
還須要注意一下: 填充padding通常是環形填充, 假如padding=1, 那麼上下左右 都會添加一層。
固然: tensorflow的padding是能夠設置形狀的

padding的種類(tensorflow)

valid:

不填充

same:

自動去填充,使得輸入圖像 與 輸出圖像 的大小相等

舒適提示:關於三通道卷積 和 多個卷積核的區別

三通道卷積:

假如你只有一個卷積核
即便你圖片是3通道的(三層)
即便你卷積核也是三通道的(三層)
可是: 卷積輸出結果是依然是一個  m x n 的形狀  (一層"薄紙") 
你的疑惑: 不是三層嘛? 最後怎麼變成一層了 ???
個人解釋: 每滑動一次,3層通道,各自都會計算各自層的卷積,而後求總和,並填入一層"薄紙"的對應位置

多個卷積核:

上面說了: 1個卷積核,不管圖片是什麼形狀的、有幾個通道,卷積後輸出的形狀 是 一層薄紙: m x n

而若是你有多個卷積核: 那麼你就會有多層薄紙摞起來。  就像 一個長方形 摞成了一個 長方體
明確點:  假如你用了 6個卷積核, 那麼你的輸出就變成了   m x n x 6      (三維的一個長方體了)

上面說的:就是我最開始學的CNN時候常常理解不清楚的地方。我相信你也同樣, qaq ....
下面作個總結:

C個通道:  一點都不會影響輸出維度。 注意我說的是維度。 
    假如你的輸入是 m x n , 那麼你的輸出依然是  p x q   (注意維度便可,維度沒變,二維)

f個卷積核: 會影響輸出的維度。 輸出的維度將會增長一個維度f
    假如你的輸入是 m x n  ,  那麼你的輸出依然是  p x q x f  (增長一個維度F,變成了)

也許你當你學TF的時候會有另外一個理解障礙:那就是TF數據格式(以圖片爲例): 
    一般TF數據格式是這樣的:  [圖片數量B,  圖片高度H,  圖片寬度W,  通道數C]
    假如你使用 F 個卷積核作了卷積:
    那麼他的卷積結果的特徵的形狀就變變成: [B, H, W, F]
    發現沒輸出結果和通道數C,沒有關係的。 只和 卷積核的個數 f有關係。

可是注意: 雖然結果和C不要緊。可是 須要卷積核中具備 C的數量,還作惟獨匹配。橋樑運算。
    對應上例, 咱們的卷積核的形狀應該是這樣的 :   [F, C, H, W]
    注意一下: 這裏面有 卷積核數量f, 也有通道數量C。

若是最後一步的卷積核形狀不理解:

不要緊。之後是TF20的天下。 對應API不須要你指定卷積核的形狀。
所以,你不必記住卷積核的形狀。
你只須要 傳遞,卷積核的個數, 和 寬高 和 步長 便可。 固然這些都是獨立的命名參數。
摘一小段Conv2D的源碼:
    def __init__(self,
           filters,                # 你只須要傳遞這個參數, 卷積核的個數
           kernel_size,            # 卷積核的寬高,假如 3x3 你只需寫  [3,3] 便可
           strides=(1, 1),         # 這是步長, 你不傳,他也會給你填默認值, 步長爲1
           padding='valid',        # 這時 padding策略,前面說過,這個通常都會設爲 "same"
           
或許你還有些疑問:
    剛纔上面不是提到了卷積核應該設置 通道數C麼。
    原則上是的。由於要和 輸出的樣本作卷積。要匹配才行。
    可是在Tensorflow中。 特別是 Tenosrflow.Keras中,定義模型層
    咱們只須要把整個模型,從上到下鏈接起來。(就像前後排隊同樣)
    而對於一些先後流動貫通的參數,好比剛纔提到的通道C。
    這些參數,Tensorflow會自動幫咱們上下文識別管理。
    
    因此咱們作的只是須要,把原始數據的形狀設置好傳 給第一層(給排頭髮數據)
    至於你這些在中間層流動的參數,Tensorflow會自動幫你計算,你不用傳。
    雖然不用傳,但你最好清楚每層是什麼結構(固然這時後話,可能須要一些時間理解)
    
    到最後,我再給你設置一個輸出形狀,你能給我輸出出來便可 (隊尾接數據)
    
基本TF參數流動機制講到這裏,剛開始學的時候,也是各類苦思冥想,想不明白qaq...

透過現象看本質(卷積 => 線性)

其實咱們作的每一步 (每個)卷積就至關於一個矩陣線性操做: x1 @ w1
以後,基於常理話, 我會還會給它一個誤差: b 變成 ===> x1 @ w1 + b

咱們說過,可能會給出不少個卷積核進行運算。

上面  x1 @ w1 + b    是每個卷積核的卷積結果
咱們還須要講全部卷積覈計算結果堆疊在一塊兒: 記爲  X @ W + b     # m x n x f
最後將堆疊在一塊兒的結果,作一層非線性變換 relu ( X @ W + b )    # CNN 一般用 relu

eg: 現有圖片 5 x 5 x 3 的圖像 (暫時不考慮樣本個數,就認爲是一個樣本).
     咱們用的是 2 x 2 x  20 的卷積核 (步長爲1,不作padding)
     那麼輸出結果就是   (5-2+1) x (5-2+1) x 20  ===  4 x 4 x 20

忘記說了,還有一個公式,用來計算 每層卷積的權重參數量的個數的:

公式:  每層權重參數量(W) = 卷積核個數 x 卷積核高 x 卷積核寬 x 卷積核通道數
公式:  每層誤差數量(b) = 1 x 卷積核的個數        # 由於每一個卷積核只有一個誤差b

舒適提示: 有太多人喜歡把卷積核個數 與 卷積核通道稱做:"輸入/輸出"通道。
        這樣的稱呼是沒問題的, 但我在計算參數量的時候,不喜歡這樣的稱呼,易混淆。 

前情回顧: 記不記得普通神經網絡了。每一個神經元節點,都有它們本身的參數。所以它們的參數量是巨大的
迴歸正文: 而卷積核是共享的, 由於它是在一張圖片上滑動的。(挨個服務)因此權重參數也是共享的。

池化層 (Pooling Layer)

卷積層(激活函數) => 池化層
池化層主要分兩種: MaxPooling 和 AvgPooling

池化層輸出圖片形狀計算公式:

聲明: 池化層也有滑動窗口,而且輸出形狀計算公式,和 卷積的輸出形狀計算公式同樣:

h圖片輸出 = (h圖片輸入 - h卷積核 + 2padding) / strides + 1
w圖片輸出 = (w圖片輸入 - w卷積核 + 2padding) / strides + 1

由於池化層,的基本都是放在卷積層以後,所以池化層的通道數 也就瓜熟蒂落的 和 卷積層通道同樣

舉個例子:
卷積層數據形狀爲:  m x n x f
那麼池化層形狀同爲: p x q x f

我想主要強調的是: 通道數不變,變得是 寬高。

池化層 滑動窗口參數相關配置

仍是,把Tensorflow, 源碼搬過來,標註一下:

def __init__(self,
           pool_size=(2, 2),   # 滑動窗口大小 2x2
           strides=None,       # 步長,一般設爲2 
           padding='valid',    # Maxpooling 一般不用padding

通常都是使用組合 pool_size=(2, 2) 和 stride = 2

因此,公式來了:
                                                輸入h         滑動窗口h
    輸出h = (輸入h - 滑動窗口h) / stride + 1 = ----------  -   --------  + 1
                                                stride         stride

一般咱們把 pooling層做稱做數據的降採樣:

因此大多數經驗者,都會把 滑動窗口 和 stride步長  設爲相等大小。
因此帶入上面公式:
                                            輸入h           1            輸入h
輸出h = (輸入h - 滑動窗口h) / stride + 1 = ----------  -   -----  + 1 =  -------
                                            stride          1             步長
                                            
簡化一下: (當 pool_size 和  strides 設置相等大小時):
    輸出 = 輸入 / 步長

    因此當咱們: 
        步長設爲2時,  輸出就是輸出的一半。
        步長設爲3時,  輸出就是輸出的1/3。
        ...

不知道有沒有這樣一個疑問:」爲何滑動窗口沒有設置 窗口數量 (就像設置卷積核數量)「

再次說一下Tensorflow的原理。
由於Pooling的上一層基本徹底是 Conv卷積層, 卷積層的 卷積核的個數已經設置好了。
卷積層對接池化層的時候, Tensorflow會自動判斷,並設置:

池化層滑動窗口的個數===卷積核個數
池化層通道個數的個數===卷積層通道個數===圖片的原始通道個數

MaxPooling (最大池化,經常使用)

卷積操做:以前咱們卷積不是拿着滑動窗口,對應元素相乘再相加麼?
池化操做:池化層也是拿着滑動窗口同樣滑,可是不作運算,而是隻取每一個窗口內最大值。放在一層"薄紙"上

AvgPooling (平均池化,不經常使用)

同樣滑動窗口,各類滑, 而後取每一個窗口內的數據的"平均值",  其餘就不說了,同 MaxPooling

額外提醒(池化層的參數是否訓練)

池化層的是"沒有"參數能夠訓練的。因此,反向傳播,也不爲所動~~~

全鏈接層 (Dense Layer)

什麼是全鏈接層??

你很熟悉的, 全鏈接層其實就是以前講的普通的NN(神經網絡),因此並無什麼好說的。
只是拼接在池化層以後罷了。
但其實仍是有一些細節須要注意。尤爲以前的東西沒搞懂,那麼這裏的參數形狀你會垮掉~~~

展平 及 參數

以前爲了圖方便,參數我都沒怎麼提到樣本參數。
下面我要把樣本參數也加進來一塊兒嘮嘮了。我感受講這裏,直接上例子比較直觀。
好了,如今咱們有個需求, 想要作一個10分類的任務:

卷積層-池化層: 這個照常作, 設置你還能夠堆疊
    卷積層1 + 池化層1 + 卷積層2 + 池化層2 ...
等堆疊的差很少了: (你自我感受良好了。。。),咱們須要作一層展平處理!

展平處理(特地拿出來講)

假如你疊加到最後一層池化層數據形狀是:(1000,4,4,8)==> 1000個樣本,寬高8 x 8, 輸出通道爲8 
你展平後的形狀爲: (1000, 4*4*8) == (1000, 128)  
    展平操做第一種API:  tf.keras.Flatten()     # tensorflow2.0的Flatten被做爲網絡層使用
    展平操做第一種API:  tf.reshape(x, [-1, 128])  # 手動變換, -1是補位計算的意思
而後在加全鏈接層,形狀爲: (1000, 50)        # 50表明輸出,起到過渡做用
而後在加全鏈接層,形狀爲: (1000, 10)        # 最終,10表明輸出,由於咱們說了,要作10分類嘛
    1. 其實你中間能夠加不少全鏈接層,我這裏只加了一層,控制最後一層全鏈接是你想要的輸出就行。
    2. 特別注意, 這裏的每一層全鏈接計算,是須要有激活函數跟着的。
       除了最後一層全鏈接,其餘層的全鏈接都設置爲 Relu 激活函數便可。
    3. 由於咱們作的是10分類(多分類天然應想到 softmax參數, 若是是其餘業務,你也能夠不加激活函數)
       沒作,也就是最後一層。咱們要再添加一層激活函數 Softmax。

1 x 1 卷積的做用

降採樣(控制輸出通道數量):

假如,前一個卷積層參數爲: (1000,32,32,256)
若是你下一層使用1x1x128的卷積,則對應參數爲: (1000,32,32,128)  # 256通道變成了128通道

CNN文本分類(也許你看完下面的RNN再回來看這個會更好)

一般CNN大多數都是用來作CV工做。對於某些文本分類。CNN也能夠完成。以下變通概念:

  1. 句子的長度 看做 (圖片的高度)
  2. embedding的維度, 看做 (圖片的寬度)
  3. 卷積核是鋪滿一行(或者多行),而後沿着高度豎着滑下來的。 你也能夠有多個卷積核
    eg: 一個句子 10個詞語,20dim, 這個句子的輸入形狀就是(10 x 20)
    咱們準備3個卷積核分別是(3x20),(2x20), (1x20)
    每一個卷積核豎着滑下來,最後按次序獲得向量形狀爲(10,3)
    你能夠看做輸出三通道(對應卷積核個數,這和以前講的CNN原理如出一轍)
    最終提取出來這個(10,3)是,一個句子3個通道的特徵信息。
  4. 將10x3特徵矩陣,經過maxpooling壓縮成 (1x3)的特徵矩陣
  5. 放入Dense層,構建多輸出單元的n分類模型。

ResNet (殘差網絡 Residual Networks)

問題引入:

是否網絡層數越多越好,雖然堆疊更多的網絡,可使得參數豐富,而且能夠學到更多更好的特徵。
可是實際效果並不是如此,而是出現,過擬合等現象。

ResNet做者 何凱明:有感而發:按理說模型是應該越豐富越好的。但是出現了過擬合這種現象。
最少,更深層的網絡的效果,應該比淺層網絡的效果好吧。不至於差了那麼多。
所以,他將此問題轉換爲一個深度模型優化的問題。

ResNet相關配置

  1. batch-size: 256
  2. optimizer: SGD+Momentum(0.9)
  3. learning_rate: 初始化爲0.1, 驗證集出現梯度不降低的狀況下,learning_rate每次除以10衰減
  4. 每一層卷積層以後,都作 Batch Normalization
  5. 不使用 Dropout (其實應該是用了 BN,因此就沒有 Dropout)

RNN (循環神經網絡)

直接引用 Andrew Ng的降解圖

clipboard.png
能夠看到,上圖中有一些輸入和輸出:慢慢捋清。

  1. 第一個輸入 x<1> 表明 (你分詞後的每個句子中的第一個單詞) x<2>就是第二個單詞嘍
  2. 第二個輸入 a<0> 表明 初始輸入, (通常初始化爲0矩陣)
  3. 前面兩個輸入, 各會乘上各自的 權重矩陣,而後求和 得出 a<1> (這是臨時輸出)
  4. a<1> 乘上 一個權重參數 獲得輸出一: y<1> (這是終極輸出一) (這就是圖中黑框頂部的輸出分支)
  5. a<1> 乘上 又一個新權重參數後, 再加上 x<2> 乘以本身的權重參數獲得 a<2>
  6. ......你會發現 1-5步是個循環的過程, 到第5步, a<1> 就至關於 最開始a<0>的地位,做爲下一層的輸入
  7. 題外話。其實每層的輸出y1 會替代下一層的x做爲下一層的輸入。 (我會放到下面"防坑解釋"中說)

而後將上述途中最後2行的公式化簡,可獲得以下形式:

clipboard.png

防坑解釋 (RNN語言模型)

若是你看過了上面的圖,你會很清楚, 有多少個x, 就會輸出多少個y。
上面第7點說過 : "其實每層的輸出y1 會替代下一層的x做爲下一層的輸入", 該如何理解這句話???

假如你有這樣一段文本: "我精通各類語言"   => 分詞後的結果會變成 "我","精通","各類","語言"
通常的問答對這種的句子,處理流程是: (這裏只先說一個):
那就是:在句子的末尾添加一個 <END> 標識符
因此句子變成了:  "我","精通","各類","語言", "END"
這些單詞都會預先轉爲 (One-Hot編碼 或者Embedding編碼)

x1(初始值0) => y1(我)       y1有必定機率輸出 "我", 下面全部的y同理,只是機率性。
x2(y1) => y2(精通)          如此每一層嵌套下來,至關於條件機率  P(精通|我)
x3(y2) => y3(各類)          P(各類|(我,精通))
x4(y3) => y4(語言)          ...
x5(y4) => y5(<END>)         ...

不知道看了上例,你會不會有下面一連串的問號臉??:

  1. 爲何y1-y5 輸出都是精準的文字?
    答:我只是方便書寫表示,其實每一個輸出的Y都是一個從詞典選拔出來的詞的機率。(多分類)
  2. 不是說x1-x5每一個x應該輸入固定句子的每一個單詞麼???爲何變成了輸入y1-y5
    答:的確是這樣的,可是咱們的 y1-y5 都是朝着預測x1-x5的方向前進的。(這也是咱們要訓練的目標)

    因此: 能夠近似把y1-y5等價於x1-x5。 因此用 y1-y5 替代了 x1-x5
    這樣: 也能夠把先後單詞串聯起來,讓整個模型具備很強的關聯性。
    好比: 你第一個y1就預測錯了。那麼以後的y極可能都預測錯。(個人例子是:雙色球機率)
    可是: 假如咱們預測對了。那說明咱們的模型的效果,已經特別好了(雙色球每一個球都預測對了~)
  3. 那咱們就靠這x和y就能把先後語義都關聯起來嗎???
    答: 固然不只於此。 你別忘了咱們還有貫穿橫向的輸入啊,如最開始RNN圖的 a<0>. a<1> 這些
  4. 既然你說y 是從詞典選拔出來的詞的機率屬性。 那麼這個機率怎麼算?
    答: 這問得太好了~~~

    前面說了: 通常都會預先給數據作 One-Hot或 Embedding編碼。
    因此數據格式爲: [0,0,....,1,...]   # 只有一個爲1
    基本上咱們最後給輸出都會套一層: softmax激活函數, softmax應該知道吧:e^x /(e^x1+..+e^x)
    因此: softmax結果就是一個 和One-Hot形狀同樣的機率列表集合: [.....,最高几率,...]
    softmax的結果(機率列表) :(表明着預測 在詞典中每個單詞的可能性)
  5. 那麼損失函數怎麼算呢??
    答:沒錯,損失函數也是咱們最關注的。

    前面:咱們已經求出了softmax對應的結果列表 (....,最高几率,...)
    損失函數: 咱們使用的是交叉熵。
    交叉熵知道吧:  -( Σp*logq )         # p爲真實值One-hot, q爲預測值
    簡單舉個例子: 
        假如 softmax預測結果列表爲 :[0.01,0.35, 0.09, 0.55]  # 舒適提示,softmax和爲1
        你的真實標籤One-Hot列表爲:  [0,   0,    0,    1]
        那麼交叉熵損失就等於: -(  0*log0.01 + 0*log0.35 + 0*log0.09 + 1*log0.55 ) = ...
        
    到此爲止,咱們第一層NN的輸出的損失函數就已經計算完畢了。
    而咱們訓練整個網絡須要計算總體的損失函數。
    因此,咱們須要把上面的交叉熵損失求和, 優化損失。

梯度爆炸 & 梯度消失

RNN 的梯度是累乘,因此NN層若是不少,可能會達到 指數級的梯度。

你應該聽過一個小關於指數的小案例吧~~ (學如逆水行舟,不進則退~)
>>> 1.01 ** 365
37.78343433288728        # 天天進步0.01 ,一年能夠進步這些 (對應梯度爆炸)
>>> 0.99 ** 365
0.025517964452291125     # 天天退步0.01 , 一年能夠淪落至此(對應梯度消失)

梯度爆炸:

就是上面例子的原理。 就很少說了。
解決方式:梯度裁剪

梯度消失:

同上例, 很差解決(因而LSTM網絡出現, 和LSTM)

Tensorflow2.0(Stable) API

import tensorflow.keras as tk     # 注意我用的是TF20標準版,因此這樣導入

tk.layers.SimpleRNN(
    units=單元層,            # units單元數,對應着你每一個單詞的個數
    return_sequences=False   # 默認值就是False     
)

GRU

GRU比RNN的每一層的多了一個 記憶信息(至關於RNN的 h),這個記憶信息就像傳送帶同樣,一直流通各層RNN
而後還多了2個門 (r門和U門), 這2個門就是負責控制(是否從傳送帶上取記憶, 且取多少記憶)

註明: GRU 只有一個 c(橫向, 傳送帶) , 沒有h

簡化版(只有U門):

C新' = tanh( w @ [C舊, x新] + b )   # 根據傳動帶的舊信息, 生產出 傳送帶的新信息
u門 = sigmoid (w @ [c舊, x新] + b)     # 一個門控單元,起到過濾信息的做用

C新 = u門 * C新' + (1-u門) * C舊    #  通過u門控單元的控制過濾後, 最終放到傳送帶的信息
若是: u門爲1,則傳送帶上全是新信息(舊的全忘掉)
若是: u門爲0,則傳送帶上全是舊信息(新的不要)

強調一下: 我不方便寫公式負號,因而用了 "新","舊" 代替
新: 表明當前 t
舊: 表明前一時刻 t-1

完整版(同時具備r門和u門)
添加這一行:

r門 = sigmoid (w @ [c舊, x新] + b)     # 和下面的U門幾乎類似,只不過換了一下權重和誤差
C新' = tanh( w @ [r門 @ C舊, x新] + b )   # 修改這一行: C舊 ==變爲===>  r門 @ C舊

u門 = sigmoid (w @ [c舊, x新] + b)     # 一個門控單元,起到過濾信息的做用
C新 = u門 * C新' + (1-u門) * C舊    #  通過u門控單元的控制過濾後, 最終放到傳送帶的信息

Tensorflow2.0(Stable) API

import tensorflow.keras as tk     # 注意我用的是TF20標準版,因此這樣導入

tk.layers.GRU(                    # 參數同上面RNN我就不解釋了
    units=64,            
    return_sequences=False       # 這些參數看下面LSTM我會講到
)

LSTM

LSTM和GRU很像,可是比GRU複雜。
LSTM結構包括: u門(更新門)+ f門(遺忘門)+ o門(輸出門)

註明: LSTM 不只有個傳送帶C(橫向) , 他還有個RNN的 h 信息(橫向)

f門 = sigmoid (w @ [c舊, x新] + b)     # 和下面的U門幾乎類似,只不過換了一下權重和誤差
o門 = sigmoid (w @ [c舊, x新] + b)     # 和下面的U門幾乎類似,只不過換了一下權重和誤差

C新' = tanh( w @ [C舊, x新] + b )      # 注意,這裏沒有r門了

u門 = sigmoid (w @ [c舊, x新] + b)     # 一個門控單元,起到過濾信息的做用
C新 = u門 * C新' + f門 * C舊        #  "(1-u門)"  換成了 f門
h = o門 * tanh( C新 )

Tensorflow2.0(Stable) API

import tensorflow.keras as tk     # 注意我用的是TF20標準版,因此這樣導入

keras.layers.LSTM(
    units=64,
    return_state=True                 # 佔坑,下面剖析
    return_sequences=False            # 佔坑,下面源碼剖析
    recurrent_initializer='glorot_uniform',   # 均勻分佈的權重參數初始化

    # stateful=True, # API文檔:若爲True,則每一批樣本的state的狀態,都會繼續賦予下一批樣本
)

return_state 和 return_sequences 這兩個參數到底有什麼用???
個人另外一篇文章單獨源碼分析這兩個參數:https://segmentfault.com/a/11...

總結對比 GRU 和 LSTM

GRU有2個門:   u門 和 r門
LSTM有3個門:  u門 和 f門 和 o門

GRU有一個C:          # 就有一條傳送帶c, 他的先後單元信息僅靠這一條傳送帶來溝通(捨棄與保留)
LSTM有一個C和一個h:  # 不只有傳送帶c, 還有h, 他的先後單元信息 靠 c 和 h 聯合溝通。


再說一下每一個門控單元: 無論你是什麼門,  都是由 Sigmoid() 包裹着的。
因此: 說是 0 和 1 , 但嚴格意義上,只是無窮接近。可是微乎其微,因此咱們理解爲近似相等0和1

RNN-LSTM-GRU拓展

雙向(Bidirection)

首先說明:

雙向模型,對於 RNN/LSTM/GRU  所有都適用

因爲單向的模型,不能關聯以後信息。好比:你只能根據以前的單詞預測下一個單詞。
而雙向的模型,能夠根據先後上下文的語境,來預測當前的下一個單詞。
或者舉一個更直白的例子(我本身認爲):

好比說: 你作英語完型填空題, 你是否是 須要 把空缺部分的 前面 和 後面 都得讀一遍,才能填上。

單向與雙向結構對好比下:

單向: 1 -> 2 -> 3 -> 4
雙向: 1 -> 2 -> 3 -> 4 
                     |
      1 <- 2 <- 3 <- 4

注意: 上下對齊,表明一層。

Tensorflow2.0(Stable) API

import tensorflow.keras as tk     # 注意我用的是TF20標準版,因此這樣導入

tk.layers.Bidirectional(        # 就在上上面那些API的基礎上,外面嵌套一個 這個層便可。
    tk.layers.GRU(              
        units=64,            
        return_sequences=False
    )
),

模型深層堆疊(縱向堆疊)

首先說明:

層疊模型對於 RNN/LSTM/GRU  一樣所有都適用

以前單層單向模型是這種結構

1 -> 2 -> 3
計算公式是:  單元 = tanh ( W @ (x, h左) )

而多層單向是這種結構(咱們以2層爲例):

y1   y2   y3        輸出層
^    ^    ^
|    |    |
7 -> 8 -> 9         二層單元
^    ^    ^
|    |    |
4 -> 5 -> 6         一層單元
^    ^    ^
|    |    |
x1-> x2 ->x3        輸入層
你   好   啊

計算公式是: (我寫的可能只按本身的意思了~)
    一層每一個單元 = tanh ( W @ (x, h左) )     # 由於是第一層嘛:因此輸入爲 x 和 左邊單元h 
    二層每一個單元 = tanh ( W @ (h下, h左) )   # 第二層就沒有x了:而是下邊單元h 和 左邊單元h

詞嵌入(Word Embedding)

單詞之間類似度計算

c1 @ c2
餘弦定理,求 cosθ = ------------------
                     ||c1|| * ||c2||
           
或者你可使用歐氏距離。

原始詞嵌入並訓練

  1. 假如咱們經過一個句子的一部分來預測,這個句子的最後一個單詞。
  2. 把詞典的每一個詞作成One-Hot即是形式,記做矩陣 O
  3. 隨機高維權重矩陣, 記爲 E
  4. E @ O 矩陣乘積後記爲詞向量 W
    可見以下案例:

    若是:  咱們分詞後詞典總大小爲1000
    那麼:  他的 One-Hot 矩陣形狀爲  [6, 1000] (假如咱們這裏經過句子6個詞來預測最後一個詞)
    而且:  隨機高維權重矩陣 形狀爲 [1000, 300]     (注意,這個300是維度,可自行調整選擇)
    注意:  上面權重矩陣是隨機初始化的, 後面訓練調節的。
    最後:  E @ O 後獲得詞向量W 的形狀爲  [6, 300]
  5. 送進NN(打成1000類) 做爲輸出
  6. 加一層 Softmax (算出1000個單詞的機率) 做爲最終輸出y_predict
  7. y_predict 與 y真正的單詞標籤(one-hot後的) 作交叉熵loss
  8. 優化loss,開始訓練。

Word2Vec 的 skip-grams(不是太懂, Pass)

說下我的的理解,可能不對
skip-grams: 拿出中間 一個詞,來預測若干(這是詞距,本身給定)上下文的單詞。
例子以下:

seq = 今天去吃飯

給定單詞   標籤值(y_true)
去         今
去         天
去         吃
去         飯

訓練過程就是上面說過的小節 "原始詞嵌入並訓練",你只需把 y_true改成 "今","天","吃","飯"訓練便可。
Word2Vec 除了 skip-grams, 還有 CBOW 模型。 它的做用是 給定上下文,來預測中間的詞。
聽說效率等某種緣由(softmax計算慢,由於分母巨大),這兩個都沒看。(Pass~)

負採樣(Negative Sampling)

解決Word2Vec 的 softmax計算慢。
負採樣說明(假如咱們有1000長度的詞典):

從上下文(指定詞距):   隨機,選擇一個正樣本對,n個負樣本對(5-10個便可)
主要機制: 將 Word2Vec的 softmax(1000分類) 換成 1000個 sigmoid作二分類。
由於: 是隨機採樣(假設,採樣1個正樣本 和 5個負樣本)。
因此: 1000個 sigmoid二分類器,每次只用到 6 個對應分類器(1個正樣本分類器,5個負樣本分類器)

負採樣,樣本隨機選擇公式:

單個詞頻 ^ (3/4)    
-----------------
Σ(全部詞頻 ^ (3/4))
相關文章
相關標籤/搜索