教程 | 從頭開始在Python中開發深度學習字幕生成模型

 2017-12-12 10:39:00.0

原標題:教程 | 從頭開始在Python中開發深度學習字幕生成模型

選自Machine Learning Mastery

作者:Jason Brownlee

參與:路雪、蔣思源

本文從數據預處理開始詳細地描述瞭如何使用 VGG 和循環神經網絡構建圖像描述系統,對讀者使用 Keras 和 TensorFlow 理解與實現自動圖像描述很有幫助。本文的代碼都有解釋,非常適合圖像描述任務的入門讀者詳細瞭解這一過程。

圖像描述是一個有挑戰性的人工智能問題,涉及爲給定圖像生成文本描述。字幕生成是一個有挑戰性的人工智能問題,涉及爲給定圖像生成文本描述。

一般圖像描述或字幕生成需要使用計算機視覺方法來了解圖像內容,也需要自然語言處理模型將對圖像的理解轉換成正確順序的文字。近期,深度學習方法在該問題的多個示例上獲得了頂尖結果。

深度學習方法在字幕生成問題上展現了頂尖的結果。這些方法最令人印象深刻的地方:給定一個圖像,我們無需複雜的數據準備和特殊設計的流程,就可以使用端到端的方式預測字幕。

本教程將介紹如何從頭開發能生成圖像字幕的深度學習模型。

完成本教程,你將學會:

  • 如何爲訓練深度學習模型準備圖像和文本數據。

  • 如何設計和訓練深度學習字幕生成模型。

  • 如何評估一個訓練後的字幕生成模型,並使用它爲全新的圖像生成字幕。

教程概覽

該教程共分爲 6 部分:

1. 圖像和字幕數據集

2. 準備圖像數據

3. 準備文本數據

4. 開發深度學習模型

5. 評估模型

6. 生成新的圖像字幕

Python 環境

本教程假設你已經安裝了 Python SciPy 環境,該環境完美適合 Python 3。你必須安裝 Keras(2.0 版本或更高),TensorFlow 或 Theano 後端。本教程還假設你已經安裝了 scikit-learn、Pandas、NumPy 和 Matplotlib 等科學計算與繪圖軟件庫。

我推薦在 GPU 系統上運行代碼。你可以在 Amazon Web Services 上用廉價的方式獲取 GPU:如何在 AWS GPU 上運行 Jupyter noterbook?

圖像和字幕數據集

圖像字幕生成可使用的優秀數據集有 Flickr8K 數據集。原因在於它逼真且相對較小,即使你的工作站使用的是 CPU 也可以下載它,並用於構建模型。

對該數據集的明確描述見 2013 年的論文《Framing Image Deion as a Ranking Task: Data, Models and Evaluation Metrics》。

作者對該數據集的描述如下:

我們介紹了一種用於基於句子的圖像描述和搜索的新型基準集合,包括 8000 張圖像,每個圖像有五個不同的字幕描述對突出實體和事件提供清晰描述。

圖像選自六個不同的 Flickr 組,往往不包含名人或有名的地點,而是手動選擇多種場景和情形。

該數據集可免費獲取。你必須填寫一份申請表,然後就可以通過電子郵箱收到數據集。申請表鏈接:https://illinois.edu/fb/sec/1713398。

很快,你會收到電子郵件,包含以下兩個文件的鏈接:

  • Flickr8k_Dataset.zip(1 Gigabyte)包含所有圖像。

  • Flickr8k_text.zip(2.2 Megabytes)包含所有圖像文本描述。

下載數據集,並在當前工作文件夾裏進行解壓縮。你將得到兩個目錄:

  • Flicker8k_Dataset:包含 8092 張 JPEG 格式圖像。

  • Flickr8k_text:包含大量不同來源的圖像描述文件。

該數據集包含一個預製訓練數據集(6000 張圖像)、開發數據集(1000 張圖像)和測試數據集(1000 張圖像)。

用於評估模型技能的一個指標是 BLEU 值。對於推斷,下面是一些精巧的模型在測試數據集上進行評估時獲得的大概 BLEU 值(來源:2017 年論文《Where to put the Image in an Image Caption Generator》):

  • BLEU-1: 0.401 to 0.578.

  • BLEU-2: 0.176 to 0.390.

  • BLEU-3: 0.099 to 0.260.

  • BLEU-4: 0.059 to 0.170.

稍後在評估模型部分將詳細介紹 BLEU 值。下面,我們來看一下如何加載圖像。

準備圖像數據

我們將使用預訓練模型解析圖像內容,且目前有很多可選模型。在這種情況下,我們將使用 Oxford Visual Geometry Group 或 VGG(該模型贏得了 2014 年 ImageNet 競賽冠軍)。

Keras 可直接提供該預訓練模型。注意,第一次使用該模型時,Keras 將從互聯網上下載模型權重,大概 500Megabytes。這可能需要一段時間(時間長度取決於你的網絡連接)。

我們可以將該模型作爲更大的圖像字幕生成模型的一部分。問題在於模型太大,每次我們想測試新語言模型配置(下行)時在該網絡中運行每張圖像非常冗餘。

我們可以使用預訓練模型對「圖像特徵」進行預計算,並保存至文件中。然後加載這些特徵,將其饋送至模型中作爲數據集中給定圖像的描述。在完整的 VGG 模型中運行圖像也是這樣,我們需要提前運行該步驟。

優化可以加快模型訓練過程,消耗更少內存。我們可以使用 VGG class 在 Keras 中運行 VGG 模型。我們將移除加載模型的最後一層,因爲該層用於預測圖像的分類。我們對圖像分類不感興趣,我們感興趣的是分類之前圖像的內部表徵。這些就是模型從圖像中提取出的「特徵」。

Keras 還提供工具將加載圖像改造成模型的偏好大小(如 3 通道 224 x 224 像素圖像)。

下面是 extract_features() 函數,即給出一個目錄名,該函數將加載每個圖像、爲 VGG 準備圖像數據,並從 VGG 模型中收集預測到的特徵。圖像特徵是包含 4096 個元素的向量,該函數向圖像特徵返回一個圖像標識符(identifier)詞典。

