博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Referring Relationships 代码阅读笔记
阅读量:5237 次
发布时间:2019-06-14

本文共 15719 字,大约阅读时间需要 52 分钟。

前言

Ranjay Krishna,Ines Chami,Michael Bernstein,Li Fei-Fei,CVPR,2018,代码:

Referring Relationship 是 Li Fei-Fei 团队 2018 年 CVPR 的一篇论文,定义了这样的一个任务:给定一个 relationship <subject-predicate-object>,检测出图像中 relationship 涉及到的目标,如论文中下图所示:

作者指出在此类任务中,已知 relationship 中的某个目标位置会对另一个目标的检测带来帮助,同时通过将连接 relationship 中两个目标的 predicate 形式化为 attention 的转移,而非之前相关工作中将其形式化为某种特征(作者指出因为 predicate 的视觉表现差异很大),本文可以更好的利用目标间的关系来定位目标。论文给出算法的整个模型框架,如下图所示:

主要流程:

1. 给定图像和 relationship,图像经一个pre-trained 的网络提取图像视觉特征,image_feature(Resnet 或 VGG,可在输出后添加若干层卷积以便更有针对性地提取特征),特征 feature map 的尺寸是(L, L, C),C 通道数;

2. 将 subject 和 object(作者在代码中将 subject 和 object 表示为其类别 id,为一个整数)映射为一个稠密 C 维向量,embedded_subject,embedded_object

3. image_feature 分别与 embedded_subject、embedded_object逐位置进行内积,计算初始的 subject attention mapobject attention map,尺寸均为 (L, L, 1)

4. 以 subject attention map 为输入,经若干层卷积处理(卷积核尺寸为 kxk,中间层 feature map 通道数为 c,最后一层通道数为 1),计算 subject->objectpredicate shift  (L, L, 1);同时以 object attention map 为输入,经若干层卷积处理(配置与 subject->object 相同),计算 object->subjectpredicate shift  (L, L, 1)

5. 将 subject->object predicate shift 与 image_feature 相乘(相当于对 image_feature 的每个位置进行加权)后,逐位置 embedded_object 进行内积,计算得到新的 object attention map;同时, 将 object->subject predicate shift 与 image_feature 相乘,并逐位置与 embedded_subject 进行内积,计算得到新的 subject attention map

6. 更新 subject/object attention map,进行第 4 步,循环迭代多次;

7. 迭代完成之后,基于 subject attention map 计算 subject 区域,基于 object attention map 计算 object 区域(所以说论文里提供的上面流程图中最后的示例图像是反了吗?);

注意:第 5 步利用了作者所指出的:利用一个已知目标的位置,帮助另一个目标的检测。

 

代码阅读

阅读论文对整个算法的思想和流程有一个概念性的理解之后,通过阅读作者提供的,进一步理解算法。作者代码是基于 编写,不得不说,高手写的代码就是简洁美观,值得学习! 

数据准备

数据准备的代码在 中,主要是对训练和测试数据进行组织,便于网络训练时的数据载入。作者将图像和 relationship 组织为 hdf5 文件,其中 relationship 的 hdf5 文件包含 3 个 databasde,图像的 hdf5 文件包含一个 database,如下代码展示:

1 dataset = h5py.File(os.path.join(save_dir, 'dataset.hdf5'), 'w')2 categories_db = dataset.create_dataset('categories', (total_relationships, 4), dtype='f')3 subject_db = dataset.create_dataset('subject_locations', (total_relationships, self.output_dim, self.output_dim), dtype='f')4 object_db = dataset.create_dataset('object_locations', (total_relationships, self.output_dim, self.output_dim), dtype='f')5 6 dataset = h5py.File(os.path.join(save_dir, 'images.hdf5'), 'w')7 images_db = dataset.create_dataset('images',(num_images, self.im_dim, self.im_dim, 3), dtype='f')

total_relationships 是图像集合所有图像包含的 relationship 总数,output_dim 是网络输出的 subject/object attention map 尺寸,即前面介绍的 L

categories_db 存储了所有的 relationship,可以将其理解为 relationship x 4 的矩阵,矩阵每一行记录一个 relationship,形如:subject-predicate-object-image_id。subject、predicate、object分别为各自的类别标签,为整数,对于 VRD 数据集,subject、object 类别数为 100,predicate 类别数为 70,根据 image_id 可以在存储图像的 hdf5 文件中获取对应图像。

