图像字幕

使用CNN和Transformer实现一个图像字幕模型。
作者

A_K_Nain

dfalbel- R翻译

设置

图书馆(tensorflow)图书馆(keras)图书馆(tfdatasets)

下载数据集

本教程将使用Flickr8K数据集。这个数据集包括8000多张图片,每张图片都配有5个不同的标题。

flickr_images<-get_file“fickr8k.zip”“https://github.com/jbrownlee/Datasets/releases/download/Flickr8k/Flickr8k_Dataset.zip”flickr_text<-get_file“flickr9k_text.zip”“https://github.com/jbrownlee/Datasets/releases/download/Flickr8k/Flickr8k_text.zip”如果fs::dir_exists(fs::路径(fs::path_dir(flickr_text),“Flicker8k_Dataset”))) {解压缩(flickr_imagesexdir =fs::path_dir(flickr_images))解压缩(flickr_textexdir =fs::path_dir(flickr_text))
#图像的路径IMAGES_PATH<-“Flicker8k_Dataset”#所需图像尺寸IMAGE_SIZE<-形状299299词汇量VOCAB_SIZE<-10000#任何序列允许的固定长度SEQ_LENGTH<-25#图像嵌入和标记嵌入的维度EMBED_DIM<-512#前馈网络中的每层单位FF_DIM<-512#其他培训参数BATCH_SIZE<-64时代<-30.自动调谐<-特遣部队数据自动调谐

准备数据集

标题<-fs::路径(fs::path_dir(flickr_text),“Flickr8k.token.txt”% > %readr::read_delimcol_names =c“img”“标题”),delim =\ t% > %tidyr::单独的(img,在=c“img”“caption_id”),9月=“#”% > %dplyr::选择(img,标题)% > %dplyr::group_by(img)% > %dplyr::总结标题=列表(标题))% > %dplyr::变异img =fs::路径(fs::path_dir(flickr_text),“Flicker8k_Dataset”, img))火车<-fs::路径(fs::path_dir(flickr_text),“Flickr_8k.trainImages.txt”% > %readr::read_line()有效的<-fs::路径(fs::path_dir(flickr_text),“Flickr_8k.devImages.txt”% > %readr::read_line()测验<-fs::路径(fs::path_dir(flickr_text),“Flickr_8k.testImages.txt”% > %readr::read_line()train_data<-标题% > %dplyr::过滤器(fs::path_file(img)%, %火车)valid_data<-标题% > %dplyr::过滤器(fs::path_file(img)%, %测试)dplyr::n_distinct(train_dataimg)dplyr::n_distinct(valid_dataimg)

向量化文本数据

我们将使用text_vectorization层来向量化文本数据,也就是说,将原始字符串转换为整数序列,其中每个整数表示词汇表中一个单词的索引。我们将使用自定义的字符串标准化方案(除标点符号之外的条形字符)<而且>)和默认的分割方案(对空白进行分割)。

标点符号<-c"!"\ \\”“#”“$”“%”“&”“””(““)”“*”“+””、““-”“。”“/””:“”;““=”“?”“@”“(”\ \\ \“]”“^”“_”“”“{”“|”“}”“~”再保险<-网状::进口“重新”punctuation_group<-标点符号% > %酸式焦磷酸钠(重新逃避)% > %paste0崩溃=""% > %sprintf“(% s)”,)。custom_standardization<-函数(input_string) {小写字母<-特遣部队字符串较低的(input_string)特遣部队字符串regex_replace(punctuation_group小写""向量化<-layer_text_vectorizationmax_tokens =VOCAB_SIZE,output_mode =“int”output_sequence_length =SEQ_LENGTH,标准化=custom_standardization,向量化% > %适应unlist(train_data标题))#图像数据增强image_augmentation<-keras_model_sequential()% > %layer_random_flip“水平”% > %layer_random_rotation0.2% > %layer_random_contrast0.3

构建用于训练的TensorFlow数据集管道

我们将生成图像对和相应的标题使用tf数据集美元对象。该管道由两个步骤组成:

  1. 从磁盘读取映像
  2. 将图片对应的五个标题标记化
