继论文之后,开始读代码
本文主要集中于两个模块的代码
推荐先阅读论文,否则这篇文章理解起来可能有些困难
Cross-domain Object Detection through Coarse-to-Fine Feature Adaptation-CVPR2020论文阅读
代码阅读
ART模块
-
在
train.py
中的train
方法中调用了source_forward
和target_forward
,两个方法都有类似如下的代码:gt_pooled_feat, gt_labels, rois, cls_prob, bbox_pred, \ rpn_loss_cls, rpn_loss_box, RCNN_loss_cls, RCNN_loss_bbox, \ rois_label, domain_scores, weight_map = model(im_data, im_info, gt_boxes, num_boxes)
- 注意这里的
weight_map
是重点,这个参数就是已经上采样之后的注意力图(这里已经把三个域分类器所需要的大小的注意力图进行了review和在维度1上的拼接),这个参数的维度应该是[batchsize, n],n代表了base_feat1的长*宽
(传给第一个分类器的特征图大小)+base_feat2的长*宽
(传给第二个分类器的特征图大小)+base_feat3的长*宽
(传给第三个分类器的特征图大小) - 另一个值得关注的参数是
domain_scores
,这个参数就是三个域分类器的得分结果(二分类),形状已经调成和weight_map
类似[batchsize, m],这个m是不是等于n还需要进一步确定 - 调整上面两个参数形状的过程在
lib/model/faster_rcnn/faster_rcnn.py
这个文件中 - ART实际上就是上面两个参数的运算,具体看论文,运算的代码就是下方的代码块:
domain_labels = Variable(torch.ones_like(domain_scores).float().cuda()) loss_domain = F.binary_cross_entropy(domain_scores, domain_labels, (1.0 + weight_map.data))
- 这个
loss_domain
就是论文里说的 L A R T L_{ART} LART了
- 注意这里的
-
接下来我们进入
model
函数去看那两个返回值到底是怎么来的,model
调用的实际上是网络模块类的forward
方法,这里用的是VGG16
网络,所以我们去找lib/model/faster_rcnn/vgg16.py
,结果我们发现这里没有forward
函数,不要急,我们会发现这个vgg16实际上是faster_rcnn的子类,既然子类没有,那么forward
肯定在父类里,也就是lib/model/faster_rcnn/faster_rcnn.py
这个文件中。 -
但是vgg16那个文件并不是没有的,下面先说一下
lib/model/faster_rcnn/vgg16.py
中和ART有关的内容(实际都有关,但我们这里捡一些对我们修改有帮助的内容说),它定义了三个域分类器的位置,代码如下:self.RCNN_base1 = nn.Sequential(*list(vgg.features._modules.values())[:17]) # 256 self.RCNN_base2 = nn.Sequential(*list(vgg.features._modules.values())[17:24]) # 512 self.RCNN_base3 = nn.Sequential(*list(vgg.features._modules.values())[24:-1]) # 512
-
另外还定义了三个域分类器的网络结构,代码如下:
self.Domain_classifier1 = Discriminator(256) self.Domain_classifier2 = Discriminator(512) self.Domain_classifier3 = Discriminator(512)
-
Discriminator
这个类的定义也在vgg16这个文件里,其实很简单的三层fc,代码如下:class Discriminator(nn.Module): def __init__(self, dim): super(Discriminator, self).__init__() self.conv1 = conv1x1(dim, 256) self.in1 = nn.InstanceNorm2d(256) self.conv2 = conv1x1(256, 128) self.in2 = nn.InstanceNorm2d(128) self.conv3 = conv1x1(128, 1) def forward(self, x): x = F.leaky_relu(self.in1(self.conv1(x))) x = F.leaky_relu(self.in2(self.conv2(x))) x = F.sigmoid(self.conv3(x)) # x = x.view(x.size(0), -1) return x
-
-
好了,接下来我们去看
lib/model/faster_rcnn/faster_rcnn.py
中的forward
代码,找到vgg16
的父类_fasterRCNN
中的forward
-
首先将
vgg16
文件中定义的三个域分类器位置的特征提取出来:base_feat1 = self.RCNN_base1(im_data) base_feat2 = self.RCNN_base2(base_feat1) base_feat3 = self.RCNN_base3(base_feat2)
-
接下来调用三个分类器分类并对结果进行拼接和调整维度,这里的
grad_reverse
应该就是对抗思想的GRL了,domain_scores
这个值就是我们之前讲的train.py
里的domain_scores
:if self.training: ds1 = self.Domain_classifier1(grad_reverse(base_feat1)) ds2 = self.Domain_classifier2(grad_reverse(base_feat2)) ds3 = self.Domain_classifier3(grad_reverse(base_feat3)) domain_scores = torch.cat((ds1.view(batch_size, -1), \ ds2.view(batch_size, -1), \ ds3.view(batch_size, -1)), 1)
-
然后调用rpn网络得到
attention_map
,这里怎么得到这个返回值需要去看rpn的代码(暂留):rois, rpn_loss_cls, rpn_loss_bbox, rpn_feat, attention_map = self.RCNN_rpn(base_feat3, im_info, gt_boxes, num_boxes, target=target)
-
对注意力图进行上采样和拼接,调整维度,这里的
attention_scores
就是train.py
里的weight_map
了:if self.training: at1 = F.upsample(attention_map, size=base_feat1.shape[2:4], mode='bilinear', align_corners=False) at2 = F.upsample(attention_map, size=base_feat2.shape[2:4], mode='bilinear', align_corners=False) at3 = F.upsample(attention_map, size=base_feat3.shape[2:4], mode='bilinear', align_corners=False) attention_scores = torch.cat((at1.view(batch_size, -1), \ at2.view(batch_size, -1), \ at3.view(batch_size, -1)), 1)
-
-
这个ART模块差不多就是这些内容了
PSA模块
-
我们还是从头开始看,实现回顾PSA有什么,两个全局原型(两个域),对应变量名是
sgp
和tgp
;两个对应的局部原型,一个loss,没了… -
main.py
中在设定完优化器之后,大概107行开始,初始化:if args.prior: log('[*] Use Prior prototypes!') sgp = torch.from_numpy(np.load(args.sp)).type(torch.float32) tgp = torch.from_numpy(np.load(args.tp)).type(torch.float32) else: sgp = torch.zeros(imdb.num_classes - 1, feat_dim).type(torch.float32) tgp = torch.zeros(imdb.num_classes - 1, feat_dim).type(torch.float32)
- 训练时没有设定这个参数,所以应该是全0初始,然后在调用
train
时将这两个值传给train.py
。
- 训练时没有设定这个参数,所以应该是全0初始,然后在调用
-
train.py
在调用函数时将sgp
传给source_forward
,将tgp
传给target_forward
,并且函数还会将值返回,确保可以在函数中修改。-
两个函数差不多,我以
source_forward
为例,在source_forward
中利用局部原型更新全局原型,这部分主要是计算,计算方法详见论文,论文里说的更清楚一些,代码:if step > args.warmup_steps: for c in range(1, num_classes): mask = (gt_labels == c) if torch.sum(mask).item() > 0: # 利用局部原型更新全局原型 local_prototypes = torch.mean(gt_pooled_feat[mask], 0) alpha = (F.cosine_similarity(sgp[c - 1], local_prototypes, dim=0).item() + 1) / 2.0 sgp[c - 1] = (1.0 - alpha) * sgp[c - 1] + alpha * local_prototypes
-
注意这里的限制条件,warmup结束后才开始调用PSA模块,这里论文也有说明
-
更新之后会把
sgp
再返回到train
函数中 -
warmup之后,
train
函数中会计算 L A R T L_{ART} LART,也就是sgp
和tgp
之间的差异,在代码中变量名为loss_class
,代码如下:if step >= args.warmup_steps: loss_class = args.lam * step / args.max_steps * (sgp - tgp).pow(2).sum(1).mean()
-
ART模块的内容较少,就这些了
-