subject_db 存储了所有 relationship 中 subject 的 groundtruth mask,每个 mask 的尺寸 output_dim x output_dim

object_db 存储了所有 relationship 中 object 的 groundtruth mask,每个 mask 尺寸为 output_dim x output_dim

images_db 存储了所有的图像,图像已经过缩放和预处理,满足网络的输入要求,num_images 是图像数目,im_dim 为缩放后的图像尺寸;

获取数据的代码如下所示:

1 images = h5py.File(os.path.join(self.data_dir, 'images.hdf5'), 'r')2 dataset = h5py.File(os.path.join(self.data_dir, 'dataset.hdf5'), 'r')3 self.images = images['images']4 self.categories = dataset['categories']5 self.subjects = dataset['subject_locations']6 self.objects = dataset['object_locations']

 

训练时数据获取

作者使用 Keras 的数据自动生成器,继承自 keras.utils.Sequence,集合 fit_generator 实现在训练是并行读取数据,节约内存。对应的代码位于 ,重载了 __getitem__ 方法实现每个训练迭代时的批数据获取,核心代码如下:

inputs = [batch_image]if self.use_subject:  subject_masks = np.random.choice( 2, end_idx-start_idx, p=[self.subject_droprate, 1.0 - self.subject_droprate,])   # 以 subject_droprate 的概率丢弃训练样本的 subject   subject_cats = batch_rel[:, 0]  subject_cats[subject_masks == 0] = self.num_objects  inputs.append(subject_cats)if self.use_predicate:  if self.categorical_predicate:    inputs.append(to_categorical(batch_rel[:, 1], num_classes=self.num_predicates))  else:    inputs.append(batch_rel[:, 1]) if self.use_object:  object_masks = np.random.choice( 2, end_idx-start_idx, p=[self.object_droprate, 1.0 - self.object_droprate])   # 以 object_droprate 概率丢弃训练样本中的 object  object_cats = batch_rel[:, 2]  object_cats[object_masks == 0] = self.num_objects  inputs.append(object_cats)outputs = [batch_s_regions, batch_o_regions]return inputs, outputs

batch_rel 是从 hdrf5 文件读取当前批次 relationship 信息(Bx4),batch_image 是尺寸为 BxHxWx3 的 tensor,B 为 batch size;

指明训练时使用 subject 信息时,以 subject_droprate 的概率丢弃当前 batch 中某些训练样本的 subject 信息,得到 subject_cats (B,),append 至 inputs

指明训练是使用 predicat 信息时,将 predicate 信息进行处理,变换为 one-hot 的向量(训练 SSAS模型时),尺寸为 B x num_predicates,append 至 inputs

指明训练时使用 object 信息时,以 subject_droprate 的概率丢弃当前 batch 中某些训练样本的 object 信息,得到 object_cats (B,),append 至 inputs

inputs = [batch_image (BxHxWx3),  subject_cats (B,),predicat (B x num_predicates), object_cats (B, )]

对从 hdf5 文件中读取的 mask 进行了 reshape (L, L -> LxL) 构成当前批训练样本的目标输出,batch_s_regions, batch_o_regions 尺寸为 (B, LxL)

outputs = [batch_s_regions (B, LxL), batch_o_regions (B, LxL)],

 

网络模型搭建

以 SSAS 的模型构建为例,代码位于 以及  文件

relationships_model = ReferringRelationshipsModel(args)model = relationships_model.build_model()

第一行代码为网络的参数进行赋值,如下:

