Image Captioning with RNNs

  • 2019 年 10 月 5 日
  • 筆記

Image Captioning with RNNs

0.導語1.下載數據集2.Look at the data3.Vanilla RNN3.1 step forward3.2 step backward3.3 forward3.4 backward4.Word embedding4.1 forward4.2 backward5.RNN for image captioning6.問題7.作者的話

0.導語

終於來到最後一個作業assignment3,這次主要學習RNN或LSTM時序模型!有關什麼是RNN以及LSTM的學習,在後面會出相應的文章解釋,本節則是針對cs231n上Image Caption做的一個實踐及學習程式碼的詳解流程。下面一起來完成這個作業吧!

1.下載數據集

在做這一節作業的時候,先下載assignment3,並運行D:Jupterassignment3cs231ndatasets下面的get_assignment3_data.sh腳本,然後再去進行本節作業,完成RNN_Captioning.ipynb。

./get_assignment3_data.sh  

如果你的電腦是win系統,如何實現上述操作,你如果用的git,提示wget命令沒找到你,如何實現了?看下面解決方案!

win 10上如何嵌入linux的wget命令?

https://gist.github.com/evanwill/0207876c3243bbb6863e65ec5dc3f058 https://superuser.com/questions/1075437/mingw64-bash-wget-command-not-found https://eternallybored.org/misc/wget/

這裡放出實現這個的兩個鏈接,簡單說一下,下載git,然後安裝上面鏈接中的wget,如果git裡面有就不需要了,第一個鏈接需要牆(你懂得!)。下載wget流程如下 :

