如何用TensorFlow和Keras进行图像标注(附代码)

459 阅读19分钟

当我们看一个图像时,我们对该特定图像的视觉感知可以解释很多不同的东西。例如,在上面的图像中,我们大脑中的视觉感知的解释之一可能是 "河中有乘客的船在航行",或者我们可以完全不考虑船,而专注于其他元素的解释,如 "描绘一些建筑物与桥梁的美丽风景"。 虽然任何提供的图像的这些观点都没有错,但有时解决图像模式中的主要实体并对其评论一行变得非常重要。人脑在观看任何特定的视觉模式时,获取以下信息应该没有问题。然而,神经网络是否真的有可能像我们拥有完美眼球感受器的人脑一样,发现如此复杂的问题并解释其解决方案?

这个问题是相当耐人寻味的。这样的问题的解决方案看起来是非常乐观的,因为只要有正确的想法,我们就可以成功地执行前面提到的任务的实施。综合使用我们之前的两篇文章,即《用序列到序列模型和点状注意力机制进行机器翻译》和《转移学习的完整直观指南》,就可以解决图像字幕的问题。 我强烈建议任何还没有查看以下两篇文章的读者,如果他们没有关于这两个要素的完整理论知识,就立即查看。用于分析图像的转移学习和用于解释适当文本数据的 "注意"(Sequence to Sequence)模型的综合知识对于解决图像字幕问题很有帮助。

通过简单的介绍和其他一些较小的错综复杂的细节,本指南将直接深入到用TensorFlow和Keras收缩的图像字幕项目中。我强烈建议任何深度学习的初学者,在不跳过任何基本概念的情况下,查看大部分的基本主题。然而,如果你对图像字幕项目很熟悉,只想关注某些具体内容或要领,下面提供了目录清单。这个指南应该能够指导你完成本文所讨论的各种重要议题。

简介

在我们直接进入项目建设之前,让我们了解一下图像字幕的概念。那么,究竟什么是图片说明? 图像说明是一种为任何提供的视觉表现(如图像或视频)生成文本描述的方法。虽然对任何人类来说,为特定的图像想出适当的字幕或标题的过程不是一个复杂的问题,但这种情况对深度学习模型或一般的机器来说是不一样的。然而,只要有适量的信息和数据集,完美的架构结构构建,以及足够的有用资源,就可以实现具有相当高准确性的图像标题任务。

在现代社会中,图像说明的应用极其繁多。提到图像说明的几个显著的应用,我们可以包括它们在编辑应用程序中的推荐、在虚拟助手中的使用、图像索引、为视障人士、社交媒体以及其他大量的自然语言处理应用程序中的使用案例。总的来说,在TensorFlow和Keras的帮助下,从头开始构建也是一个有趣的项目,但请确保你的环境满足运行这样一个项目的要求。

深度学习框架TensorFlow和Keras的知识对于理解本项目中实现的所有理论和实践概念极为重要。在你深入研究任何进一步的代码块之前,强烈建议你查看我以前关于这些主题的文章,这些文章具体地涵盖了TensorFlowKeras的内容。另外,如前所述,我们将利用转移学习的方法学来进行模型的可视化推理。我们还可以利用 "关注序列"(Sequence To Sequence Modeling)的概念来成功生成文字描述或以下每张图片的标题。那些至少对这些主题有基本认知的人将从这篇文章中受益最多。


方法和途径

对于这个关于使用TensorFlow和Keras的图像标题的项目,我们的第一个目标是搜集和收集所有可用的有用信息和数据。用于这项任务的流行数据集之一是Flickr数据集。一旦我们为训练过程和构建模型架构收集了足够的信息,我们将了解预处理的基本方法。我们将对图像和它们各自的标签/标题进行相应的预处理和准备。

对于标题的准备,我们将删除任何不必要的元素,并将重点放在主要要求上。首先,我们要进行自然语言处理(NLP)式的清理和数据集准备。我们将把所有的字母更新为小写格式,并删除句子中的任何单字母或标点符号。为了处理每张图片,我们将利用V3的转移学习模型。这个转移学习模型的目的是将我们的图像转换为适当的矢量表示。对于这一步,我们将排除最后的SoftMax函数层。我们将使用2048个特征来进行准确的矢量编码。

