[深度应用]·Kaggle人类蛋白质图谱图像分类第一名解决方案

[深度应用]·Kaggle人类蛋白质图谱图像分类第一名解决方案
 

来自CNN分类器和度量学习模型,第一名解决方案

主题5个月前在人类蛋白质图谱图像分类

编译:小宋是呢

 

祝贺所有获奖者,并感谢主持人和kaggle举办了这样一场有趣的比赛。
我很抱歉迟到,我最近几天努力准备它,试图验证我的解决方案,并确保它的可重复性,稳定性,高效性和解释性。

概述 在此输入图像描述挑战:
极度不平衡,难以训练和预测的罕见类别,但在分数中发挥重要作用。
列车集,测试集和HPA v18外部数据中的数据分布不一致。
图像质量很高,但我们必须在模型效率和准确度之间找到平衡点。

对CNN的验证:
我根据https://www.kaggle.com/c/human-protein-atlas-image-classification/discussion/67819拆分了val集,非常感谢@trentb

我发现整个val集的焦点损失是模型能力的一个相对好的度量,F1不是一个好的度量,因为它对阈值敏感,阈值取决于列车和val集的分布。

我试图通过将每个类的比率设置为与列车组相同来评估模型的能力。我这样做是因为我认为我不应该根据公共LB调整阈值,但是如果我设置预测的比率稳定,并且如果模型更强,则得分会提高。也就是说,我使用公共LB作为另一个验证集。

训练时间增加:
旋转90度,从768x768图像中翻转并随机裁剪512x512补丁(或从1536x1536图像中裁剪1024x1024补丁)

数据预处理: 使用用于查找测试集泄漏的哈希方法从v18外部数据中删除大约6000个重复样本。

使用train + test计算平均值和标准值,并在将图像输入模型之前使用它们。

模型训练:
优化器:Adam 
Scheduler

lr = 30e-5
如果epoch> 25:
    lr = 15e-5
如果epoch> 30:
    lr = 7.5e-5
如果epoch> 35:
    lr = 3e-5
如果epoch> 40:
    lr = 1e-5

损失函数:FocalLoss + Lovasz,我没有使用宏F1软丢失,因为批量很小而且有些类很少,我认为它不适合这个竞争。我使用了lovasz损失函数因为我认为虽然IOU和F1不一样,但它可以在某种程度上平衡Recall和Precision。

 

