2018_CVPR_Learning to Adapt Structured Output Space for Semantic Segmentation

学习如何适应结构化输出空间进行语义分割-Learning to Adapt Structured Output Space for Semantic Segmentation

题目:在输出空间进行适应。

论文内容分析:

》提出通过对抗学习的进行像素级的语义分割的域适应方法。

》在输出空间的域适应可以有效地对齐源域和目标的场景布局和局部上下文。

作者的目的是:源域、目标域的图像的分割预测Ps,Pt更接近。在目标预测的对抗损失下,将判别器的梯度传递到生成器(分割模型),让生成器(分割模型)对目标域图像分割,生成与源域分割预测类似的目标域分割预测。

》提出多层次对抗学习来适应分割模型的不同层次的特征,改善性能。

参考深度监督网络【1】,在低层次特征空间中(low-level feature),引入对抗模块,从而保证分割网络对输入的目标域图像所提取的低层特征与源域类似。

作者所提出的方法由 分割网络 和 判别器组成。源域图像和目标域图像共享一个分割网络。

代码分析:

5层卷积层,第6层卷积层的输入图像的大小相同,但是特征通道数量不同。56两层的卷积核的数量,种类相同,由4种不同空洞大小,填充参数的卷积(每种卷积的填充参数与其空洞大小参数相同),

最后将不同的卷积操作的结果的输出进行相加(如图3所示),提取到不同感受野的特征。

5层卷积层的输入的特征通道数量为1024 输出特征通道数量为2(分割的类数量),

6层卷积层的输入的特征通道数量为2048 输出特征通道数量为2(分割的类数量)。

2. 训练过程分析

 

 for i_iter in range(args.num_steps):

        loss_seg_value1 = 0
        loss_adv_target_value1 = 0
        loss_D_value1 = 0

        loss_seg_value2 = 0
        loss_adv_target_value2 = 0
        loss_D_value2 = 0

        optimizer.zero_grad()
        adjust_learning_rate(optimizer, i_iter)

        optimizer_D1.zero_grad()
        optimizer_D2.zero_grad()
        adjust_learning_rate_D(optimizer_D1, i_iter)
        adjust_learning_rate_D(optimizer_D2, i_iter)

        for sub_i in range(args.iter_size):# 梯度累加

            # train G

            # don't accumulate grads in D
            for param in model_D1.parameters():
                param.requires_grad = False

            for param in model_D2.parameters():
                param.requires_grad = False

            # train with source

            _, batch = trainloader_iter.next() # 加载输入源域图像,训练分割模型
            images, labels, _, _ = batch
            images = Variable(images).cuda(args.gpu)

            pred1, pred2 = model(images)
            pred1 = interp(pred1)
            pred2 = interp(pred2)

            loss_seg1 = loss_calc(pred1, labels, args.gpu)
            loss_seg2 = loss_calc(pred2, labels, args.gpu)
            loss = loss_seg2 + args.lambda_seg * loss_seg1

            # proper normalization
            loss = loss / args.iter_size
            loss.backward()# 计算得到分割损失,反向传播,但是梯度不更新
            loss_seg_value1 += loss_seg1.data.cpu().numpy()[0] / args.iter_size
            loss_seg_value2 += loss_seg2.data.cpu().numpy()[0] / args.iter_size

            # train with target

            _, batch = targetloader_iter.next()# 加载目标域图像
            images, _, _ = batch
            images = Variable(images).cuda(args.gpu)

            pred_target1, pred_target2 = model(images)
            pred_target1 = interp_target(pred_target1) # 对model输出结果进行上采样
            pred_target2 = interp_target(pred_target2)

            D_out1 = model_D1(F.softmax(pred_target1))# 为什么对model的输出进行softmax函数计算?
            D_out2 = model_D2(F.softmax(pred_target2))

            loss_adv_target1 = bce_loss(D_out1,
                                       Variable(torch.FloatTensor(D_out1.data.size()).fill_(source_label)).cuda(
                                           args.gpu))

            loss_adv_target2 = bce_loss(D_out2,
                                        Variable(torch.FloatTensor(D_out2.data.size()).fill_(source_label)).cuda(
                                            args.gpu))

            loss = args.lambda_adv_target1 * loss_adv_target1 + args.lambda_adv_target2 * loss_adv_target2
            loss = loss / args.iter_size
            loss.backward()
            loss_adv_target_value1 += loss_adv_target1.data.cpu().numpy()[0] / args.iter_size
            loss_adv_target_value2 += loss_adv_target2.data.cpu().numpy()[0] / args.iter_size

            # train D
            #设置判别器的参数requires_grad=True, 可以对其参数进行求导
            # bring back requires_grad
            for param in model_D1.parameters():
                param.requires_grad = True

            for param in model_D2.parameters():
                param.requires_grad = True

            # train with source
            pred1 = pred1.detach()
            pred2 = pred2.detach()

            D_out1 = model_D1(F.softmax(pred1))
            D_out2 = model_D2(F.softmax(pred2))

            loss_D1 = bce_loss(D_out1,
                              Variable(torch.FloatTensor(D_out1.data.size()).fill_(source_label)).cuda(args.gpu))

            loss_D2 = bce_loss(D_out2,
                               Variable(torch.FloatTensor(D_out2.data.size()).fill_(source_label)).cuda(args.gpu))

            loss_D1 = loss_D1 / args.iter_size / 2
            loss_D2 = loss_D2 / args.iter_size / 2

            loss_D1.backward()
            loss_D2.backward()

            loss_D_value1 += loss_D1.data.cpu().numpy()[0]
            loss_D_value2 += loss_D2.data.cpu().numpy()[0]

            # train with target
            pred_target1 = pred_target1.detach()
            pred_target2 = pred_target2.detach()

            D_out1 = model_D1(F.softmax(pred_target1))
            D_out2 = model_D2(F.softmax(pred_target2))

            loss_D1 = bce_loss(D_out1,
                              Variable(torch.FloatTensor(D_out1.data.size()).fill_(target_label)).cuda(args.gpu))

            loss_D2 = bce_loss(D_out2,
                               Variable(torch.FloatTensor(D_out2.data.size()).fill_(target_label)).cuda(args.gpu))

            loss_D1 = loss_D1 / args.iter_size / 2
            loss_D2 = loss_D2 / args.iter_size / 2

            loss_D1.backward()
            loss_D2.backward()

            loss_D_value1 += loss_D1.data.cpu().numpy()[0]
            loss_D_value2 += loss_D2.data.cpu().numpy()[0]

        optimizer.step()
        optimizer_D1.step()
        optimizer_D2.step()

        print('exp = {}'.format(args.snapshot_dir))
        print(
        'iter = {0:8d}/{1:8d}, loss_seg1 = {2:.3f} loss_seg2 = {3:.3f} loss_adv1 = {4:.3f}, loss_adv2 = {5:.3f} loss_D1 = {6:.3f} loss_D2 = {7:.3f}'.format(
            i_iter, args.num_steps, loss_seg_value1, loss_seg_value2, loss_adv_target_value1, loss_adv_target_value2, loss_D_value1, loss_D_value2))

        if i_iter >= args.num_steps_stop - 1:
            print 'save model ...'
            torch.save(model.state_dict(), osp.join(args.snapshot_dir, 'GTA5_' + str(args.num_steps_stop) + '.pth'))
            torch.save(model_D1.state_dict(), osp.join(args.snapshot_dir, 'GTA5_' + str(args.num_steps_stop) + '_D1.pth'))
            torch.save(model_D2.state_dict(), osp.join(args.snapshot_dir, 'GTA5_' + str(args.num_steps_stop) + '_D2.pth'))
            break

        if i_iter % args.save_pred_every == 0 and i_iter != 0:
            print('taking snapshot ...')
            torch.save(model.state_dict(), osp.join(args.snapshot_dir, 'GTA5_' + str(i_iter) + '.pth'))
            torch.save(model_D1.state_dict(), osp.join(args.snapshot_dir, 'GTA5_' + str(i_iter) + '_D1.pth'))
            torch.save(model_D2.state_dict(), osp.join(args.snapshot_dir, 'GTA5_' + str(i_iter) + '_D2.pth'))