一旦我们有了所有的图像向量和单词转换为各自的索引号,我们将定义一个预先设想的最大长度并创建适当的嵌入。下一个基本步骤是构建我们的数据加载器/数据生成器,以便相应地提供各自的输入和输出。最后,我们将构建我们的LSTM模型架构,用于计算图像字幕的任务。一旦训练过程完成,我们将保存模型和它们的权重。这些保存的模型可用于重新训练以提高准确性。你也可以将这个保存的模型用于部署目的,对其他图像和数据集进行预测。有了对这个主题的基本要点和理解,让我们开始讨论主要目标。


数据集收集

任何深度学习项目的第一个基本步骤是收集可用来接近特定任务的有用数据。对于图像字幕这个问题,我们要考虑的两个主要的数据收集来源如下:

  1. 一个GB的FLIKR数据集 - www.kaggle.com/adityajn105…
  2. 完整的FLIKR数据集 -www.kaggle.com/hsankesara/…

来自Kaggle数据集存储库的一GB压缩文件包含8000多张图片。以下每张图片都来自六个不同的Flickr小组。然后,这些图片被配上了五个不同的标题。所有这些使用的标题都对可能的特征、突出的实体和其他事件提供了清晰准确的描述。第二个URL链接为用户提供了一个机会,使用更大的数据集来建立一个整体的复杂而有效的模型。该数据集包含更多的信息和数据,可用于构建有效的模型架构。

然而,在这篇文章中,我们将坚持使用较小的一吉字节的数据集,因为它更方便。而且它也适合于更多的受众,因为他们因为缺乏资源而无法准备和训练更复杂的数据集。不管怎么说,如果你对大数据集的工作感兴趣,如果你想用有限的PC资源开发更高端的模型,我强烈建议你去看看Paperspace上的Gradient平台。Gradient使个人和团队能够快速开发、跟踪和协作任何规模和复杂性的机器学习模型。


数据集准备

我们项目的第一个基本步骤是对我们使用的所有数据进行预处理。这个过程是以这样的方式进行的,即所有获得的结果都是以一种兼容的形式获得最佳结果,同时使用深度学习模型,即LSTM层,来获得自然语言处理任务的背景知识。在这一步的第一部分,我们将导入解决图像字幕任务所需的所有基本库。我们将需要TensorFlow和Keras深度学习框架,以及其他一些必要的库,如numpy、glob、cv2等,以成功完成这个项目。如果你想了解更多关于这两个深度学习框架的信息,你可以从以下链接中查看它们。下面的链接是TensorFlow,这个链接是Keras。

导入必要的库

import tensorflow as tf
from tensorflow import keras
import matplotlib.pyplot as plt
from tensorflow.keras.applications import InceptionV3
from tensorflow.keras.models import Model
from keras.preprocessing.sequence import pad_sequences
from keras.utils import to_categorical
import numpy as np
from numpy import array
import pandas as pd
import cv2
from glob import glob
import PIL
import time
from tqdm import tqdm
import os

一旦我们导入了完成任务所需的所有必要库,我们就可以继续添加各自的路径,并在存储图像的目录中找到图像的总数。为了执行这一操作,我们将利用glob库,并计算出我们的图像目录中.jpg格式的图像总数。如果路径与你使用的特定系统不同,请随意更改:

image_path = "Images/"
images = glob(image_path + "*.jpg")
len(images)

输出

8091

将数据可视化

在下面这块,我们将对一小部分数据样本(大约五张图片)进行可视化,以了解我们在这个数据集中所处理的图片类型。matplotlib库被用来绘制这些图像,计算机视觉OpenCV库被用来读取图像,然后将它们从默认的BGR格式转换为RGB格式:

for i in range(5):
    plt.figure()
    image = cv2.imread(images[i])
    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
    plt.imshow(image)

2张来自Flickr数据集的样本图像,转换为RGB格式。

这是你可以通过运行下面的代码来可视化的前两张图片。还有三张图片,你可以在运行代码块后看到。整个Jupyter笔记本可以在这里找到。欢迎查看完整的代码和所提供的笔记本中的可视化内容,看看其他三张图片是怎样的。

对数据进行预处理