- Download the lastest wget binary for windows from [eternallybored](https://eternallybored.org/misc/wget/) (they are available as a zip with documentation, or just an exe)  - If you downloaded the zip, extract all (if windows built in zip utility gives an error, use [7-zip](http://www.7-zip.org/)).  - Rename the file `wget64.exe` to `wget.exe` if necessary.  - Move `wget.exe` to your `Gitmingw64bin`.  

2.Look at the data

為了載入HDF5文件,我們需要安裝h5py,如果你的電腦已經裝上了這個包,並且後面數據運行沒錯,說明正常,如果裝上了這個包,可是後面也運行出粗,則可能是anaconda的conda安裝與pip安裝衝突問題,那麼需要pip先卸載,在用conda重裝,重啟jupter即可完美解決!

在Look at th data這一節,當運行這節程式碼時會報如下錯誤,什麼問題導致的呢?

結果手動去刪除的時候,發現文件在運行中,自然也就刪除不掉了,這個只是個備份文件而已,所以我們找到這個命令,發現在:/assignment3/cs231n/image_utils.py文件中,找到os.remove(fname),並注釋掉,然後再次運行就可以了!

3.Vanilla RNN

3.1 step forward

看下面圖片所示,圖片來源於cs231n2018ppt:

我們需要根據公式完成,RNN前向傳播(cs231n/rnn_layers.py),實現如下:

輸入: – x: Input data for this timestep, of shape (N, D). – prev_h: Hidden state from previous timestep, of shape (N, H) – Wx: Weight matrix for input-to-hidden connections, of shape (D, H) – Wh: Weight matrix for hidden-to-hidden connections, of shape (H, H) – b: Biases of shape (H,)

返回:

  • next_h: Next hidden state, of shape (N, H)
  • cache: Tuple of values needed for the backward pass.
def rnn_step_forward(x, prev_h, Wx, Wh, b):      next_h, cache = None, None      ##############################################################################      # TODO: Implement a single forward step for the vanilla RNN. Store the next  #      # hidden state and any values you need for the backward pass in the next_h   #      # and cache variables respectively.                                          #      ##############################################################################      #     pass      ##############################################################################      next_h=np.tanh(prev_h.dot(Wh)+x.dot(Wx)+b)      cache=(x,next_h,prev_h,Wx,Wh)      ##############################################################################      return next_h, cache  

3.2 step backward

輸入: – dnext_h: Gradient of loss with respect to next hidden state, of shape (N, H) – cache: Cache object from the forward pass

輸出:

- dx: Gradients of input data, of shape (N, D)  - dprev_h: Gradients of previous hidden state, of shape (N, H)  - dWx: Gradients of input-to-hidden weights, of shape (D, H)  - dWh: Gradients of hidden-to-hidden weights, of shape (H, H)  - db: Gradients of bias vector, of shape (H,)  

反向傳播求梯度一個難點在於注意 tanh(x),tanh(x) 的導數是

(1−tanh(x)*tanh(x))

def rnn_step_backward(dnext_h, cache):      dx, dprev_h, dWx, dWh, db = None, None, None, None, None      ##############################################################################      # TODO: Implement the backward pass for a single step of a vanilla RNN.      #      # HINT: For the tanh function, you can compute the local derivative in terms #      # of the output value from tanh.                                             #      ##############################################################################      #     pass      ##############################################################################      x,next_h,prev_h,Wx,Wh=cache      # next_h shape(N.H)      # x shape(N,D)      # Wx shape(D,H)      # tmp shape(N,H)      # d(tanh) = 1 - tanh * tanh      tmp=(1-next_h*next_h)*dnext_h      dx=tmp.dot(Wx.T)      # shape(N,H)      # Wh shape(H,H)      dprev_h=tmp.dot(Wh.T)      # dWx(D,H)      # x (N,D)      dWx=x.T.dot(tmp)      # dWh(H,H)      dWh=prev_h.T.dot(tmp)      db=np.sum(tmp,axis=0)      ##############################################################################      return dx, dprev_h, dWx, dWh, db  

3.3 forward

輸入:

  • x: Input data for the entire timeseries, of shape (N, T, D).
  • h0: Initial hidden state, of shape (N, H)
  • Wx: Weight matrix for input-to-hidden connections, of shape (D, H)
  • Wh: Weight matrix for hidden-to-hidden connections, of shape (H, H)
  • b: Biases of shape (H,)

輸出:

  • h: Hidden states for the entire timeseries, of shape (N, T, H).
  • cache: Values needed in the backward pass
def rnn_forward(x, h0, Wx, Wh, b):      ##############################################################################      N, T, D = x.shape      N, H = h0.shape        h = np.zeros((N, T, H))      prev_h = h0        cache = {}        for t in range(T):          prev_h, cache_t = rnn_step_forward(x[:, t, :], prev_h, Wx, Wh, b)          h[:, t, :] = prev_h          cache[t] = cache_t      ##############################################################################  

3.4 backward

輸入:

  • dh: Upstream gradients of all hidden states, of shape (N, T, H).

輸出:

  • dx: Gradient of inputs, of shape (N, T, D)
  • dh0: Gradient of initial hidden state, of shape (N, H)
  • dWx: Gradient of input-to-hidden weights, of shape (D, H)
  • dWh: Gradient of hidden-to-hidden weights, of shape (H, H)
  • db: Gradient of biases, of shape (H,)
def rnn_backward(dh, cache):      ##############################################################################      N, T, H = dh.shape      x = cache[0][0]      N, D = x.shape      dx = np.zeros((N, T, D))      dh0 = np.zeros((N, H))      dWx = np.zeros((D, H))      dWh = np.zeros((H, H))      db = np.zeros(H)      dprev_h = np.zeros((N, H))      for t in reversed(range(T)):          # Watch out the `NOTE` for dh!          dnext_h = dh[:, t, :] + dprev_h          dx[:, t, :], dprev_h, dWx_tmp, dWh_tmp, db_tmp = rnn_step_backward(dnext_h, cache[t])          dWx += dWx_tmp          dWh += dWh_tmp          db += db_tmp        dh0 = dprev_h      ##############################################################################  

總結:上面先進行了單步的前向與後向,隨後將其拓展到多步的前向與後向。

4.Word embedding

4.1 forward

輸入:

  • x: 維度為(N,T)的整數列,每一項是相應辭彙對應的索引。
  • W: 維度為(V,D)權值矩陣,V是詞表的大小,每一列對應著一個詞的向量表示

輸出:

  • out: 維度為(N, T, D),由所有輸入詞的詞向量所組成
  • cache: 反向傳播時需要的變數
def word_embedding_forward(x, W):      ##############################################################################      out = W[x, :]      cache = (W, x)      # 上述等價於下面注釋掉的程式碼  #     N, T = x.shape  #     V, D = W.shape  #     out = np.zeros((N, T, D))  #     for i in range(N):  #         for j in range(T):  # #             out[i, j,:] = W[x[i,j],:]  #             out[i, j] = W[x[i,j]]      cache = (x, W)      ##############################################################################      return out, cache  

4.2 backward

輸入:

  • dout: 梯度, 維度(N, T, D)
  • cache: 前向傳播存的變數

輸出:

  • dW: 詞嵌入矩陣的梯度,維度 (V, D).
def word_embedding_backward(dout, cache):      dW = None      ##############################################################################      x,W = cache      dW = np.zeros(W.shape)      # 將dW在x位置上與dout相加      np.add.at(dW,x,dout)      # 上述程式碼等價於下面幾行  #     N,T=x.shape  #     dW = np.zeros(W.shape)  #     for row in range(N):  #         for col in range(T):  #             dW[x[row,col],:] += dout[row,col,:]      ##############################################################################      return dW  

5.RNN for image captioning

loss完成圖片注釋生成系統

完善/assignment3/cs231n/classifiers/rnn.py程式碼:計算訓練時RNN/LSTM的損失函數。我們輸入影像特徵和正確的圖片注釋,使用RNN/LSTM計算損失函數和所有參數的梯度! 輸入:

  • features: 輸入影像特徵,維度 (N, D) ptions: 正確的影像注釋; 維度為(N, T)的整數列

輸出:

  • loss: 標量損失函數值
  • grads: 所有參數的梯度

提示:

(1) 使用仿射變換從影像特徵計算初始隱藏狀態。最終輸出 shape (N, H)。

(2) 用詞嵌入層將captions_in中詞的索引轉換成詞響亮,得到一個維度為(N, T, W)的數組。

(3) 使用vanilla RNN或LSTM(取決於self.cell_type)來處理輸入字向量序列並為所有時間步長產生隱藏狀態向量,從而產生形狀(N,T,H)的數組。

(4) 使用(時間)仿射變換在每個時間步使用隱藏狀態計算辭彙表上的分數,給出形狀(N,T,V)的數組。

(5) 使用(temporal)softmax使用captions_out計算損失,使用上面的掩碼忽略輸出字的點。

def loss(self, features, captions):      # 這裡將captions分成了兩個部分,captions_in是除了最後一個詞外的所有詞,是輸入到RNN/LSTM的輸入;captions_out是除了第一個詞外的所有詞,是RNN/LSTM期望得到的輸出。      captions_in = captions[:, :-1]      captions_out = captions[:, 1:]      # You'll need this      mask = (captions_out != self._null)      # Weight and bias for the affine transform from image features to initial      # hidden state      W_proj, b_proj = self.params['W_proj'], self.params['b_proj']      # 詞嵌入矩陣      W_embed = self.params['W_embed']      # RNN/LSTM參數      Wx, Wh, b = self.params['Wx'], self.params['Wh'], self.params['b']      # 每一隱藏層到輸出的權值矩陣和偏差      W_vocab, b_vocab = self.params['W_vocab'], self.params['b_vocab']      loss, grads = 0.0, {}      #####################################################################      # 實現第一步      hidden_init, cache_init = affine_forward(features, W_proj, b_proj)      # 實現第二步      captions_in_init, cache_embed = word_embedding_forward(captions_in,W_embed)      # 實現第三步      if self.cell_type == 'rnn':          hidden_rnn, cache_rnn = rnn_forward(captions_in_init, hidden_init, Wx, Wh, b)      else:          hidden_rnn, cache_rnn = lstm_forward(captions_in_init, hidden_init, Wx, Wh, b)      # 實現第四步      scores, cache_scores = temporal_affine_forward(hidden_rnn, W_vocab, b_vocab)      # 實現第五步      loss, dscores = temporal_softmax_loss(scores, captions_out, mask)      # 實現反向傳播      dhidden_rnn, grads['W_vocab'], grads['b_vocab'] = temporal_affine_backward(dscores, cache_scores)      if self.cell_type == 'rnn':          dcaptions_in_init, dhidden_init, grads['Wx'], grads['Wh'], grads['b'] = rnn_backward(dhidden_rnn, cache_rnn)      else:          dcaptions_in_init, dhidden_init, grads['Wx'], grads['Wh'], grads['b'] = lstm_backward(dhidden_rnn, cache_rnn)      grads['W_embed'] = word_embedding_backward(dcaptions_in_init, cache_embed)      dfeatures, grads['W_proj'], grads['b_proj'] = affine_backward(dhidden_init, cache_init)  

圖片注釋取樣完成sample

輸入:

  • captions: 輸入影像特徵,維度 (N, D)
  • max_length: 生成的注釋的最長長度

輸出:

  • captions: 取樣得到的注釋,維度(N, max_length), 每個元素是辭彙的索引

提示:

(1) 使用學習的單詞嵌入嵌入前一個單詞。 (2) 使用先前的隱藏狀態和嵌入的當前字進行RNN步驟以獲得下一個隱藏狀態。 (3) 將學習的仿射變換應用於下一個隱藏狀態,以獲得辭彙表中所有單詞的分數。 (4) 選擇分數最高的單詞作為下一個單詞,將其(單詞索引)寫入標題變數中的相應插槽。

def sample(self, features, max_length=30):      hidden_init, _ = affine_forward(features, W_proj, b_proj)      # 實現(1)      start_word_embed, _ = word_embedding_forward(self._start, W_embed)      hidden_curr = hidden_init      cell_curr = np.zeros_like(hidden_curr)      word_embed = start_word_embed      for step in range(max_length):          if self.cell_type == 'rnn':              # 實現(2)              hidden_curr, _ = rnn_step_forward(word_embed, hidden_curr, Wx, Wh, b)          else:              hidden_curr, cell_curr, _ = lstm_step_forward(word_embed, hidden_curr, cell_curr, Wx, Wh, b)          # 實現(3)          step_scores, _ = affine_forward(hidden_curr, W_vocab, b_vocab)          # 實現(4)          captions[:, step] = np.argmax(step_scores, axis=1)          # word_embed作為下一次的輸入          word_embed, _ = word_embedding_forward(captions[:, step], W_embed)  

6.問題

在我們當前的影像字幕設置中,我們的RNN語言模型在每個時間步長處生成一個單詞作為其輸出。 然而,提出問題的另一種方法是訓練網路對字元(例如'a','b'等)進行操作而不是單詞,以便在每個時間步長處,它接收前一個字元作為輸入 並嘗試預測序列中的下一個字元。 例如,網路可能會生成一個標題

'A','','c','a','t','','o','n','','a','','b','e','d「

您能描述使用字元級RNN的影像字幕模型的一個優點嗎? 你能描述一個缺點嗎? 提示:有幾個有效的答案,但比較單詞級和字元級模型的參數空間可能很有用。

以單詞為單位的 RNN,辭彙表可以很大,而且每次的輸出至少能夠保證是有意義的單詞;而以字母為單位的 RNN,辭彙表是固定大小,但是輸出的範圍幾乎是無窮的,並且不能保證輸出的組合是有意義的單詞。所以一字母為單位的 RNN 效果應該不如一單詞為單位的 RNN。

參考文章:https://blog.csdn.net/FortiLZ/article/details/80935136

7.作者的話

本篇文章闡述了cs231n中assignment3的第一個作業,希望能夠對各位有所收穫!