欢迎来到《看图说话实战教程》系列第三节。在这一节中,我们正式进入看图说话深度模型的构建与训练。

文章目录

  • 1. 加载数据
  • 2. 构建模型
  • 3. 拟合模型
  • 4. 完整代码
  • 5. 结束语

1. 加载数据

我们在教程第二节已经对看图说话模型所需的原始图片及文本数据进行了预处理,并另存为文件形式。此时,我们需要将这些数据加载进来,开始喂给我们的模型。

使用训练集中所有的图片及其文字描述来训练看图说话模型。训练过程中使用验证集来监测模型性能表现并决定何时保存模型。使用测试集来验证模型的实际效果。

Flickr8k 数据集已经包含了拆分好的训练集、验证集与测试集,我们不必在程序中显示地进行拆分。训练集、验证集与测试集分别保存在了 Flickr_8k.trainImages.txtFlickr_8k.devImages.txtFlickr_8k.testImages.txt 三个文件当中。数据形式如下所示:

2513260012_03d33305cf.jpg
2903617548_d3e38d7f88.jpg
3338291921_fe7ae0c8f8.jpg
488416045_1c6d903fe0.jpg
2644326817_8f45080b87.jpg
218342358_1755a9cce1.jpg
2501968935_02f2cd8079.jpg
2699342860_5288e203ea.jpg
2638369467_8fc251595b.jpg
2926786902_815a99a154.jpg
...

我们定义一个函数加载这些预定义好的图片数据集:

def load_set(filename):doc = load_doc(filename)dataset = list()# 按行处理for line in doc.split('\n'):# 跳过空行if len(line) < 1:continue# 获取图片标志符(ID)identifier = line.split('.')[0]dataset.append(identifier)return set(dataset)# 加载训练集
filename = 'Flickr8k_text/Flickr_8k.trainImages.txt'
train = load_set(filename)
print('Dataset: %d' % len(train))

接下来最重要的一个步骤就是加载已经经过预处理的图片的文字描述,同时生成模型所需的文字序列。这些文字描述保存在 descriptions.txt 文件中。

模型生成图片文字描述的过程是每次一个字。先前生成好的文字又将被当做新的输入来预测下一个单词,直到遇到序列终止符号才停止生成新的单词。因此,我们需要两个符号分别标记序列生成过程开始与结束。

在这篇教程中,我们使用字符串 startseqendseq 来表示序列生成开始与结束。因此,在加载处理好的图片文字描述的过程中需要生成模型所需的数据形式。

我们定义一个函数来完成这样的过程:

def load_clean_descriptions(filename, dataset):doc = load_doc(filename)descriptions = dict()for line in doc.split('\n'):tokens = line.split()image_id, image_desc = tokens[0], tokens[1:]if image_id in dataset:if image_id not in descriptions:descriptions[image_id] = list()desc = 'startseq ' + ' '.join(image_desc) + ' endseq'descriptions[image_id].append(desc)return descriptions# 加载文字描述
train_descriptions = load_clean_descriptions('descriptions.txt', train)
print('Descriptions: train=%d' % len(train_descriptions))

接下来,我们需要加载VGG模型处理过的图片的特征向量文件,保存在了 features.pkl 文件中。我们定义一个函数完成这个功能:

def load_photo_features(filename, dataset):all_features = load(open(filename, 'rb'))# 过滤特征features = {k: all_features[k] for k in dataset}return features# 加载图片特征向量
train_features = load_photo_features('features.pkl', train)
print('Photos: train=%d' % len(train_features))

至此,我们已经完成了模型所需数据的加载过程。代码运行结果如下:

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

但是,除了VGG模型生成好的图片的特征向量可以被模型直接使用之外,图片的文字描述是不能直接被模型使用的。虽然已经为每个文字描述序列添加了开始符号与终止符号,我们依然需要对文字描述进行处理。

首先,在喂给模型前,图片的文字描述需要被编码成数字形式。我们需要定义一个前后一致的字典把单词映射成独一无二的整型数值。Keras框架已经提供了 Tokenizer 类来完成这样的映射过程,我们可以直接使用它的API。

我们定义两个函数,一个用来获取所有图片的文字描述,一个用来生成统一的Tokenizer实例。

from keras.preprocessing.text import Tokenizerdef to_lines(descriptions):all_desc = list()for key in descriptions.keys():[all_desc.append(d) for d in descriptions[key]]return all_descdef create_tokenizer(descriptions):lines = to_lines(descriptions)tokenizer = Tokenizer()tokenizer.fit_on_texts(lines)return tokenizer# 生成Tokenizer
tokenizer = create_tokenizer(train_descriptions)
vocab_size = len(tokenizer.word_index) + 1
print('Vocabulary Size: %d' % vocab_size)