现在我们对我们的可视化的外观和我们正在处理的图片的类型有了一个简单的概念,现在是时候预处理标题了。让我们创建一个函数来加载适当的文件,然后进行相应的查看:

# Defining the function to load the captions

def load(filename):
    file = open(filename, 'r')
    text = file.read()
    file.close()
    return text

file = "captions.txt"
info = load(file)
print(info[:484])

输出

image,caption
1000268201_693b08cb0e.jpg,A child in a pink dress is climbing up a set of stairs in an entry way .

你还会收到五个输出,但它们与我们目前的目标无关。我们可以看到,每张图片都是用逗号与它的特定声明分开。我们的目标是将这两个实体分开。但首先,让我们删除第一行,这是不必要的,同时确保文本文件的末尾没有空行。重命名并保存这个文件为captions1.txt。做这一步是为了确保,万一我们犯了错误,我们可以用最初的captions.txt重做这些步骤。一般来说,保存预处理数据的副本也是很好的做法。现在让我们来看看你的数据应该是什么样子:

# Delete the first line as well as the last empty line and then rename the file as captions1.txt

file = "captions1.txt"
info = load(file)
print(info[:470])

输出

1000268201_693b08cb0e.jpg,A child in a pink dress is climbing up a set of stairs in an entry way .

在我们的下一步,我们将创建一个字典来存储图像文件和相应的标题。我们知道,每张图片有五个不同的标题可供选择。因此,我们将把.jpg图像定义为键,其各自的五个标题代表值。我们将适当地分割这些值,并将它们存储在一个字典中。下面的函数可以写成这样。请注意,我们将所有这些信息存储在数据变量中:

# Creating a function to create a dictionary for the images and their respective captions

def load_captions(info):
    dict_1 = dict()
    count = 0
    for line in info.split('\n'):
        
        splitter = line.split('.jpg,')
#         print(splitter)
        # The image_code and image_captions are the list of the images with their respective captions
        image_code, image_caption = splitter[0], splitter[1]
        
        # Create dictionary
        if image_code not in dict_1:
            dict_1[image_code] = list()
            
        dict_1[image_code].append(image_caption)
        
    return dict_1

data = load_captions(info)
print(len(data))

你可以通过下面几行查看字典的键和值:

list(data.keys())[:5]
['1000268201_693b08cb0e', '1001773457_577c3a7d70', '1002674143_1b742ab4b8', '1003163366_44323f5815', '1007129816_e794419615']
data['1000268201_693b08cb0e']
['A child in a pink dress is climbing up a set of stairs in an entry way .',
 'A girl going into a wooden building .',
 'A little girl climbing into a wooden playhouse .',
 'A little girl climbing the stairs to her playhouse .',
 'A little girl in a pink dress going into a wooden cabin .']

我们的下一步是对数据集进行进一步的预处理,通过做一些必要的修改来准备字幕数据。我们将确保每个句子中的所有单词都被转换为小写字母,因为我们不希望在计算问题的过程中,同一个单词被存储为两个不同的向量。我们还将删除长度小于2的单词,确保删除无关的字符,如单字母和标点符号。完成这一任务的函数和代码写如下:

# Cleanse and pre-process the data

def cleanse_data(data):
    dict_2 = dict()
    for key, value in data.items():
        for i in range(len(value)):
            lines = ""
            line1 = value[i]
            for j in line1.split():
                if len(j) < 2:
                    continue
                j = j.lower()
                lines += j + " "
            if key not in dict_2:
                dict_2[key] = list()
            
            dict_2[key].append(lines)
            
    return dict_2

data2 = cleanse_data(data)
print(len(data2))    

请注意,我们将所有这些基本的清理过的信息存储在data2变量中,在整个编程部分的其余部分中,需要时将会用到该变量。

更新词汇和数据

一旦我们完成了文本文件中图像标题的清理和预处理,我们就可以着手创建一个词汇表,并计算出存在的独特实体的总数量。我们将在完成建立模型架构后,更新用于计算和预测的独特词汇。让我们看一下执行以下任务的代码块:

# convert the following into a vocabulary of words and calculate the total words

def vocabulary(data2):
    all_desc = set()
    for key in data2.keys():
        [all_desc.update(d.split()) for d in data2[key]]
    return all_desc