decode_and_resize<-函数(img_path) {img_path% > %特遣部队ioread_file()% > %特遣部队图像decode_jpeg渠道=3.% > %特遣部队图像调整(IMAGE_SIZE)% > %特遣部队图像convert_image_dtype(特遣部队float32)process_input<-函数(img_path,标题){网状::元组decode_and_resize(img_path),向量化(字幕)make_dataset<-函数(数据){数据% > %unname()% > %tensor_slices_dataset()% > %dataset_shufflenrow(数据)% > %dataset_map(process_inputnum_parallel_calls =自动调谐)% > %dataset_batch(BATCH_SIZE)% > %dataset_prefetch(自动调谐)#传递图像列表和相应的标题列表train_dataset<-make_dataset(train_data)valid_dataset<-make_dataset(valid_data)

构建模型

我们的图像字幕架构由三个模型组成:

  1. CNN:用于提取图像特征
  2. TransformerEncoder:然后将提取的图像特征传递给基于Transformer的编码器,该编码器生成输入的新表示
  3. TransformerDecoder:该模型将编码器输出和文本数据(序列)作为输入,并尝试学习生成标题。
get_cnn_model<-函数() {base_model<-application_efficientnet_b0input_shape =c(IMAGE_SIZE3.),include_top =重量=“imagenet”#我们冻结我们的特征提取器base_model可训练的<-base_model_out<-base_model输出% > %layer_reshapetarget_shape =c-1尾巴昏暗的(base_model输出),1)))keras_model(base_model输入、base_model_out)transformer_encoder_block<-new_layer_class“transformer_encoder_block”初始化=函数(embed_dim, dense_dim, num_heads,…){超级()__init__(…)自我embed_dim<-embed_dim自我dense_dim<-dense_dim自我num_heads<-num_heads自我attention_1<-layer_multi_head_attentionnum_heads =num_heads,key_dim =embed_dim,辍学=0.0自我layernorm_1<-layer_normalization()自我layernorm_2<-layer_normalization()自我dense_1<-layer_dense单位=embed_dim,激活=“relu”},电话=函数(输入、培训掩码=) {输入<-自我layernorm_1(输入)输入<-自我dense_1(输入)attention_output_1<-自我attention_1查询=输入,值=输入,关键=输入,attention_mask =培训=培训,out_1<-自我layernorm_2(输入+attention_output_1)out_1positional_embedding<-new_layer_class“positional_embedding”初始化=函数(sequence_length, vocab_size, embed_dim,…){超级()__init__(…)自我token_embeddings<-layer_embeddinginput_dim =vocab_size,output_dim =embed_dim自我position_embeddings<-layer_embeddinginput_dim =sequence_length,output_dim =embed_dim自我sequence_length<-sequence_length自我vocab_size<-vocab_size自我embed_dim<-embed_dim自我embed_scale<-特遣部队数学√6(特遣部队(embed_dim特遣部队float32))},电话=函数(输入){长度<-尾巴昏暗的(输入),1职位<-特遣部队范围开始=0 l,限制=长度,δ=1 l)embedded_tokens<-自我token_embeddings(输入)embedded_tokens<-embedded_tokens自我embed_scaleembedded_positions<-自我position_embeddings(职位)embedded_tokens+embedded_positions},compute_mask =函数(输入,掩码){特遣部队数学not_equal(输入0 l)transformer_decoder_block<-new_layer_class“transformer_decoder_block”初始化=函数(embed_dim, ff_dim, num_heads,…){超级()__init__(…)自我embed_dim<-embed_dim自我ff_dim<-ff_dim自我num_heads<-num_heads自我attention_1<-layer_multi_head_attentionnum_heads =num_heads,key_dim =embed_dim,辍学=0.1自我attention_2<-layer_multi_head_attentionnum_heads =num_heads,key_dim =embed_dim,辍学=0.1自我ffn_layer_1<-layer_dense单位=ff_dim,激活=“relu”自我ffn_layer_2<-layer_dense单位=embed_dim)自我layernorm_1<-layer_normalization()自我layernorm_2<-layer_normalization()自我layernorm_3<-layer_normalization()自我嵌入<-positional_embeddingembed_dim =EMBED_DIM,sequence_length =SEQ_LENGTH,vocab_size =VOCAB_SIZE自我<-layer_dense单位=VOCAB_SIZE,激活=“softmax”自我dropout_1<-layer_dropout率=0.3自我dropout_2<-layer_dropout率=0.5自我卡塔尔世界杯欧洲预选赛赛程表supports_masking<-真正的},电话=函数(input, encoder_outputs, training,掩码=) {输入<-自我嵌入(输入)causal_mask<-自我get_causal_attention_mask(输入)如果is.null(面具)){padding_mask<-特遣部队(掩码[,,tf .newaxis),dtype =特遣部队int32)combined_mask<-特遣部队(面具,特遣部队newaxis,),dtype =特遣部队int32)combined_mask<-特遣部队最低(combined_mask causal_mask)attention_output_1<-自我attention_1查询=输入,值=输入,关键=输入,attention_mask =combined_mask,培训=培训,out_1<-自我layernorm_1(输入+attention_output_1)attention_output_2<-自我attention_2查询=out_1,值=encoder_outputs,关键=encoder_outputs,attention_mask =padding_mask,培训=培训,out_2<-自我layernorm_2(out_1+attention_output_2)ffn_out<-自我ffn_layer_1(out_2)ffn_out<-自我dropout_1(ffn_out培训=培训)ffn_out<-自我ffn_layer_2(ffn_out)ffn_out<-自我layernorm_3(ffn_out+out_2,培训=培训)ffn_out<-自我dropout_2(ffn_out培训=培训)仅仅<-自我(ffn_out)仅仅},get_causal_attention_mask =函数(输入){input_shape<-特遣部队形状(输入)batch_size<-input_shape [1sequence_length<-input_shape [2<-特遣部队范围(sequence_length)[,特遣部队newaxis]j<-特遣部队范围(sequence_length)面具<-特遣部队(我> =j,dtype =“int32”面具<-特遣部队重塑(面具,列表(1 l, input_shape [2], input_shape [2)))<-特遣部队concat列表特遣部队expand_dims(batch_size-1 l),as_tensorc(1 l, l),dtype =特遣部队int32)),轴=0 l)特遣部队瓷砖(面具,乘)image_captioning_model<-new_model_class“image_captioning_model”初始化=函数(cnn_model,编码器,解码器,num_captions_per_image =5image_aug =) {超级()__init__()自我cnn_model<-cnn_model自我编码器<-编码器自我译码器<-译码器自我loss_tracker<-metric_meanname =“损失”自我acc_tracker<-metric_meanname =“准确性”自我num_captions_per_image<-num_captions_per_image自我image_aug<-image_aug},calculate_loss =函数(y_true, y_pred, mask) {损失<-自我损失(y_true y_pred)面具<-特遣部队(面具,dtype =损失dtype)损失<-损失面具特遣部队reduce_sum(损失)/特遣部队reduce_sum(口罩)},calculate_accuracy =函数(y_true, y_pred, mask) {精度<-特遣部队平等的(y_true特遣部队argmax(y_pred轴=2 l))精度<-特遣部队数学logical_and(面具、准确性)精度<-特遣部队(精度,dtype =特遣部队float32)面具<-特遣部队(面具,dtype =特遣部队float32)特遣部队reduce_sum(精度)/特遣部队reduce_sum(口罩)},.compute_caption_loss_and_acc =函数(batch_seq img_embed培训=真正的) {encoder_out<-自我编码器(img_embed培训=培训)batch_seq_inp<-batch_seq (,:-2batch_seq_true<-batch_seq (,2面具<-特遣部队数学not_equal(batch_seq_true 0 l)batch_seq_pred<-自我译码器batch_seq_inp encoder_out,培训=培训,掩码=面具损失<-自我calculate_loss(batch_seq_true, batch_seq_pred, mask)acc<-自我calculate_accuracy(batch_seq_true, batch_seq_pred, mask)列表(acc)损失},train_step =函数(batch_data) {batch_img<-batch_data [[1]]batch_seq<-batch_data [[2]]batch_loss<-0batch_acc<-0如果is.null(自我image_aug)) {batch_img<-自我image_aug(batch_img)# 1。获取图像嵌入img_embed<-自我cnn_model(batch_img)# 2。将五个标题逐一传递给解码器#与编码器输出一起,并计算损失和精度#为每个标题。(我seq_len(自我num_captions_per_image)) {(特遣部队GradientTape()%, %胶带,{c(acc)损失% < - %自我.compute_caption_loss_and_accImg_embed, batch_seq[, i,],培训=真正的# 3。更新损耗和精度batch_loss<-batch_loss+损失batch_acc<-batch_acc+acc})# 4。拿到所有可训练重量的清单train_vars<-c(自我编码器trainable_variables,自我译码器trainable_variables)# 5。获取渐变毕业生<-磁带梯度(train_vars损失)# 6。更新可训练的重量自我优化器apply_gradientszip_lists(毕业生train_vars))# 7。更新跟踪器batch_acc<-batch_acc/自我num_captions_per_image自我loss_trackerupdate_state(batch_loss)自我acc_trackerupdate_state(batch_acc)# 8。返回损失和精度值列表损失=自我loss_tracker结果(),acc =自我acc_tracker结果()},test_step =函数(batch_data) {batch_img<-batch_data [[1]]batch_seq<-batch_data [[2]]batch_loss<-0batch_acc<-0# 1。获取图像嵌入img_embed<-自我cnn_model(batch_img)# 2。将五个标题逐一传递给解码器#与编码器输出一起,并计算损失和精度#为每个标题。(我seq_len(自我num_captions_per_image)) {(特遣部队GradientTape()%, %胶带,{c(acc)损失% < - %自我.compute_caption_loss_and_accImg_embed, batch_seq[, i,],培训=真正的# 3。更新损耗和精度batch_loss<-batch_loss+损失batch_acc<-batch_acc+acc})batch_acc<-batch_acc/自我num_captions_per_image# 4。更新跟踪器自我loss_trackerupdate_state(batch_loss)自我acc_trackerupdate_state(batch_acc)# 5。返回损失和精度值列表“损失”自我loss_tracker结果(),“acc”自我acc_tracker结果()},指标=mark_active函数() {#我们需要在这里列出我们的指标,这样' reset_states() '就可以#自动调用。列表(自我loss_tracker,自我acc_tracker)})cnn_model<-get_cnn_model()编码器<-transformer_encoder_blockembed_dim =EMBED_DIM,dense_dim =FF_DIM,num_heads =1译码器<-transformer_decoder_blockembed_dim =EMBED_DIM,ff_dim =FF_DIM,num_heads =2caption_model<-image_captioning_modelcnn_model =cnn_model,编码器=编码器,解码器=译码器,image_aug =image_augmentation

模型训练

#定义损失函数cross_entropy<-loss_sparse_categorical_crossentropyfrom_logits =减少=“没有”#早期停止标准early_stopping<-callback_early_stopping耐心=3.restore_best_weights =真正的#学习率调度器的优化lr_schedule<-new_learning_rate_schedule_class“lr_schedule”初始化=函数(post_warmup_learning_rate, warmup_steps) {超级()__init__()自我post_warmup_learning_rate<-post_warmup_learning_rate自我warmup_steps<-warmup_steps},电话=函数(步骤){global_step<-特遣部队(步骤,特遣部队float32)warmup_steps<-特遣部队(自我warmup_steps,特遣部队float32)warmup_progress<-global_step/warmup_stepswarmup_learning_rate<-自我post_warmup_learning_ratewarmup_progress特遣部队气孔导度global_step<warmup_steps,函数() warmup_learning_rate,函数()的自我post_warmup_learning_rate#创建一个学习速度表num_train_steps<-长度(train_dataset)时代num_warmup_steps<-num_train_steps% / %15lr<-lr_schedulepost_warmup_learning_rate =1的军医warmup_steps =num_warmup_steps)#编译模型caption_model% > %编译优化器=optimizer_adamlearning_rate =lr),损失=cross_entropy#适合模型caption_model% > %适合train_dataset,时代=时代,validation_data =valid_dataset,回调函数=列表(early_stopping)

检查样本预测

词汇<-get_vocabulary(向量化)max_decoded_sentence_length<-SEQ_LENGTH-1valid_images<-valid_dataimggenerate_caption<-函数() {#从验证数据集中随机选择一张图像sample_img<-样本(valid_images1#从磁盘中读取映像sample_img<-decode_and_resize(sample_img)img<-as.array(特遣部队clip_by_value(sample_img0255))img% > %as.rastermax =255% > %情节()#把图片传给CNNimg<-特遣部队expand_dims(sample_img 0 l)img<-caption_modelcnn_model(img)#传递图像特征到Transformer编码器encoded_img<-caption_model编码器(img,培训=使用Transformer解码器生成标题decoded_caption<-“<开始>”(我seq_len(max_decoded_sentence_length)) {tokenized_caption<-向量化列表(decoded_caption))面具<-特遣部队数学not_equal(tokenized_caption 0 l)预测<-caption_model译码器tokenized_caption encoded_img,培训=掩码=面具sampled_token_index<-特遣部队argmax(预测[1, i,])sampled_token<-词汇(as.integer(sampled_token_index)+1如果(sampled_token= =“< >”) {打破decoded_caption<-粘贴(sampled_token decoded_caption9月=”““预测标题:”decoded_caption)#检查几个样本的预测generate_caption()generate_caption()generate_caption()

最后指出

我们看到,在几个epoch之后,模型开始生成合理的标题。为了让这个例子易于运行,我们用一些限制来训练它,比如最少数量的注意力头。为了改进预测,您可以尝试更改这些训练设置,并为您的用例找到一个好的模型。