# extract features from each photo in the directorydefextract_features(directory):# load the modelmodel = VGG16() # re-structure the modelmodel.layers.pop() model = Model(inputs=model.inputs, outputs=model.layers[-1].output) # summarizeprint(model.summary()) # extract features from each photofeatures = dict() forname inlistdir(directory): # load an image from filefilename = directory + '/'+ name image = load_img(filename, target_size=(224, 224)) # convert the image pixels to a numpy arrayimage = img_to_array(image) # reshape data for the modelimage = image.reshape((1, image.shape[0], image.shape[1], image.shape[2])) # prepare the image for the VGG modelimage = preprocess_input(image) # get featuresfeature = model.predict(image, verbose=0) # get image idimage_id = name.split('.')[0] # store featurefeatures[image_id] = feature print('>%s'% name) returnfeatures

我們調用該函數爲模型測試準備圖像數據,然後將詞典保存至 features.pkl 文件。

完整示例如下:

fromos importlistdirfrompickle importdumpfromkeras.applications.vgg16 importVGG16fromkeras.preprocessing.image importload_imgfromkeras.preprocessing.image importimg_to_arrayfromkeras.applications.vgg16 importpreprocess_inputfromkeras.models importModel# extract features from each photo in the directorydefextract_features(directory):# load the modelmodel = VGG16() # re-structure the modelmodel.layers.pop() model = Model(inputs=model.inputs, outputs=model.layers[-1].output) # summarizeprint(model.summary()) # extract features from each photofeatures = dict() forname inlistdir(directory): # load an image from filefilename = directory + '/'+ name image = load_img(filename, target_size=(224, 224)) # convert the image pixels to a numpy arrayimage = img_to_array(image) # reshape data for the modelimage = image.reshape((1, image.shape[0], image.shape[1], image.shape[2])) # prepare the image for the VGG modelimage = preprocess_input(image) # get featuresfeature = model.predict(image, verbose=0) # get image idimage_id = name.split('.')[0] # store featurefeatures[image_id] = feature print('>%s'% name) returnfeatures# extract features from all imagesdirectory = 'Flicker8k_Dataset'features = extract_features(directory)print('Extracted Features: %d'% len(features))# save to filedump(features, open('features.pkl', 'wb'))

運行該數據準備步驟可能需要一點時間,時間長度取決於你的硬件,帶有 CPU 的現代工作站可能需要一個小時。

運行結束時,提取出的特徵將存儲在 features.pkl 文件中以備後用。該文件大概 127 Megabytes 大小。

準備文本數據

該數據集中每個圖像有多個描述,文本描述需要進行最低限度的清洗。首先,加載包含所有文本描述的文件。

# load doc into memorydefload_doc(filename):# open the file as read onlyfile = open(filename, 'r') # read all texttext = file.read() # close the filefile.close() returntextfilename = 'Flickr8k_text/Flickr8k.token.txt'# load deionsdoc = load_doc(filename)

每個圖像有一個獨有的標識符,該標識符出現在文件名和文本描述文件中。

接下來,我們將逐步對圖像描述進行操作。下面定義一個 load_deions() 函數:給出一個需要加載的文本文檔,該函數將返回圖像標識符詞典。每個圖像標識符映射到一或多個文本描述。

# extract deions for imagesdefload_deions(doc):mapping = dict() # process linesforline indoc.split('n'): # split line by white spacetokens = line.split() iflen(line) < 2: continue# take the first token as the image id, the rest as the deionimage_id, image_desc = tokens[0], tokens[1:] # remove filename from image idimage_id = image_id.split('.')[0] # convert deion tokens back to stringimage_desc = ' '.join(image_desc) # create the list if neededifimage_id notinmapping: mapping[image_id] = list() # store deionmapping[image_id].append(image_desc) returnmapping# parse deionsdeions = load_deions(doc)print('Loaded: %d '% len(deions))

下面,我們需要清洗描述文本。因爲描述已經經過符號化,所以它十分易於處理。

我們將用以下方式清洗文本,以減少需要處理的詞彙量:

  • 所有單詞全部轉換成小寫。

  • 移除所有標點符號。

  • 移除所有少於或等於一個字符的單詞(如 a)。

  • 移除所有帶數字的單詞。

下面定義了 clean_deions() 函數:給出描述的圖像標識符詞典,遍歷每個描述,清洗文本。

importstringdefclean_deions(deions):# prepare translation table for removing punctuationtable = str.maketrans('', '', string.punctuation) forkey, desc_list indeions.items(): fori inrange(len(desc_list)): desc = desc_list[i] # tokenizedesc = desc.split() # convert to lower casedesc = [word.lower() forword indesc] # remove punctuation from each tokendesc = [w.translate(table) forw indesc] # remove hanging 's' and 'a'desc = [word forword indesc iflen(word)>1] # remove tokens with numbers in themdesc = [word forword indesc ifword.isalpha()] # store as stringdesc_list[i] = ' '.join(desc)# clean deionsclean_deions(deions)

清洗後,我們可以總結詞彙量。

理想情況下,我們希望使用盡可能少的詞彙而得到強大的表達性。詞彙越少則模型越小、訓練速度越快。

對於推斷,我們可以將乾淨的描述轉換成一個集,將它的規模打印出來,這樣就可以瞭解我們的數據集詞彙量的大小了。

# convert the loaded deions into a vocabulary of wordsdefto_vocabulary(deions):# build a list of all deion stringsall_desc = set() forkey indeions.keys(): [all_desc.update(d.split()) ford indeions[key]] returnall_desc# summarize vocabularyvocabulary = to_vocabulary(deions)print('Vocabulary Size: %d'% len(vocabulary))

最後,我們保存圖像標識符詞典和描述至一個新文本 deions.txt,該文件中每行只有一個圖像和一個描述。

下面我們定義了 save_doc() 函數,即給出一個包含標識符和描述之間映射的詞典和文件名,將該映射保存至文件中。

# save deions to file, one per linedefsave_deions(deions, filename):lines = list() forkey, desc_list indeions.items(): fordesc indesc_list: lines.append(key + ' '+ desc) data = 'n'.join(lines) file = open(filename, 'w') file.write(data) file.close()# save deionssave_doc(deions, 'deions.txt')

彙總起來,完整的函數定義如下所示:

importstring# load doc into memorydefload_doc(filename):# open the file as read onlyfile = open(filename, 'r') # read all texttext = file.read() # close the filefile.close() returntext# extract deions for imagesdefload_deions(doc):mapping = dict() # process linesforline indoc.split('n'): # split line by white spacetokens = line.split() iflen(line) < 2: continue# take the first token as the image id, the rest as the deionimage_id, image_desc = tokens[0], tokens[1:] # remove filename from image idimage_id = image_id.split('.')[0] # convert deion tokens back to stringimage_desc = ' '.join(image_desc) # create the list if neededifimage_id notinmapping: mapping[image_id] = list() # store deionmapping[image_id].append(image_desc) returnmappingdefclean_deions(deions):# prepare translation table for removing punctuationtable = str.maketrans('', '', string.punctuation) forkey, desc_list indeions.items(): fori inrange(len(desc_list)): desc = desc_list[i] # tokenizedesc = desc.split() # convert to lower casedesc = [word.lower() forword indesc] # remove punctuation from each tokendesc = [w.translate(table) forw indesc] # remove hanging 's' and 'a'desc = [word forword indesc iflen(word)>1] # remove tokens with numbers in themdesc = [word forword indesc ifword.isalpha()] # store as stringdesc_list[i] = ' '.join(desc)# convert the loaded deions into a vocabulary of wordsdefto_vocabulary(deions):# build a list of all deion stringsall_desc = set() forkey indeions.keys(): [all_desc.update(d.split()) ford indeions[key]] returnall_desc# save deions to file, one per linedefsave_deions(deions, filename):lines = list() forkey, desc_list indeions.items(): fordesc indesc_list: lines.append(key + ' '+ desc) data = 'n'.join(lines) file = open(filename, 'w') file.write(data) file.close()filename = 'Flickr8k_text/Flickr8k.token.txt'# load deionsdoc = load_doc(filename)# parse deionsdeions = load_deions(doc)print('Loaded: %d '% len(deions))# clean deionsclean_deions(deions)# summarize vocabularyvocabulary = to_vocabulary(deions)print('Vocabulary Size: %d'% len(vocabulary))# save to filesave_deions(deions, 'deions.txt')

運行示例首先打印出加載圖像描述的數量(8092)和乾淨詞彙量的規模(8763 個單詞)。

Loaded: 8,092Vocabulary Size: 8,763

最後,把乾淨的描述寫入 deions.txt。

查看文件,我們能夠看到該描述可用於建模。文件中描述的順序可能會發生改變。

2252123185_487f21e336 bunch on people are seated instadium2252123185_487f21e336 crowded stadium isfull of people watching an event2252123185_487f21e336 crowd of people fill up packed stadium2252123185_487f21e336 crowd sitting inan indoor stadium2252123185_487f21e336 stadium full of people watch game...

開發深度學習模型

本節我們將定義深度學習模型,在訓練數據集上進行擬合。本節分爲以下幾部分:

1. 加載數據。

2. 定義模型。

3. 擬合模型。

4. 完成示例。

加載數據

首先,我們必須加載準備好的圖像和文本數據來擬合模型。

我們將在訓練數據集中的所有圖像和描述上訓練數據。訓練過程中,我們計劃在開發數據集上監控模型性能,使用該性能確定什麼時候保存模型至文件。

訓練和開發數據集已經預製好,並分別保存在 Flickr_8k.trainImages.txt 和 Flickr_8k.devImages.txt 文件中,二者均包含圖像文件名列表。從這些文件名中,我們可以提取圖像標識符,並使用它們爲每個集過濾圖像和描述。

如下所示,load_set() 函數將根據訓練或開發集文件名加載一個預定義標識符集。

# load doc into memorydefload_doc(filename):# open the file as read onlyfile = open(filename, 'r') # read all texttext = file.read() # close the filefile.close() returntext# load a pre-defined list of photo identifiersdefload_set(filename):doc = load_doc(filename) dataset = list() # process line by lineforline indoc.split('n'): # skip empty linesiflen(line) < 1: continue# get the image identifieridentifier = line.split('.')[0] dataset.append(identifier) returnset(dataset)

現在,我們可以使用預定義訓練或開發標識符集加載圖像和描述了。

下面是 load_clean_deions() 函數,該函數從給定標識符集的 deions.txt 中加載乾淨的文本描述,並向文本描述列表返回標識符詞典。

我們將要開發的模型能夠生成給定圖像的字幕,一次生成一個單詞。先前生成的單詞序列作爲輸入。因此,我們需要一個 first word 來開啓生成步驟和一個 last word 來表示字幕生成結束。

我們將使用字符串 startseq 和 endseq 完成該目的。這些標記被添加至加載描述,像它們本身就是加載出的那樣。在對文本進行編碼之前進行該操作非常重要,這樣這些標記才能得到正確編碼。

# load clean deions into memorydefload_clean_deions(filename, dataset):# load documentdoc = load_doc(filename) deions = dict() forline indoc.split('n'): # split line by white spacetokens = line.split() # split id from deionimage_id, image_desc = tokens[0], tokens[1:] # skip images not in the setifimage_id indataset: # create listifimage_id notindeions: deions[image_id] = list() # wrap deion in tokensdesc = 'startseq '+ ' '.join(image_desc) + ' endseq'# storedeions[image_id].append(desc) returndeions

接下來,我們可以爲給定數據集加載圖像特徵。

下面定義了 load_photo_features() 函數,該函數加載了整個圖像描述集,然後返回給定圖像標識符集你感興趣的子集。

這不是很高效,但是,這可以幫助我們啓動,快速運行。

# load photo featuresdefload_photo_features(filename, dataset):# load all featuresall_features = load(open(filename, 'rb')) # filter featuresfeatures = {k: all_features[k] fork indataset} returnfeatures

我們可以在這裏暫停一下,測試目前開發的所有內容。

完整的代碼示例如下:

# load doc into memorydefload_doc(filename):# open the file as read onlyfile = open(filename, 'r') # read all texttext = file.read() # close the filefile.close() returntext# load a pre-defined list of photo identifiersdefload_set(filename):doc = load_doc(filename) dataset = list() # process line by lineforline indoc.split('n'): # skip empty linesiflen(line) < 1: continue# get the image identifieridentifier = line.split('.')[0] dataset.append(identifier) returnset(dataset)# load clean deions into memorydefload_clean_deions(filename, dataset):# load documentdoc = load_doc(filename) deions = dict() forline indoc.split('n'): # split line by white spacetokens = line.split() # split id from deionimage_id, image_desc = tokens[0], tokens[1:] # skip images not in the setifimage_id indataset: # create listifimage_id notindeions: deions[image_id] = list() # wrap deion in tokensdesc = 'startseq '+ ' '.join(image_desc) + ' endseq'# storedeions[image_id].append(desc) returndeions# load photo featuresdefload_photo_features(filename, dataset):# load all featuresall_features = load(open(filename, 'rb')) # filter featuresfeatures = {k: all_features[k] fork indataset} returnfeatures# load training dataset (6K)filename = 'Flickr8k_text/Flickr_8k.trainImages.txt'train = load_set(filename)print('Dataset: %d'% len(train))# deionstrain_deions = load_clean_deions('deions.txt', train)print('Deions: train=%d'% len(train_deions))# photo featurestrain_features = load_photo_features('features.pkl', train)print('Photos: train=%d'% len(train_features))

