深度學習與CV教程(11) | 迴圈神經網路及視覺應用

ShowMeAI發表於2022-06-05

ShowMeAI研究中心


Recurrent Neural Networks; 深度學習與計算機視覺; Stanford CS231n

本系列為 史丹佛CS231n 《深度學習與計算機視覺(Deep Learning for Computer Vision)》的全套學習筆記,對應的課程視訊可以在 這裡 檢視。更多資料獲取方式見文末。


本篇重點

  • RNN的概念與多種形式
  • 語言模型
  • 影像標註、視覺問答、注意力模型
  • RNN梯度流

1.RNN的概念與多種形式

關於RNN的詳細知識也可以對比閱讀ShowMeAI的以下內容

1.1 形式

普通的神經網路會有1個固定維度的輸入(如1張圖片、1個向量),經過一系列隱層計算得到1個固定的輸出(如不同類別的得分/概率向量)。我們今天提到的迴圈神經網路(Recurrent Neural Networks)是一種特殊的神經網路,可以對序列資料做很好的建模,RNN很靈活,可以實現輸入和輸出的不同型別,如下圖所示:

RNN; 多種形式

1) 1對1

這種情況,輸入輸出都是固定的。

2) 1對多

這種情況,輸入固定尺寸,比如1張圖片;輸出是可變長度的序列,比如1段描述文字。文字的單詞數可能就會隨圖片不同而不同,因此輸出值的長度需要是一個變數。 (「圖片描述/看圖說話」image captioning)

3) 多對1

這種情況,輸入的尺寸是變化的,輸出是固定的。如情感分類任務,輸入的一段長度可變的文字序列,得到一個文字情感屬性的類別;再比如可以輸入時間長度不同的視訊,然後判斷視訊中的活動(固定)。

4) 多對多

這種情況,輸入輸出的尺寸都是可變的,如機器翻譯任務,英文翻譯成中文。輸入輸出的文字長度都是可變的,並且兩者長度不要求相同。

5) 多對多(一一對應)

這種情況,輸入是可變序列,輸出是針對輸入的每個元素做出判斷。如幀級別視訊分類任務,輸入是幀數可變的視訊,輸出對每一幀進行決策。

即使是輸入輸出尺寸都是固定的情形也可以使用迴圈神經網路 RNN。

  • 比如1張寫滿數字的固定尺寸的圖片,想要識別上面的數字,可以是經過一系列的觀察,觀察圖片的不同部分,然後做出決策;
  • 再比如生成一張固定尺寸的圖片,上面寫滿了數字,也可以經過一系列的步驟,一次只能生成一部分。

1.2 概念

如下圖所示,RNN有一個反覆出現的小迴圈核心單元,輸入 \(x\) 給 RNN,計算得到內部隱狀態 hidden state,且其會在每次讀取新的輸入時進行更新。當模型下一次讀取輸入時,內部隱狀態會將結果反饋給模型。

RNN; 小迴圈核心單元


通常,我們想讓 RNN 在每一個時間步都給出輸出,是這樣的模式:

  • 讀取輸入 → 更新隱狀態 → 基於隱狀態計算得到輸出。

RNN; 小迴圈核心單元


由於輸入是一系列 \(x\) 向量,在每一個時刻綠色的 RNN 部分可以使用迴圈公式來表示:

\[h_t=f_W(h_{t-1}, x_t \]

其中, \(x_t\) 是某一時刻輸入的向量,\(h_{t-1}\) 是該時刻之前的隱狀態, \(f_W\) 是引數 \(W\) 的函式, \(h_t\) 是更新後的隱狀態。這樣的結構不斷迴圈。

如果想要在每一個時間步驟得到一個輸出,那麼可以把更新後的隱狀態 \(h_t\) 作為輸入,經過全連線網路,得到決策。注意: \(f_W\) 在每一個時間步都是相同的。

下面是一個最簡單的例子,"Vanilla RNN":

\[h_t=tanh(W_{hh}h_{t-1}, W_{xh}x_t) \]

\[y_t=W_{hy}h_t \]

舊狀態和輸入 \(x\) 都是與權重矩陣相乘再求和經過非線性函式 \(tanh()\),輸出也是新狀態與權重矩陣相乘。每個隱狀態都只有一個唯一的 \(h\) 向量。

1.3 計算圖

1) 多對多(xy一一對應)