# summarize vocabulary
vocabulary_data = vocabulary(data2)
print(len(vocabulary_data))

最后,在完成所有这些基本步骤后,我们现在也可以用以下代码更新我们的captions1.txt文件:

# save descriptions to file, one per line

def save_dict(data2, filename):
    lines = list()
    for key, value in data2.items():
        for desc in value:
            lines.append(key + ' ' + desc)
    data = '\n'.join(lines)
    file = open(filename, 'w')
    file.write(data)
    file.close()

save_dict(data2, 'captions1.txt')

你的文本文件和存储的数据应该看起来与下面显示的行相似:

1000268201_693b08cb0e child in pink dress is climbing up set of stairs in an entry way 
1000268201_693b08cb0e girl going into wooden building 

随着文本数据的初步预处理的完成,让我们进入下一个步骤,构建我们的模型架构,以预测相应图像的适当标题。


对图像进行预处理

现在我们已经完成了文本描述的预处理,让我们继续对图像进行相应的预处理。我们将利用预处理功能,加载每张图片的图像路径,以适当地执行这一动作。让我们先看一下代码,然后了解我们的下一步行动。另外,请注意,在文章的其余部分,我们将利用以下两个参考资料来简化全部操作的图像标题任务。我们将使用TensorFlow的官方文档"Image Captioning with Visual Attention "和以下GitHub参考链接来成功计算以下任务。现在让我们继续看一下将对我们的图像进行相应预处理的代码块:

images = 'Images/'
img = glob(images + '*.jpg')
print(len(img))

def preprocess(image_path):
    # Convert all the images to size 299x299 as expected by the inception v3 model
    img = keras.preprocessing.image.load_img(image_path, target_size=(299, 299))
    # Convert PIL image to numpy array of 3-dimensions
    x = keras.preprocessing.image.img_to_array(img)
    # Add one more dimension
    x = np.expand_dims(x, axis=0)
    # pre-process the images using preprocess_input() from inception module
    x = keras.applications.inception_v3.preprocess_input(x)
    return x

这一步是最关键的步骤之一,因为我们将利用Inception V3转移学习模型将各自的图像转换为其固定的向量大小。这个自动特征提取过程涉及到排除最终的SoftMax激活函数到2096个向量特征的瓶颈。该模型利用预先训练好的图像网的权重,相对轻松地实现了以下任务的计算。众所周知,Inception V3模型在有图像的数据集上表现更好。然而,我也建议尝试其他转移学习模型,看看它们在同一问题上的表现:

# Load the inception v3 model
input1 = InceptionV3(weights='imagenet')

# Create a new model, by removing the last layer (output layer) from the inception v3
model = Model(input1.input, input1.layers[-2].output)

model.summary()

一旦我们完成了对图像的预处理的计算,我们将把所有这些值保存在一个pickle文件中。将这些文件作为单独的元素保存,将有助于我们在预测过程中分别利用这些模型。这个过程可以通过以下代码块来完成:

# Function to encode a given image into a vector of size (2048, )
def encode(image):
    image = preprocess(image) # preprocess the image
    fea_vec = model.predict(image) # Get the encoding vector for the image
    fea_vec = np.reshape(fea_vec, fea_vec.shape[1]) # reshape from (1, 2048) to (2048, )
    return fea_vec
    
 encoding = {}

for i in tqdm(img):
    encoding[i[len(images):]] = encode(i)
    
import pickle

# Save the features in the images1 pickle file
with open("images1.pkl", "wb") as encoded_pickle:
    pickle.dump(encoding, encoded_pickle)

创建我们的数据加载器

在我们创建数据加载器和数据生成器之前,让我们保存所有的基本文件,其中包含要索引的独特单词的信息,以及与各自单词相关的所有索引。执行这一操作对于模型的生成器和预测阶段都是至关重要的。我们还将为这个操作定义一个固定的最大长度,以便不超过我们设定的限制。整个过程包括将这些必要的文件保存在pickle文件中,可以按以下方式进行:

# Create a list of all the training captions

all_train_captions = []

for key, val in data2.items():
    for cap in val:
        all_train_captions.append(cap)
        
len(all_train_captions)

# Consider only words which occur at least 10 times in the corpus

word_count_threshold = 10
word_counts = {}
nsents = 0