運行該示例首先在測試數據集中加載 6000 張圖像標識符。這些特徵之後將用於加載乾淨描述文本和預計算的圖像特徵。

Dataset: 6,000Deions: train=6,000Photos: train=6,000

描述文本在作爲輸入饋送至模型或與模型預測進行對比之前需要先編碼成數值。

編碼數據的第一步是創建單詞到唯一整數值之間的持續映射。Keras 提供 Tokenizer class,可根據加載的描述數據學習該映射。

下面定義了用於將描述詞典轉換成字符串列表的 to_lines() 函數,和對加載圖像描述文本擬合 Tokenizer 的 create_tokenizer() 函數。

# convert a dictionary of clean deions to a list of deionsdefto_lines(deions):all_desc = list() forkey indeions.keys(): [all_desc.append(d) ford indeions[key]] returnall_desc# fit a tokenizer given caption deionsdefcreate_tokenizer(deions):lines = to_lines(deions) tokenizer = Tokenizer() tokenizer.fit_on_texts(lines) returntokenizer# prepare tokenizertokenizer = create_tokenizer(train_deions)vocab_size = len(tokenizer.word_index) + 1print('Vocabulary Size: %d'% vocab_size)

我們現在對文本進行編碼。

每個描述將被分割成單詞。我們向該模型提供一個單詞和圖像,然後模型生成下一個單詞。描述的前兩個單詞和圖像將作爲模型輸入以生成下一個單詞,這就是該模型的訓練方式。

例如,輸入序列「a little girl running in field」將被分割成 6 個輸入-輸出對來訓練該模型:

X1, X2 (text sequence), y (word)photo startseq, littlephoto startseq, little, girlphoto startseq, little, girl, runningphoto startseq, little, girl, running, inphoto startseq, little, girl, running, in, fieldphoto startseq, little, girl, running, in, field, endseq

稍後,當模型用於生成描述時,生成的單詞將被連結起來,遞歸地作爲輸入以生成圖像字幕。

下面是 create_sequences() 函數,給出 tokenizer、最大序列長度和所有描述和圖像的詞典,該函數將這些數據轉換成輸入-輸出對來訓練模型。該模型有兩個輸入數組:一個用於圖像特徵,一個用於編碼文本。模型輸出是文本序列中編碼的下一個單詞。

輸入文本被編碼爲整數,被饋送至詞嵌入層。圖像特徵將被直接饋送至模型的另一部分。該模型輸出的預測是所有單詞在詞彙表中的概率分佈。

因此,輸出數據是每個單詞的 one-hot 編碼,它表示一種理想化的概率分佈,即除了實際詞位置之外所有詞位置的值都爲 0,實際詞位置的值爲 1。

# create sequences of images, input sequences and output words for an imagedefcreate_sequences(tokenizer, max_length, deions, photos):X1, X2, y = list(), list(), list() # walk through each image identifierforkey, desc_list indeions.items(): # walk through each deion for the imagefordesc indesc_list: # encode the sequenceseq = tokenizer.texts_to_sequences([desc])[0] # split one sequence into multiple X,y pairsfori inrange(1, len(seq)): # split into input and output pairin_seq, out_seq = seq[:i], seq[i] # pad input sequencein_seq = pad_sequences([in_seq], maxlen=max_length)[0] # encode output sequenceout_seq = to_categorical([out_seq], num_classes=vocab_size)[0] # storeX1.append(photos[key][0]) X2.append(in_seq) y.append(out_seq) returnarray(X1), array(X2), array(y)

我們需要計算最長描述中單詞的最大數量。下面是一個有幫助的函數 max_length()。

# calculate the length of the deion with the most wordsdefmax_length(deions):lines = to_lines(deions) returnmax(len(d.split()) ford inlines)

現在我們可以爲訓練和開發數據集加載數據,並將加載數據轉換成輸入-輸出對來擬合深度學習模型。

定義模型

我們將根據 Marc Tanti, et al. 在 2017 年論文中描述的「merge-model」定義深度學習模型。

  • Where to put the Image in an Image Caption Generator,2017

  • What is the Role of Recurrent Neural Networks (RNNs) in an Image Caption Generator?,2017

論文作者提供了該模型的簡圖,如下所示:

我們將從三部分描述該模型:

  • 圖像特徵提取器:這是一個在 ImageNet 數據集上預訓練的 16 層 VGG 模型。我們已經使用 VGG 模型(沒有輸出層)對圖像進行預處理,並將使用該模型預測的提取特徵作爲輸入。

  • 序列處理器:合適一個詞嵌入層,用於處理文本輸入,後面是長短期記憶(LSTM)循環神經網絡層。

  • 解碼器:特徵提取器和序列處理器輸出一個固定長度向量。這些向量由密集層(Dense layer)融合和處理,來進行最終預測。

圖像特徵提取器模型的輸入圖像特徵是維度爲 4096 的向量,這些向量經過全連接層處理並生成圖像的 256 元素表徵。

序列處理器模型期望饋送至嵌入層的預定義長度(34 個單詞)輸入序列使用掩碼來忽略 padded 值。之後是具備 256 個循環單元的 LSTM 層。

兩個輸入模型均輸出 256 元素的向量。此外,輸入模型以 50% 的 dropout 率使用正則化,旨在減少訓練數據集的過擬合情況,因爲該模型配置學習非常快。

解碼器模型使用額外的操作融合來自兩個輸入模型的向量。然後將其饋送至 256 個神經元的密集層,然後輸送至最終輸出密集層,從而在所有輸出詞彙上對序列中的下一個單詞進行 softmax 預測。

下面的 define_model() 函數定義和返回要擬合的模型。

