你不才面的图片中看到了什么?

你能写一个标题吗?

你们当中有些人可能会说“一只白色的狗趴在草地上”,有些人可能会说“有一只带有褐色斑点的白狗”,还有一些人可能会说“狗在草地上还有一些粉赤色的花朵”。

绝对所有这些标题都与此图像干系,也可能还有其他一些标题。
但我想说的是,对付我们人类来说,看一眼照片就能够用适当的措辞来描述它,这很随意马虎。
纵然是5岁的孩子也可以轻松地做到这一点。

我用Keras 教电脑为图像生成标题

但是,你能编写一个打算机程序,将图像作为输入并产生干系的标题作为输出吗?

大略的架构

在深度神经网络发展之前,纵然是打算机视觉领域最前辈的研究职员,这个问题也是很难想象的。
但随着深度学习的涌现,如果您拥有所需的数据集,则可以非常轻松地办理此问题。

Andrej Karapathy在斯坦福大学的博士论文中对这个问题进行了很好的研究[1],他现在也是特斯拉的AI主任。

这篇文章的目的是阐明(尽可能大略的话)深度学习如何用于办理为给定图像天生标题的问题,因此名称为Image Captioning。

为了更好地理解这个问题,我强烈建议利用Microsoft创建的这个别系,称为Caption Bot。
只需转到此链接并考试测验上传您想要的任何图片;这个别系会为它天生一个标题。
(https://www.captionbot.ai/)

2.动机

我们必须首先理解这个问题对现实天下场景的主要性。
在这些运用程序中,它的问题的办理方案可能非常有用。

自动驾驶汽车- 自动驾驶是最大的寻衅之一,如果我们可以适当地描述汽车周围的场景,它可以提升自动驾驶系统。
对盲人的帮助- 我们可以为盲人创造一种产品,勾引他们在没有其他人支持的情形下在公路上行驶。
我们可以通过首先将场景转换为文本然后将文本转换为语音来实现此目的。
两者现在都是深度学习的著名运用。
在本日闭路电视摄像机无处不在,但在不雅观看这个天下的同时,如果我们还可以天生干系字幕,那么一旦某些恶意活动发生,我们就可以立即发出警报。
这可能有助于减少一些犯罪和/或事件。
自动字幕可以帮助使Google图片搜索与Google搜索一样好用,由于每个图片都可以先转换为标题,然后根据标题实行搜索。

3.先决条件

本文假设您熟习基本的深度学习观点,如多层感知器、卷积神经网络、递归神经网络、迁移学习、梯度低落、过度拟合、概率、文本处理、Python语法和数据构造、Keras库等。

4.数据网络

有很多开源数据集可用于此问题,如Flickr 8k(包含8k图像),Flickr 30k(包含30k图像),MS COCO(包含180k图像)等。

但是为了本案例研究的目的,我利用了Flickr 8k数据集(https://forms.illinois.edu/sec/1713398)在不是非常高真个PC /条记本电脑的系统上,演习具有大量图像的模型可能是不可行的。

该数据集包含8000个图像,每个图像有5个字幕(正如我们在“简介”部分中已经看到的那样,图像可以有多个字幕,所有字幕都是同时干系的)。

这些图像分叉如下:

演习集- 6000图像Dev Set - 1000张图片测试集- 1000张图像

5.理解数据

如果您从我供应的链接下载了数据,那么,与图像一起,您还将得到一些与图像干系的文本文件。
个中一个文件是“Flickr8k.token.txt”,个中包含每个图像的名称及其5个标题。
我们可以按如下办法阅读此文件:

文本文件如下:

因此,每行包含<图像名称> #i <caption>,个中0≤i≤4

即图像名称,标题号(0到4)和实际标题。

现在,我们创建一个名为“描述”的字典,个中包含图像的名称(不带.jpg扩展名)作为键,以及相应图像的5个标题列表作为值。

例如,参考上面的截图,字典将如下所示:

6.数据清理

当我们处理文本时,我们常日会实行一些基本的清理事情,例如低层包装所有单词(否则“hello”和“Hello”将被视为两个单独的单词),删除分外标记(如'%','$','#'等),肃清包含数字的单词(如'hey199'等)。

以下代码实行以下基本清理步骤:

在数据集中80005(即40000)图像字幕(语料库)中创建所有独特单词的词汇表:

这意味着我们在所有40000个图像标题中都有8763个独特的单词。
我们将所有这些标题及其图像名称写在一个新文件中,即“descript.txt”并将其保存在磁盘上。

但是,如果我们仔细考虑一下,个中的很多这些词只会涌现很少次,比如1、2或3次。
由于我们正在创建一个预测模型,我们不肯望我们的词汇表中包含所有单词,而是更随意马虎涌现更常见的单词。
这有助于模型对非常值变得更加稳健并减少缺点。

因此,我们只考虑在全体语料库中涌现至少10次的那些词。
代码如下:

以是现在我们的词汇表中只有1651个独特的单词

7.加载演习集

文本文件“Flickr_8k.trainImages.txt”包含属于演习集的图像的名称。
以是我们将这些名称加载到列表“train”中。

因此,我们将名为“train”的列表中的6000个演习图像分开。

现在我们从Python词典“train_descriptions”中的“descriptions.txt”(保存在硬盘上)加载这些图像的描述。

但是,当我们加载它们时,我们将在每个标题中添加两个标记,如下所示(稍后阐明):

'startseq' - >这是一个开始序列标记,将在每个标题的开头添加。

'endseq' - >这是一个结束序列标记,将在每个标题的末端添加。

8.数据预处理- 图像

图像只是我们模型的输入(X)。
您可能已经知道,模型的任何输入都必须以向量的形式给出。

我们须要将每个图像转换为固定大小的矢量,然后将其作为输入馈送到神经网络。
为此,我们利用Google Research创建的InceptionV3模型(卷积神经网络)选择迁移学习。

该模型在Imagenet数据集上进行演习,以对1000种不同类别的图像进行图像分类。
但是,我们的目的不是对图像进行分类,而是为每个图像获取固定长度的信息矢量。
此过程称为自动特色工程。

我们只是从模型中删除末了一个softmax图层,并为每个图像提取2048长度向量(瓶颈特色),如下所示:

特色向量提取(特色工程)

代码如下:

现在我们将每个图像通报给该模型以得到相应的2048长度特色向量,如下所示:

我们将所有瓶颈演习功能保存在Python字典中,并利用Pickle文件将其保存在磁盘上,即“encoded_train_images.pkl”,其键是图像名称,值是对应的2048长度特色向量。

把稳:如果您没有高端PC /条记本电脑,此过程可能须要一到两个小时。

类似地,我们编码所有测试图像并将它们保存在文件“encoded_test_images.pkl”中。

9.数据预处理- 字幕

我们必须把稳,字幕是我们想要预测的。
因此,在演习期间,标题将是模型正在学习预测的目标变量(Y)。

但对全体标题的预测是不可能同时发生的,我们将逐字预测字幕。
因此,我们须要将每个单词编码为固定大小的向量。
然而,这部分将在后面看到模型设计时看到,但是现在我们将创建两个Python词典,即“wordtoix”(发音为- word to index)和“ixtoword”(发音为- index to word)。

大略地说,我们将用整数(索引)表示词汇表中的每个唯一单词。
如上所示,我们在语料库中有1652个唯一的单词,因此每个单词将由1到1652之间的整数索引表示。

这两个Python字典可以利用如下:

wordtoix ['abc'] - >返回单词'abc'的索引

ixtoword [k] - >返回索引为'k'的单词

利用的代码如下:

还有一个我们须要打算的参数,即标题的最大长度,我们这样做如下:

以是任何标题的最大长度是34。

10.利用天生器函数准备数据

这是本案例研究中最主要的步骤之一。
在这里,我们将理解如何以便于作为深度学习模型的输入的办法准备数据。

从现在开始,我将考试测验通过以下示例阐明剩余的步骤:

考虑我们有3个图像及其3个相应的标题如下:

Caption_1 - >黑猫坐在草地上

Caption_2 - >白猫正在路上行走

Caption_3 - >黑猫正走在草地上闲步

现在,假设我们将利用前两个图像及其标题来演习模型,我们将利用第三个图像来测试我们的模型。

现在我们要回答的问题是:我们如何将其构建为监督学习问题?数据矩阵是什么样的?我们有多少数据点?等

首先,我们须要将两个图像转换为它们对应的2048长度特色向量,如上所述。
设“Image_1”和“Image_2”分别为前两个图像的特色向量

其次,让我们通过在两者中添加两个标记“startseq”和“endseq”来构建前两个(列车)字幕的词汇表:(假设我们已经实行了基本的清理步骤)

Caption_1 - >“startseq黑猫坐在草地上endseq”

Caption_2 - >“startseq白猫正在路上行走”

vocab = {black,cat,endseq,grass,is,on,road,sat,startseq,the,walking,white}

让我们给出词汇表中每个单词的索引:

玄色-1,猫-2,endseq -3,草-4,是-5,在-6,道路-7,坐-8,startseq -9,-10,行走-11,白色-12

现在让我们考试测验将其构建为监督学习问题,个中我们有一组数据点D = {Xi,Yi},个中Xi是数据点'i'的特色向量,Yi是对应的目标变量。

让我们拍摄第一张图像矢量Image_1及其相应的标题“startseq,黑猫坐在草地上”。
回忆一下,Image vector是输入,标题是我们须要预测的。
但我们预测标题的办法如下:

我们第一次供应图像矢量和第一个单词作为输入,并考试测验预测第二个单词,即:

输入= Image_1 +'startseq';输出='the'

然后我们供应图像矢量和前两个单词作为输入并考试测验预测第三个单词,即:

输入= Image_1 +'startseq';输出='猫'

等等…

因此,我们可以总结一个图像的数据矩阵及其相应的标题如下:

对应于一个图像及其标题的数据点

必须把稳的是,一个图像+标题不是单个数据点,而是多个数据点,详细取决于标题的长度。

同样,如果我们同时考虑图像及其标题,我们的数据矩阵将如下所示:

图像和标题的数据矩阵

我们现在必须明白,在每个数据点中,不仅仅是作为系统输入的图像,还有一个部分字幕,它有助于预测序列中的下一个字。

由于我们正在处理序列,我们将利用循环神经网络来读取这些部分字幕(稍后将详细先容)。

但是,我们已经谈论过,我们不会通报标题的实际英文文本,而是我们将通报索引序列,个中每个索引代表一个唯一的单词。

由于我们已经为每个单词创建了一个索引,现在让我们用它们的索引更换单词,并理解数据矩阵将是什么样子:

用索引更换单词后的数据矩阵

由于我们将进行批处理(稍后阐明),我们须要确保每个序列的长度相等。
因此,我们须要在每个序列的末端追加0。
但是我们该当在每个序列中添加多少个零?

这便是我们打算标题最大长度为34的缘故原由(如果你还记得)。
因此,我们将附加许多零,这将导致每个序列的长度为34。

数据矩阵将如下所示:

在每个序列上附加零,使它们全长相同34

须要数据天生器:

我希望这能让您更好地理解我们如何为此问题准备数据集。
然而,这有一个很大的问题。
在上面的例子中,我只考虑了2个图像和标题,这些图像和标题导致了15个数据点。

但是,在我们的实际演习数据集中,我们有6000张图像,每张图像有5个字幕。
这使得统共30000个图像和标题。
纵然假设均匀每个字幕只有5个字长,也会导致总计30000 5,即150000个数据点。

我们再做一些打算:

每个数据点的长度是多少?

数据点的长度=图像矢量的长度+部分字幕的长度。

图像特色向量的长度= 2048(已经谈论过)

但部分字幕的长度是多少?

嗯,你可能认为它是34,但这是错的。

每个单词(或索引)将通过一种单词嵌入技能映射(嵌入)到更高维度的空间。

之后,在模型构建阶段,我们将看到每个单词/索引利用预先演习的GLOVE单词嵌入模型映射到200长的向量。

现在每个序列包含34个索引,个中每个索引是长度为200的向量。
因此,一个数据点的长度为:

2048 +(34 256)= 8848。

从最小的方面来看,我们至少可以得到150,000个数据点。
因此,数据矩阵的大小是:

150,000 10752 = 1327200000块。

现在,纵然我们假设一个块占用2个字节,然后,为了存储该数据矩阵,我们将须要靠近3 GB的主存储器。
(回忆一下,我们假设字幕的均匀长度为5个字,可能更多)。

这是非常大的哀求,纵然我们能够设法将这么多数据加载到RAM中,它也会使系统变得非常慢。

出于这个缘故原由,我们在深度学习中利用了很多数据天生器。
数据天生器是一种在Python中本机实现的功能。
Keras API供应的ImageDataGenerator类只不过是Python中天生器函数的实现。

那么利用天生器函数如何办理这个问题呢?

如果您理解深度学习的根本知识,那么您必须知道要在特天命据集上演习模型,我们须要利用某些版本的随机梯度低落(SGD),如Adam,Rmsprop,Adagrad等。

对付SGD,我们不打算全体数据集的丢失来更新梯度。
而是在每次迭代中,我们打算一批数据点(常日为64,128,256等)上的丢失以更新梯度。

这意味着我们不须要立即将全体数据集存储在内存中。
纵然我们在内存中有当前的一批点,但它足以达到我们的目的。

Python中的天生器函数完备用于此目的。
它就像一个迭代器,从末了一次调用它的位置规复功能。

数据天生器的代码如下:

11.字嵌入

如上所述,我们将把每个单词(索引)映射到一个200长的向量,为此,我们将利用预先演习好的GLOVE模型:

现在,对付我们词汇表中的所有1652个独特单词,创建了一个嵌入矩阵,该矩阵将在演习之前加载到模型中。

12.模型架构

由于输入由两部分组成,即图像矢量和部分字幕,因此我们无法利用Keras库供应的Sequential API。
出于这个缘故原由,我们利用Functional API,它许可我们创建合并模型。

首先让我们看一下包含高等子模块的简要架构:

高水平的架构

我们将模型定义如下:

让我们看看模型择要:

模型中的参数择要

下图有助于可视化网络构造并更好地理解两个输入流:

带有注释的架构图

右侧的玄色文本是为您供应的注释,用于将您对数据准备的理解映射到模型体系构造。

LSTM(是非期影象)层只是一个专门的回归神经网络来处理序列输入(在我们的例子中是部分字幕)。

如果您已按照上一节进行操作,我认为阅读这些注释可帮助您以直接的办法理解模型体系构造。

回忆一下,我们已经从预先演习的GLOVE模型中创建了一个嵌入矩阵,我们须要在开始演习之前将其包含在模型中:

请把稳,由于我们利用的是预先演习好的嵌入层,因此我们须要在演习模型之前将其冻结(trainable = False),以便在反向传播期间不会更新它。

末了,我们利用adam优化器编译模型

演习期间的超参数:

然后将该模型演习30个期间,初始学习率为0.001,每批3个图片(批量)。
然而,在20个期间之后,学习率降落到0.0001并且模型被演习为每批6张图片。

这常日是有道理的,由于在演习的后期阶段,模型正趋向收敛,我们必须降落学习率,以便我们朝着最小值迈出更小的步伐。
随着韶光的推移增加批量大小有助于您的梯度更新更强大。

韶光:我在www.paperspace.com上利用了GPU + Gradient Notebook,因此我花了大约一个小时演习模型。
但是,如果您在没有GPU的PC上进行演习,则可能须要8到16个小时,详细取决于您的系统配置。

13.推理

以是到目前为止我们已经看到了如何准备数据和构建模型。
在本系列的末了一步中,我们将理解如何通过传入新图像来测试(推断)我们的模型,即如何为新测试图像天生标题。

回忆一下,在我们看到如何准备数据的示例中,我们只利用了前两个图像及其标题。
现在让我们利用第三个图像,并考试测验理解我们希望如何天生标题。

第三个图像矢量和标题如下:

Image_3 - >黑猫正在草地上行走

此示例中的词汇还包括:

vocab = {black,cat,endseq,grass,is,on,road,sat,startseq,the,walking,white},具有以下索引:

玄色-1,猫-2,endseq -3,草-4,是-5,在-6,道路-7,坐-8,startseq -9,-10,行走-11,白色-12

我们将迭代天生标题,一次一个字如下:

迭代1:

我们供应图像矢量Image_3以及'startseq'作为模型的部分标题。
(您现在该当理解'startseq'的主要性,它在推理期间用作任何图像的初始部分标题)。

我们现在期望我们的模型预测第一个单词“the”。

但等等,该模型天生一个12长的向量(在示例中,而在原始示例中为1652长向量),这是词汇表中所有单词的概率分布。
出于这个缘故原由,我们贪婪地选择具有最大概率的单词,给定特色向量和部分标题。

如果模型演习得很好,我们必须期望单词“the”的概率最大:

推论1

这称为最大似然估计(MLE),即我们根据给定输入的模型选择最可能的单词。
有时这种方法也被称为贪婪搜索,由于我们贪婪地选择具有最大概率的单词。

迭代2:

这一次让我们假设模型已经预测了前一次迭代中的“the”。
以是现在我们将模型的输入作为图像矢量Image_3和部分标题“startseq the”。
现在我们期望模型在给定图像特色向量和部分字幕的情形下产生“玄色”一词的最高概率。

推论2

通过这种办法,我们连续迭代以天生序列中的下一个单词。
但这里的一个主要问题是我们什么时候停滞?

因此,当知足以下两个条件之一时,我们就会停滞:

我们碰着'endseq',这意味着模型认为这是标题的结尾。
(您现在该当理解'endseq'标记的主要性)我们达到模型天生的单词数的最大阈值。

如果知足上述任何条件,我们将冲破循环并将天生的标题报告为给定图像的模型输出。
推理代码如下:

14.评估

为了理解模型有多好,让我们考试测验在测试数据集的图像上天生标题(即模型在演习期间没有看到的图像)。

输出- 1

把稳:我们必须理解模型如何精确识别颜色。

输出- 2

输出- 3

输出- 4

输出- 5

当然,如果我只向你展示得当的字幕,那我便是在骗你。
天下上没有任何模型是完美的,这种模式也会犯缺点。
让我们看一些例子,个中标题不是很干系,有时乃至是不干系的。

输出 - 6

可能是衬衫的颜色与背景中的颜色稠浊在一起

输出- 7

为什么模特将著名的拉斐尔·纳达尔归为女性?可能是由于长发。

输出- 8

这次模型得到的语法禁绝确

输出- 9

很明显,该模型尽力理解情景,但标题仍旧不是很好。

输出- 10

再一个例子,模型失落败,标题无关紧要。

总而言之,我必须说,我的初始化模型,没有任何严格的超参数调度,在天生图像标题方面做得不错。

很主要的一点:

我们必须明白,用于测试的图像必须在语义上与用于演习模型的图像干系。
例如,如果我们在猫、狗等的图像上演习我们的模型,我们就不能在飞机、瀑布等图像上进行测试。
这是一个例子,火车和测试装置的分布会有很大不同,在这种情形下,天下上没有机器学习模型可以供应良好的性能。

15.结论和未来的事情

请参阅我的GitHub链接,以访问Jupyter Notebook中编写的完全代码。

(https://github.com/hlamba28/Automatic-Image-Captioning.git)

请把稳,由于模型的随机性,您天生的标题(如果您考试测验复制代码)可能与我的情形下天生的标题不完备相似。

当然,这只是第一个办理方案,可以进行大量修正以改进此办理方案,如:

利用更大的数据集。
改变模型架构,例如包括一个把稳模块。
进行更多超参数调度(学习率、批量大小、层数、单位数、辍学率等)。
利用交叉验证集来理解过度拟合。
在推理期间利用Beam Search而不是Greedy Search。
利用BLEU评分来评估和衡量模型的性能。
以适当的面向工具的办法编写代码,以便其他人更随意马虎复制:-)

16.参考文献

https://cs.stanford.edu/people/karpathy/cvpr2015.pdf

https://arxiv.org/abs/1411.4555

https://arxiv.org/abs/1703.09137

https://arxiv.org/abs/1708.02043

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

https://www.youtube.com/watch?v=yk6XDFm3J2c

https://www.appliedaicourse.com/