for sent in all_train_captions:
    nsents += 1
    for w in sent.split(' '):
        word_counts[w] = word_counts.get(w, 0) + 1

vocab = [w for w in word_counts if word_counts[w] >= word_count_threshold]
print('preprocessed words %d -> %d' % (len(word_counts), len(vocab)))

# Converting the words to indices and vice versa.

ixtoword = {}
wordtoix = {}

ix = 1
for w in vocab:
    wordtoix[w] = ix
    ixtoword[ix] = w
    ix += 1
    
vocab_size = len(ixtoword) + 1 # one for appended 0's
vocab_size

# Save the words to index and index to word as pickle files

with open("words.pkl", "wb") as encoded_pickle:
    pickle.dump(wordtoix, encoded_pickle)
    
with open("words1.pkl", "wb") as encoded_pickle:
    pickle.dump(ixtoword, encoded_pickle)
    
# convert a dictionary of clean descriptions to a list of descriptions

def to_lines(descriptions):
    all_desc = list()
    for key in descriptions.keys():
        [all_desc.append(d) for d in descriptions[key]]
    return all_desc

# calculate the length of the description with the most words
def max_length(descriptions):
    lines = to_lines(descriptions)
    return max(len(d.split()) for d in lines)

# determine the maximum sequence length
max_length = max_length(data2)
print('Description Length: %d' % max_length)

建立生成器

我们的最后一步是创建数据生成器,它将加载图像向量的全部信息、模型的各自描述、最大长度、固定的词到索引值、每个历时运行的步骤数以及所有其他基本操作。每张图像都被转换为其各自的特征长度,所有的描述都被相应地填充,这样当通过LSTM类型的结构时,我们可以对未来的词进行预测。TensorFlow文档的方法与序列到序列的注意力建模,可以更好地获得更高的准确性,并通过tf.data()方法进行更好的训练。然而,我们将使用一个稍微简单的方法来解决这个问题,以达到更快的效果:

# data generator, intended to be used in a call to model.fit_generator()

def data_generator(descriptions, photos, wordtoix, max_length, num_photos_per_batch):
    X1, X2, y = list(), list(), list()
    n=0
    # loop for ever over images
    while 1:
        for key, desc_list in descriptions.items():
            n+=1
            # retrieve the photo feature
            photo = photos[key+'.jpg']
            for desc in desc_list:
                # encode the sequence
                seq = [wordtoix[word] for word in desc.split(' ') if word in wordtoix]
                # split one sequence into multiple X, y pairs
                for i in range(1, len(seq)):
                    # split into input and output pair
                    in_seq, out_seq = seq[:i], seq[i]
                    # pad input sequence
                    in_seq = pad_sequences([in_seq], maxlen=max_length)[0]
                    # encode output sequence
                    out_seq = to_categorical([out_seq], num_classes=vocab_size)[0]
                    # store
                    X1.append(photo)
                    X2.append(in_seq)
                    y.append(out_seq)
            # yield the batch data
            if n==num_photos_per_batch:
                yield ([array(X1), array(X2)], array(y))
                X1, X2, y = list(), list(), list()
                n=0

随着训练生成器的构建,适当地加载我们的数值,我们可以进行最后一步,即构建模型架构,以完成图像字幕的任务。


构建最终的模型架构

现在我们已经完成了预处理标题标签、文本描述和图像数据的大部分单独步骤,我们终于可以着手构建模型了。然而,在我们构建模型之前,我们需要为每个固定长度的独特单词创建单词嵌入向量。词嵌入方法是用来将每一个词的索引映射到一个200长度的向量,使其成为一个大小均匀的分布。请从这个链接中下载以下手套向量。预先训练好的glove.6B.200d.txt被用于这个模型,为所有独特的词创建一个均匀分布的200长度的嵌入矩阵。下面的代码块执行了所需的操作:

# Download, install, and then Load the Glove vectors
# https://github.com/stanfordnlp/GloVe

embeddings_index = {} # empty dictionary
f = open('glove.6B.200d.txt', encoding="utf-8")

for line in f:
    values = line.split()
    word = values[0]
    coefs = np.asarray(values[1:], dtype='float32')
    embeddings_index[word] = coefs
f.close()
print('Found %s word vectors.' % len(embeddings_index))