# define the captioning modeldefdefine_model(vocab_size, max_length):# feature extractor modelinputs1 = Input(shape=(4096,)) fe1 = Dropout(0.5)(inputs1) fe2 = Dense(256, activation='relu')(fe1) # sequence modelinputs2 = Input(shape=(max_length,)) se1 = Embedding(vocab_size, 256, mask_zero=True)(inputs2) se2 = Dropout(0.5)(se1) se3 = LSTM(256)(se2) # decoder modeldecoder1 = add([fe2, se3]) decoder2 = Dense(256, activation='relu')(decoder1) outputs = Dense(vocab_size, activation='softmax')(decoder2) # tie it together [image, seq] [word]model = Model(inputs=[inputs1, inputs2], outputs=outputs) model.compile(loss='categorical_crossentropy', optimizer='adam') # summarize modelprint(model.summary()) plot_model(model, to_file='model.png', show_shapes=True) returnmodel

要了解模型結構,特別是層的形狀,請參考下表中的總結。

____________________________________________________________________________________________________Layer (type) Output Shape Param # Connected to====================================================================================================input_2 (InputLayer) (None, 34) 0____________________________________________________________________________________________________input_1 (InputLayer) (None, 4096) 0____________________________________________________________________________________________________embedding_1 (Embedding) (None, 34, 256) 1940224input_2[0][0]____________________________________________________________________________________________________dropout_1 (Dropout) (None, 4096) 0input_1[0][0]____________________________________________________________________________________________________dropout_2 (Dropout) (None, 34, 256) 0embedding_1[0][0]____________________________________________________________________________________________________dense_1 (Dense) (None, 256) 1048832dropout_1[0][0]____________________________________________________________________________________________________lstm_1 (LSTM) (None, 256) 525312dropout_2[0][0]____________________________________________________________________________________________________add_1 (Add) (None, 256) 0dense_1[0][0] lstm_1[0][0]____________________________________________________________________________________________________dense_2 (Dense) (None, 256) 65792add_1[0][0]____________________________________________________________________________________________________dense_3 (Dense) (None, 7579) 1947803dense_2[0][0]====================================================================================================Total params: 5,527,963Trainable params: 5,527,963Non-trainable params: 0____________________________________________________________________________________________________

我們還創建了一幅圖來可視化網絡結構,幫助理解兩個輸入流。

圖像字幕生成深度學習模型示意圖

擬合模型

現在我們已經瞭解如何定義模型了,那麼接下來我們要在訓練數據集上擬合模型。

該模型學習速度快,很快就會對訓練數據集產生過擬合。因此,我們需要在留出的開發數據集上監控訓練模型的泛化情況。如果模型在開發數據集上的技能在每個 epoch 結束時有所提升,則我們將整個模型保存至文件。

在運行結束時,我們能夠使用訓練數據集上具備最優技能的模型作爲最終模型。

通過在 Keras 中定義 ModelCheckpoint,使之監控驗證數據集上的最小損失,我們可以實現以上目的。然後將該模型保存至文件名中包含訓練損失和驗證損失的文件中。

# define checkpoint callbackfilepath = 'model-ep{epoch:03d}-loss{loss:.3f}-val_loss{val_loss:.3f}.h5'checkpoint = ModelCheckpoint(filepath, monitor='val_loss', verbose=1, save_best_only=True, mode='min')

之後,通過 fit() 中的 callbacks 參數指定檢查點。我們還需要 fit() 中的 validation_data 參數指定開發數據集。

我們僅擬合模型 20 epoch,給出一定量的訓練數據,在一般硬件上每個 epoch 可能需要 30 分鐘。

# fit modelmodel.fit([X1train, X2train], ytrain, epochs=20, verbose=2, callbacks=[checkpoint], validation_data=([X1test, X2test], ytest))

完成示例

在訓練數據上擬合模型的完整示例如下:

fromnumpy importarrayfrompickle importloadfromkeras.preprocessing.text importTokenizerfromkeras.preprocessing.sequence importpad_sequencesfromkeras.utils importto_categoricalfromkeras.utils importplot_modelfromkeras.models importModelfromkeras.layers importInputfromkeras.layers importDensefromkeras.layers importLSTMfromkeras.layers importEmbeddingfromkeras.layers importDropoutfromkeras.layers.merge importaddfromkeras.callbacks importModelCheckpoint# load doc into memorydefload_doc(filename):# open the file as read onlyfile = open(filename, 'r') # read all texttext = file.read() # close the filefile.close() returntext# load a pre-defined list of photo identifiersdefload_set(filename):doc = load_doc(filename) dataset = list() # process line by lineforline indoc.split('n'): # skip empty linesiflen(line) < 1: continue# get the image identifieridentifier = line.split('.')[0] dataset.append(identifier) returnset(dataset)# load clean deions into memorydefload_clean_deions(filename, dataset):# load documentdoc = load_doc(filename) deions = dict() forline indoc.split('n'): # split line by white spacetokens = line.split() # split id from deionimage_id, image_desc = tokens[0], tokens[1:] # skip images not in the setifimage_id indataset: # create listifimage_id notindeions: deions[image_id] = list() # wrap deion in tokensdesc = 'startseq '+ ' '.join(image_desc) + ' endseq'# storedeions[image_id].append(desc) returndeions# load photo featuresdefload_photo_features(filename, dataset):# load all featuresall_features = load(open(filename, 'rb')) # filter featuresfeatures = {k: all_features[k] fork indataset} returnfeatures# covert a dictionary of clean deions to a list of deionsdefto_lines(deions):all_desc = list() forkey indeions.keys(): [all_desc.append(d) ford indeions[key]] returnall_desc# fit a tokenizer given caption deionsdefcreate_tokenizer(deions):lines = to_lines(deions) tokenizer = Tokenizer() tokenizer.fit_on_texts(lines) returntokenizer# calculate the length of the deion with the most wordsdefmax_length(deions):lines = to_lines(deions) returnmax(len(d.split()) ford inlines)# create sequences of images, input sequences and output words for an imagedefcreate_sequences(tokenizer, max_length, deions, photos):X1, X2, y = list(), list(), list() # walk through each image identifierforkey, desc_list indeions.items(): # walk through each deion for the imagefordesc indesc_list: # encode the sequenceseq = tokenizer.texts_to_sequences([desc])[0] # split one sequence into multiple X,y pairsfori inrange(1, len(seq)): # split into input and output pairin_seq, out_seq = seq[:i], seq[i] # pad input sequencein_seq = pad_sequences([in_seq], maxlen=max_length)[0] # encode output sequenceout_seq = to_categorical([out_seq], num_classes=vocab_size)[0] # storeX1.append(photos[key][0]) X2.append(in_seq) y.append(out_seq) returnarray(X1), array(X2), array(y)# define the captioning modeldefdefine_model(vocab_size, max_length):# feature extractor modelinputs1 = Input(shape=(4096,)) fe1 = Dropout(0.5)(inputs1) fe2 = Dense(256, activation='relu')(fe1) # sequence modelinputs2 = Input(shape=(max_length,)) se1 = Embedding(vocab_size, 256, mask_zero=True)(inputs2) se2 = Dropout(0.5)(se1) se3 = LSTM(256)(se2) # decoder modeldecoder1 = add([fe2, se3]) decoder2 = Dense(256, activation='relu')(decoder1) outputs = Dense(vocab_size, activation='softmax')(decoder2) # tie it together [image, seq] [word]model = Model(inputs=[inputs1, inputs2], outputs=outputs) model.compile(loss='categorical_crossentropy', optimizer='adam') # summarize modelprint(model.summary()) plot_model(model, to_file='model.png', show_shapes=True) returnmodel# train dataset# load training dataset (6K)filename = 'Flickr8k_text/Flickr_8k.trainImages.txt'train = load_set(filename)print('Dataset: %d'% len(train))# deionstrain_deions = load_clean_deions('deions.txt', train)print('Deions: train=%d'% len(train_deions))# photo featurestrain_features = load_photo_features('features.pkl', train)print('Photos: train=%d'% len(train_features))# prepare tokenizertokenizer = create_tokenizer(train_deions)vocab_size = len(tokenizer.word_index) + 1print('Vocabulary Size: %d'% vocab_size)# determine the maximum sequence lengthmax_length = max_length(train_deions)print('Deion Length: %d'% max_length)# prepare sequencesX1train, X2train, ytrain = create_sequences(tokenizer, max_length, train_deions, train_features)# dev dataset# load test setfilename = 'Flickr8k_text/Flickr_8k.devImages.txt'test = load_set(filename)print('Dataset: %d'% len(test))# deionstest_deions = load_clean_deions('deions.txt', test)print('Deions: test=%d'% len(test_deions))# photo featurestest_features = load_photo_features('features.pkl', test)print('Photos: test=%d'% len(test_features))# prepare sequencesX1test, X2test, ytest = create_sequences(tokenizer, max_length, test_deions, test_features)# fit model# define the modelmodel = define_model(vocab_size, max_length)# define checkpoint callbackfilepath = 'model-ep{epoch:03d}-loss{loss:.3f}-val_loss{val_loss:.3f}.h5'checkpoint = ModelCheckpoint(filepath, monitor='val_loss', verbose=1, save_best_only=True, mode='min')# fit modelmodel.fit([X1train, X2train], ytrain, epochs=20, verbose=2, callbacks=[checkpoint], validation_data=([X1test, X2test], ytest))