我没有使用过采样。
模型结构: 我最好的模型是一个densenet121模型,非常简单,模型的头部与公共内核几乎相同https://www.kaggle.com/iafoss/pretrained-resnet34-with-rgby-0-460 -ia-lb by @iafoss

  (1): AdaptiveConcatPool2d(
    (ap): AdaptiveAvgPool2d(output_size=(1, 1))
    (mp): AdaptiveMaxPool2d(output_size=(1, 1))
  )
  (2): Flatten()
  (3): BatchNorm1d(2048, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (4): Dropout(p=0.5)
  (5): Linear(in_features=2048, out_features=1024, bias=True)
  (6): ReLU()
  (7): BatchNorm1d(1024, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (8): Dropout(p=0.5)
  (9): Linear(in_features=1024, out_features=28, bias=True)

我根据多标签分类论文尝试了各种网络结构,结果没有改进,而不是它们背后的漂亮结构和理论。:) 
预测时间增加: 我用4最佳焦点丢失时期预测测试集种子随机从768x768图像中裁剪512x512补丁,从预测中获得最大值。

后处理: 在比赛的最后阶段,我决定生成两个提交:1。第一个是保持标签与公共测试集的比例,因为我们不知道稀有类的比例,I将它们设置为火车组的比率。第二个是保持标签的比例与列车组和公共测试组的平均比率。

为什么?虽然我试图通过2-5个样本增加或减少稀有类别的数量,但是公共LB可以改进,但这是一种危险的方式。我只是用它来评估可能的重组。


公制学习:我参加了2018年5月的里程碑识别挑战,https: //www.kaggle.com/c/landmark-recognition-challenge,我曾计划在该竞赛中使用公制学习,但时间有限我完成了TalkingData比赛。但我读了很多相关的论文,之后做了很多实验。

 

当我分析我的模型的预测时,我想找到最接近的样本进行比较,我首先使用了CNN模型的特征,我发现它们不太好,所以我决定尝试Metric Learning。

我发现在这次比赛中训练非常困难,我花了很多时间但结果不太好,我发现同样的算法在鲸鱼识别比赛中能很好地运作,但我没有放弃,终于找到了过去两天的好模特。

通过使用该模型,我可以在验证集上找到最近的样本,top1精度> 0.9 这些是演示:
正确的样本,带有单个标签正确的样本多个标签带有稀有标签的 正确样本:脂质液滴带有稀有标签的正确样品:棒和戒指错过标签错误地添加标签 

由于top1精度> 0.9,我想我可以使用度量学习结果来设置测试集的标签。但是我发现测试集与V18略有不同,有些样本在火车组和V18中找不到最近邻居。所以我设置了一个阈值并用找到的样本替换标签。幸运的是,阈值对阈值不敏感。替换测试集中的1000个样本与替换1300个样本的评分几乎相同。通过这样做,我的得分可以提高0.03 +,这是本次比赛的一个巨大进步。

我认为我的方法很重要,不仅可以提高分数,还可以通过以下方式帮助HPA及其用户:1。
当有人想要标记或学习标记图像或检查质量时,他可以获取最近的图像以供参考。
2.我们可以按度量对图像进行聚类,找到标签噪声,然后提高标签的质量。
我们可以通过可视化预测来解释为什么模型是好的。

合奏: 为了保持解决方案简单,我不在这里讨论合奏,单个模型甚至单个折叠+度量学习结果足以获得第一名。

LB上的分数: 
对不起,我现在无法描述这部分的细节,正如我之前提到的,鲸鱼鉴定比赛仍在进行中。

自省:在我参加本次比赛之前,我从没想过我能找到出路,很难建立稳定的简历,而且得分对稀有班级的分布非常敏感。金牌是我最大的期望。
我觉得参加比赛变得越来越难。老实说,没有秘密,只有努力工作。我把每一场比赛视为推动我前进的力量。我强迫自己不要学习和使用太多的竞争技巧,而是需要解决的知识真正的问题。
很幸运我在本次比赛中找到了一个相对较好的解决方案,因为我未能在Track ML中找到强化学习算法,并且未能在Quick Draw比赛中及时完成一个好的CNN-RNN模型,但无论如何,如果我们只参加竞争胜利,我们可能会失败,如果我们争取学习并为主持人提供有用的解决方案,没有什么可失去的。

更新,度量学习部分:

对不起,迟到了!

因为我注意到具有相同抗体-id的样品具有几乎相同的标记,所以我认为我可以将抗体-id视为面部id,并且在HPA v18数据集上使用面部识别算法。

在训练时,我使用V18数据抗体ID来分割样本,将样本保存在验证集中,并将具有相同ID的其他样本放入训练集中。我使用top1-acc作为验证度量。

度量学习模型: 网络:resnet50增强:旋转90,翻转损失函数:ArcFaceLoss优化器:Adam调度程序:lr = 10e-5,50个纪元。

型号细节:

class ArcFaceLoss(nn.modules.Module):
    def __init__(self,s=30.0,m=0.5):
        super(ArcFaceLoss, self).__init__()
        self.classify_loss = nn.CrossEntropyLoss()
        self.s = s
        self.easy_margin = False
        self.cos_m = math.cos(m)
        self.sin_m = math.sin(m)
        self.th = math.cos(math.pi - m)
        self.mm = math.sin(math.pi - m) * m

    def forward(self, logits, labels, epoch=0):
        cosine = logits
        sine = torch.sqrt(1.0 - torch.pow(cosine, 2))
        phi = cosine * self.cos_m - sine * self.sin_m
        if self.easy_margin:
            phi = torch.where(cosine > 0, phi, cosine)
        else:
            phi = torch.where(cosine > self.th, phi, cosine - self.mm)

        one_hot = torch.zeros(cosine.size(), device='cuda')
        one_hot.scatter_(1, labels.view(-1, 1).long(), 1)
        # -------------torch.where(out_i = {x_i if condition_i else y_i) -------------
        output = (one_hot * phi) + ((1.0 - one_hot) * cosine)
        output *= self.s
        loss1 = self.classify_loss(output, labels)
        loss2 = self.classify_loss(cosine, labels)
        gamma=1
        loss=(loss1+gamma*loss2)/(1+gamma)
        return loss

class ArcMarginProduct(nn.Module):
    r"""Implement of large margin arc distance: :
        Args:
            in_features: size of each input sample
            out_features: size of each output sample
            s: norm of input feature
            m: margin
            cos(theta + m)
        """
    def __init__(self, in_features, out_features):
        super(ArcMarginProduct, self).__init__()
        self.weight = Parameter(torch.FloatTensor(out_features, in_features))
        # nn.init.xavier_uniform_(self.weight)
        self.reset_parameters()

    def reset_parameters(self):
        stdv = 1. / math.sqrt(self.weight.size(1))
        self.weight.data.uniform_(-stdv, stdv)

    def forward(self, features):
        cosine = F.linear(F.normalize(features), F.normalize(self.weight.cuda()))
        return cosine

 def __init__(self,....
    ... ...
    self.avgpool = nn.AdaptiveAvgPool2d(1)
    self.arc_margin_product=ArcMarginProduct(512, num_classes)
    self.bn1 = nn.BatchNorm1d(1024 * self.EX)
    self.fc1 = nn.Linear(1024 * self.EX, 512 * self.EX)
    self.bn2 = nn.BatchNorm1d(512 * self.EX)
    self.relu = nn.ReLU(inplace=True)
    self.fc2 = nn.Linear(512 * self.EX, 512)
    self.bn3 = nn.BatchNorm1d(512)

def forward(self, x):
    ... ...
    x = torch.cat((nn.AdaptiveAvgPool2d(1)(e5), nn.AdaptiveMaxPool2d(1)(e5)), dim=1)
    x = x.view(x.size(0), -1)
    x = self.bn1(x)
    x = F.dropout(x, p=0.25)
    x = self.fc1(x)
    x = self.relu(x)
    x = self.bn2(x)
    x = F.dropout(x, p=0.5)

    x = x.view(x.size(0), -1)

    x = self.fc2(x)
    feature = self.bn3(x)

    cosine=self.arc_margin_product(feature)
    if self.extract_feature:
        return cosine, feature
    else:
        return cosine

请参考论文:ArcFace:深层人脸识别的附加角度边缘损失 https://arxiv.org/pdf/1801.07698v1.pdf 深度识别:调查
https://arxiv.org/pdf/1804.06655.pdf

由于我在这场比赛后非常忙碌(并且将会持续一段时间),我使用了几乎相同的模型来完成鲸鱼比赛并且获胜者的模型非常好,所以我想我不需要写出那场比赛的总结。我重新认识相关论文和解决方案是鲸鱼比赛的不错选择。

谢谢你的耐心阅读!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值