embedding_dim = 200

# Get 200-dim dense vector for each of the 10000 words in out vocabulary
embedding_matrix = np.zeros((vocab_size, embedding_dim))

for word, i in wordtoix.items():
    #if i < max_words:
    embedding_vector = embeddings_index.get(word)
    if embedding_vector is not None:
        # Words not found in the embedding index will be all zeros,
        embedding_matrix[i] = embedding_vector
        
embedding_matrix.shape

输出

Found 400001 word vectors.
(1976, 200)

最后,让我们定义LSTM和嵌入层来创建我们的模型架构。对于这个模型,我们将利用两个输入,即图像向量和字幕的词嵌入,来进行预测。嵌入向量通过一个LSTM架构,该架构将学习如何进行适当的单词预测。图像向量和LSTM预测被合并为一个单元,并通过一些密集层和一个SoftMax激活函数,这相当于预先定义的词汇量的大小。最后,我们将构建这个模型并开始我们的训练过程。

from tensorflow.keras.layers import Dense, Input, Conv2D, MaxPool2D, LSTM, add
from tensorflow.keras.layers import Activation, Dropout, Flatten, Embedding
from tensorflow.keras.models import Model

inputs1 = Input(shape=(2048,))
fe1 = Dropout(0.5)(inputs1)
fe2 = Dense(256, activation='relu')(fe1)

inputs2 = Input(shape=(max_length,))
se1 = Embedding(vocab_size, embedding_dim, mask_zero=True)(inputs2)
se2 = Dropout(0.5)(se1)
se3 = LSTM(256)(se2)

decoder1 = add([fe2, se3])
decoder2 = Dense(256, activation='relu')(decoder1)
outputs = Dense(vocab_size, activation='softmax')(decoder2)

model = Model(inputs=[inputs1, inputs2], outputs=outputs)
model.summary()
Model: "model_1"
__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
input_3 (InputLayer)            [(None, 33)]         0                                            
__________________________________________________________________________________________________
input_2 (InputLayer)            [(None, 2048)]       0                                            
__________________________________________________________________________________________________
embedding (Embedding)           (None, 33, 200)      395200      input_3[0][0]                    
__________________________________________________________________________________________________
dropout (Dropout)               (None, 2048)         0           input_2[0][0]                    
__________________________________________________________________________________________________
dropout_1 (Dropout)             (None, 33, 200)      0           embedding[0][0]                  
__________________________________________________________________________________________________
dense (Dense)                   (None, 256)          524544      dropout[0][0]                    
__________________________________________________________________________________________________
lstm (LSTM)                     (None, 256)          467968      dropout_1[0][0]                  
__________________________________________________________________________________________________
add (Add)                       (None, 256)          0           dense[0][0]                      
                                                                 lstm[0][0]                       
__________________________________________________________________________________________________
dense_1 (Dense)                 (None, 256)          65792       add[0][0]                        
__________________________________________________________________________________________________
dense_2 (Dense)                 (None, 1976)         507832      dense_1[0][0]                    
==================================================================================================
Total params: 1,961,336
Trainable params: 1,961,336
Non-trainable params: 0
__________________________________________________________________________________________________

现在我们已经完成了整体模型架构的构建,让我们继续用分类交叉熵损失函数和亚当优化器来编译这个模型。由于时间限制,我将对模型进行大约10个epochs的训练。然而,你可以在你方便的时候选择探索和训练更多的epochs数量:

model.layers[2].set_weights([embedding_matrix])
model.layers[2].trainable = False

model.compile(loss='categorical_crossentropy', optimizer='adam')

epochs = 10
number_pics_per_bath = 3
steps = len(data2)//number_pics_per_bath

features = pickle.load(open("images1.pkl", "rb"))

# https://stackoverflow.com/questions/58352326/running-the-tensorflow-2-0-code-gives-valueerror-tf-function-decorated-functio

tf.config.run_functions_eagerly(True)

for i in range(epochs):
    generator = data_generator(data2, features, wordtoix, max_length, number_pics_per_bath)
    model.fit(generator, epochs=1, steps_per_epoch=steps, verbose=1)
    model.save('model_' + str(i) + '.h5')

输出