self.input_dim = args.input_dim                # 网络输入图像尺寸self.feat_map_dim = args.feat_map_dim      # 网络输出的 feature map 尺寸,即前面介绍过的 Lself.hidden_dim = args.hidden_dim              # 在 pre-trained 网络输出 feature map 后添加卷积层学习 feature map 的 *卷积核数目*,即前面介绍的 Cself.num_objects = args.num_objects            # 训练集中 subject/object 类别数self.num_predicates = args.num_predicates      # 训练集中 predicate 类别数self.dropout = args.dropout                    # 网络中某些层学习时的 dropput 参数self.use_subject = args.use_subject            # 训练时是否使用 subject 信息self.use_predicate = args.use_predicate        # 训练时是否使用 predicate 信息self.use_object = args.use_object              # 训练时是否使用 object 信息self.nb_conv_att_map = args.nb_conv_att_map    # 学习 predicate shift 时,使用的 *卷积层数目*self.nb_conv_im_map = args.nb_conv_im_map      # 在 pre-trained 网络基础添加卷积层学习 feature map 的 *卷积层数目*self.cnn = args.cnn                            # pre-trained 网络种类, resnet or vggself.feat_map_layer = args.feat_map_layer      # 使用 pre-trained 网络的 *哪一层* 作为输出,进一步学习 feature mapself.conv_im_kernel = args.conv_im_kernel      # pre-trained 网络输出基础上添加卷积层的 *卷积核尺寸*self.conv_predicate_kernel = args.conv_predicate_kernel      # 学习 predicate shift 时,*卷积核尺寸*,即前面介绍的 kself.conv_predicate_channels = args.conv_predicate_channels  # 学习 predicate shift 时,*卷积核数目*,即前面介绍的 cself.model = args.model                                      # 建立何种类型的网络,本文以 SSAS 为例介绍self.use_internal_loss = args.use_internal_loss              # 训练目标 是否加入 predicate shift 迭代中间过程输出的 predict shift map 指导学习self.internal_loss_weight = args.internal_loss_weight        # 每个中间迭代结果的权重self.iterations = args.iterations                            # 计算 predicate shift 的迭代次数self.attention_conv_kernel = args.attention_conv_kernel      # 未用到self.refinement_conv_kernel = args.refinement_conv_kernel    # 未用到self.output_dim = args.output_dim                            # 网络目标检测输出 heatmap 尺寸,即 Lself.embedding_dim = args.embedding_dim                      # subject/object 映射为稠密向量时,第一层映射的全连接数目self.finetune_cnn = args.finetune_cnn                        # 训练时是否对 pre-trained 网络进行 finetune

 

第二行代码完成网络模型创建,创建网络的代码位于 。以 SSAS 模型为例:

1. 定义网络输入

input_im = Input(shape=(self.input_dim, self.input_dim, 3))input_subj = Input(shape=(1,))input_obj = Input(shape=(1,))if self.use_predicate:     input_pred = Input(shape=(self.num_predicates,)) inputs=[input_im, input_subj, input_pred, input_obj]else:    inputs=[input_im, input_subj, input_obj]

网络输入的类型需要跟前面定义的训练数据获取 generator 中生成的训练样本类型相同(忽略 batch_size 维度);

 

2. 基于 pre-trained 网络创建提取图像特征的网络计算图 im_feature,对应于开始流程图的第一步,主要代码如下:

im_features = self.build_image_model(input_im)def build_image_model(self, input_im):  if self.cnn == "resnet":    base_model = ResNet50( weights='imagenet', include_top=False, input_shape=(self.input_dim, self.input_dim, 3))  elif self.cnn == "vgg":    base_model = VGG19( weights='imagenet', include_top=False, input_shape=(self.input_dim, self.input_dim, 3))  else:    raise ValueError('--cnn must be [resnet, vgg] but got {}'.format(self.cnn))  if self.finetune_cnn:    for layer in base_model.layers:      layer.trainable = True      layer.training = True  else:    for layer in base_model.layers:      layer.trainable = False      layer.training = False   output = base_model.get_layer(self.feat_map_layer).output      # 获取 basemodel 指定层为基础特征  image_branch = Model(inputs=base_model.input, outputs=output)  # 以函数式模型构建方式,声明提取图像基础特征的网络  im_features = image_branch(input_im)                           # 提取图像特征的计算图  im_features = Dropout(self.dropout)(im_features)  for i in range(self.nb_conv_im_map):                           # 在基础特征后接 nb_conv_im_map 个卷积层,每个卷积层卷积核个数为 hidden_dim,尺寸为 conv_im_kernel x conv_im_kernel    im_features = Conv2D(self.hidden_dim, self.conv_im_kernel, strides=(1, 1), padding='same', activation='relu')(im_features)     im_features = Dropout(self.dropout)(im_features)  return im_features                                             # 返回图像特征,尺寸为 feat_map_dim x feat_map_dim x hidden_dim

 

3. 定义 subject/object 由类别 id (整数)映射为稠密高维向量的网络计算图,计算 embedded_subject/object 对应于开始流程图的第二步