這裡的多對多指的是輸入 \(x\) 和輸出 \(y\) 都是序列,且在時間步上有一一對應關係。如下是一個多對多的計算圖:

RNN 計算圖; 多對多 many to many


初始狀態為 \(h_0\),在時間步 \(t=1\) 時刻有輸入 \(x_1\),將 \(h_0\)\(x_1\) 帶入引數函式 \(f\) 得到 \(h_1\) 更新隱狀態作為下一個時刻的狀態,這樣以此類推得到 \(x_t\) 時刻的隱狀態 \(h_t\)

計算 \(h_t\) 的每一步都使用的相同的引數 \(W\)引數函式 \(f\) 也是完全相同的。這樣在反向傳播計算梯度時,需要將每一個時刻的梯度累加起來得到最終 \(W\) 的梯度。

除了更新隱狀態,如果每一個時刻都想得到一個輸出,可以直接將 \(h_t\) 經過計算(比如softmax)得到 \(y_t\) ,如果對於輸入 \(x\) 的每一個時刻都對應一個真實標籤的話,在計算出 \(y_t\) 的同時可以得到每一步的損失 \(L_t\) ,最終的損失也是所有的 \(L_t\) 加起來。

2) 多對一

典型的應用之一是情感分析任務,根據最後一個隱狀態得到輸出。模型經過迭代,在最後一個隱狀態包含了之前所有的資訊。

RNN 計算圖; 多對一 many to one

3) 一對多

一對多的情形會接受固定長度的輸入項,輸出不定長的輸出項,這個固定長度的輸入項會用來初始化初始隱狀態,然後 RNN 會對輸出的單元逐個處理,最終會得到不定長的輸出序列,輸出的每個元素都得以展現。

RNN 計算圖; 一對多 one to many

4) 多對多

輸入輸出都是不定長序列的情形,典型應用如機器翻譯任務,可以看作是多對一與一對多的組合。首先輸入一個不定長的 \(x\),將這個序列編碼成一個單獨的向量,然後作為輸入,輸入到一對多的模型中,得到輸出序列,可能是用另一種語言表述的相同意思的句子。

然後對這個不定長的輸出,每一個時間步都會做出預測,比如接下來使用什麼詞。想象一下整個訓練過程和計算圖的展開,對輸出序列的損失求和,然後像之前一樣反向傳播。

RNN 計算圖; 多對多 many to many

2.語言模型

關於語言模型的詳細知識也可以對比閱讀ShowMeAI的以下內容

語言模型(Language Model)問題中,我們讓模型讀取大量語料語句,讓神經網路在一定程度上學會字詞的組合規律並能生成自然語言。

舉個字元層面上的例子(即對語料中的內容以字元粒度進行學習),比如網路會讀取一串字元序列,然後模型需要預測這個字元流的下一個字元是什麼。我們有一個很小的字元表 [h, e, l, o] 包含所有字母,以及有一個訓練序列 hello ,使用迴圈公式:

\[h_t=tanh(W_{hh}h_{t-1}, W_{xh}x_t) \]

語言模型訓練階段,我們將訓練序列作為輸入項,每一個時間步的輸入都是一個字元,首先需要做的是在神經網路中表示這個單詞。

語言模型; 訓練階段


我們在這裡使用長為4的獨熱向量來表示每個字元(只有字元對應的位置是 \(1\),其他位置為\(0\))。比如 h 可以用向量 \([1 0 0 0]\) 表示,l 使用 \([0 0 1 0]\) 表示。現在在第一個時間步中,網路會接收輸入 h,進入第一個RNN單元,然後得到輸出 \(y_1\),作為對接下來的字元的一個預測,也就是網路認為的接下來應該輸入的字元。

由於第一個輸入的是 h,所以接下來輸入的應該是 e,但是模型只是在做預測,如下圖所示,模型可能認為接下來要輸入的是 o。在這種錯誤預測下,我們可以基於 softmax 計算損失來度量我們對預測結果的不滿意程度。