2697/2697 [==============================] - 986s 365ms/step - loss: 4.5665
2697/2697 [==============================] - 983s 365ms/step - loss: 3.4063
2697/2697 [==============================] - 987s 366ms/step - loss: 3.1929
2697/2697 [==============================] - 987s 366ms/step - loss: 3.0570
2697/2697 [==============================] - 989s 366ms/step - loss: 2.9662
2697/2697 [==============================] - 995s 369ms/step - loss: 2.9011
2697/2697 [==============================] - 989s 367ms/step - loss: 2.8475
2697/2697 [==============================] - 986s 366ms/step - loss: 2.8062
2697/2697 [==============================] - 986s 366ms/step - loss: 2.7689
2697/2697 [==============================] - 987s 365ms/step - loss: 2.7409

我们可以注意到,只用了十个epochs的训练,我们的损失已经从最初的数值中明显减少。我只训练了10个历时的模型,因为你可以注意到每个历时在我的GPU上运行大约需要1000秒。如果你有一个计算速度更快的GPU或更多的时间,你可以训练更多的epochs模型,以达到一个更好的整体结果。


进行预测

在进行适当的预测时,我们将利用一个单独的笔记本。这样做是为了证明所有保存的文件和模型都可以独立使用,以便对给定的图像数据做出最精确的预测。让我们首先导入所有必要的库,并开始执行这一特定任务。

导入所需的库

这些是你为定义预测功能必须导入的一些基本库:

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.models import load_model
from tensorflow.keras.preprocessing.sequence import pad_sequences
import numpy as np
import matplotlib.pyplot as plt
import pickle

加载各自的特征。

让我们把所有保存的文件加载到各自的变量中。这个过程是按以下方式进行的:

features = pickle.load(open("images1.pkl", "rb"))
model = load_model('model_9.h5')
images = "Images/"
max_length = 33
words_to_index = pickle.load(open("words.pkl", "rb"))
index_to_words = pickle.load(open("words1.pkl", "rb"))

进行预测

最后,让我们定义相应的函数,该函数将接收图像,加载其向量,创建单词嵌入,并利用保存的模型进行适当的预测。让我们在两张图片上随机测试,并将它们各自的结果可视化:

def Image_Caption(picture):
    in_text = 'startseq'
    for i in range(max_length):
        sequence = [words_to_index[w] for w in in_text.split() if w in words_to_index]
        sequence = pad_sequences([sequence], maxlen=max_length)
        yhat = model.predict([picture,sequence], verbose=0)
        yhat = np.argmax(yhat)
        word = index_to_words[yhat]
        in_text += ' ' + word
        if word == 'endseq':
            break
    final = in_text.split()
    final = final[1:-1]
    final = ' '.join(final)
    return final

我将随机选择一张图片,我们将尝试将我们的模型对几张图片产生的输出可视化:

z = 20
pic = list(features.keys())[z]
image = features[pic].reshape((1,2048))
x = plt.imread(images+pic)
plt.imshow(x)
plt.show()
print("Caption:", Image_Caption(image))
z = 100
pic = list(features.keys())[z]
image = features[pic].reshape((1,2048))
x = plt.imread(images+pic)
plt.imshow(x)
plt.show()
print("Caption:", Image_Caption(image))

我们可以注意到,对于仅仅10个 epochs的训练和一个小的数据集来说,我们的图像说明模型的性能是相当好的。虽然图像标题任务的效果相当不错,但值得注意的是,可以进一步减少损失,以达到更高的准确性和精确度。可以做的两个主要改变和改进是增加数据集的大小和在当前模型上运行以下计算,以获得更多的历时。为了取得更好的结果,也可以使用带注意力的序列到序列模型。也可以对不同类型的转移学习模型进行其他实验。


结语

在这个用Keras和TensorFlow生成图片标题的演示中,我们展示了如何使用先前从Flickr数据集中识别的文本标题数据,以产生新的标题。在这个过程中包括了加载相关库的步骤,对图像和文本数据的预处理,创建数据加载器和生成器,以及最后构建一个LSTM架构来生成预测的标题。用户可以通过使用Gradient-AI的GitHub上的公共笔记本 ,进一步了解细节,并尝试重新创建这项工作

今天就为你的机器学习工作流程增加速度和简单性吧