每个文字描述需要被拆分成一个个单词形式,将每个单词及图片特征向量喂给模型来生成下一个单词,接着将这两个单词和图片特征向量喂给模型来生成下一个单词,循环此过程直到序列生成终止。

让我们举例说明这样的过程。输入序列 little girl running in field 会被拆分成6个输入-输出对形式,如下:

X1(图片) X2(文字序列) y (下一个单词)
photo startseq, little
photo startseq, little, girl
photo startseq, little, girl, running
photo startseq, little, girl, running, in
photo startseq, little, girl, running, in, field
photo startseq, little, girl, running, in, field, endseq

所以,我们也需要将每个文字描述编码成这样的形式。下面,我们定义一个函数来完成这个过程。

from numpy import array
from keras.preprocessing.sequence import pad_sequences
from keras.utils import to_categoricaldef create_sequences(tokenizer, max_length, descriptions, photos, vocab_size):X1, X2, y = list(), list(), list()for key, desc_list in descriptions.items():for desc in desc_list:seq = tokenizer.texts_to_sequences([desc])[0]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]X1.append(photos[key][0])X2.append(in_seq)y.append(out_seq)return array(X1), array(X2), array(y)# 生成文本序列
X1train, X2train, ytrain = create_sequences(tokenizer, max_length, train_descriptions, train_features, vocab_size)

我们需要计算模型能够接收到的文字序列的最大长度。函数如下:

def max_length(descriptions):lines = to_lines(descriptions)return max(len(d.split()) for d in lines)# 计算最大序列长度
max_length = max_length(train_descriptions)
print('Description Length: %d' % max_length)

至此,我们已经完成了模型训练前的数据集处理过程。

2. 构建模型

正如教程第一节所说,用文字描述图片信息这项任务对人类而言非常简单,人类天生具备整合图片与文本信息的能力。但是机器却不可以。因此,我们必须用图片与文本两种类型的信息作为输入来训练我们的看图说话模型。如何在同一个模型当中整合两种类型的信息?本教程采用相加合并的方式去整合。我们的看图说话模型基本的架构图如下所示:

也有一些其他的合并方式,大家可以参见下面两篇论文查看详细的论述:

  1. 《Where to put the Image in an Image Caption Generator》
  2. 《What is the Role of Recurrent Neural Networks (RNNs) in an Image Caption Generator?》

从架构图中可以看出,模型主要分为三个部分:

  1. 图片特征提取器:用VGG16模型去提取图片特征,去掉VGG16模型的最后一层图片分类层,用最后预测出来的图片特征作为看图说话模型的图片输入。VGG16模型预测的结果是一个4096维的向量,我们用Dense层处理后生成256维特征表示。
  2. 文本序列处理器:首先用Embedding层将文字映射为对应的词向量,然后利用LSTM长短程记忆网络处理文本序列,生成最后的序列特征向量,维度为256。
  3. 序列解码器:我们将图片特征与文本特征进行相加合并,用Dense层进行处理后预测下一个单词。

模型定义代码如下:

def define_model(vocab_size, max_length):# 图片特征提取模型inputs1 = Input(shape=(4096, ))fe1 = Dropout(0.5)(inputs1)fe2 = Dense(256, activation='relu')(fe1)# 文本序列模型inputs2 = Input(shape=(max_length, ))se1 = Embedding(vocab_size, 256, mask_zeros=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.compile(loss='categorical_crossentropy', optimizer='adam')# 打印模型概要信息print(model.summary())return model

我们的看图说话模型的概要信息如下:

3. 拟合模型

模型构建完毕后,我们就要开始在训练集上训练我们的模型了。

为了避免模型过拟合,需要用验证集去监测模型的性能表现。这也是模型训练的必需的做法。当每一次迭代结束在验证集上的模型性能有所提升时,我们就需要保存当前最优模型。在模型训练完成后,我们依然需要全局最优的模型去进行测试与部署。

Keras框架已经用 ModelCheckpoint 类实现了模型性能检测回调,详细的使用方法如下:

# 定义模型文件的保存路径
filepath = '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 值为新建的 ModelCheckpoint 的实例。而且,我们也需要在 fit 函数中指定参数 validation_data 值为加载的验证集。

我们设置训练20个epochs,每个epoch迭代完成大约需要花费30分钟,时间长短取决于机器性能。