subj_obj_embedding = self.build_embedding_layer(self.num_objects, self.embedding_dim)  # 定义全连接,将数字映射维度为 embedding_dim 的向量 def build_embedding_layer(self, num_categories, emb_dim):   return Embedding(num_categories, emb_dim, input_length=1)
embedded_subject = subj_obj_embedding(input_subj)  # 将 subject 应为为向量,R^(embedding_dim) embedded_subject = Dense(self.hidden_dim, activation="relu")(embedded_subject)# 增加一个全连接层,将 R^(embedding_dim) 向量应为为 R^(hidden_dim)向量,与上一步定义的图像特征通道数相同 embedded_subject = Dropout(self.dropout)(embedded_subject)
# object 的操作与 subject 相同embedded_object = subj_obj_embedding(input_obj)embedded_object = Dense(self.hidden_dim, activation="relu")(embedded_object)embedded_object = Dropout(self.dropout)(embedded_object)

 

4. 基于图像特征 im_featuresubject/object embedding,计算 subject/ojbect attention map 的网络计算图,对应开始流程图的第三步

subject_att = self.attend(im_features, embedded_subject, name='subject-att-0')# 基于 im_features (LxLxC) 和 embedded_subject (R^C),计算 subject attention mapobject_att = self.attend(im_features, embedded_object, name='object-att-0')def attend(self, feature_map, query, name=None):    query = Reshape((1, 1, self.hidden_dim,))(query)            # 输入向量由 R^(hidden_dim) reshape 为 (1,1, hidden_dim)    attention_weights = Multiply()([feature_map, query])        # 在 feature_map 每个位置的 hidden_dim 个通道上与 query 进行 element-wise 求乘积    attention_weights = Lambda(lambda x: K.sum(x, axis=3, keepdims=True))(attention_weights)  # 每个位置上的对所有通道的乘积结果求和    attention_weights = Activation("relu", name=name)(attention_weights)  # 使用 relu 激活函数计算响应,得到 attention map    return attention_weights

 

5. 基于 subject/ojbect attention mappredicate,计算 predicate-shift-map 的网络计算图,对应开始流程的第四步

首先定义所有 predicate 对应的 predicate-shift-map 网络计算图,由多个卷积层连接而成:

# 定义基于 subject attention map 和 predicate,计算 subject->object 的 predicate shift 网络流程图 # 注意,这里对 *所有的 predicate *都定义了一个专属计算 predicate-shift/inv-predicate-shift 网络流程图 predicate_modules = self.build_conv_modules(basename='conv{}-predicate{}') inverse_predicate_modules = self.build_conv_modules(basename='conv{}-inv-predicate{}')def build_conv_modules(self, basename):  predicate_modules = []  for k in range(self.num_predicates):      # 训练集有 num_predicates 个 predicate,每个 predicate 定义一个网络流程图    predicate_module_group = []    for i in range(self.nb_conv_att_map-1):  # 前 nb_conv_att_map-1 个卷积层,卷积核数目为 conv_predicate_channels,尺寸为 conv_predicate_kernel      predicate_conv = Conv2D(self.conv_predicate_channels, self.conv_predicate_kernel,             strides=(1, 1), padding='same', use_bias=False, activation='relu',                    name=basename.format(i, k))      predicate_module_group.append(predicate_conv)    # last conv with only one channel,最后一个卷积层,卷积核数目为 1,保证 predicate shift 通道数为 1    predicate_conv = Conv2D(1, self.conv_predicate_kernel,                strides=(1, 1), padding='same', use_bias=False,                activation='relu',                name=basename.format(self.nb_conv_att_map-1, k))     predicate_module_group.append(predicate_conv)     # 同一个 predicate 的所有卷积构成一组    predicate_modules.append(predicate_module_group)  # 保存所有 predicate 的卷积层组合  return predicate_modules

 

以初始 subject/object attentioninput_pred 为基础,迭代计算更新 predicate-shift-mapsubject/object attention map,对应开始流程图的第五步

循环流程如下:

subject-attetion-map + predicate ->  subject-to-object-shift-map  -> subject-to-object-shift-map + im_feature + object-embedding -> new-object-attention-map 

object-attention-map + predicate -> object-to-subject-shift-map -> object-to-subject-shift-map + im_feature + subject-embedding -> new-subject-attention-map

