重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
目录:
创新互联建站长期为上1000家客户提供的网站建设服务,团队从业经验10年,关注不同地域、不同群体,并针对不同对象提供差异化的产品和服务;打造开放共赢平台,与合作伙伴共同营造健康的互联网生态环境。为牙克石企业提供专业的网站设计、成都网站制作,牙克石网站改版等技术服务。拥有十多年丰富建站经验和众多成功案例,为您定制开发。介绍
动机
先决条件
数据采集
了解数据
数据清理
加载训练集
数据预处理- 图像
数据预处理- 字幕
使用生成器函数准备数据
Word嵌入
模型架构
推理
评估
结论和未来的工作
参考
1.简介
你在下面的图片中看到了什么?
你能写一个标题吗?
你们当中有些人可能会说“一只白色的狗趴在草地上”,有些人可能会说“有一只带有褐色斑点的白狗”,还有一些人可能会说“狗在草地上还有一些粉红色的花朵”。
绝对所有这些标题都与此图像相关,也可能还有其他一些标题。但我想说的是,对于我们人类来说,看一眼照片就能够用适当的语言来描述它,这很容易。即使是5岁的孩子也可以轻松地做到这一点。
但是,你能编写一个计算机程序,将图像作为输入并产生相关的标题作为输出吗?
简单的架构
在深度神经网络发展之前,即使是计算机视觉领域最先进的研究人员,这个问题也是很难想象的。但随着深度学习的出现,如果您拥有所需的数据集,则可以非常轻松地解决此问题。
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
即图像名称,标题号(0到4)和实际标题。
现在,我们创建一个名为“描述”的字典,其中包含图像的名称(不带.jpg扩展名)作为键,以及相应图像的5个标题列表作为值。
例如,参考上面的截图,字典将如下所示:
6.数据清理
当我们处理文本时,我们通常会执行一些基本的清理工作,例如低层包装所有单词(否则“hello”和“Hello”将被视为两个单独的单词),删除特殊标记(如'%','$','#'等),消除包含数字的单词(如'hey199'等)。
以下代码执行以下基本清理步骤:
在数据集中8000*5(即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/