# 模型拟合
model.fit([X1train, X2train], ytrain, epochs=20, verbose=2, callbacks=[checkpoint], validation_data=[X1dev, X2dev], ytest)

4. 完整代码

本小节涉及到的完整代码如下:

from numpy import array
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
from keras.utils import to_categoricaldef load_set(filename):doc = load_doc(filename)dataset = list()# 按行处理for line in doc.split('\n'):# 跳过空行if len(line) < 1:continue# 获取图片标志符(ID)identifier = line.split('.')[0]dataset.append(identifier)return set(dataset)def load_clean_descriptions(filename, dataset):doc = load_doc(filename)descriptions = dict()for line in doc.split('\n'):tokens = line.split()image_id, image_desc = tokens[0], tokens[1:]if image_id in dataset:if image_id not in descriptions:descriptions[image_id] = list()desc = 'startseq ' + ' '.join(image_desc) + ' endseq'descriptions[image_id].append(desc)return descriptionsdef load_photo_features(filename, dataset):all_features = load(open(filename, 'rb'))# 过滤特征features = {k: all_features[k] for k in dataset}return featuresdef to_lines(descriptions):all_desc = list()for key in descriptions.keys():[all_desc.append(d) for d in descriptions[key]]return all_descdef create_tokenizer(descriptions):lines = to_lines(descriptions)tokenizer = Tokenizer()tokenizer.fit_on_texts(lines)return tokenizerdef create_sequences(tokenizer, max_length, descriptions, photos, vocab_size):X1, X2, y = list(), list(), list()for key, desc_list in descriptions.items():for desc in desc_list:seq = tokenizer.texts_to_sequences([desc])[0]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]X1.append(photos[key][0])X2.append(in_seq)y.append(out_seq)return array(X1), array(X2), array(y)def max_length(descriptions):lines = to_lines(descriptions)return max(len(d.split()) for d in lines)# 加载训练集
filename = 'Flickr8k_text/Flickr_8k.trainImages.txt'
train = load_set(filename)
print('Dataset: %d' % len(train))
# 加载文字描述
train_descriptions = load_clean_descriptions('descriptions.txt', train)
print('Descriptions: train=%d' % len(train_descriptions))
# 加载图片特征向量
train_features = load_photo_features('features.pkl', train)
print('Photos: train=%d' % len(train_features))
# 生成Tokenizer
tokenizer = create_tokenizer(train_descriptions)
vocab_size = len(tokenizer.word_index) + 1
print('Vocabulary Size: %d' % vocab_size)
# 计算最大序列长度
max_length = max_length(train_descriptions)
print('Description Length: %d' % max_length)
# 生成文本序列
X1train, X2train, ytrain = create_sequences(tokenizer, max_length, train_descriptions, train_features, vocab_size)# 验证集
filename = 'Flickr8k_text/Flickr_8k.devImages.txt'
dev = load_set(filename)
print('Dataset: %d' % len(dev))
dev_descriptions = load_clean_descriptions('descriptions.txt', dev)
print('Descriptions: dev=%d' % len(dev_descriptions))
dev_features = load_photo_features('features.pkl', dev)
print('Photos: dev=%d' % len(dev_features))
# prepare sequences
X1dev, X2dev, ydev = create_sequences(tokenizer, max_length, dev_descriptions, dev_features, vocab_size)# 模型定义
model = define_model(vocab_size, max_length)
# 模型回调定义
filepath = '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')
# 模型拟合
model.fit([X1train, X2train], ytrain, epochs=20, verbose=2, callbacks=[checkpoint], validation_data=([X1dev, X2dev], ydev))

5. 结束语

感谢花费宝贵时间阅读本节教程,敬请期待下一节!希望您能这篇教程中受益匪浅!也特别欢迎大家在评论区提出宝贵的改进意见。如有错误或表述不当之处,也欢迎指正出来!


想要了解更多的自然语言处理最新进展、技术干货及学习教程,欢迎关注微信公众号“语言智能技术笔记簿”或扫描二维码添加关注。

