我是靠谱客的博主 傻傻水壶,最近开发中收集的这篇文章主要介绍记一次不成功的迁移学习《Do We Really Need to Access the Source Data? Source Hypothesis Transfer for Unsupervise》,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

Do We Really Need to Access the Source Data? Source Hypothesis Transfer for
Unsupervised Domain Adaptation

The pipeline of our SHOT framework
个人感觉这篇论文的思想还是挺好的,出发点很贴近实际,我对于迁移学习的理解很少,但据我所知,之前有关迁移学习的大多模型都是需要用到源域的数据或者特征,而这在现实中其实是不切实际的,因为源域的数据其实也包含着隐私,如果直接将其暴露那势必会遭到隐私泄露,这似乎也是该文章的一个目的。 所以该文章在只包含源域训练好的模型下,固定分类器,利用目标域数据来训练预训练好的特征提取器,使其最后再目标域上也能有良好的性能表现。

于是自己首先复现了这篇论文,发现在MNIST >>>>usps的迁移效果很好,而且是否使用标签平滑也就是代码中’–cls_par’的选择对最终的结果影响并不大,感觉会不会是数据集的原因?
目标域最终的测试精度
在源码中,整个算法大致也就是三个过程,首先训练源域数据,该过程没有什么特别的

 #加过标签平滑的交叉熵损失  只会在源域的训练中使用到
 classifier_loss = loss.CrossEntropyLabelSmooth(num_classes=args.class_num, epsilon=args.smooth)(outputs_source, labels_source)            
    

而CrossEntropyLabelSmooth损失在loss.py中的形式就是

		log_probs = self.logsoftmax(inputs)
        targets = torch.zeros(log_probs.size()).scatter_(1, targets.unsqueeze(1).cpu(), 1)
        if self.use_gpu: targets = targets.cuda()

        #target表示的就是标签平滑后的损失 epsilon是就标签平滑系数
        targets = (1 - self.epsilon) * targets + self.epsilon / self.num_classes
        if self.size_average:   #如果采用size_average的话会取一个平均值
            loss = (- targets * log_probs).mean(0).sum()
        else:
            loss = (- targets * log_probs).sum(1)
        return loss

在训练好源域得到模型后,随即丢弃源域数据,然后用训练好的网路来初始化目标域对应的网络,最后固定住模型的分类器,只训练F以及B,具体地,在代码中的展现过程就是:

	#读取预训练好的模型参数
    args.modelpath = args.output_dir + '/source_F.pt'   
    netF.load_state_dict(torch.load(args.modelpath))
    args.modelpath = args.output_dir + '/source_B.pt'   
    netB.load_state_dict(torch.load(args.modelpath))
    args.modelpath = args.output_dir + '/source_C.pt'    
    netC.load_state_dict(torch.load(args.modelpath))
    netC.eval()
    for k, v in netC.named_parameters():
        v.requires_grad = False     #此操作是不是代表着固定住分类器,不参与训练

在原文中,目标域的损失函数为:
目标域损失函数
它在论文中的体现就是:

   		inputs_test = inputs_test.cuda()   #加载测试输入
        features_test = netB(netF(inputs_test)) #提取输入的特征
        outputs_test = netC(features_test)   #得到对应的的输出结果

        if args.cls_par > 0:  #cls_par 表示的应该是整个损失的β值  如果选用该策略就提取伪标签
            pred = mem_label[tar_idx]
            classifier_loss = args.cls_par * nn.CrossEntropyLoss()(outputs_test, pred) #该损失就是分类器交叉熵损失
        else:
            classifier_loss = torch.tensor(0.0).cuda() #如果不取用伪标签 则整个损失的最后一项就是0

        if args.ent: #默认使用信息最大化损失  IM loss
            softmax_out = nn.Softmax(dim=1)(outputs_test)
            entropy_loss = torch.mean(loss.Entropy(softmax_out)) # 该损失应该就是对应着Lent
            if args.gent:
                msoftmax = softmax_out.mean(dim=0) #msoftmax表示的应该是Pk
                entropy_loss -= torch.sum(-msoftmax * torch.log(msoftmax + 1e-5))
                #torch.sum(-msoftmax * torch.log(msoftmax + 1e-5))   该损失应该对应着Ldiv

            im_loss = entropy_loss * args.ent_par  #还有一个乘法系数?
            classifier_loss += im_loss  #感觉这里整个符号是不是取反了? 跟论文里的损失描述不一致

        optimizer.zero_grad()
        classifier_loss.backward()
        optimizer.step()

如果不选用伪标签策略,其实到这里,整个训练过程也就结束了,就可以得到上面那张图的结果。如果选取了伪标签策略,那就还有一个obtain_label的过程,其中核心代码为:

    all_fea = torch.cat((all_fea, torch.ones(all_fea.size(0), 1)), 1) #
    all_fea = (all_fea.t() / torch.norm(all_fea, p=2, dim=1)).t()
    all_fea = all_fea.float().cpu().numpy()

    print(all_fea.size())

    K = all_output.size(1)
    aff = all_output.float().cpu().numpy()
    initc = aff.transpose().dot(all_fea)
    initc = initc / (1e-8 + aff.sum(axis=0)[:,None])
    dd = cdist(all_fea, initc, 'cosine')  #计算两组输入之间的距离 对应论文中聚类的过程
    pred_label = dd.argmin(axis=1)
    acc = np.sum(pred_label == all_label.float().numpy()) / len(all_fea)

    for round in range(1):     #实验结果证明只用更新一次就可以得到一个较好的结果
        aff = np.eye(K)[pred_label]  #
        initc = aff.transpose().dot(all_fea)
        initc = initc / (1e-8 + aff.sum(axis=0)[:,None])
        dd = cdist(all_fea, initc, 'cosine')
        pred_label = dd.argmin(axis=1)
        acc = np.sum(pred_label == all_label.float().numpy()) / len(all_fea)

    log_str = '这个可是获取label过程中的哦 Accuracy = {:.2f}% -> {:.2f}%'.format(accuracy*100, acc*100)
    args.out_file.write(log_str + 'n')
    args.out_file.flush()
    print(log_str+'n')
    return pred_label.astype('int')   #返回的是预测的伪标签

这一部分代码设计到了很多矩阵计算,只能大致的理解这个过程是一个聚类的过程,但是还是有些地方不是很明白,被维数给弄昏头了,0.0

基于这个,我自己也尝试做了个实验,将同一种数据集变道两个分布,其中一个分布的数据集作为源域进行训练,然后另外一个分布做为目标域按照它的方法来进行迁移,但是最终的结果就很差,Accracy大概也就是随机猜的概率,不知道问题出在了哪里,人没了。

参考文献《Do We Really Need to Access the Source Data? Source Hypothesis Transfer for
Unsupervised Domain Adaptation》
源码地址:https://github.com/tim-learn/SHOT

最后

以上就是傻傻水壶为你收集整理的记一次不成功的迁移学习《Do We Really Need to Access the Source Data? Source Hypothesis Transfer for Unsupervise》的全部内容,希望文章能够帮你解决记一次不成功的迁移学习《Do We Really Need to Access the Source Data? Source Hypothesis Transfer for Unsupervise》所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(42)

评论列表共有 0 条评论

立即
投稿
返回
顶部