图书馆(tensorflow)图书馆(keras)图书馆(tfdatasets)
图像字幕
使用CNN和Transformer实现一个图像字幕模型。
设置
下载数据集
本教程将使用Flickr8K数据集。这个数据集包括8000多张图片,每张图片都配有5个不同的标题。
<-get_file( flickr_images“fickr8k.zip”,“https://github.com/jbrownlee/Datasets/releases/download/Flickr8k/Flickr8k_Dataset.zip” )<-get_file( flickr_text“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)) }
#图像的路径<-“Flicker8k_Dataset” IMAGES_PATH#所需图像尺寸<-形状(299,299) IMAGE_SIZE词汇量<-10000 VOCAB_SIZE#任何序列允许的固定长度<-25 SEQ_LENGTH#图像嵌入和标记嵌入的维度<-512 EMBED_DIM#前馈网络中的每层单位<-512 FF_DIM#其他培训参数<-64 BATCH_SIZE<-30. 时代<-特遣部队$数据$自动调谐 自动调谐
准备数据集
<-fs::路径(fs::path_dir(flickr_text),“Flickr8k.token.txt”)% > % 标题::read_delim( readrcol_names =c(“img”,“标题”),delim ="\ t"% > % )::单独的(img,在=c(“img”,“caption_id”),9月=“#”)% > % tidyr::选择(img,标题)% > % dplyr::group_by(img)% > % dplyr::总结(标题=列表(标题))% > % dplyr::变异(img =fs::路径(fs::path_dir(flickr_text),“Flicker8k_Dataset”, img)) dplyr<-fs::路径(fs::path_dir(flickr_text),“Flickr_8k.trainImages.txt”)% > % 火车::read_line() readr<-fs::路径(fs::path_dir(flickr_text),“Flickr_8k.devImages.txt”)% > % 有效的::read_line() readr<-fs::路径(fs::path_dir(flickr_text),“Flickr_8k.testImages.txt”)% > % 测验::read_line() readr<-标题% > % train_data::过滤器(fs::path_file(img)%, %火车) dplyr<-标题% > % valid_data::过滤器(fs::path_file(img)%, %测试) dplyr::n_distinct(train_data$img) dplyr::n_distinct(valid_data$img) dplyr
向量化文本数据
我们将使用text_vectorization
层来向量化文本数据,也就是说,将原始字符串转换为整数序列,其中每个整数表示词汇表中一个单词的索引。我们将使用自定义的字符串标准化方案(除标点符号之外的条形字符)<
而且>
)和默认的分割方案(对空白进行分割)。
<-c("!","\ \","\”",“#”,“$”,“%”,“&”,“”,”(“,“)”,“*”, 标点符号“+”,”、“,“-”,“。”,“/”,”:“,”;“,“=”,“?”,“@”,“(”,"\ \","\ \",“]”,“^”,“_”,“”,“{”,“|”,“}”,“~”)<-网状::进口(“重新”) 再保险<-标点符号% > % punctuation_group酸式焦磷酸钠(重新$逃避)% > %paste0(崩溃="")% > %sprintf(“(% s)”,)。<-函数(input_string) { custom_standardization<-特遣部队$字符串$较低的(input_string) 小写字母$字符串$regex_replace(punctuation_group小写"") 特遣部队 }<-layer_text_vectorization( 向量化max_tokens =VOCAB_SIZE,output_mode =“int”,output_sequence_length =SEQ_LENGTH,标准化=custom_standardization, )% > %适应(unlist(train_data$标题)) 向量化#图像数据增强<-keras_model_sequential()% > % image_augmentationlayer_random_flip(“水平”)% > %layer_random_rotation(0.2)% > %layer_random_contrast(0.3)
构建用于训练的TensorFlow数据集管道
我们将生成图像对和相应的标题使用tf数据集美元
对象。该管道由两个步骤组成:
- 从磁盘读取映像
- 将图片对应的五个标题标记化
<-函数(img_path) { decode_and_resize% > % img_path$io$read_file()% > % 特遣部队$图像$decode_jpeg(渠道=3.)% > % 特遣部队$图像$调整(IMAGE_SIZE)% > % 特遣部队$图像$convert_image_dtype(特遣部队$float32) 特遣部队 }<-函数(img_path,标题){ process_input::元组( 网状decode_and_resize(img_path),向量化(字幕) ) }<-函数(数据){ make_dataset% > %unname()% > % 数据tensor_slices_dataset()% > %dataset_shuffle(nrow(数据)% > %dataset_map(process_inputnum_parallel_calls =自动调谐)% > %dataset_batch(BATCH_SIZE)% > %dataset_prefetch(自动调谐) }#传递图像列表和相应的标题列表<-make_dataset(train_data) train_dataset<-make_dataset(valid_data) valid_dataset
构建模型
我们的图像字幕架构由三个模型组成:
- CNN:用于提取图像特征
- TransformerEncoder:然后将提取的图像特征传递给基于Transformer的编码器,该编码器生成输入的新表示
- TransformerDecoder:该模型将编码器输出和文本数据(序列)作为输入,并尝试学习生成标题。
<-函数() { get_cnn_model<-application_efficientnet_b0( base_modelinput_shape =c(IMAGE_SIZE3.),include_top =假,重量=“imagenet” )#我们冻结我们的特征提取器$可训练的<-假 base_model<-base_model$输出% > % base_model_outlayer_reshape(target_shape =c(-1,尾巴(昏暗的(base_model$输出),1)))keras_model(base_model$输入、base_model_out) }<-new_layer_class( transformer_encoder_block“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_attention( 自我num_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_1( attention_output_1查询=输入,值=输入,关键=输入,attention_mask =零,培训=培训, )<-自我$layernorm_2(输入+attention_output_1) out_1 out_1 } )<-new_layer_class( positional_embedding“positional_embedding”,初始化=函数(sequence_length, vocab_size, embed_dim,…){超级()$`__init__`(…)$token_embeddings<-layer_embedding( 自我input_dim =vocab_size,output_dim =embed_dim )$position_embeddings<-layer_embedding( 自我input_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) 职位<-自我$token_embeddings(输入) embedded_tokens<-embedded_tokens*自我$embed_scale embedded_tokens<-自我$position_embeddings(职位) embedded_positions+embedded_positions embedded_tokens },compute_mask =函数(输入,掩码){$数学$not_equal(输入0 l) 特遣部队 } )<-new_layer_class( transformer_decoder_block“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_attention( 自我num_heads =num_heads,key_dim =embed_dim,辍学=0.1 )$attention_2<-layer_multi_head_attention( 自我num_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_embedding( 自我embed_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,掩码=零) {<-自我$嵌入(输入) 输入<-自我$get_causal_attention_mask(输入) causal_mask如果(!is.null(面具)){<-特遣部队$投(掩码[,,tf .$newaxis),dtype =特遣部队$int32) padding_mask<-特遣部队$投(面具,特遣部队$newaxis,),dtype =特遣部队$int32) combined_mask<-特遣部队$最低(combined_mask causal_mask) combined_mask }<-自我$attention_1( attention_output_1查询=输入,值=输入,关键=输入,attention_mask =combined_mask,培训=培训, )<-自我$layernorm_1(输入+attention_output_1) out_1<-自我$attention_2( attention_output_2查询=out_1,值=encoder_outputs,关键=encoder_outputs,attention_mask =padding_mask,培训=培训, )<-自我$layernorm_2(out_1+attention_output_2) out_2<-自我$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<-自我$出(ffn_out) 仅仅 仅仅 },get_causal_attention_mask =函数(输入){<-特遣部队$形状(输入) input_shape<-input_shape [1] batch_size<-input_shape [2] sequence_length<-特遣部队$范围(sequence_length)[,特遣部队$newaxis] 我<-特遣部队$范围(sequence_length) j<-特遣部队$投(我> =j,dtype =“int32”) 面具<-特遣部队$重塑(面具,列表(1 l, input_shape [2], input_shape [2))) 面具<-特遣部队$concat(列表( 乘$expand_dims(batch_size-1 l), 特遣部队as_tensor(c(1 l, l),dtype =特遣部队$int32)轴=0 l) ),$瓷砖(面具,乘) 特遣部队 } )<-new_model_class( image_captioning_model“image_captioning_model”,初始化=函数(cnn_model,编码器,解码器,num_captions_per_image =5,image_aug =零) {超级()$`__init__`()$cnn_model<-cnn_model 自我$编码器<-编码器 自我$译码器<-译码器 自我$loss_tracker<-metric_mean(name =“损失”) 自我$acc_tracker<-metric_mean(name =“准确性”) 自我$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培训=真正的) {<-自我$编码器(img_embed培训=培训) encoder_out<-batch_seq (,零:-2] batch_seq_inp<-batch_seq (,2:零] batch_seq_true<-特遣部队$数学$not_equal(batch_seq_true 0 l) 面具<-自我$译码器( batch_seq_pred培训=培训,掩码=面具 batch_seq_inp encoder_out, )<-自我$calculate_loss(batch_seq_true, batch_seq_pred, mask) 损失<-自我$calculate_accuracy(batch_seq_true, batch_seq_pred, mask) acc列表(acc)损失 },train_step =函数(batch_data) {<-batch_data [[1]] batch_img<-batch_data [[2]] batch_seq<-0 batch_loss<-0 batch_acc如果(!is.null(自我$image_aug)) {<-自我$image_aug(batch_img) batch_img }# 1。获取图像嵌入<-自我$cnn_model(batch_img) img_embed# 2。将五个标题逐一传递给解码器#与编码器输出一起,并计算损失和精度#为每个标题。为(我在seq_len(自我$num_captions_per_image)) {与(特遣部队$GradientTape()%, %胶带,{c(acc)损失% < - %自我$.compute_caption_loss_and_acc(培训=真正的 Img_embed, batch_seq[, i,], )# 3。更新损耗和精度<-batch_loss+损失 batch_loss<-batch_acc+acc batch_acc })# 4。拿到所有可训练重量的清单<-c(自我$编码器$trainable_variables, train_vars$译码器$trainable_variables) 自我# 5。获取渐变<-磁带$梯度(train_vars损失) 毕业生# 6。更新可训练的重量$优化器$apply_gradients(zip_lists(毕业生train_vars)) 自我 }# 7。更新跟踪器<-batch_acc/自我$num_captions_per_image batch_acc$loss_tracker$update_state(batch_loss) 自我$acc_tracker$update_state(batch_acc) 自我# 8。返回损失和精度值列表(损失=自我$loss_tracker$结果(),acc =自我$acc_tracker$结果() ) },test_step =函数(batch_data) {<-batch_data [[1]] batch_img<-batch_data [[2]] batch_seq<-0 batch_loss<-0 batch_acc# 1。获取图像嵌入<-自我$cnn_model(batch_img) img_embed# 2。将五个标题逐一传递给解码器#与编码器输出一起,并计算损失和精度#为每个标题。为(我在seq_len(自我$num_captions_per_image)) {与(特遣部队$GradientTape()%, %胶带,{c(acc)损失% < - %自我$.compute_caption_loss_and_acc(培训=真正的 Img_embed, batch_seq[, i,], )# 3。更新损耗和精度<-batch_loss+损失 batch_loss<-batch_acc+acc batch_acc }) }<-batch_acc/自我$num_captions_per_image batch_acc# 4。更新跟踪器$loss_tracker$update_state(batch_loss) 自我$acc_tracker$update_state(batch_acc) 自我# 5。返回损失和精度值列表(“损失”=自我$loss_tracker$结果(),“acc”=自我$acc_tracker$结果() ) },指标=mark_active(函数() {#我们需要在这里列出我们的指标,这样' reset_states() '就可以#自动调用。列表(自我$loss_tracker,自我$acc_tracker) }) )<-get_cnn_model() cnn_model<-transformer_encoder_block(embed_dim =EMBED_DIM,dense_dim =FF_DIM,num_heads =1) 编码器<-transformer_decoder_block(embed_dim =EMBED_DIM,ff_dim =FF_DIM,num_heads =2) 译码器<-image_captioning_model( caption_modelcnn_model =cnn_model,编码器=编码器,解码器=译码器,image_aug =image_augmentation )
模型训练
#定义损失函数<-loss_sparse_categorical_crossentropy( cross_entropyfrom_logits =假,减少=“没有” )#早期停止标准<-callback_early_stopping(耐心=3.,restore_best_weights =真正的) early_stopping#学习率调度器的优化<-new_learning_rate_schedule_class( lr_schedule“lr_schedule”,初始化=函数(post_warmup_learning_rate, warmup_steps) {超级()$`__init__`()$post_warmup_learning_rate<-post_warmup_learning_rate 自我$warmup_steps<-warmup_steps 自我 },电话=函数(步骤){<-特遣部队$投(步骤,特遣部队$float32) global_step<-特遣部队$投(自我$warmup_steps,特遣部队$float32) warmup_steps<-global_step/warmup_steps warmup_progress<-自我$post_warmup_learning_rate*warmup_progress warmup_learning_rate$气孔导度( 特遣部队<warmup_steps, global_step函数() warmup_learning_rate,函数()的自我$post_warmup_learning_rate ) } )#创建一个学习速度表<-长度(train_dataset)*时代 num_train_steps<-num_train_steps% / %15 num_warmup_steps<-lr_schedule(post_warmup_learning_rate =1的军医,warmup_steps =num_warmup_steps) lr#编译模型% > %编译( caption_model优化器=optimizer_adam(learning_rate =lr),损失=cross_entropy )#适合模型% > %适合( caption_model train_dataset,时代=时代,validation_data =valid_dataset,回调函数=列表(early_stopping) )
检查样本预测
<-get_vocabulary(向量化) 词汇<-SEQ_LENGTH-1 max_decoded_sentence_length<-valid_data$img valid_images<-函数() { generate_caption#从验证数据集中随机选择一张图像<-样本(valid_images1) sample_img#从磁盘中读取映像<-decode_and_resize(sample_img) sample_img<-as.array(特遣部队$clip_by_value(sample_img0,255)) img% > %as.raster(max =255)% > %情节() img#把图片传给CNN<-特遣部队$expand_dims(sample_img 0 l) img<-caption_model$cnn_model(img) img#传递图像特征到Transformer编码器<-caption_model$编码器(img,培训=假) encoded_img使用Transformer解码器生成标题<-“<开始>” decoded_caption为(我在seq_len(max_decoded_sentence_length)) {<-向量化(列表(decoded_caption)) tokenized_caption<-特遣部队$数学$not_equal(tokenized_caption 0 l) 面具<-caption_model$译码器( 预测培训=假,掩码=面具 tokenized_caption encoded_img, )<-特遣部队$argmax(预测[1, i,]) sampled_token_index<-词汇(as.integer(sampled_token_index)+1] sampled_token如果(sampled_token= =“< >”) {打破 }<-粘贴(sampled_token decoded_caption9月=”“) decoded_caption }猫(“预测标题:”decoded_caption) }#检查几个样本的预测generate_caption()generate_caption()generate_caption()
最后指出
我们看到,在几个epoch之后,模型开始生成合理的标题。为了让这个例子易于运行,我们用一些限制来训练它,比如最少数量的注意力头。为了改进预测,您可以尝试更改这些训练设置,并为您的用例找到一个好的模型。