在下一個時間步,我們會輸入 e,利用這個輸入和之前的隱狀態計算出新的隱狀態,然後利用新的隱狀態對接下來的字元進行預測,我們希望下一個字元是 l,但這裡模型可能也預測錯了,這裡又可以計算損失。這樣經過不斷的訓練,模型就會學會如何根據當前的輸入預測接下來的輸入。

在語言模型測試階段,我們想用訓練好的模型測試樣本或者生成新的文字(類似於訓練時使用的文字)。

語言模型; 測試階段


方法是輸入文字的字首來測試模型,上述例子中的字首是 h,現在在RNN的第一步輸入 h,它會產生基於詞庫所有字母得分的一個 softmax 概率分佈,然後使用這個概率分佈預測接下來的輸出(這個過程叫做sample/取樣),如果我們足夠幸運得到了字元 e,然後把這個得到的 e 重新寫成 \(01\) 向量的形式反饋給模型,作為下一個時間步的輸入,以此類推。

2.1 截斷反向傳播(Truncated Backpropagation )

在前向傳播中需要遍歷整個序列累加計算損失,在反向傳播中也需要遍歷整個序列來計算梯度。我們可以想象一下,如果我們的語料庫非常大(例如維基百科中所有文字),那麼時間花費以及記憶體佔用都是巨大的,如下圖所示。

語言模型; 截斷反向傳播


實際應用中,通常使用沿時間的截斷反向傳播(Truncated Backpropagation),這樣輸入的序列可以接近無窮。

前向傳播時不再使用整個序列計算損失,而是使用序列的一個塊,比如 100 個時間步,計算出損失值,然後反向傳播計算梯度。然後基於第 2 批資料再次計算和更新。

如上的過程迴圈操作,「截斷」使得開銷大大減小。

語言模型; 截斷反向傳播


這個過程的完整實現程式碼:點選這裡

2.2 RNN語言模型應用

  • 使用莎士比亞的文集對語言模型進行訓練,然後生成新的文字。結果可以生成莎士比亞風格的文章(當然,結果不是完美的,其中有些部分有錯誤),如下圖所示

語言模型; RNN 語言模型應用


  • 使用拓撲學教材,可以自動生成一些定理、公式甚至可以畫圖,雖然都沒有意義,但可以學會這些結構。

語言模型; RNN 語言模型應用


  • 使用 linux 原始碼進行訓練。可以生成 C 程式碼,有縮排有變數宣告甚至會寫註釋等等,看起來非常像 C 程式碼(當然程式碼邏輯不一定正常,編譯會有很多錯誤)。

語言模型; RNN 語言模型應用

2.3 RNN語言模型解釋

在 RNN 中有隱藏向量,每一步都會更新,我們在這些隱藏向量中尋找可以解釋的單元。

比如在語言模型訓練中觀察隱向量中的某一個元素值,元素值會隨著每一個時間步進行改變。大多數的元素值變化都是雜亂無章的,似乎在進行一些低階的語言建模。但是有一些元素卻有特殊的表現:

  • 藍色位置數值低,紅色位置數值高
  • 比如某些元素遇到引號後,元素值會變得很低,然後一直保持很低直到下一個引號處被啟用,元素值變大,然後保持到下一個引號再變低。所以有可能是檢測引號的神經元

語言模型; RNN 語言模型解釋


  • 還有某些神經元在統計回車符前的字元數量,即字元有多少時會自動換行。某一行開始處元素的值很低,然後慢慢增大,達到一定值後自動變成0,然後文字換行。

語言模型; RNN 語言模型解釋


  • 在訓練程式碼的例子中,有些神經元似乎在判斷是否在 if 語句,是否在註釋內,以及表示不同的縮排層級。

語言模型; RNN 語言模型解釋


上述細節內容可以看出,在訓練文字的過程中,RNN 學到一些文字的結構。(雖然這些已經不是計算機視覺的內容了)

3.看圖說話、視覺問答、注意力模型

