欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-05-09 21:07:02
...

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

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

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

编译:小宋是呢

 

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

概述 [深度应用]·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 这些是演示:
正确的样本,带有单个标签[深度应用]·Kaggle人类蛋白质图谱图像分类第一名解决方案正确的样本多个标签[深度应用]·Kaggle人类蛋白质图谱图像分类第一名解决方案带有稀有标签的 正确样本:脂质液滴[深度应用]·Kaggle人类蛋白质图谱图像分类第一名解决方案带有稀有标签的正确样品:棒和戒指[深度应用]·Kaggle人类蛋白质图谱图像分类第一名解决方案错过标签[深度应用]·Kaggle人类蛋白质图谱图像分类第一名解决方案错误地添加标签 [深度应用]·Kaggle人类蛋白质图谱图像分类第一名解决方案

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

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

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

LB上的分数: [深度应用]·Kaggle人类蛋白质图谱图像分类第一名解决方案
对不起,我现在无法描述这部分的细节,正如我之前提到的,鲸鱼鉴定比赛仍在进行中。

自省:在我参加本次比赛之前,我从没想过我能找到出路,很难建立稳定的简历,而且得分对稀有班级的分布非常敏感。金牌是我最大的期望。
我觉得参加比赛变得越来越难。老实说,没有秘密,只有努力工作。我把每一场比赛视为推动我前进的力量。我强迫自己不要学习和使用太多的竞争技巧,而是需要解决的知识真正的问题。
很幸运我在本次比赛中找到了一个相对较好的解决方案,因为我未能在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

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

谢谢你的耐心阅读!