1. 程式人生 > >image caption解讀系列(二):《Show, Attend and Tell_Neural Image Caption》

image caption解讀系列(二):《Show, Attend and Tell_Neural Image Caption》

一、相關工作

二、 基本思想

      文章在NIC的基礎上加入了attention機制

三、模型結構

對LSTM部分做出的改動,其餘與NIC相同。

     

四、程式碼分析

       (0)預處理

      首先是把資料中長度大於20的caption刪除,這是第一次篩選。然後建立詞彙庫(程式碼中的大小為5000), 對資料再次篩選,只保留所有的單詞都在詞彙庫中的句子。建立資料集,一共361258個影象和caption對。資料集包含幾個部分:

        self.image_ids = np.array(image_ids)   #每一幅影象的id
        self.image_files = np.array(image_files) #每一幅影象對應的路徑
        self.word_idxs = np.array(word_idxs)    #(361258,20)每個caption中的單詞用單詞對應的id代替詞彙庫中單詞的下標索引(包括標點符符號和<start>)
        self.masks = np.array(masks)               #(361258,20) 用來記錄長度,有詞是1,沒詞是0
        self.batch_size = batch_size                 #定義讀取資料時候的batch_size

     (1) 首先是CNN(VGG網路)提取特徵,最後得到的特徵圖是(batch_size,16,16,512),16*16代表了原本影象196個區域,每個區域用512維的特徵來表示。rshape成(batch_size,196,512)

reshaped_conv5_3_feats = tf.reshape(conv5_3_feats,[config.batch_size, 196, 512])

      (2)建立 embedding_matrix 

        大小為(5000,512),也就是說詞彙庫裡的5000個單詞,每個單詞用512維的向量來表示 並且做初始化(使用預訓練好的word_embedding)

        (3)建立RNN

        不再直接使用影象特徵a,而是對不同的區域加上不同的權重,得到上下文z(context)。

        首先,影象特徵作為最初的context.,使用兩個全連線層得到最初的memory(c0)和out(o0),作為LSTM最初的state。        

            context_mean = tf.reduce_mean(self.conv_feats, axis = 1)  #影象特徵作為最初的context (batch_size,512)
            initial_memory, initial_output = self.initialize(context_mean)#使用兩個全連線層得到最初的memory(c)和out(o)
            initial_state = initial_memory, initial_output     #最初的輸入state

        輸入的caption預設為(batch_size,max_length),這裡max_length取20。不到20的後面補0,並且用masks做了標記.

       對於每個時刻的單詞,首先引入attention,加入權重。並得到加權後的context和masks。

αt維度為L=196L=196,記錄釋義aa每個畫素位置獲得的關注。

權重αt可以由前一步系統隱變數htht經過若干全連線層獲得。編碼et用於儲存前一步的資訊。灰色表示模組中有需要優化的引數。這裡寫圖片描述

“看哪兒”不單和實際影象有關,還受之前看到東西的影響。

第一步權重完全由影象特徵aa決定:這裡寫圖片描述

 alpha = self.attend(contexts, last_output)  #引入注意力機制,加入權重  (batch_size,196)對196個區域的權重
                context = tf.reduce_sum(contexts*tf.expand_dims(alpha, 2),
                                        axis = 1)  #加權之後的context (batch_size,512)
                if self.is_train:
                    tiled_masks = tf.tile(tf.expand_dims(masks[:, idx], 1),
                                         [1, self.num_ctx])  #(batch_size,196)  masks[:, idx] 全部批次某個時刻的mask
                    masked_alpha = alpha * tiled_masks   #得到加權後的結果  如果maskd對應的是0 權重也就變成了0
                    alphas.append(tf.reshape(masked_alpha, [-1]))  #masked_alpha: (batch_size,196)

githubs上tensorflow 版本的程式碼    attend部分的具體實現略有不同,這裡就不再給出細節。

把當前時刻的權重存入列表。

alphas.append(tf.reshape(masked_alpha, [-1]))  #masked_alpha: (batch_size,196)

    把word_embedding和加權之後的context連線起來,作為當前時刻的輸入,得到out_put和state.

current_input = tf.concat([context, word_embed], 1)  #當前時刻的輸入是 加權後context 和word_embeeding的結合  (bacth_size,1024)
                output, state = lstm(current_input, last_state)  #(batch_size,512)
                memory, _ = state   #其他show and tell一樣  (bacth_size,512)  (batch_size,512)

利用得到的輸出和加權的context計算下一個單詞的概率。做出預測

                logits = self.decode(expanded_output)  #(bacth_size,5000)
                probs = tf.nn.softmax(logits)
                prediction = tf.argmax(logits, 1)

    最後,為下個時刻提供上個時刻的輸出和state等。

last_output = output
                last_memory = memory
                last_state = state
                last_word = sentences[:, idx]  #開始下一個單詞