運行該示例首先打印加載訓練和開發數據集的摘要。

Dataset: 6,000Deions: train=6,000Photos: train=6,000Vocabulary Size: 7,579Deion Length: 34Dataset: 1,000Deions: test=1,000Photos: test=1,000

之後,我們可以瞭解訓練和驗證(開發)輸入-輸出對的整體數量。

Train on 306,404samples, validate on 50,903samples

然後運行模型,將最優模型保存至.h5 文件。

在運行過程中,我把最優驗證結果的模型保存至文件中:

  • model-ep002-loss3.245-val_loss3.612.h5

該模型在第 2 個 epoch 中結束時被保存,在訓練數據集上的損失爲 3.245,在開發數據集上的損失爲 3.612,每個人的具體結果不同。如果你在 AWS 中運行上述示例,那麼將模型文件複製回你當前的工作文件夾。

評估模型

模型擬合之後,我們可以在留出的測試數據集上評估它的預測技能。

使模型對測試數據集中的所有圖像生成描述,使用標準代價函數評估預測,從而評估模型。

首先,我們需要使用訓練模型對圖像生成描述。輸入開始描述的標記「startseq」,生成一個單詞,然後遞歸地用生成單詞作爲輸入啓用模型直到序列標記到「endseq」或達到最大描述長度。

下面的 generate_desc() 函數實現該行爲,並基於給定訓練模型和作爲輸入的準備圖像生成文本描述。它啓用 word_for_id() 函數以映射整數預測至單詞。

# map an integer to a worddefword_for_id(integer, tokenizer):forword, index intokenizer.word_index.items(): ifindex == integer: returnword returnNone# generate a deion for an imagedefgenerate_desc(model, tokenizer, photo, max_length):# seed the generation processin_text = 'startseq'# iterate over the whole length of the sequencefori inrange(max_length): # integer encode input sequencesequence = tokenizer.texts_to_sequences([in_text])[0] # pad inputsequence = pad_sequences([sequence], maxlen=max_length) # predict next wordyhat = model.predict([photo,sequence], verbose=0) # convert probability to integeryhat = argmax(yhat) # map integer to wordword = word_for_id(yhat, tokenizer) # stop if we cannot map the wordifword isNone: break# append as input for generating the next wordin_text += ' '+ word # stop if we predict the end of the sequenceifword == 'endseq': breakreturnin_text

我們將爲測試數據集和訓練數據集中的所有圖像生成預測。

下面的 evaluate_model() 基於給定圖像描述數據集和圖像特徵評估訓練模型。收集實際和預測描述,使用語料庫 BLEU 值對它們進行評估。語料庫 BLEU 值總結了生成文本和期望文本之間的相似度。

# evaluate the skill of the modeldefevaluate_model(model, deions, photos, tokenizer, max_length):actual, predicted = list(), list() # step over the whole setforkey, desc_list indeions.items(): # generate deionyhat = generate_desc(model, tokenizer, photos[key], max_length) # store actual and predictedreferences = [d.split() ford indesc_list] actual.append(references) predicted.append(yhat.split()) # calculate BLEU scoreprint('BLEU-1: %f'% corpus_bleu(actual, predicted, weights=(1.0, 0, 0, 0))) print('BLEU-2: %f'% corpus_bleu(actual, predicted, weights=(0.5, 0.5, 0, 0))) print('BLEU-3: %f'% corpus_bleu(actual, predicted, weights=(0.3, 0.3, 0.3, 0))) print('BLEU-4: %f'% corpus_bleu(actual, predicted, weights=(0.25, 0.25, 0.25, 0.25)))

BLEU 值用於在文本翻譯中評估譯文和一或多個參考譯文的相似度。

這裏,我們將每個生成描述與該圖像的所有參考描述進行對比,然後計算 1、2、3、4 等 n 元語言模型的 BLEU 值。