看图说话实战教程 | 第三节 | 模型构建及训练相关推荐

  1. 看图说话实战教程 | 第一节 | 教程简介及准备工作

    引言 从上图中你看到了什么? 你会说"一条站在草地上的小狗"."草地上的一条白色小狗"等等,这些都是能够描述这张图片传达的信息的. 对我们人类而言,用文字描述图 ...

  2. 实战 | 一行命令实现看图说话(Google的im2txt模型)

    一行命令实现看图说话|Google的im2txt模型 1.项目介绍 这次给大家介绍一个google的深度学习模型im2txt,这个模型的作用跟它的名字一样,image-to-text,把图像转为文字, ...

  3. 超详细!“看图说话”(Image Caption)项目实战

    超详细!基于pytorch的"看图说话"(Image Caption)项目实战 0.简介 1.运行环境 1.1 我的环境 1.2 建立环境 2.理论介绍 3.运行项目 3.1 项目 ...

  4. 谷歌发布最新看图说话模型,可实现零样本学习,多类型任务也能直接上手

    点击上方"视学算法",选择加"星标"或"置顶" 重磅干货,第一时间送达 兴坤 发自 凹非寺 量子位 报道 | 公众号 QbitAI 谷歌新推 ...

  5. 《预训练周刊》第27期:谷歌发布最新看图说话模型、GitHub:平台上30%的新代码受益于AI助手Copilot...

    No.27 智源社区 预训练组 预 训 练 研究 观点 资源 活动 关于周刊 本期周刊,我们选择了9篇预训练相关的论文,涉及少样本理解.图像检测.决策图.大模型微调.对话微调.分子建模.蛋白质结构预测 ...

  6. cytoscape3.2.0 java_【看图说话】Cytoscape的“傻瓜式”教程

    原标题:[看图说话]Cytoscape的"傻瓜式"教程 在这个世界,一个人的颜值和内涵很重要:在科研界中,优秀的科研论文除了创新的研究内容外,也需要"高颜值"的 ...

  7. 谷歌浏览器中文版_中国科学家设计超薄指尖传感器,厚度不到A4纸五分之一 / 谷歌发布地图时光机:百年前,你家街道啥样?/ AI看图说话首超人类...

    关注我们了解计算机视觉最新动态 ! 动态先览 1 中国科学家设计超薄指尖传感器,厚度 不到A4纸五分之一 2 谷歌发布地图「时光机」:100年前, 你家街道长啥样? 3 仿真环境跟车2分钟,就让自动驾 ...

  8. 教你用PyTorch实现“看图说话”(附代码、学习资源)

    作者:FAIZAN SHAIKH 翻译:和中华 校对:白静 本文共2200字,建议阅读10分钟. 本文用浅显易懂的方式解释了什么是"看图说话"(Image Captioning), ...

  9. 【深度学习】实验6布置:图像自然语言描述生成(让计算机“看图说话”)

    DL_class 学堂在线<深度学习>实验课代码+报告(其中实验1和实验6有配套PPT),授课老师为胡晓林老师.课程链接:https://www.xuetangx.com/training ...

最新文章

  1. 4、数据类型二:Lists
  2. elasticsearch5.3安装插件head
  3. spark中用println输出一些控制信息
  4. python neo4j嵌入_Neo4j推出基于Python的嵌入式图数据存储
  5. 浮点数的表示和基本运算 【转载】
  6. ::selection
  7. 20个让Web Developer开发生涯更加轻松的工具
  8. pytorch1.0 用torch script导出模型
  9. java 字符界面 curses_[C++]Linux之图形界面编程库[curses库]之入门教程
  10. SQL On Linux 初体验
  11. BZOJ-1050-[HAOI2006]旅行comf(并查集)
  12. 【VR】虚拟现实软件开发工具与技术
  13. 详解缠中说禅走势中枢
  14. 如何去除微信小程序 wxParse 解析富文本图片间隙问题
  15. 用devc++表白_表白墙第42期|别人深夜买醉,我只想买你的心
  16. Tomcat文件包含漏洞:CNVD-2020-10487(简介/验证/利用/修复)
  17. WWDC22:ARKit 6低调发布,新功能瞄准VST MR头显
  18. 费曼技巧:学习任何东西的最佳方法
  19. 小米手机便签软件的语音玩法介绍
  20. Linux死锁调试之softlockup

热门文章

  1. 【已解决】微信更新后截图画质模糊修复
  2. 关于c语言程序设计的想法,关于《C语言程序设计》学习的几点思考
  3. 我不强求你做快乐的孩子——心理咨询师父亲给女儿的公开信
  4. puzzle(0916)智行营救、勇往直前
  5. 一个美妙的网络流总结
  6. 【电气专业知识问答】问:高压断路器失灵保护的工作原理是什么?
  7. 对象、super、isa、class 和 SuperClass 的理解
  8. Amberprepgen | 准备”氨基酸”式的残基库文件
  9. 基于微信小程序的宠物店商城的设计与实现
  10. sp2 xp 英文版序列号_原版Windows XP SP2 VOL的安装序列号