Image captioning(三)-WITH ATTENTION

摘要

背景我們已經介紹了,現在我們上篇文章的基礎上面引入比較流行的Attention機制
說下本篇文章的貢獻:

  1. image captioning中使用同一種框架引入兩種atttention機制。
  2. 可以洞察模型觀察的點在哪裏where, 以及觀察的是什麼what
  3. 代碼我只會演示第二種attention 機制

模型

  1. image encoder
    第一層還是卷積層來處理圖像信息,但是這裏不同的是,我們不像上一篇提到的那樣直接複用已有的模型,這裏直接處理原始的圖片。
    爲什麼要處理原始圖片?因爲如果要做attention,那麼勢必要在decoder階段需要知道聚焦在圖片的哪個位置,這樣我們就不能直接用encoder出來的很高級的單向量了
    需要抽取出來一些原始的特徵,每個特徵能夠表徵圖像的某一部分,這樣在做decoder的時候,attention機制可以知道可以聚焦在哪一塊,這樣就提高了decoder描述的準確性
    假設我們處理圖片後生成L=196個D=512維的向量:

    a=(a1,...,aL),aiRD 
  2. decoder
    主要框架我們還是用LSTM,爲了引入attention,我們稍微做下變形,就是在原有的state基礎上面再增加一個圖片的content的信息
    假設需要decoder的序列爲:

    經典的LSTM結構:
    it ft ot gt Ct ht=σ(wi.[Eyt1,ht1]+bi)=σ(wf.[Eyt1,ht1]+bf)=σ(wo.[Eyt1,ht1]+bo)=tanh(wc.[Eyt1,ht1]+bc)=ftCt1+itgt=ottanh(Ct)

    _, (c, h) = lstm_cell(inputs=x, axis=1), state=[c, h])

    調整後:
    it ft ot gt Ct ht =σ(wi.[Eyt1,ht1,ẑ t]+bi)=σ(wf.[Eyt1,ht1,ẑ t]+bf)=σ(wo.[Eyt1,ht1,ẑ t]+bo)=tanh(wc.[Eyt1,ht1,ẑ t]+bc)=ftCt1+itgt=ottanh(Ct)

    _, (c, h) = lstm_cell(inputs=tf.concat([x, context], axis=1), state=[c, h])

    ẑ t 就是我們的content
    後面文中描述的content就是指這個向量,它代表的是我們decoder到目前這個詞,應該聚焦的圖片信息的哪塊內容。
    對比兩組公式可以直接看出,在每一個計算input是原始的input x增加了content , 那麼這樣的content是怎麼生成?

    以下的計算邏輯和機器翻譯seq2seq的attention模型計算類似,
    顯現需要聚焦的區域向量爲

    a=(a1,...,aL)

    當前hidden output爲:ht1
    針對每一個ai 我們會得到一個它和當前的output的相關性分數
    eti=fatt(ai,ht1) 
    w = tf.get_variable('w', [self.H, self.D], initializer=self.weight_initializer)
    b = tf.get_variable('b', [self.D], initializer=self.const_initializer)
    w_att = tf.get_variable('w_att', [self.D, 1], initializer=self.weight_initializer)
    
    # features_proj=a_i, h = h_t-1
    
    h_att = tf.nn.relu(features_proj + tf.expand_dims(tf.matmul(h, w), 1) + b)    # (N, L, D)
    out_att = tf.reshape(tf.matmul(tf.reshape(h_att, [-1, self.D]), w_att), [-1, self.L])

    這裏fatt 是一個多層感知機層的attention 模型
    然後得到softmax 分數

    αti=exp(eti)Lk=1exp(etk) 
    alpha = tf.nn.softmax(out_att)  

    這樣我們可以得到內容向量:

    ẑ t=ϕ({ai},{αi})

    這裏ϕ 將多向量轉化成單向量的函數,具體怎麼計算我們下節介紹:

    然後是初始的c0,h0 , 他們是 a=(a1,a2,...,aL) 的向量平均分別經過多個線性層
    c0 h0=finit,c(1Niαi)=finit,h(1Niαi)

    with tf.variable_scope('initial_lstm'):
        # [batch_size, dim_D]
        features_mean = tf.reduce_mean(features, 1)
    
        w_h = tf.get_variable('w_h', [self.D, self.H], initializer=self.weight_initializer)
        b_h = tf.get_variable('b_h', [self.H], initializer=self.const_initializer)
        # [batch_size, dim_hidden]
        h = tf.nn.tanh(tf.matmul(features_mean, w_h) + b_h)
    
        w_c = tf.get_variable('w_c', [self.D, self.H], initializer=self.weight_initializer)
        b_c = tf.get_variable('b_c', [self.H], initializer=self.const_initializer)
        c = tf.nn.tanh(tf.matmul(features_mean, w_c) + b_c)
    

    到這裏,常規的做法我們已經得到輸出 ht ,常用的預測yt 的方式是利用softmax
    yt=argmaxC(softmax(f(ht)))
    f是一個或者多個線性層,然後經過softmax之後,得到概率最大的一個詞就是預測值yt

    這裏作者f定義爲:

    f=Lo(Eyt1+Lhht+Lzẑ t) 

    LoRkm,LhRmn,LzRmD
    這樣:
    p(yt|a,yt11=a.exp(Lo(Eyt1+Lhht+Lzẑ t))) 

