[深度应用]·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

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

谢谢你的耐心阅读!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券