NLTK Python 庫在 corpus_bleu() 函數中實現了 BLEU 值計算。分值越接近 1.0 越好,越接近 0 越差。

我們可以結合前面加載數據部分中的函數。首先加載訓練數據集來準備 Tokenizer,以使我們將生成單詞編碼成模型的輸入序列。使用模型訓練時使用的編碼機制對生成單詞進行編碼非常關鍵。

然後使用這些函數加載測試數據集。完整示例如下:

fromnumpy importargmaxfrompickle importloadfromkeras.preprocessing.text importTokenizerfromkeras.preprocessing.sequence importpad_sequencesfromkeras.models importload_modelfromnltk.translate.bleu_score importcorpus_bleu# load doc into memorydefload_doc(filename):# open the file as read onlyfile = open(filename, 'r') # read all texttext = file.read() # close the filefile.close() returntext# load a pre-defined list of photo identifiersdefload_set(filename):doc = load_doc(filename) dataset = list() # process line by lineforline indoc.split('n'): # skip empty linesiflen(line) < 1: continue# get the image identifieridentifier = line.split('.')[0] dataset.append(identifier) returnset(dataset)# load clean deions into memorydefload_clean_deions(filename, dataset):# load documentdoc = load_doc(filename) deions = dict() forline indoc.split('n'): # split line by white spacetokens = line.split() # split id from deionimage_id, image_desc = tokens[0], tokens[1:] # skip images not in the setifimage_id indataset: # create listifimage_id notindeions: deions[image_id] = list() # wrap deion in tokensdesc = 'startseq '+ ' '.join(image_desc) + ' endseq'# storedeions[image_id].append(desc) returndeions# load photo featuresdefload_photo_features(filename, dataset):# load all featuresall_features = load(open(filename, 'rb')) # filter featuresfeatures = {k: all_features[k] fork indataset} returnfeatures# covert a dictionary of clean deions to a list of deionsdefto_lines(deions):all_desc = list() forkey indeions.keys(): [all_desc.append(d) ford indeions[key]] returnall_desc# fit a tokenizer given caption deionsdefcreate_tokenizer(deions):lines = to_lines(deions) tokenizer = Tokenizer() tokenizer.fit_on_texts(lines) returntokenizer# calculate the length of the deion with the most wordsdefmax_length(deions):lines = to_lines(deions) returnmax(len(d.split()) ford inlines)# map an integer to a worddefword_for_id(integer, tokenizer):forword, index intokenizer.word_index.items(): ifindex == integer: returnword returnNone# generate a deion for an imagedefgenerate_desc(model, tokenizer, photo, max_length):# seed the generation processin_text = 'startseq'# iterate over the whole length of the sequencefori inrange(max_length): # integer encode input sequencesequence = tokenizer.texts_to_sequences([in_text])[0] # pad inputsequence = pad_sequences([sequence], maxlen=max_length) # predict next wordyhat = model.predict([photo,sequence], verbose=0) # convert probability to integeryhat = argmax(yhat) # map integer to wordword = word_for_id(yhat, tokenizer) # stop if we cannot map the wordifword isNone: break# append as input for generating the next wordin_text += ' '+ word # stop if we predict the end of the sequenceifword == 'endseq': breakreturnin_text# evaluate the skill of the modeldefevaluate_model(model, deions, photos, tokenizer, max_length):actual, predicted = list(), list() # step over the whole setforkey, desc_list indeions.items(): # generate deionyhat = generate_desc(model, tokenizer, photos[key], max_length) # store actual and predictedreferences = [d.split() ford indesc_list] actual.append(references) predicted.append(yhat.split()) # calculate BLEU scoreprint('BLEU-1: %f'% corpus_bleu(actual, predicted, weights=(1.0, 0, 0, 0))) print('BLEU-2: %f'% corpus_bleu(actual, predicted, weights=(0.5, 0.5, 0, 0))) print('BLEU-3: %f'% corpus_bleu(actual, predicted, weights=(0.3, 0.3, 0.3, 0))) print('BLEU-4: %f'% corpus_bleu(actual, predicted, weights=(0.25, 0.25, 0.25, 0.25)))# prepare tokenizer on train set# load training dataset (6K)filename = 'Flickr8k_text/Flickr_8k.trainImages.txt'train = load_set(filename)print('Dataset: %d'% len(train))# deionstrain_deions = load_clean_deions('deions.txt', train)print('Deions: train=%d'% len(train_deions))# prepare tokenizertokenizer = create_tokenizer(train_deions)vocab_size = len(tokenizer.word_index) + 1print('Vocabulary Size: %d'% vocab_size)# determine the maximum sequence lengthmax_length = max_length(train_deions)print('Deion Length: %d'% max_length)# prepare test set# load test setfilename = 'Flickr8k_text/Flickr_8k.testImages.txt'test = load_set(filename)print('Dataset: %d'% len(test))# deionstest_deions = load_clean_deions('deions.txt', test)print('Deions: test=%d'% len(test_deions))# photo featurestest_features = load_photo_features('features.pkl', test)print('Photos: test=%d'% len(test_features))# load the modelfilename = 'model-ep002-loss3.245-val_loss3.612.h5'model = load_model(filename)# evaluate modelevaluate_model(model, test_deions, test_features, tokenizer, max_length)

運行示例打印 BLEU 值。我們可以看到 BLEU 值處於該問題較優的期望範圍內,且接近最優水平。並且我們並沒有對選擇的模型配置進行特別的優化。

BLEU-1: 0.579114BLEU-2: 0.344856BLEU-3: 0.252154BLEU-4: 0.131446

生成新的圖像字幕

現在我們瞭解瞭如何開發和評估字幕生成模型,那麼我們如何使用它呢?

我們需要模型文件中全新的圖像,還需要 Tokenizer 用於對模型生成單詞進行編碼,生成序列和定義模型時使用的輸入序列最大長度。

我們可以對最大序列長度進行硬編碼。文本編碼後,我們就可以創建 tokenizer,並將其保存至文件,這樣我們可以在需要的時候快速加載,無需整個 Flickr8K 數據集。另一個方法是使用我們自己的詞彙文件,在訓練過程中將其映射到取整函數。

我們可以按照之前的方式創建 Tokenizer,並將其保存爲 pickle 文件 tokenizer.pkl。完整示例如下:

fromkeras.preprocessing.text importTokenizerfrompickle importdump# load doc into memorydefload_doc(filename):# open the file as read onlyfile = open(filename, 'r') # read all texttext = file.read() # close the filefile.close() returntext# load a pre-defined list of photo identifiersdefload_set(filename):doc = load_doc(filename) dataset = list() # process line by lineforline indoc.split('n'): # skip empty linesiflen(line) < 1: continue# get the image identifieridentifier = line.split('.')[0] dataset.append(identifier) returnset(dataset)# load clean deions into memorydefload_clean_deions(filename, dataset):# load documentdoc = load_doc(filename) deions = dict() forline indoc.split('n'): # split line by white spacetokens = line.split() # split id from deionimage_id, image_desc = tokens[0], tokens[1:] # skip images not in the setifimage_id indataset: # create listifimage_id notindeions: deions[image_id] = list() # wrap deion in tokensdesc = 'startseq '+ ' '.join(image_desc) + ' endseq'# storedeions[image_id].append(desc) returndeions# covert a dictionary of clean deions to a list of deionsdefto_lines(deions):all_desc = list() forkey indeions.keys(): [all_desc.append(d) ford indeions[key]] returnall_desc# fit a tokenizer given caption deionsdefcreate_tokenizer(deions):lines = to_lines(deions) tokenizer = Tokenizer() tokenizer.fit_on_texts(lines) returntokenizer# load training dataset (6K)filename = 'Flickr8k_text/Flickr_8k.trainImages.txt'train = load_set(filename)print('Dataset: %d'% len(train))# deionstrain_deions = load_clean_deions('deions.txt', train)print('Deions: train=%d'% len(train_deions))# prepare tokenizertokenizer = create_tokenizer(train_deions)# save the tokenizerdump(tokenizer, open('tokenizer.pkl', 'wb'))

現在我們可以在需要的時候加載 tokenizer,無需加載整個標註訓練數據集。下面,我們來爲一個新圖像生成描述,下面這張圖是我從 Flickr 中隨機選的一張圖像。

海灘上的狗

我們將使用模型爲它生成描述。首先下載圖像,保存至本地文件夾,文件名設置爲「example.jpg」。然後,我們必須從 tokenizer.pkl 中加載 Tokenizer,定義生成序列的最大長度,在對輸入數據進行填充時需要該信息。

# load the tokenizertokenizer = load(open('tokenizer.pkl', 'rb'))# pre-define the max sequence length (from training)max_length = 34

然後我們必須加載模型,如前所述。

# load the modelmodel = load_model('model-ep002-loss3.245-val_loss3.612.h5')

接下來,我們必須加載要描述和提取特徵的圖像。

重定義該模型、向其中添加 VGG-16 模型,或者使用 VGG 模型來預測特徵,使用這些特徵作爲現有模型的輸入。我們將使用後一種方法,使用數據準備階段所用的 extract_features() 函數的修正版本,該版本適合處理單個圖像。

# extract features from each photo in the directorydefextract_features(filename):# load the modelmodel = VGG16() # re-structure the modelmodel.layers.pop() model = Model(inputs=model.inputs, outputs=model.layers[-1].output) # load the photoimage = load_img(filename, target_size=(224, 224)) # convert the image pixels to a numpy arrayimage = img_to_array(image) # reshape data for the modelimage = image.reshape((1, image.shape[0], image.shape[1], image.shape[2])) # prepare the image for the VGG modelimage = preprocess_input(image) # get featuresfeature = model.predict(image, verbose=0) returnfeature# load and prepare the photographphoto = extract_features('example.jpg')

之後使用評估模型定義的 generate_desc() 函數生成圖像描述。爲單個全新圖像生成描述的完整示例如下:

frompickle importloadfromnumpy importargmaxfromkeras.preprocessing.sequence importpad_sequencesfromkeras.applications.vgg16 importVGG16fromkeras.preprocessing.image importload_imgfromkeras.preprocessing.image importimg_to_arrayfromkeras.applications.vgg16 importpreprocess_inputfromkeras.models importModelfromkeras.models importload_model# extract features from each photo in the directorydefextract_features(filename):# load the modelmodel = VGG16() # re-structure the modelmodel.layers.pop() model = Model(inputs=model.inputs, outputs=model.layers[-1].output) # load the photoimage = load_img(filename, target_size=(224, 224)) # convert the image pixels to a numpy arrayimage = img_to_array(image) # reshape data for the modelimage = image.reshape((1, image.shape[0], image.shape[1], image.shape[2])) # prepare the image for the VGG modelimage = preprocess_input(image) # get featuresfeature = model.predict(image, verbose=0) returnfeature# map an integer to a worddefword_for_id(integer, tokenizer):forword, index intokenizer.word_index.items(): ifindex == integer: returnword returnNone# generate a deion for an imagedefgenerate_desc(model, tokenizer, photo, max_length):# seed the generation processin_text = 'startseq'# iterate over the whole length of the sequencefori inrange(max_length): # integer encode input sequencesequence = tokenizer.texts_to_sequences([in_text])[0] # pad inputsequence = pad_sequences([sequence], maxlen=max_length) # predict next wordyhat = model.predict([photo,sequence], verbose=0) # convert probability to integeryhat = argmax(yhat) # map integer to wordword = word_for_id(yhat, tokenizer) # stop if we cannot map the wordifword isNone: break# append as input for generating the next wordin_text += ' '+ word # stop if we predict the end of the sequenceifword == 'endseq': breakreturnin_text# load the tokenizertokenizer = load(open('tokenizer.pkl', 'rb'))# pre-define the max sequence length (from training)max_length = 34# load the modelmodel = load_model('model-ep002-loss3.245-val_loss3.612.h5')# load and prepare the photographphoto = extract_features('example.jpg')# generate deiondeion = generate_desc(model, tokenizer, photo, max_length)print(deion)

這種情況下,生成的描述如下:

startseq dog isrunning across the beach endseq

移除開始和結束的標記,或許這就是我們希望模型生成的語句。至此,我們現在已經完整地使用模型爲圖像生成文本描述,雖然這一實現非常基礎與簡單,但它是我們繼續學習強大圖像描述模型的基礎。我們也希望本文能帶領給爲讀者實操地理解圖像描述模型。

因微信代碼展示不便,準確的格式請查看原文或機器之心官網。

原文鏈接:https://machinelearningmastery.com/develop-a-deep-learning-caption-generation-model-in-python/

本文爲機器之心編譯,轉載請聯繫本公衆號獲得授權。

責任編輯:

文章來源:機器之心