Attention 機制

本節我們介紹兩種attention機制,隨機attention 和 確定性 attention

隨機attention

顧名思義,隨機性attention的在計算ai 的權重時時從分佈中sample出來的,
每次的結果可能不一樣,所以是一種隨機方法。
定義:st=(st1,st1,...,stL)
它代表在生成第t個詞的時候,聚焦在L中各個區域的參數

st 是一個one-hot向量,也就是隻有其中一個元素是1,其他是0
這裏用了p=multinoulli多項分佈,那麼sti=1 的概率爲αt,i , 所以αt 是分佈p的參數。
我們從分佈p生成變量st=(st1,st1,...,stL)

stMultinoulliL(αi) 

然後:zt^=ist,iai

本節以下可以忽略

上一篇文章我們知道,loss函數我們可以採用l2 loss 或者softmax 交叉熵的方式
這裏因爲參數是sample出來的,顯然它是不能求導,從而不適用於鏈式法則,

下面我們引入對數似然來解決優化可導問題
它的對數似然函數爲:
logp(y|a)

這裏我們引入了變量st ,所以我們的似然函數可以寫成:
logp(y|a)  =logsp(s|a)p(y|s,a)>=sp(s|a)logp(y|s,a)

我們令 Ls=sp(s|a)logp(y|s,a)
我們對Ls 進行求導:

LsW=sp(s|a)Wlogp(y|s,a)+p(s|a)logp(y|s,a)W

其中:

logp(s|a)W=1p(s|a)p(s|a)W 

我們替換掉上式,最終得到:

LsW=sp(s|a)[logp(s|a)Wlogp(y|s,a)+logp(y|s,a)W] 

這裏的s是從multinoulli分佈裏面sample出來的,

s¯t=MultinoulliL(αi)

所以這裏:
p(s|a) 的概率我們可以直接用1N
LsW1Nn=1N[logp(s¯n|a)Wlogp(y|s¯n,a)+logp(y|s¯n,a)W] 

整體的優化如上面所示,但是由於是採用採樣的方式得到中間參數,所以算法波動可能很大,
這裏我們使用兩種方式降低波動:

  1. 梯度優化裏面動量的機制類似,第k次mini-batch迭代使用大部分的k-1次的對數似然信息,這樣保證足夠穩定

    bk=0.9bk1+0.1log(y|s¯k,a)

  2. 另外一種是在後面加上一個熵的量

    H(s)=np(sn|a)log(p(sn|a))
    最終得到:

    LsW1Nn=1N[logp(s¯n|a)W(logp(y|s¯n,a)b)λr+λeH(s¯n)W+logp(y|s¯n,a)W]

這裏要注意點有兩個:

  1. ẑ t=ist,iai , 這裏的st,i 是從分佈裏面sample出來的,分佈的參數是αi
  2. 目標函數是最大化似然函數,似然函數有兩種優化形式來降低sample造成的波動

確定性的機制

上面我們使用sample出來的st,i 來計算內容向量ẑ t
這裏我們不用sample的方式,直接求期望:
content:Ep(st|a)[ẑ t]=iαt,iai

with tf.variable_scope('attention_layer', reuse=reuse):
    w = tf.get_variable('w', [self.H, self.D], initializer=self.weight_initializer)
    b = tf.get_variable('b', [self.D], initializer=self.const_initializer)
    w_att = tf.get_variable('w_att', [self.D, 1], initializer=self.weight_initializer)
    # (N, L, D)
    h_att = tf.nn.relu(features_proj + tf.expand_dims(tf.matmul(h, w), 1) + b)
    # (N, L)    
    out_att = tf.reshape(tf.matmul(tf.reshape(h_att, [-1, self.D]), w_att), [-1, self.L])
    alpha = tf.nn.softmax(out_att)  
    context = tf.reduce_sum(features * tf.expand_dims(alpha, 2), 1, name='context')   #(N, D)

這樣其實整個模型就是可導的

另外:我們可以在這個內容前面增加一個gate常量,這個gate是和ht1 有關
βt=σ(fbeta(ht1))
fbeta 是一個線性層

這樣content:ϕ({ai},{αi})=βtiαt,iai

with tf.variable_scope('selector', reuse=reuse):
    w = tf.get_variable('w', [self.H, 1], initializer=self.weight_initializer)
    b = tf.get_variable('b', [1], initializer=self.const_initializer)
    beta = tf.nn.sigmoid(tf.matmul(h, w) + b, 'beta')    # (N, 1)
    context = tf.multiply(beta, context, name='selected_context')
    return context, beta

當然到了這裏其實就可以了

以下可以忽略

下面的步驟其實是爲了可以和上面的 margin 對數似然logp(y|a) 統一框架
證明了一波,其實它們兩種機制在對數似然的期望上面是一致的

所以統一後,負的對數似然函數爲:

Ld=log(P(y|x))+λi(1tαti)2 

訓練要點

作者用的是RMSProp/Adam,採用的是自適應學習率
這裏還有一個重點是圖像生成的特徵向量a=(ai)
這裏作者直接用VGGNet 在ImageNet上面的預先訓練的模型,每張圖片抽取出 L=196個D=512 維的像向量

效果:

這裏寫圖片描述

這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章