之前提過很多次 圖片描述/看圖說話(Image Captioning),即訓練一個模型,輸入一張圖片,然後得到它的自然語言語義描述。這裡輸出的結果文字可能長度不同,單詞字元數不同,這個任務天生適合RNN模型建模。

如下圖的一個例子,影像標註模型訓練階段一般都先通過卷積神經網路處理影像生成影像向量(用其作為影像內容的表徵),然後輸入到RNN語言模型的第一個時間步中,RNN 會計算調整隱狀態,進而基於softmax 得到結果並計算損失,後續語言模型在每個時間步都生成1個組成描述文字的單詞。

看圖說話 ; image captioning 影像標註訓練階段


測試階段/推理階段和之前字元級的語言模型類似。

  • 我們把測試影像輸入到卷積神經網路,通過 CNN 得到模型最後1個全連線層之前的1個影像向量,作為整張影像的內容表徵。
  • 之後會給語言模型輸入一個開始標誌,告訴模型開始生成以這個影像為條件的文字。不同於以往的隱狀態公式,在這個任務中我們會把影像資訊輸入到每個時間步用於更新隱狀態:

\[h = tanh(Wxh \ast x + Whh \ast h + Wih \ast v) \]

  • 現在就可以根據影像的內容生成一個詞彙表(有很多詞彙)中所有單詞的一個 softmax 得分概率分佈,sample/取樣之後作為下一個時間步的輸入。
  • 直到取樣到「結束符號」整個預測過程結束,文字也生成完畢。

看圖說話; 影像描述測試階段


這些訓練後的模型在測試時對和訓練集類似的片會表現的很好,對和訓練集差距大的圖片可能變現不佳,比如對一些沒見過的物體進行誤判,以及分不清扔球還是接球等。

3.1 基於注意力的「看圖說話」模型

下面我們來看看基於「注意力機制」的 圖片描述/看圖說話 模型,這個模型包含的 RNN 在生成單詞時,會將注意力放在影像不同的部分。

在這個模型中,CNN處理影像後,不再返回一個單獨的向量,而是得到影像不同位置的特徵向量,比如 \(L\) 個位置,每個位置的特徵有 \(D\) 維,最終返回的CNN結果資料是一個 \(L \times D\) 的特徵圖。(想象CNN的中間層得到的特徵圖)

這樣在 RNN 的每一個時間步,除了得到詞彙表的取樣,還會得到基於圖片位置的分佈,它代表了 RNN 想要觀察影像的哪個部分。這種位置分佈,就是 RNN 模型應該觀察影像哪個位置的「注意力」。

在第1個隱狀態會計算基於圖片位置的分佈 \(a_1\),這個分佈會返回到影像特徵圖 \(L \times D\),給得出一個單一的具有統計性質的 \(D\) 維特徵向量,即把注意力集中在影像的一部分。這個特徵向量會作為下一個時間步的額外輸入,另一個輸入是單詞。然後會得到兩個輸出,一個是基於詞彙的分佈,一個是基於位置的分佈。這個過程會一直持續下去,每個步驟都有兩個輸入兩個輸出。

整個過程如下圖所示:

看圖說話; 基於注意力的看圖說話模型


上圖 \(z\) 的計算公式可以是:

\[z = \sum_{i=1}^L {p_iv_i} \]

\(p_i\) 就是基於位置的分佈,\(v_i\) 就是 \(L\) 個特徵向量中的一個,最後得到一個統計向量 $ z$。

這種結合所有特徵的分佈方式稱為軟注意力(Soft attention),與之對應的是硬注意力(Hard attention)。

硬注意力每次只產生一個單獨的特徵向量,不是所有特徵的組合,但它反向傳播比較複雜,因為(區域)選擇的過程本身不是一個可微的函式。

看圖說話; 軟注意力與硬注意力


這樣模型會自己學習每一個時間步應該主要把注意力集中在圖片的什麼位置得到什麼詞彙,效果通常也很好。如下圖所示:

看圖說話; 模型結果


這個結構的模型也可以用於其他任務,比如視覺問答(Visual Question Answering)。

視覺問答; Visual Question Answering


在視覺問答任務中,會有兩個輸入,一個是影像,一個是關於影像的用自然語言描述的問題。模型從一些答案中選擇一個正確的。