通过分析发现,模型每次迭代一个epoch,并没有迭代所有的训练数据,而是一个epoch,模型迭代 args.iter_size 次,每次加载batch size 个数据;模型在迭代 args.iter_size 次之前 使用 optimizer.zero_grad(),optimizer_D1.zero_grad(),optimizer_D2.zero_grad() 对分割网络,判别器的的梯度进行清0;模型在迭代训练的时候,只进行损失函数的计算,没有进行反向传播,而是在迭代完args.iter_size之后,模型进行反向传播,为什么通过这种方式进行参数更新,以及模型的训练?其数学意义是什么?相关的解释:梯度累加[1][2]。

判别器训练文章中z为0的时候,样本从目标域中采样得到;z=1,样本从源域采样。但是代码中判别器训练,目标域对应的z是1, 源域对应0 ? 为什么

3.对抗训练方式分析:

模型先训练分割网络,然后训练判别器。

分割网络训练阶段

》1. 先对判别器的参数的 requires_grad 设置为False, 因此在优化分割网络,只优化判别器的参数。

》2.加载源域图像,以及对应的标签,训练分割网络,计算分割交叉熵损失函数,并反向传播;

》3. 加载目标域图像,输入到分割网络中,得到对每个类的预测概率图(segmentation softmax output)pred_target1,pred_target2;判别器利用如下损失函数进行函数计算,并反向传播。

4. 学习率调整方式

判别器的初始学习率:0.0001

分割网络的初始学习率:0.00025

学习调整方式:随着迭代次数增加,而下降。

def lr_poly(base_lr, iter, max_iter, power):
    return base_lr * ((1 - float(iter) / max_iter) ** (power))

LR = base\_lr * (1-(\frac{iter}{max\_iter})^{power})

其值随着迭代次数增加的变化曲线

Github 有帮助的ISSUEs

1. https://github.com/wasidennis/AdaptSegNet/issues/88

在输出层次,仅使用单层LS-GAN域适应。

(2)关于Batch Normalizaiton 层参数的requires_grad = False。

https://github.com/wasidennis/AdaptSegNet/issues/79

(3)关于对抗损失函数L_{adv}

https://github.com/wasidennis/AdaptSegNet/issues/33

 

 

参考文献:

【1】https://towardsdatascience.com/what-is-gradient-accumulation-in-deep-learning-ec034122cfa

【2】https://www.cnblogs.com/lart/p/11628696.html

 

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值