predicate_masks = Reshape((1, 1, self.num_predicates))(input_pred) # 将 predicate 的 one-hot 向量 reshape 为 (1,1,num_predicates) for iteration in range(self.itreations):   # 以 subject attention map 为输出,使用 predicate 对应的卷积层配置,计算 subject->object 的 shift map    predicate_att = self.shift_conv_attention(subject_att, predicate_modules, predicate_masks)   # 计算 *subject->object* 的 predicate shift map  predicate_att = Lambda(lambda x: x, name='shift-{}'.format(iteration+1))(predicate_att)  new_image_features = Multiply()([im_features, predicate_att])                                # 对 im_feature 不同区域进行加权,选择 object 显著区域  new_object_att = self.attend(new_image_features, embedded_object, name='object-att-{}'.format(iteration+1))   # 更新 object attention map   # 以 object attention map 为输入,使用 predicate 对应的卷积层配置,计算 object->subject 的 shift-map  inv_predicate_att = self.shift_conv_attention(object_att, inverse_predicate_modules, predicate_masks) # 计算 *object->subject* 的 predicate shift map  inv_predicate_att = Lambda(lambda x: x, name='inv-shift-{}'.format(iteration+1))(inv_predicate_att)  new_image_features = Multiply()([im_features, inv_predicate_att])                 # 对 im_feature 不同区域加权,选择 subject 显著区域  new_subject_att = self.attend(new_image_features, embedded_subject, name='subject-att-{}'.format(iteration+1)) # 更新 subject attention map  if self.use_internal_loss:                    # 如果指定了目标函数考虑中间结果,则将中间结果保存    object_outputs.append(new_object_att)    subject_outputs.append(new_subject_att)  object_att = new_object_att                   # 更新 subject/object attention map,准备下次迭代  subject_att = new_subject_attdef shift_conv_attention(self, att, merged_modules, predicate_masks):  conv_outputs = []  for group in merged_modules:    att_map = att    for conv_module in group:      att_map = conv_module(att_map)      conv_outputs.append(att_map)  merged_output = Concatenate(axis=3)(conv_outputs)     # 使用所有 predicate 的 shift 计算网络,计算 predicate-shfit map,所有的计算结果 concat  predicate_att = Multiply()([predicate_masks, merged_output])  # 与 predicate_mask 相乘,输入 predicate 对应的 shift map 保留,其余置 0  predicate_att = Lambda(lambda x: K.sum(x, axis=3, keepdims=True))(predicate_att) # 乘积结果相加,得到正确的 shift-map  predicate_att = Activation("tanh")(predicate_att)     # 使用 tanh 为响应函数,输出最终的 shift-map (LxLx1)  return predicate_att

 

最后,以迭代计算最终的 subject/object attention map 为基础,预测 subject/object 目标位置,定义最终的网络模型计算图

subject_att = Activation("tanh")(subject_att)object_att = Activation("tanh")(object_att)subject_regions = Reshape((self.output_dim * self.output_dim,), name="subject")(subject_att)object_regions = Reshape((self.output_dim * self.output_dim,), name="object")(object_att) model = Model(inputs=inputs, outputs=[subject_regions, object_regions]) return model

至此,SSAS 网络模型定义完成,注意,网络的 inputs 和 outputs 要和训练数据获取的类型尺寸相同(去掉 batch-size 维度)

 

网络训练

所有工作完成之后,使用 model.compile 配置网络训练,如目标函数,优化器,测试函数等;使用 model.fit_generator 进行训练,通过看工程代码可以很清楚的了解,此处不再赘述了

 

 

 

 

转载于:https://www.cnblogs.com/beshining/p/10148134.html

你可能感兴趣的文章
iOS-解决iOS8及以上设置applicationIconBadgeNumber报错的问题
查看>>
亡灵序曲-The Dawn
查看>>
实验五
查看>>
leetcode 347 priority,map的使用
查看>>
vue 校验插件 veeValidate使用
查看>>
WCF应用(二)
查看>>
jQuery实现返回顶部效果
查看>>
Java实现将数字转为大写汉字
查看>>
面试题3:二维数组中的查找
查看>>
android单元测试——初识
查看>>
MFC学习(6)——以数组矩阵形式表示读取出来的BMP图像||将数组矩阵数据转成BMP图像...
查看>>
Vuex入门(5)—— 为什么要用Action管理异步操作
查看>>
axios实现拦截器
查看>>
libevent简单介绍
查看>>
接口返回json
查看>>
mojo 接口返回键值对的json格式
查看>>
面试题:判断一个数是否为素数
查看>>
程序设计第三章思维导图
查看>>
Vue 实现按钮单选
查看>>
实现两边文本框对齐
查看>>