這是一個多對一模型,我們需要將問題作為序列輸入,針對序列的每一個元素建立 RNN,可以將問題概括成一個向量。然後把影像也概括成一個向量,現在將兩個向量結合通過 RNN 程式設計預測答案。結合方式可以是直接連線起來也可以是進行復雜的運算。

4.RNN梯度流

4.1 多層RNN(Multilayer RNNs)

我們之前看到的樸素RNN,隱狀態只有1層,在「多層RNN」中隱狀態有3層。一次執行得到 RNN 第1個隱狀態的序列,然後作為第2個隱狀態的輸入。如下圖所示:

RNN 梯度流; 多層 RNN


4.2 普通RNN梯度流

我們來看看普通RNN的梯度流,在前向傳播過程中計算 \(h_t\)

\[\begin{aligned} h_t & = tanh(W_{hh}h_{t-1}+W_{xh}x_t) \\ & = tanh \bigl (\bigl (\begin{matrix}W_{hh} \\ &W_{xh} \end{matrix}\bigr)\bigl(\begin{matrix}h_{t-1}\\x_{t} \end{matrix}\bigr)\bigr) \\ & =tanh\bigl(W\bigl(\begin{matrix}h_{t-1}\\ x_{t} \end{matrix}\bigr)\bigr) \end{aligned} \]

反向傳播梯度流從 \(h_t\)\(h_{t-1}\) 需要乘 \(W_{hh}^T\)

RNN 梯度流; LSTM 梯度流


有一個問題,在上述 RNN 訓練過程中,從最後一個隱狀態傳到第一個隱狀態,中間要乘很多次權重,如下圖所示。

如果累計相乘的值頻繁大於 \(1\),就可能會梯度爆炸;如果頻繁小於 \(1\),梯度就可能會慢慢趨近 \(0\)(梯度消失)。

RNN 梯度流; LSTM 梯度流


對於梯度爆炸,一種處理方法是給梯度設定一個閾值,如果梯度的 L2 正規化超過這個閾值就要減小梯度,程式碼如下:

grad_num = np.sum(grad * grad)
if grad_num > threshold:
    grad *= (threshold / grad_num)

對於梯度消失問題,我們可以改造 RNN 網路結構,得到更適合長距離建模的結構,如下面要展開講解到的 LSTM 和 GRU。

4.3 LSTM(Long Short Term Memory )

關於LSTM的詳細講解也可以對比閱讀ShowMeAI深度學習教程 | 吳恩達專項課程 · 全套筆記解讀 中的文章 序列模型與 RNN 網路 中對於LSTM的講解。

LSTM(長短期記憶)網路就是用來解決「梯度爆炸」和「梯度消失」問題的,與其在輸出上限制梯度,LSTM 的網路結構更加複雜。

\[\begin{aligned} \left(\begin{array}{l} i \\ f \\ o \\ g \end{array}\right) &=\left(\begin{array}{c} \sigma \\ \sigma \\ \sigma \\ \tanh \end{array}\right) W\left(\begin{array}{c} h_{t-1} \\ x_{t} \end{array}\right) \\ c_{t} &=f \odot c_{t-1}+i \odot g \\ h_{t} &=o \odot \tanh \left(c_{t}\right) \end{aligned} \]

LSTM 在每一個時間步都會維持兩個隱狀態:

  • \(h_t\) 和普通 RNN 中的一致,是 RNN 網路的隱狀態;
  • 單元狀態向量 \(c_t\) ,是保留在 LSTM 內部的隱狀態,不會完全暴露到外部去。計算公式可以看出,LSTM會使用輸入和之前的隱狀態來更新四個組成 \(c_t\) 的門,然後使用 \(c_t\) 來更新 \(h_t\) .

與普通 RNN 不同的是,LSTM 不直接將權重矩陣 \(W\)\(x_t\)\(h_{t-1}\) 拼接成的向量再經過 \(tanh()\) 函式得到隱狀態 \(h_t\)

