用TensorFlow為圖片添加字幕
如何使用TensorFlow來構建和訓練一個圖片字幕生成器
圖片字幕生成模型結合了近年來計算機視覺和機器翻譯方麵的進步,通過使用神經網絡來生成現實圖片的字幕。對於一個給定的輸入圖片,神經圖像字幕模型被訓練來最大化生成一個字幕的可能性。可以被用來產生新穎的圖像描述。例如,下麵是用MS COCO數據集訓練的一個神經圖像字幕生成器所產生的字幕。
圖1. 來源:Paul Puri。圖片來自MS COCO數據集
在這篇文章裏,我們會介紹一個中級程度的教程,教大家如何使用穀歌的“Show and Tell”模型的一種變形和Flickr30k數據集來訓練一個圖片字幕生成器。我們使用TensorFlow的框架來構建、訓練和測試我們的模型,因為它相對容易使用而且也有一個日益龐大的在線社區。
為什麼要生成字幕?
近年來在計算機視覺和自然語言處理任務上應用深度神經網絡的成功激勵著AI研究人員去探索新的研究機會,交叉連接這些之前互相獨立的領域。字幕生成模型就必須去對視覺線索和自然語言的理解進行平衡。
這兩個傳統上無關的領域的交叉有可能在更大的範圍內產生變革。這一技術現在已經有一些很直接的應用。比如,為YouTube視頻自動生成摘要或是標注未標記的圖片。而更多的有創造力的應用則會大幅度提高一個更廣泛的人群的生活質量。與傳統的計算機視覺試圖去讓計算機能更好地接觸和理解這個世界一樣,這一新技術具有進一步讓這個世界對人類更加可達與可理解的潛力。它可以是一個導遊,甚至可以成為日常生活的一個視覺幫助服務。比如意大利的AI公司Eyra所開發的Horus可穿戴設備所展示的這個場景。
需要一些安裝工作
在我們正式開始前,需要先做一些整理工作。
首先,你需要安裝TensorFlow。如果這是你第一次使用TensorFlow,我們推薦你先看看這篇文章《你好,TensorFlow!從零開始構建和訓練你的第一個TensorFlow圖》
你需要安裝pandas、OpenCV2和Jupyter庫來保證相關的代碼可以運行。不過為了簡化安裝的過程,我們強烈推薦你使用與本文關聯的GitHub庫裏的這個Docker安裝指南。
你還需要下載Flickr30k圖片文件和圖片字幕數據集。我們的GitHub庫裏有也提供了下載鏈接。
現在,讓我們開始吧!
圖片字幕生成模型
圖2. 來源:Shannon Shih獲取自加州大學伯克利分校機器學習組織。馬的圖片來自MS COCO
概括來看,這就是我們將要訓練的模型。每張圖片都可以被一個深度卷積神經網絡編碼成一個4096維的向量表示。一個語言生成RNN(循環神經網絡)將會對這個表示按順序解碼,並生成自然語言的描述。
字幕生成是圖像分類的一種擴展
作為計算機視覺的一個任務,圖片分類有著很長的曆史,且有非常多好的模型。分類需要模型能把圖像裏與形狀和物體相關的視覺信息拚接在一起,然後把這個圖片分入一個類別。其他的計算機視覺的機器學習模型,諸如物體檢查和圖片分割等,則不僅對呈現的信息進行識別,還通過學習如何解讀二維的空間,並把這兩種理解融合起來,從而能判斷出圖片裏分布的物體信息。對於字幕生成,有兩個主要的問題:
-
在獲取圖片裏的重要信息時,我們如何基於圖像分類模型的成功結果?
-
我們的模型如何能學習去融合對於語言的理解和對於圖片的理解?
利用遷移學習
我們可以利用已有的模型來幫助實現圖片生成字幕。遷移學習讓我們可以把從其他任務上訓練出來的神經網絡的數據變換應用到我們自己的數據上。在我們的這個場景裏,VGG-16圖片分類模型是把224 x224像素的圖片作為輸入,產生4096維度的特征向量表示,用於分類圖片。
我們可以利用這些VGG-16模型生成的表示(也叫做圖向量)來訓練我們的模型。限於本文的篇幅,我們省略了VGG-16的架構,而是直接用已經計算出來的4096維的特征來加快訓練的過程。
導入VGG圖片特征和圖片字幕是相當得簡單直接:
def get_data(annotation_path, feature_path):
annotations = pd.read_table(annotation_path, sep=’\t’, essay-header=None, names=[‘image’, ‘caption’])
return np.load(feature_path,’r’), annotations[‘caption’].values
理解字幕
現在我們已經有了圖片的表示了,還需要我們的模型能學會把這些表示解碼成能被理解的字幕。因為文字天生的序列特性,我們會利用一個RNN/LSTM網絡裏的循環特點(想了解更多,請參考這篇“理解LSTM網絡”)。這些網絡被訓練來預測在給定的一係列前置詞匯和圖片表示的情況下的下一個詞。
長短期記憶(LSTM)單元能讓這個模型能更好地選擇什麼樣的信息可以用於字幕詞匯序列,什麼需要記憶,什麼信息需要忘掉。TensorFlow提供了一個封裝的功能可以對於給定的輸入和輸出維度生成一個LSTM層。
為了把詞匯能變化成適合LSTM的固定長度表示的輸入,我們使用一個向量層來把詞匯映射成256維的特征(也叫詞向量)。詞向量幫助我們把詞匯表示成向量,同時相似的詞向量在語義上也相似。想了解更多關於詞向量如何獲取不同詞匯之間的關係,請看這篇《用深度學習來獲取文本語義》。
在這個VGG-16圖片分類器裏,卷積層抽取出了4096維表示,然後送入最後的softmax層來做分類。因為LSTM單元需要的是256維的文本輸入,我們需要把圖片表示轉化成目標字幕所需的這種表示。為了實現這個目標,我們需要加入另外一個向量層來學習把4096維的圖片特征映射成256維的文本特征空間。
構建和訓練這個模型
全合在一起,Show and Tell模型就大概像這個樣子:
圖3. 來源《Show and Tell:2015 MSCOCO圖片字幕大賽所獲得的經驗教訓》
圖3中,{s0, s1, …, sN}表示我們試著去預測的字幕詞匯,{wes0, wes1, …, wesN-1}是每個詞的詞向量。LSTM的輸出{p1, p2, …, pN}是這個模型產生的句子裏下一個詞的概率分布。模型的訓練目標是最小化對所有的詞概率取對數後求和的負值。
def build_model(self):
# declaring the placeholders for our extracted image feature vectors, our caption, and our mask
# (describes how long our caption is with an array of 0/1 values of length `maxlen`
img = tf.placeholder(tf.float32, [self.batch_size, self.dim_in])
caption_placeholder = tf.placeholder(tf.int32, [self.batch_size, self.n_lstm_steps])
mask = tf.placeholder(tf.float32, [self.batch_size, self.n_lstm_steps])
# getting an initial LSTM embedding from our image_imbedding
image_embedding = tf.matmul(img, self.img_embedding) + self.img_embedding_bias
# setting initial state of our LSTM
state = self.lstm.zero_state(self.batch_size, dtype=tf.float32)
total_ loss = 0.0
with tf.variable_scope(“RNN”):
for i in range(self.n_lstm_steps):
if i > 0:
#if this isn’t the first iteration of our LSTM we need to get the word_embedding corresponding
# to the (i-1)th word in our caption
with tf.device(“/cpu:0”):
current_embedding = tf.nn.embedding_lookup(self.word_embedding, caption_placeholder[:,i-1]) + self.embedding_bias
else:
#if this is the first iteration of our LSTM we utilize the embedded image as our input
current_embedding = image_embedding
if i > 0:
# allows us to reuse the LSTM tensor variable on each iteration
tf.get_variable_scope().reuse_variables()
out, state = self.lstm(current_embedding, state)
print (out,self.word_encoding,self.word_encoding_bias)
if i > 0:
#get the one-hot representation of the next word in our caption
labels = tf.expand_dims(caption_placeholder[:, i], 1)
ix_range=tf.range(0, self.batch_size, 1)
ixs = tf.expand_dims(ix_range, 1)
concat = tf.concat([ixs, labels],1)
onehot = tf.sparse_to_dense(
concat, tf.stack([self.batch_size, self.n_words]), 1.0, 0.0)
#perform a softmax classification to generate the next word in the caption
logit = tf.matmul(out, self.word_encoding) + self.word_encoding_bias
xentropy = tf.nn.softmax_cross_entropy_with_logits(logits=logit, labels=onehot)
xentropy = xentropy * mask[:,i]
loss = tf.reduce_sum(xentropy)
total_loss += loss
total_loss = total_loss / tf.reduce_sum(mask[:,1:])
return total_loss, img, caption_placeholder, mask
使用推斷來生成字幕
完成訓練後,我們就獲得了一個在給定圖片和所有的前置詞匯的前提下,可以給出字幕裏下一個詞概率的模型。那麼我們怎麼用這個模型來生成字幕?
最簡單的方法就是把一張圖片作為輸入,循環輸出下一個概率最大的詞,由此生成一個字幕。
def build_generator(self, maxlen, batchsize=1):
#same setup as `build_model` function
img = tf.placeholder(tf.float32, [self.batch_size, self.dim_in])
image_embedding = tf.matmul(img, self.img_embedding) + self.img_embedding_bias
state = self.lstm.zero_state(batchsize,dtype=tf.float32)
#declare list to hold the words of our generated captions
all_words = []
print (state,image_embedding,img)
with tf.variable_scope(“RNN”):
# in the first iteration we have no previous word, so we directly pass in the image embedding
# and set the `previous_word` to the embedding of the start token ([0]) for the future iterations
output, state = self.lstm(image_embedding, state)
previous_word = tf.nn.embedding_lookup(self.word_embedding, [0]) + self.embedding_bias
for i in range(maxlen):
tf.get_variable_scope().reuse_variables()
out, state = self.lstm(previous_word, state)
# get a one-hot word encoding from the output of the LSTM
logit = tf.matmul(out, self.word_encoding) + self.word_encoding_bias
best_word = tf.argmax(logit, 1)
with tf.device(“/cpu:0”):
# get the embedding of the best_word to use as input to the next iteration of our LSTM
previous_word = tf.nn.embedding_lookup(self.word_embedding, best_word)
previous_word += self.embedding_bias
all_words.append(best_word)
return img, all_words
很多情況下,這個方法都能用。但是“貪婪”地使用下一個概率最大的詞可能並不能產生總體上最合適的字幕。
規避這個問題的一個可行的方法就是“束搜索(Beam Search)”。這個算法通過遞歸的方法在最多長度為t的句子裏尋找k個最好的候選者來生成長度為t+1的句子,且每次循環都僅僅保留最好的那k個結果。這樣就可以去探索一個更大的最佳的字幕空間,同時還能讓推斷在可控的計算規模內。在下圖的例子裏,算法維護了在每個垂直時間步驟裏的一係列k=2的候選句子(用粗體字表示)。
圖4 來源:Daniel Ricciardelli
局限和討論
神經圖片字幕生成器給出了一個有用的框架,能學習從圖片到人能理解的圖片字幕間的映射。通過訓練大量的圖片-字幕對,這個模型學會提取從視覺特征到相關語義信息的關係。
但是,對於一個靜態圖片,我們的字幕生成器是關注圖片裏有利於分類的特征,而這並不一定是有利於字幕生成的特征。為了改進每個特征裏與字幕相關的信息量,我們可以把這個圖片向量模型(這個用來編碼特征的VGG-16模型)作為整個字幕生成模型的一部分。這就可以讓我們能更精細地調優圖片編碼器來更好地承擔字幕生成的角色。
而且,如果我們去仔細地觀察生成的字幕,就會發現它們其實相當的模煳與普通化。用下麵這個圖片-字幕對為例:
圖5. 來源:Raul Puri,圖片來自MS COCO數據集
這個圖片當然是“長頸鹿站立在樹旁邊”。但是如果看看其他的圖片,我們就可能注意到它會對於任何有長頸鹿的圖片都生成“長頸鹿站立在樹旁邊”,因為在訓練集裏,長頸鹿通常都出現在樹的附近。
下一步工作
首先,如果你想改進這裏介紹的模型,請閱讀以下穀歌的開源“Show and Tell網絡”。它可以用Inception-v3圖片向量和MS COCO數據集來訓練。
目前最前沿的圖片字幕模型包含了一個視覺注意力機製。可以讓模型在生產字幕時,發現圖片裏的引起興趣的區域來有選擇地關注圖片內容。
同時,如果你有對最前沿的字幕生成器的實現感興趣,請閱讀這個論文《展示、關注和說出:使用視覺注意力的神經圖片字幕生成》
原文發布時間為:2017-05-07
本文來自雲棲社區合作夥伴“大數據文摘”,了解相關信息可以關注“BigDataDigest”微信公眾號
最後更新:2017-05-16 17:01:20