自动照片字幕是这样一种问题:模型必须在给定一张照片的情况下生成人类可读的文本描述。
这是人工智能领域中一个具有挑战性的问题,它同时需要计算机视觉领域的图像理解以及自然语言处理领域的语言生成。
现在,您可以使用深度学习以及免费提供的照片及其描述数据集来开发自己的图像字幕模型。
在本教程中,您将学习如何准备照片和文本描述,为开发深度学习自动照片字幕生成模型做好准备。
完成本教程后,您将了解:
- 关于 Flickr8K 数据集,该数据集包含超过 8,000 张照片,每张照片最多有 5 个字幕。
- 如何使用深度学习对照片和文本数据进行通用加载和准备。
- 如何在 Keras 中为两种不同类型的深度学习模型专门编码数据。
启动您的项目,阅读我的新书《深度学习自然语言处理》,其中包含分步教程和所有示例的Python源代码文件。
让我们开始吧。
- 更新于 2017 年 11 月:修复了“整段描述序列模型”部分代码中的一些小笔误。感谢 Moustapha Cheikh 和 Matthew。
- 更新于 2019 年 2 月:提供了 Flickr8k_Dataset 数据集的直接链接,因为官方网站已关闭。

如何准备用于训练深度学习模型的照片标题数据集
照片作者:beverlyislike,部分权利保留。
教程概述
本教程分为 9 个部分:
- 下载 Flickr8K 数据集
- 如何加载照片
- 预计算照片特征
- 如何加载描述
- 准备描述文本
- 整段描述序列模型
- 逐词模型
- 渐进式加载
- 预计算照片特征
Python 环境
本教程假定您已安装 Python 3 SciPy 环境。您可以使用 Python 2,但可能需要更改某些示例。
您必须安装 Keras(2.0 或更高版本),并使用 TensorFlow 或 Theano 后端。
本教程还假定您已安装 scikit-learn、Pandas、NumPy 和 Matplotlib。
如果您在环境方面需要帮助,请参阅此帖子
需要深度学习处理文本数据的帮助吗?
立即参加我的免费7天电子邮件速成课程(附代码)。
点击注册,同时获得该课程的免费PDF电子书版本。
下载 Flickr8K 数据集
用于开始图像字幕的不错数据集是 Flickr8K 数据集。
原因是它很真实且相对较小,因此您可以在工作站上下载它并使用 CPU 来构建模型。
该数据集的权威描述载于 2013 年的论文“将图像描述作为排序任务:数据、模型和评估指标”。
作者对该数据集的描述如下:
我们提出了一个用于基于句子的图像描述和搜索的新基准集合,该集合包含 8,000 张图像,每张图像都配有五个不同的字幕,提供了对显着实体和事件的清晰描述。
…
图像是从六个不同的 Flickr 群组中挑选出来的,并且倾向于不包含任何知名人士或地点,但经过手动选择以描绘各种场景和情况。
— 将图像描述作为排序任务:数据、模型和评估指标,2013。
该数据集是免费提供的。您必须填写一份申请表,然后数据集的链接将通过电子邮件发送给您。我很乐意为您提供链接,但电子邮件明确要求:“请勿重新分发数据集”。
您可以使用下面的链接申请数据集
短时间内,您将收到一封包含两个文件链接的电子邮件
- Flickr8k_Dataset.zip(1 GB)所有照片的存档。
- Flickr8k_text.zip(2.2 MB)照片所有文本描述的存档。
更新(2019 年 2 月):官方网站似乎已关闭(尽管表格仍然有效)。以下是我 datasets GitHub 存储库中的一些直接下载链接。
下载数据集并将其解压缩到当前工作目录。您将获得两个目录:
- Flicker8k_Dataset:包含 8092 张 jpeg 格式的照片。
- Flickr8k_text:包含大量文件,其中包含照片的不同来源的描述。
接下来,我们看看如何加载图像。
如何加载照片
在本节中,我们将开发一些代码,以加载照片供 Python 中 Keras 深度学习库使用。
图像文件名是唯一的图像标识符。例如,以下是一些文件名示例:
1 2 3 4 5 |
990890291_afc72be141.jpg 99171998_7cc800ceef.jpg 99679241_adc853a5c0.jpg 997338199_7343367d7f.jpg 997722733_0cb5439472.jpg |
Keras 提供了 `load_img()` 函数,可用于将图像文件直接加载为像素数组。
1 2 |
from keras.preprocessing.image import load_img image = load_img('990890291_afc72be141.jpg') |
像素数据需要转换为 NumPy 数组才能在 Keras 中使用。
我们可以使用 Keras 的 `img_to_array()` 函数来转换加载的数据。
1 2 |
from keras.preprocessing.image import img_to_array image = img_to_array(image) |
我们可能希望使用预定义的特征提取模型,例如在 ImageNet 上训练的先进深度图像分类网络。牛津视觉几何组(VGG)模型为此目的而流行,并且在 Keras 中可用。
牛津视觉几何组(VGG)模型为此目的而流行,并且在 Keras 中可用。
如果我们决定使用这个预训练模型作为我们模型中的特征提取器,我们可以使用 Keras 中的 `preprocess_input()` 函数来预处理模型所需的像素数据,例如:
1 2 3 4 5 6 |
from keras.applications.vgg16 import preprocess_input # 将数据重塑为单个图像样本 image = image.reshape((1, image.shape[0], image.shape[1], image.shape[2])) # 为VGG模型准备图像 image = preprocess_input(image) |
我们还可以强制加载照片时具有与 VGG 模型相同的像素尺寸,即 224 x 224 像素。我们可以在 `load_img()` 调用中做到这一点,例如:
1 |
image = load_img('990890291_afc72be141.jpg', target_size=(224, 224)) |
我们可能希望从图像文件名中提取唯一的图像标识符。我们可以通过按“.”(句点)字符拆分文件名字符串并取结果数组的第一个元素来做到这一点。
1 |
image_id = filename.split('.')[0] |
我们可以将所有这些整合起来,开发一个函数,该函数接受包含照片的目录名称,为 VGG 模型加载和预处理所有照片,并将它们以以唯一图像标识符为键的字典形式返回。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
from os import listdir from keras.preprocessing.image import load_img from keras.preprocessing.image import img_to_array from keras.applications.vgg16 import preprocess_input def load_photos(directory): images = dict() for name in listdir(directory): # 从文件中加载图像 filename = directory + '/' + name image = load_img(filename, target_size=(224, 224)) # 将图像像素转换为 numpy 数组 image = img_to_array(image) # 重塑数据以适应模型 image = image.reshape((1, image.shape[0], image.shape[1], image.shape[2])) # 准备图像以适应 VGG 模型 image = preprocess_input(image) # 获取图像 id image_id = name.split('.')[0] images[image_id] = image return images # 加载图像 directory = 'Flicker8k_Dataset' images = load_photos(directory) print('Loaded Images: %d' % len(images)) |
运行此示例将打印加载的图像数量。运行需要几分钟时间。
1 |
Loaded Images: 8091 |
如果您没有足够的 RAM 来容纳所有图像(据我估计约 5GB),则可以添加一个 if 语句,在加载 100 张图像后提前中断循环,例如:
1 2 |
if (len(images) >= 100): break |
预计算照片特征
可以使用预训练模型从数据集中提取照片特征并将特征存储到文件中。
这是一种效率提升,意味着将照片特征转换为文本描述的模型语言部分可以独立于特征提取模型进行训练。这样做的好处是,无需在训练语言模型时加载、内存占用和使用非常大的预训练模型来处理每张照片。
之后,可以将特征提取模型和语言模型重新组合起来,以便对新照片进行预测。
在本节中,我们将扩展上一节开发的照片加载功能,以加载所有照片,使用预训练的 VGG 模型提取它们的特征,并将提取的特征存储到新文件中,该文件可以加载并用于训练语言模型。
第一步是加载 VGG 模型。此模型直接在 Keras 中提供,可以如下加载。请注意,这会将 500MB 的模型权重下载到您的计算机,可能需要几分钟。
1 2 3 4 5 |
from keras.applications.vgg16 import VGG16 # 加载模型 in_layer = Input(shape=(224, 224, 3)) model = VGG16(include_top=False, input_tensor=in_layer, pooling='avg') print(model.summary()) |
这将加载 VGG 16 层模型。
通过设置 `include_top=False`,移除了两个全连接输出层以及分类输出层。从最后一个池化层的输出来获取从图像提取的特征。
接下来,我们可以像上一节一样遍历图像目录中的所有图像,并为每个准备好的图像调用模型的 `predict()` 函数以获取提取的特征。然后可以将这些特征存储在以图像 ID 为键的字典中。
完整的示例如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
from os import listdir from pickle import dump from keras.applications.vgg16 import VGG16 from keras.preprocessing.image import load_img from keras.preprocessing.image import img_to_array from keras.applications.vgg16 import preprocess_input from keras.layers import Input # 从目录中的每张照片中提取特征 def extract_features(directory): # 加载模型 in_layer = Input(shape=(224, 224, 3)) model = VGG16(include_top=False, input_tensor=in_layer) print(model.summary()) # 从每张照片中提取特征 features = dict() for name in listdir(directory): # 从文件中加载图像 filename = directory + '/' + name image = load_img(filename, target_size=(224, 224)) # 将图像像素转换为 numpy 数组 image = img_to_array(image) # 重塑数据以适应模型 image = image.reshape((1, image.shape[0], image.shape[1], image.shape[2])) # 准备图像以适应 VGG 模型 image = preprocess_input(image) # 获取特征 feature = model.predict(image, verbose=0) # 获取图像 id image_id = name.split('.')[0] # 存储特征 features[image_id] = feature print('>%s' % name) return features # 从所有图像中提取特征 directory = 'Flicker8k_Dataset' features = extract_features(directory) print('Extracted Features: %d' % len(features)) # 保存到文件 dump(features, open('features.pkl', 'wb')) |
这个例子可能需要一些时间才能完成,可能需要一个小时。
提取完所有特征后,该字典将存储在当前工作目录中的文件 ‘features.pkl‘ 中。
这些特征稍后可以加载并用作训练语言模型的输入。
您可以尝试 Keras 中的其他类型的预训练模型。
如何加载描述
花点时间谈谈描述很重要;有许多可用的。
文件 `Flickr8k.token.txt` 包含图像标识符(用于图像文件名)和分词描述的列表。每张图像都有多个描述。
下面是文件中的一个示例,显示了同一图像的 5 个不同描述。
1 2 3 4 5 |
1305564994_00513f9a5b.jpg#0 一个穿着街头赛车服的男人正在检查另一名赛车手的摩托车的轮胎。 1305564994_00513f9a5b.jpg#1 两名赛车手驾驶一辆白色自行车在路上行驶。 1305564994_00513f9a5b.jpg#2 两名车手骑着他们的车,这些车设计和颜色都很奇特。 1305564994_00513f9a5b.jpg#3 两名赛车手在一辆小型赛车中,在绿色山坡旁行驶。 1305564994_00513f9a5b.jpg#4 两名穿着赛车服的人在一辆街头赛车中。 |
文件 `ExpertAnnotations.txt` 指出,每张图像的哪些描述是由“专家”撰写的,哪些是由被要求描述图像的众包工人撰写的。
最后,文件 `CrowdFlowerAnnotations.txt` 提供了众包工人是否认为字幕适合每张图像的频率。这些频率可以被解释为概率性的。
该论文的作者对注释的描述如下:
… 要求注释者撰写描述所描绘场景、情况、事件和实体的句子(人、动物、其他物体)。我们为每张图像收集了多个字幕,因为许多图像的描述方式存在相当大的差异。
— 将图像描述作为排序任务:数据、模型和评估指标,2013。
还有用于训练/测试分割的照片标识符列表,以便您可以比较论文中报告的结果。
第一步是决定使用哪些字幕。最简单的方法是为每张照片使用第一个描述。
首先,我们需要一个函数来将整个注释文件(‘Flickr8k.token.txt‘)加载到内存中。下面是一个执行此操作的函数,名为 `load_doc()`,它接收一个文件名,并以字符串形式返回文档。
1 2 3 4 5 6 7 8 9 |
# 加载文档到内存 def load_doc(filename): # 以只读方式打开文件 file = open(filename, 'r') # 读取所有文本 text = file.read() # 关闭文件 file.close() 返回 文本 |
从上面的文件样本可以看出,我们只需要按空格分割每一行,并将第一个元素作为图像标识符,其余作为图像描述。例如:
1 2 3 4 |
# 按空格分割行 tokens = line.split() # 取第一个 token 作为图像 id,其余作为描述 image_id, image_desc = tokens[0], tokens[1:] |
然后我们可以清理图像标识符,移除文件名扩展名和描述编号。
1 2 |
# 从图像 id 中移除文件名 image_id = image_id.split('.')[0] |
我们还可以将描述 tokens 重新组合成字符串,以供以后处理。
1 2 |
# 将描述 tokens 转换回字符串 image_desc = ' '.join(image_desc) |
我们可以将所有这些整合到一个函数中。
下面定义了 `load_descriptions()` 函数,该函数将采用加载的文件,逐行处理,并返回一个图像标识符到其第一个描述的字典。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# 加载文档到内存 def load_doc(filename): # 以只读方式打开文件 file = open(filename, 'r') # 读取所有文本 text = file.read() # 关闭文件 file.close() return text # 提取图像描述 def load_descriptions(doc): mapping = dict() # 处理行 for line in doc.split('\n'): # 按空格分割行 tokens = line.split() if len(line) < 2: continue # 将第一个标记作为图像 id,其余作为描述 image_id, image_desc = tokens[0], tokens[1:] # 从图像 id 中删除文件名 image_id = image_id.split('.')[0] # 将描述标记转换回字符串 image_desc = ' '.join(image_desc) # 存储每张图片的首个描述 if image_id not in mapping: mapping[image_id] = image_desc return mapping filename = 'Flickr8k_text/Flickr8k.token.txt' doc = load_doc(filename) description = load_descriptions(doc) print('Loaded: %d ' % len(descriptions)) |
运行示例将打印加载的图像描述的数量。
1 |
已加载:8092 |
还有其他加载描述的方法,这些方法可能对数据更准确。
使用上面的示例作为起点,让我知道你的想法。
在下面的评论中分享你的方法。
准备描述文本
这些描述被分词了;这意味着每个词元都由空格分隔的单词组成。
这也意味着标点符号被分离为它们自己的词元,例如句点(“.”)和表示复数的撇号(“s”)。
在将描述文本用于模型之前清理它是一个好主意。我们可以形成的数据清理的一些想法包括:
- 将所有词元的大小写标准化为小写。
- 删除所有词元中的标点符号。
- 删除所有包含一个或更少字符的词元(删除标点符号后),例如“a”和单独的“s”字符。
我们可以在一个函数中实现这些简单的清理操作,该函数将清理前一节加载的字典中的每个描述。下面定义了将清理每个加载的描述的clean_descriptions()函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# 清理描述文本 def clean_descriptions(descriptions): # 准备用于删除标点符号的转换表 table = str.maketrans('', '', string.punctuation) for key, desc in descriptions.items(): # 分词 desc = desc.split() # 转换为小写 desc = [word.lower() for word in desc] # 从每个标记中删除标点符号 desc = [w.translate(table) for w in desc] # 删除悬挂的“s”和“a” desc = [word for word in desc if len(word)>1] # 存储为字符串 descriptions[key] = ' '.join(desc) |
然后,我们可以将清理后的文本保存到文件中供我们的模型稍后使用。
每一行将包含图像标识符,后跟清理后的描述。下面定义了用于将清理后的描述保存到文件的save_doc()函数。
1 2 3 4 5 6 7 8 9 |
# 将描述保存到文件,每行一个 def save_doc(descriptions, filename): lines = list() for key, desc in descriptions.items(): lines.append(key + ' ' + desc) data = '\n'.join(lines) file = open(filename, 'w') file = file.write(data) file.close() |
将以上内容与前一节加载描述的内容结合起来,完整的示例列在下面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
import string # 加载文档到内存 def load_doc(filename): # 以只读方式打开文件 file = open(filename, 'r') # 读取所有文本 text = file.read() # 关闭文件 file.close() return text # 提取图像描述 def load_descriptions(doc): mapping = dict() # 处理行 for line in doc.split('\n'): # 按空格分割行 tokens = line.split() if len(line) < 2: continue # 将第一个标记作为图像 id,其余作为描述 image_id, image_desc = tokens[0], tokens[1:] # 从图像 id 中删除文件名 image_id = image_id.split('.')[0] # 将描述标记转换回字符串 image_desc = ' '.join(image_desc) # 存储每张图片的首个描述 if image_id not in mapping: mapping[image_id] = image_desc return mapping # 清理描述文本 def clean_descriptions(descriptions): # 准备用于删除标点符号的转换表 table = str.maketrans('', '', string.punctuation) for key, desc in descriptions.items(): # 分词 desc = desc.split() # 转换为小写 desc = [word.lower() for word in desc] # 从每个标记中删除标点符号 desc = [w.translate(table) for w in desc] # 删除悬挂的“s”和“a” desc = [word for word in desc if len(word)>1] # 存储为字符串 descriptions[key] = ' '.join(desc) # 将描述保存到文件,每行一个 def save_doc(descriptions, filename): lines = list() for key, desc in descriptions.items(): lines.append(key + ' ' + desc) data = '\n'.join(lines) file = open(filename, 'w') file = file.write(data) file.close() filename = 'Flickr8k_text/Flickr8k.token.txt' # 加载描述 doc = load_doc(filename) # 解析描述 description = load_descriptions(doc) print('Loaded: %d ' % len(descriptions)) # 清理描述 clean_descriptions(descriptions) # 总结词汇表 all_tokens = ' '.join(descriptions.values()).split() vocabulary = set(all_tokens) print('Vocabulary Size: %d' % len(vocabulary)) # 保存描述 save_doc(descriptions, 'descriptions.txt') |
运行示例后,首先加载8,092个描述,清理它们,总结4,484个唯一单词的词汇,然后将它们保存到一个名为‘descriptions.txt‘的新文件中。
1 2 |
已加载:8092 词汇量大小:4484 |
在文本编辑器中打开新文件‘descriptions.txt‘并查看内容。您应该会看到一些可读的照片描述,可用于建模。
1 2 3 4 5 6 |
... 3139118874_599b30b116 两个女孩在圣诞节时摆姿势拍照 2065875490_a46b58c12b 一个人在人行道上行走,左边围栏内有一个骷髅 2682382530_f9f8fd1e89 一个穿着黑色短裤的男人正在伸腿 3484019369_354e0b88c0 冰球队穿着红白相间的衣服在溜冰场的一侧 505955292_026f1489f2 男孩骑马 |
词汇量仍然相对较大。为了简化建模,尤其是在第一次尝试时,我建议通过删除在所有描述中只出现一到两次的单词来进一步减少词汇量。
整段描述序列模型
有许多方法可以对字幕生成问题进行建模。
一种简单的方法是创建一个一次性输出整个文本描述的模型。
这是一个简单的模型,因为它给模型带来了解释照片含义和生成单词的繁重任务,然后将这些单词按正确的顺序排列。
这与Encoder-Decoder循环神经网络的语言翻译问题类似,在其中,给定输入序列的编码,输出整个翻译的句子,一次一个单词。在这里,我们将使用图像的编码来生成输出句子。
图像可以使用预先训练的图像分类模型进行编码,例如上面提到的在ImageNet模型上训练的VGG。
模型的输出将是词汇表中每个单词的概率分布。序列的长度将与最长的照片描述的长度相同。
因此,描述需要首先进行整数编码,其中词汇表中的每个单词都被分配一个唯一的整数,并且单词序列将被整数序列替换。然后需要对整数序列进行one-hot编码,以表示序列中每个单词的词汇表的理想化概率分布。
我们可以使用Keras中的工具来为这种模型准备描述。
第一步是加载存储在‘descriptions.txt‘中的图像标识符到清理后描述的映射。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# 加载文档到内存 def load_doc(filename): # 以只读方式打开文件 file = open(filename, 'r') # 读取所有文本 text = file.read() # 关闭文件 file.close() return text # 将清理过的描述加载到内存中 def load_clean_descriptions(filename): doc = load_doc(filename) descriptions = dict() for line in doc.split('\n'): # 按空格分割行 tokens = line.split() # 将 id 从描述中分离出来 image_id, image_desc = tokens[0], tokens[1:] # 存储 descriptions[image_id] = ' '.join(image_desc) return descriptions descriptions = load_clean_descriptions('descriptions.txt') print('Loaded %d' % (len(descriptions))) |
运行这段代码将8,092个照片描述加载到一个以图像标识符为键的字典中。然后可以使用这些标识符加载每个照片文件,作为模型的相应输入。
1 |
已加载 8092 |
接下来,我们需要提取所有描述文本,以便对其进行编码。
1 2 |
# 提取所有文本 desc_text = list(descriptions.values()) |
我们可以使用Keras的Tokenizer类来一致地将词汇表中的每个单词映射到一个整数。首先,创建对象,然后用描述文本进行拟合。拟合后的分词器稍后可以保存到文件中,以便对预测结果进行一致的解码,使其回到词汇单词。
1 2 3 4 5 6 |
from keras.preprocessing.text import Tokenizer # 准备分词器 tokenizer = Tokenizer() tokenizer.fit_on_texts(desc_text) vocab_size = len(tokenizer.word_index) + 1 print('Vocabulary Size: %d' % vocab_size) |
接下来,我们可以使用拟合好的分词器将照片描述编码成整数序列。
1 2 |
# 整数编码描述 sequences = tokenizer.texts_to_sequences(desc_text) |
模型将需要所有输出序列具有相同的长度来进行训练。我们可以通过将所有编码的序列填充到与最长编码序列相同的长度来实现这一点。我们可以用0值填充序列,放在单词列表之后。Keras提供了pad_sequences()函数来填充序列。
1 2 3 4 5 |
from keras.preprocessing.sequence import pad_sequences # 将所有序列填充到固定长度 max_length = max(len(s) for s in sequences) print('Description Length: %d' % max_length) padded = pad_sequences(sequences, maxlen=max_length, padding='post') |
最后,我们可以对填充后的序列进行one-hot编码,为序列中的每个单词提供一个稀疏向量。Keras提供了to_categorical()函数来执行此操作。
1 2 3 |
从 keras.utils 导入 to_categorical # One-Hot 编码 y = to_categorical(padded, num_classes=vocab_size) |
编码后,我们可以确保序列输出数据具有适合模型的正确形状。
1 2 |
y = y.reshape((len(descriptions), max_length, vocab_size)) print(y.shape) |
综合所有这些,完整的示例如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
from numpy import array from keras.preprocessing.text import Tokenizer from keras.preprocessing.sequence import pad_sequences 从 keras.utils 导入 to_categorical # 加载文档到内存 def load_doc(filename): # 以只读方式打开文件 file = open(filename, 'r') # 读取所有文本 text = file.read() # 关闭文件 file.close() return text # 将清理过的描述加载到内存中 def load_clean_descriptions(filename): doc = load_doc(filename) descriptions = dict() for line in doc.split('\n'): # 按空格分割行 tokens = line.split() # 将 id 从描述中分离出来 image_id, image_desc = tokens[0], tokens[1:] # 存储 descriptions[image_id] = ' '.join(image_desc) return descriptions descriptions = load_clean_descriptions('descriptions.txt') print('Loaded %d' % (len(descriptions))) # 提取所有文本 desc_text = list(descriptions.values()) # 准备分词器 tokenizer = Tokenizer() tokenizer.fit_on_texts(desc_text) vocab_size = len(tokenizer.word_index) + 1 print('Vocabulary Size: %d' % vocab_size) # 整数编码描述 sequences = tokenizer.texts_to_sequences(desc_text) # 将所有序列填充到固定长度 max_length = max(len(s) for s in sequences) print('Description Length: %d' % max_length) padded = pad_sequences(sequences, maxlen=max_length, padding='post') # One-Hot 编码 y = to_categorical(padded, num_classes=vocab_size) y = y.reshape((len(descriptions), max_length, vocab_size)) print(y.shape) |
运行示例首先打印加载的图像描述数量(8,092张照片)、数据集词汇量大小(4,485个单词)、最长描述的长度(28个单词),最后是用于拟合预测模型的数据形状,形式为[样本,序列长度,特征]。
1 2 3 4 |
已加载 8092 词汇量大小:4485 描述长度:28 (8092, 28, 4485) |
如前所述,输出整个序列可能对模型来说具有挑战性。
我们将在下一节中介绍一个更简单的模型。
逐词模型
生成照片字幕的一个更简单的模型是,给定图像作为输入和最后一个生成的单词,生成一个单词。
然后,需要递归调用此模型来生成描述中的每个单词,并将之前的预测作为输入。
使用单词作为输入,为模型提供预测序列中下一个单词的强制上下文。
这是先前研究中使用的模型,例如:
- 展示与讲述:一个神经图像字幕生成器, 2015.
可以使用单词嵌入层来表示输入单词。与照片的特征提取模型类似,这个模型也可以在大型语料库或所有描述的数据集上进行预训练。
模型将接受完整的单词序列作为输入;序列的长度将是数据集中描述的最大长度。
模型必须以某种东西开始。一种方法是将每个照片描述用特殊标签包围,以信号描述的开始和结束,例如“STARTDESC”和“ENDDESC”。
例如,描述
1 |
男孩骑马 |
将变成
1 |
STARTDESC 男孩骑马 ENDDESC |
并将与相同的图像输入一起提供给模型,以产生以下输入-输出单词序列对:
1 2 3 4 5 |
输入 (X),输出 (y) STARTDESC,男孩 STARTDESC,男孩,骑 STARTDESC,男孩,骑,马 STARTDESC,男孩,骑,马 ENDDESC |
数据准备将与上一节所述的非常相似。
每个描述都必须进行整数编码。编码后,序列被分成多个输入和输出对,并且只有输出单词(y)被进行one-hot编码。这是因为模型只需要一次预测一个单词的概率分布。
代码与计算序列最大长度的点相同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
... descriptions = load_clean_descriptions('descriptions.txt') print('Loaded %d' % (len(descriptions))) # 提取所有文本 desc_text = list(descriptions.values()) # 准备分词器 tokenizer = Tokenizer() tokenizer.fit_on_texts(desc_text) vocab_size = len(tokenizer.word_index) + 1 print('Vocabulary Size: %d' % vocab_size) # 整数编码描述 sequences = tokenizer.texts_to_sequences(desc_text) # 确定最大序列长度 max_length = max(len(s) for s in sequences) print('Description Length: %d' % max_length) |
接下来,我们将每个整数编码的序列分割成输入和输出对。
让我们逐步分析一个名为seq的序列,其中i是序列中的第i个单词,i >= 1。
首先,我们将前i-1个单词作为输入序列,并将第i个单词作为输出单词。
1 2 |
# 分割成输入和输出对 in_seq, out_seq = seq[:i], seq[i] |
接下来,将输入序列填充到输入序列的最大长度。使用预填充(默认)以便新单词出现在序列的末尾,而不是出现在输入的开头。
使用预填充(默认)以便新单词出现在序列的末尾,而不是出现在输入的开头。
1 2 |
# 填充输入序列 in_seq = pad_sequences([in_seq], maxlen=max_length)[0] |
输出单词进行one-hot编码,与上一节非常相似。
1 2 |
# 编码输出序列 out_seq = to_categorical([out_seq], num_classes=vocab_size)[0] |
我们可以将所有这些内容整合到一个完整的示例中,为单词到单词的模型准备描述数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
from numpy import array from keras.preprocessing.text import Tokenizer from keras.preprocessing.sequence import pad_sequences 从 keras.utils 导入 to_categorical # 加载文档到内存 def load_doc(filename): # 以只读方式打开文件 file = open(filename, 'r') # 读取所有文本 text = file.read() # 关闭文件 file.close() return text # 将清理过的描述加载到内存中 def load_clean_descriptions(filename): doc = load_doc(filename) descriptions = dict() for line in doc.split('\n'): # 按空格分割行 tokens = line.split() # 将 id 从描述中分离出来 image_id, image_desc = tokens[0], tokens[1:] # 存储 descriptions[image_id] = ' '.join(image_desc) return descriptions descriptions = load_clean_descriptions('descriptions.txt') print('Loaded %d' % (len(descriptions))) # 提取所有文本 desc_text = list(descriptions.values()) # 准备分词器 tokenizer = Tokenizer() tokenizer.fit_on_texts(desc_text) vocab_size = len(tokenizer.word_index) + 1 print('Vocabulary Size: %d' % vocab_size) # 整数编码描述 sequences = tokenizer.texts_to_sequences(desc_text) # 确定最大序列长度 max_length = max(len(s) for s in sequences) print('Description Length: %d' % max_length) X, y = list(), list() for img_no, seq in enumerate(sequences): # 将一个序列分成多个 X,y 对 for i in range(1, len(seq)): # 分割成输入和输出对 in_seq, out_seq = seq[:i], seq[i] # 填充输入序列 in_seq = pad_sequences([in_seq], maxlen=max_length)[0] # 编码输出序列 out_seq = to_categorical([out_seq], num_classes=vocab_size)[0] # 存储 X.append(in_seq) y.append(out_seq) # 转换为 numpy 数组 X, y = array(X), array(y) print(X.shape) print(y.shape) |
运行示例会打印相同的统计信息,但会打印由此产生的编码输入和输出序列的大小。
请注意,图像的输入必须遵循完全相同的顺序,其中同一张照片显示在从单个描述中提取的每个示例中。一种方法是加载照片并将其存储在为单个描述准备的每个示例中。
1 2 3 4 5 |
已加载 8092 词汇量大小:4485 描述长度:28 (66456, 28) (66456, 4485) |
渐进式加载
Flickr8K 照片和描述数据集可以放入 RAM 中,如果您有大量 RAM(例如 8 GB 或更多),并且大多数现代系统都有。
如果您想使用 CPU 拟合深度学习模型,这就可以了。
或者,如果您想使用 GPU 拟合模型,那么您将无法将数据放入普通 GPU 显卡的内存中。
一种解决方案是根据模型需要逐步加载照片和描述。
Keras 支持通过在模型上使用fit_generator()函数来逐步加载数据集。生成器是用于描述用于为模型返回样本批次的函数的术语。这可以很简单,例如一个独立的函数,其名称在拟合模型时传递给fit_generator()函数。
提醒一下,模型会进行多个 epoch 的拟合,其中一个 epoch 是完整训练数据集(例如所有照片)的一次遍历。一个 epoch 由多个示例批次组成,模型权重在每个批次的末尾进行更新。
生成器必须创建并产生一个样本批次。例如,数据集中平均句子长度为11个单词;这意味着每张照片将产生11个用于拟合模型的示例,两张照片平均将产生大约22个示例。现代硬件的一个好的默认批次大小可能是32个示例,这大约是2-3张照片的示例。
我们可以编写一个自定义生成器来加载几张照片并以单个批次返回样本。
假设我们正在处理上一节中描述的单词到单词模型,该模型期望一系列单词和准备好的图像作为输入,并预测单个单词。
让我们设计一个数据生成器,它给定一个图像标识符到清理后描述的加载字典、一个训练好的分词器和一个最大序列长度,将为每个批次加载一个图像的示例。
生成器必须永远循环并产生每个样本批次。如果生成器和yield对您来说是新概念,请考虑阅读这篇文章:
我们可以使用while循环永远循环,并在其中循环遍历图像目录中的每个图像。对于每个图像文件名,我们可以加载图像并从图像的描述中创建所有输入-输出序列对。
数据生成器函数如下。
1 2 3 4 5 6 7 8 9 10 11 12 |
def data_generator(mapping, tokenizer, max_length): # 永远循环遍历图像 directory = 'Flicker8k_Dataset' while 1: for name in listdir(directory): # 从文件中加载图像 filename = directory + '/' + name image, image_id = load_image(filename) # 创建单词序列 desc = mapping[image_id] in_img, in_seq, out_word = create_sequences(tokenizer, max_length, desc, image) yield [[in_img, in_seq], out_word] |
您可以将其扩展为将数据集目录的名称作为参数。
生成器返回一个包含模型输入(X)和输出(y)的数组。输入由一个包含图像和编码单词序列两个项目的数组组成。输出是进行one-hot编码的单词。
您可以看到它调用了一个名为load_photo()的函数来加载单个照片并返回像素和图像标识符。这是本教程开头开发的照片加载函数的简化版本。
1 2 3 4 5 6 7 8 9 10 11 12 |
# 加载一张照片,用于 VGG 特征提取模型 def load_photo(filename): image = load_img(filename, target_size=(224, 224)) # 将图像像素转换为 numpy 数组 image = img_to_array(image) # 重塑数据以适应模型 image = image.reshape((1, image.shape[0], image.shape[1], image.shape[2])) # 准备图像以适应 VGG 模型 image = preprocess_input(image)[0] # 获取图像 id image_id = filename.split('/')[-1].split('.')[0] return image, image_id |
另一个名为create_sequences()的函数被调用,用于创建图像序列、输入单词序列以及输出单词,然后我们将这些序列yield给调用者。这个函数包含了上一节讨论的所有内容,并且还为从照片描述创建的每个输入-输出对创建了图像像素的副本。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# 为图像创建图像序列、输入序列和输出单词 def create_sequences(tokenizer, max_length, descriptions, images): Ximages, XSeq, y = list(), list(),list() vocab_size = len(tokenizer.word_index) + 1 for j in range(len(descriptions)): seq = descriptions[j] image = images[j] # 整数编码 seq = tokenizer.texts_to_sequences([seq])[0] # 将一个序列分成多个 X,y 对 for i in range(1, len(seq)): # select in_seq, out_seq = seq[:i], seq[i] # 填充输入序列 in_seq = pad_sequences([in_seq], maxlen=max_length)[0] # 编码输出序列 out_seq = to_categorical([out_seq], num_classes=vocab_size)[0] # 存储 Ximages.append(image) XSeq.append(in_seq) y.append(out_seq) Ximages, XSeq, y = array(Ximages), array(XSeq), array(y) return Ximages, XSeq, y |
在准备使用数据生成器(data generator)的模型之前,我们必须加载清理后的描述,准备分词器(tokenizer),并计算最大序列长度(maximum sequence length)。这三者都必须作为参数传递给data_generator()。
我们使用之前开发的load_clean_descriptions()函数和一个新的create_tokenizer()函数,后者简化了分词器的创建。
将所有这些结合起来,完整的 数据生成器 如下所示,已准备好用于训练模型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
from os import listdir from numpy import array from keras.preprocessing.text import Tokenizer from keras.preprocessing.sequence import pad_sequences from keras.utils import to_categorical from keras.preprocessing.image import load_img from keras.preprocessing.image import img_to_array from keras.applications.vgg16 import preprocess_input # 加载文档到内存 def load_doc(filename): # 以只读方式打开文件 file = open(filename, 'r') # 读取所有文本 text = file.read() # 关闭文件 file.close() return text # 将清理过的描述加载到内存中 def load_clean_descriptions(filename): doc = load_doc(filename) descriptions = dict() for line in doc.split('\n'): # 按空格分割行 tokens = line.split() # 将 id 从描述中分离出来 image_id, image_desc = tokens[0], tokens[1:] # 存储 descriptions[image_id] = ' '.join(image_desc) return descriptions # 给定描述,拟合分词器 def create_tokenizer(descriptions): lines = list(descriptions.values()) tokenizer = Tokenizer() tokenizer.fit_on_texts(lines) return tokenizer # 加载一张照片,用于 VGG 特征提取模型 def load_photo(filename): image = load_img(filename, target_size=(224, 224)) # 将图像像素转换为 numpy 数组 image = img_to_array(image) # 重塑数据以适应模型 image = image.reshape((1, image.shape[0], image.shape[1], image.shape[2])) # 准备图像以适应 VGG 模型 image = preprocess_input(image)[0] # 获取图像 id image_id = filename.split('/')[-1].split('.')[0] return image, image_id # 为图像创建图像序列、输入序列和输出单词 def create_sequences(tokenizer, max_length, desc, image): Ximages, XSeq, y = list(), list(),list() vocab_size = len(tokenizer.word_index) + 1 # integer encode the description seq = tokenizer.texts_to_sequences([desc])[0] # 将一个序列分成多个 X,y 对 for i in range(1, len(seq)): # select in_seq, out_seq = seq[:i], seq[i] # 填充输入序列 in_seq = pad_sequences([in_seq], maxlen=max_length)[0] # 编码输出序列 out_seq = to_categorical([out_seq], num_classes=vocab_size)[0] # 存储 Ximages.append(image) XSeq.append(in_seq) y.append(out_seq) Ximages, XSeq, y = array(Ximages), array(XSeq), array(y) return [Ximages, XSeq, y] # 数据生成器,旨在用于调用 model.fit_generator() def data_generator(descriptions, tokenizer, max_length): # 永远循环遍历图像 directory = 'Flicker8k_Dataset' while 1: for name in listdir(directory): # 从文件中加载图像 filename = directory + '/' + name image, image_id = load_photo(filename) # 创建单词序列 desc = descriptions[image_id] in_img, in_seq, out_word = create_sequences(tokenizer, max_length, desc, image) yield [[in_img, in_seq], out_word] # load mapping of ids to descriptions descriptions = load_clean_descriptions('descriptions.txt') # 将单词序列整数编码 tokenizer = create_tokenizer(descriptions) # pad to fixed length max_length = max(len(s.split()) for s in list(descriptions.values())) print('Description Length: %d' % max_length) # test the data generator generator = data_generator(descriptions, tokenizer, max_length) inputs, outputs = next(generator) print(inputs[0].shape) print(inputs[1].shape) print(outputs.shape) |
可以通过调用 next() 函数来测试数据生成器。
我们可以通过以下方式测试生成器。
1 2 3 4 5 6 |
# test the data generator generator = data_generator(descriptions, tokenizer, max_length) inputs, outputs = next(generator) print(inputs[0].shape) print(inputs[1].shape) print(outputs.shape) |
运行示例将打印一个批次的输入和输出示例的形状(例如,13个输入-输出对)
1 2 3 |
(13, 224, 224, 3) (13, 28) (13, 4485) |
可以通过在模型上调用fit_generator()函数(而不是fit())并将生成器传递进去来使用该生成器来拟合模型。
我们还必须指定每个 epoch 的步数或批次数。我们可以将其估计为(10 x 训练数据集大小),如果使用7,000张图片进行训练,则可能为70,000。
1 2 3 4 |
# 定义模型 # ... # 拟合模型 model.fit_generator(data_generator(descriptions, tokenizer, max_length), steps_per_epoch=70000, ...) |
进一步阅读
如果您想深入了解此主题,本节提供了更多资源。
Flickr8K 数据集
- 将图像描述框定为排序任务:数据、模型和评估指标(主页)
- 将图像描述框定为排序任务:数据、模型和评估指标,(PDF)2013。
- 数据集申请表
- 旧的 Flickr8K 主页
API
总结
在本教程中,您了解了如何准备照片和文本描述,以用于开发自动照片字幕生成模型。
具体来说,你学到了:
- 关于 Flickr8K 数据集,该数据集包含超过 8,000 张照片,每张照片最多有 5 个字幕。
- 如何使用深度学习对照片和文本数据进行通用加载和准备。
- 如何在 Keras 中为两种不同类型的深度学习模型专门编码数据。
你有什么问题吗?
在下面的评论中提出你的问题,我会尽力回答。
这个主题包含在你的新书中了吗?
是的,我有一系列关于开发字幕生成模型的章节。
嗨 Jason,内容很棒,但我无法理解你为什么在完整代码的第78行使用了while(1)
在第78行。
不使用while(1) 也能得到相同的结果吗??
谢谢…!!
def data_generator(descriptions, tokenizer, max_length)
# loop for ever over images
directory = ‘Flicker8k_Dataset’
while 1:————————————————————————->>>>>>line of doubt
for name in listdir(directory)
# 从文件加载图像
filename = directory + ‘/’ + name
image, image_id = load_photo(filename)
# create word sequences
desc = descriptions[image_id]
in_img, in_seq, out_word = create_sequences(tokenizer, max_length, desc, image)
yield [[in_img, in_seq], out_word]
这是一个Python生成器,你可以在这里了解更多关于生成器的信息
https://wiki.python.org/moin/Generators
太棒了!!!感谢您的整理——非常感激!????
不客气,很高兴能帮到你!
嗨 Jason,我发现你的工作非常有帮助,你是否也实现了自顶向下的方法(密集字幕生成)来生成图像字幕?
这有帮助吗?
https://machinelearning.org.cn/develop-a-deep-learning-caption-generation-model-in-python/
嗨 Jason,这是自顶向下的方法,自底向上(bottom up)的方法是不同的,也称为密集字幕(dense captioning),即我们识别图像中的对象,然后将它们组合起来形成描述。
谢谢。
嗨 Jason,数据生成器函数不应该调用load_photo()而不是load_image()吗?
在完整的示例中,数据生成器确实调用了第82行的load_photo()。
嗨 Jason,我是Python和CNN的新手。我能得到一个测试源代码,输入一张图片,它就能输出字幕吗?
是的,我很快就会在博客上以及我即将出版的关于NLP深度学习的新书中提供一些。
很喜欢阅读你的文章,你解释得很详细,写得不能再好了!这篇文章非常有趣且有效。
注意:第一次提到load_clean_descriptions时有一个拼写错误
mapping[image_id] = ‘ ‘.join(image_desc)
应该是
descriptions[image_id] = ‘ ‘.join(image_desc)
感谢分享如此有趣的博客。
已修复,谢谢!
你好,
很喜欢关注你的博客。
我在这里看到一个错误
def save_doc(descriptions, filename)
lines = list()
for key, desc in mapping.items()
这让我困惑了一会儿,直到我看到mapping是从以下函数返回的
def load_descriptions(doc)
修复如下
def save_doc(descriptions, filename)
lines = list()
for key, desc in descriptions.items()
替换
for key, desc in mapping.items()
哎呀,我也修复了那个例子,谢谢。
嗨 Jason,当我运行时,我得到了以下错误:
FileNotFoundError: [Errno 2] No such file or directory: ‘Flickr8k_text/Flickr8k.token.txt’,你能帮帮我吗?我对深度学习非常陌生。
你必须下载数据集并将其放在代码所在的同一目录中。
尝试从命令行运行,有时IDE和Notebook会隐藏或引入错误。
嗨 Jason,感谢这篇很棒的文章,我非常喜欢阅读。顺便说一句,我认为你谈到每 epoch 的步数时有一个拼写错误。我认为应该是“也许70,000,如果7,000张图片用于训练。”
谢谢,已修正。
嗨,Jason,
当我运行 dump(features, open(‘features.pkl’, ‘wb’)) 时,我得到了以下错误:“feature.pkl 未编码为 UTF-8”
我还尝试转储 predict 函数的输出,只使用第一张图片。
它看起来是这样的
{‘667626_18933d713e’: array([[[[ 0. , 0. , 0. , …, 0. ,
10.62594891, 0. ],
[ 0. , 0. , 0. , …, 0. ,
0. , 0. ],
[ 0. , 0. , 0. , …, 0. ,
0. , 0. ],
……,
[ 0. , 0. , 0. , …, 0. ,
0. , 0. ],
[ 0. , 0. , 0. , …, 0. ,
0. , 0. ],
[ 0. , 0. , 0. , …, 0. ,
9.41605377, 0. ]],
[[ 0. , 0. , 0. , …, 0. ,
0. , 0. ],
[ 0. , 0. , 0. , …, 0. ,
0. , 5.36805296],
[ 0. , 0. , 0. , …, 0. ,
0. , 0. ],
……,
[ 0. , 0. , 0. , …, 1.45877278,
0. , 39.37923431],
[ 0. , 0. , 0. , …, 0. ,
0. , 1.39090693],
[ 0. , 0. , 0. , …, 0. ,
3.93747687, 0. ]],
[[ 0. , 0. , 0. , …, 0. ,
18.81423187, 0. ],
[ 0. , 0. , 0. , …, 7.79979277,
0. , 0. ],
[ 0. , 0. , 0. , …, 0. ,
0. , 9.14055347],
……,
[ 0. , 0. , 0. , …, 48.84911346,
0. , 12.12792015],
[ 0. , 0. , 0. , …, 0. ,
0. , 0. ],
[ 0. , 0. , 0. , …, 0. ,
2.0710113 , 0. ]],
……,
[[ 0. , 0. , 0. , …, 0. ,
0. , 0. ],
[ 0. , 3.75439334, 0. , …, 0. ,
0. , 0. ],
[ 3.71412587, 0. , 0. , …, 0. ,
0. , 0. ],
……,
[ 0. , 0. , 0. , …, 0. ,
0. , 0. ],
[ 0. , 0. , 0. , …, 0. ,
18.80825424, 0. ],
[ 0. , 0. , 0. , …, 0. ,
13.0358696 , 0. ]],
[[ 0. , 0. , 0. , …, 0. ,
4.03412676, 0. ],
[ 0. , 0. , 0. , …, 0. ,
0. , 0. ],
[ 0. , 0. , 0. , …, 0. ,
0. , 0. ],
……,
[ 0. , 0. , 0. , …, 0. ,
0. , 0. ],
[ 0. , 0. , 0. , …, 0. ,
7.99308109, 0. ],
[ 0. , 0. , 0. , …, 0. ,
32.52854919, 0. ]],
[[ 0. , 0. , 0. , …, 0. ,
33.73991013, 0. ],
[ 0. , 0. , 0. , …, 0. ,
14.52160454, 0. ],
[ 0. , 0. , 0. , …, 0. ,
4.05761242, 0. ],
……,
[ 0. , 0. , 0. , …, 0. ,
0. , 0. ],
[ 0. , 0. , 0. , …, 0.90452403,
0. , 0. ],
[ 0. , 0. , 0. , …, 29.89839745,
38.23991394, 0. ]]]], dtype=float32)}
而且我也不确定这个结果是否正确。
你能帮帮我吗?
我真的不知道为什么我的 feature.pkl 可以成功保存。
非常感谢。
.
抱歉,我以前没见过这个错误。也许把完整的错误信息发到stackoverflow?
谢谢你的及时回复。我会试试。
你好 Jason,
我能下载数据集,但链接不可用。你能帮帮我吗?
谢谢,
Karthik
*无法
Jason,我现在可以下载了。请忽略我上面的评论。
谢谢,Karthik
不客气。
您必须填写此表格
https://forms.illinois.edu/sec/1713398
Jason,
我得到了 model-ep005-loss3.517-val_loss4.012。
另一篇好文章。
谢谢,
Karthik
很好!
karthik 能发我一个下载模型文件的链接吗?
Karthik 你能提供模型文件的下载链接吗?
嗨,Jason
我正在使用GPU训练模型,但它需要太长的时间!
每个 epoch 大约9300秒。
我的硬件:NVIDA GTX 850M (计算能力5.0),GPU内存4GiB
我的电脑内存是8GiB
操作系统:Ubuntu 16.04
如果我使用CPU模式,我会遇到内存错误
================= Error ===============
回溯(最近一次调用)
File “ICmodel.py”, line 217, in
X1train, X2train, ytrain = create_sequences(tokenizer, max_length, train_descriptions,train_features)
File “ICmodel.py”, line 162, in create_sequences
return array(X1),array(X2),array(y)
内存错误
===============End of Error==============
所以我必须使用我的GPU来运行训练程序。这是我修改你代码后的代码,有什么修改是错误的吗?
==================== Code ====================
def data_generator(mapping, tokenizer, max_length, features)
# loop for ever over images
directory = ‘Flickr8k_Dataset’
while 1
for name in listdir(directory)
# 从文件加载图像
filename = directory + ‘/’ + name
image_id = name.split(‘.’)[0]
# create word sequences
if image_id not in mapping
continue
desc_list = mapping[image_id]
img_feature = features[image_id][0]
in_img, in_seq, out_word = create_sequences4list(tokenizer,max_length, desc_list, img_feature)
yield [[in_img,in_seq], out_word]
# create sequences of feature, input sequences and output words for an image
def create_sequences4list(tokenizer, max_length, desc_list, photo)
Xfe, XSeq, y = list(), list(),list()
vocab_size = len(tokenizer.word_index) + 1
# integer encode the description
for desc in desc_list
seq = tokenizer.texts_to_sequences([desc])[0]
# split one sequence into multiple X,y pairs
for i in range(1, len(seq))
# select
in_seq, out_seq = seq[:i], seq[i]
# 填充输入序列
in_seq = pad_sequences([in_seq], maxlen=max_length)[0]
# 编码输出序列
out_seq = to_categorical([out_seq], num_classes=vocab_size)[0]
# 存储
Xfe.append(photo)
XSeq.append(in_seq)
y.append(out_seq)
Xfe, XSeq, y = array(Xfe), array(XSeq), array(y)
return [Xfe, XSeq, y]
======================End of Code =================
耗时运行太糟糕了,您能给我一些建议吗?谢谢。
您可能需要更多的RAM。也许可以修改代码使用渐进式加载?
谢谢你的回复。
渐进式加载就是使用Python生成器吗?上面我发的就是为生成器适配的生成器函数和create_sequence函数。抱歉消失的缩进…
我感到困惑的是,我需要为每行描述yield,还是需要一次性yield所有五条描述?
好问题,我认为你可以每几条描述yield一次。甚至可以稍微试验一下,看看你的硬件能承受什么。
嘿 Jason Brownlee,我使用了这个渐进式加载,并使用了这个 https://machinelearning.org.cn/develop-a-deep-learning-caption-generation-model-in-python/#comment-429456 教程。
然后我遇到了这个错误。你能告诉我如何定义这个特定生成器的模型吗?
ValueError: Error when checking input: expected input_1 to have 2 dimensions, but got array with shape (13, 224, 224, 3)
我是机器学习新手,感谢您出色的教程!
我通过添加 inputs1 = Input(shape=(224, 224, 3)) 修复了这个问题,现在出现了另一个错误。请帮忙。
ValueError: Error when checking target: expected dense_3 to have 4 dimensions, but got array with shape (13, 4485)
请帮助解决模型部分的问题。我无法运行它,而且我还没有足够的理解来自己计算这些数字。
你解决这个问题了吗?我卡在同样的错误上了。
ConnectionResetError: [WinError 10054] 现有连接被远程主机强制关闭
下载VGG16模型时出错。
你能帮我解决这个问题吗?
很遗憾听到这个消息,听起来像是一个网络连接问题。也许再试一次?
有人能告诉我如何为这个示例中的渐进式加载编译VGG 16模型吗?
提前感谢。
请帮我定义模型,我已经使用了数据生成器,它工作正常,但定义模型时遇到了麻烦。
也许你可以用几句话总结一下你的问题?
我需要一个模型定义代码,该代码在代码中的模型拟合之前使用
# 定义模型
# …
# 拟合模型
model.fit_generator(data_generator(descriptions, tokenizer, max_length), steps_per_epoch=70000, …)
我也是,Jason,我一直在看你的电子书来寻找解决方案,但一无所获……你能提供用于渐进式加载的模型的定义代码,这样我们就可以在以下代码中使用它:
model.fit_generator(data_generator(descriptions, tokenizer, max_length), steps_per_epoch=70000, …)
同样的问题,请帮助。
你解决了吗?如果解决了,请解释一下!
提前感谢。
渐进式加载后如何评估模型以及如何为新图像生成字幕。
请看这篇文章
https://machinelearning.org.cn/develop-a-deep-learning-caption-generation-model-in-python/
你好 Jason,
关于您的讨论,我有一个问题。正如您所说,steps_per_epoch 将是训练数据量的10倍,即70,000,那么如果我们设置steps_per_epoch为70而不是70,000会发生什么?
增加steps_per_epoch的数量是否会导致模型更好?
训练速度变慢。鉴于权重更新频率的急剧增加,模型能力可能会变差。
您是否会说整个描述序列模型和逐词模型都是基于RNN的?
当然可以。
你好 Jason,
我正在尝试运行您提供的用于从flickr数据集中提取照片特征的代码,但是它显示以下错误
‘AttributeError: ‘InputLayer’ object has no attribute ‘outbound_nodes’
我在这里有一些建议
https://machinelearning.org.cn/faq/single-faq/why-does-the-code-in-the-tutorial-not-work-for-me
您是否写过关于VQA的教程?您能推荐一个 Python 学习源吗?
什么是VQA?
有人知道如何解决这个错误吗?
ValueError: Error when checking input: expected input_1 to have 2 dimensions, but got array with shape (61317, 7, 7, 512)
我在这里有一些建议
https://machinelearning.org.cn/faq/single-faq/why-does-the-code-in-the-tutorial-not-work-for-me
当我使用迁移学习时,这段代码是否可以用来准备Keras的图像数据?
是的,在一定程度上可以。
尊敬的先生,
我遇到了以下错误
python3.7/site-packages/keras/engine/training_utils.py”, line 102, in standardize_input_data
str(len(data)) + ‘ arrays: ‘ + str(data)[:200] + ‘…’)
ValueError: Error when checking model input: the list of Numpy arrays that you are passing to your model is not the size the model expected. Expected to see 1 array(s), but instead got the following list of 2 arrays: [array([[[[ 66.061 , 106.221 , 112.32 ],
[ 63.060997 , 97.221 , 111.32 ],
[ 57.060997 , 96.221 , 105.32 ],
……,
[ 43.060997 , 92.221 ,…
请指导我……
我在这里有一些建议
https://machinelearning.org.cn/faq/single-faq/why-does-the-code-in-the-tutorial-not-work-for-me
嗨 Jason,我收集了 Flicker 8k 数据集,并做了一些本地语言的翻译,但现在我想扩展我的数据集。Flicker 8k 和 Flicker 30k 数据集之间是否存在相似之处,例如 8k 是 30k 的子集?因为可以看到 8k 和 30k 中的文件名是不同的。您对此有何了解?
抱歉,我不知道。
嗨 Jason,我想对服装进行图像字幕生成,并且需要相关的数据集
如果您有此数据集,请给我,或者我很乐意您帮助我如何创建带有字幕的数据集
谢谢
这可能有帮助
https://machinelearning.org.cn/faq/single-faq/where-can-i-get-a-dataset-on-___
我应该将 flicker8k 数据集插入 jupyter notebook 吗?
我不建议使用 notebook
https://machinelearning.org.cn/faq/single-faq/why-dont-use-or-recommend-notebooks
你好,Jason。
我正在尝试使用数据生成器拟合模型,但遇到了这个错误
ValueError:在用户代码中
/usr/local/lib/python3.7/dist-packages/keras/engine/training.py:830 train_function *
返回step_function(self, iterator)
/usr/local/lib/python3.7/dist-packages/keras/engine/training.py:813 run_step *
outputs = model.train_step(data)
/usr/local/lib/python3.7/dist-packages/keras/engine/training.py:770 train_step *
y_pred = self(x, training=True)
/usr/local/lib/python3.7/dist-packages/keras/engine/base_layer.py:989 __call__ *
input_spec.assert_input_compatibility(self.input_spec, inputs, self.name)
/usr/local/lib/python3.7/dist-packages/keras/engine/input_spec.py:197 assert_input_compatibility *
raise ValueError(‘Layer ‘ + layer_name + ‘ expects ‘ +
ValueError: Layer model_15 expects 2 input(s), but it received 3 input tensors. Inputs received: [, , ]
您能否在这方面帮助我?
很抱歉听到这个消息,这些提示可能会有所帮助
https://machinelearning.org.cn/faq/single-faq/why-does-the-code-in-the-tutorial-not-work-for-me
如何一次性评估图像字幕模型?
即所有指标得分一次性计算
嗨 Pranav…以下内容可能对您感兴趣
https://machinelearning.org.cn/how-to-evaluate-pixel-scaling-methods-for-image-classification/