LSTM 中的權重矩陣計算會得到 4 個與隱狀態 \(h\) 大小相同的向量,然後分別通過不同的非線性函式就得到了單元狀態 $ c_t$ 的四個門: \(i, f, o, g\)

  • \(i\)輸入門(Input gate) ,表示有多少內容被寫到單元狀態;
  • \(f\)遺忘門(Forget gate),表示對之前的單元狀態的遺忘程度;
  • \(o\)輸出門(Output gate) ,表示單元狀態輸出多少給隱狀態;
  • \(g\)門值門(Gate gate ) ,控制寫入到單元狀態的資訊。

\(c_t\) 的計算公式來看,之所採用不同的非線性函式,可以這麼理解:

  • \(f\) 是對之前的單元狀態的遺忘,如果是 \(0\) 全部遺忘,如果是 \(1\) 就全部保留,那個圓圈加點的符號表示逐元素相乘;
  • \(i\)\(g\) 共同控制寫入到單元狀態向量的資訊,\(i\)\(0 \sim1\) 之間,\(g\)\(-1\)\(1\) 之間,這樣每個時間步,單元狀態向量的每個元素最大自增 \(1\) 或最小自減 \(1\)
  • 這樣 \(c_t\) 可以看成是對 \([-1 \quad 1]\) 的按時間步計數。然後 \(c_t\) 通過 \(tanh()\) 將這個計數壓縮到 \([0 \quad 1]\) 範圍,然後 \(o\) 來控制將多少單元狀態的資訊輸出給隱狀態 \(h_t\)

RNN 梯度流; LSTM


LSTM梯度流

如下圖所示,我們用灰色的線表示 LSTM 前向傳播,完全按照公式來的,圓圈加點運算子表示兩個向量逐元素相乘,不是矩陣的乘法。這樣從 \(c_t\)\(c_{t-1}\) 的反向傳播過程,只會與 \(f\) 進行逐元素相乘,與乘 \(W\) 相比要簡單很多。

LSTM不同的時間步 \(f\) 的值都不同,不像普通 RNN 每次都乘相同的 \(W\),這樣就一定程度避免梯度爆炸或銳減。而且 \(f\) 的值也是在 \([0, 1]\) 性質非常好。

RNN 梯度流; LSTM 梯度流


當多個單元連起來的時候,就會為梯度在單元狀態間流動提供了一條高速公路(這種形式與殘差網路類似)。

RNN 梯度流; LSTM 梯度流


4.4 GRU(Gated Recurrent Unit)

關於LSTM的詳細講解也可以對比閱讀ShowMeAI深度學習教程 | 吳恩達專項課程 · 全套筆記解讀中的文章序列模型與RNN網路中對於GRU的講解。

另一個改進普通RNN的方法得到「門迴圈單元(GRU)」模型,它總體和LSTM相似,也是應用了這種逐元素相乘與加法結合的形式,這樣梯度也能高速流動。

\[\begin{aligned} r_{t} &=\sigma\left(W_{x r} x_{t}+W_{h r} h_{t-1}+b_{r}\right) \\ z_{t} &=\sigma\left(W_{x z} x_{t}+W_{h z} h_{t-1}+b_{z}\right) \\ \tilde{h}_{t} &=\tanh \left(W_{x h} x_{t}+W_{h h}\left(r_{t} \odot h_{t-1}\right)+b_{h}\right) \\ h_{t} &=z_{t} \odot h_{t-1}+\left(1-z_{t}\right) \odot \tilde{h}_{t} \end{aligned} \]

5.推薦學習

可以點選 B站 檢視視訊的【雙語字幕】版本

7.要點總結

  • RNN 是一種輸入輸出都很靈活的網路結構;
  • Vanilla RNNs 很簡單,但是效果不好;比較普遍的是使用 LSTM 和 GRU,能有效的改善梯度流;
  • RNN 反向傳播梯度爆炸可以給梯度設定閾值,而梯度銳減可以使用複雜的網路,比如 LSTM;
  • RNN 的應用:影像標註、視覺問答,都是將 CNN 與 RNN 結合起來;
  • 理解語言模型與注意力模型;

史丹佛 CS231n 全套解讀

ShowMeAI 系列教程推薦

ShowMeAI用知識加速每一次技術成長

相關文章