![aa6bb2bad9c3b3826e394006108ada9c.png](https://i-blog.csdnimg.cn/blog_migrate/a1a4c08be5ae45b997c12c699f754bfb.jpeg)
[深度应用]·Kaggle人类蛋白质图谱图像分类第一名解决方案
作者姓名:BF大神
出处:Kaggle
原文链接:CNN分类器和度量学习模型,第一名解决方案
编译:小宋是呢 http:// yansongsong.cn
主题5个月前在人类蛋白质图谱图像分类
祝贺所有获奖者,并感谢主持人和kaggle举办了这样一场有趣的比赛。
我很抱歉迟到,我最近几天努力准备它,试图验证我的解决方案,并确保它的可重复性,稳定性,高效性和解释性。
概述
![3d9db58361d30d2dc3cea3b8bd4ace03.png](https://i-blog.csdnimg.cn/blog_migrate/c094692c99ca81d83b1cea606a2a462e.jpeg)
挑战:极度不平衡,难以训练和预测的罕见类别,但在分数中发挥重要作用。列车集,测试集和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 这些是演示:
正确的样本,带有单个标签
![7635682f42be388cd64562b2eed5a666.png](https://i-blog.csdnimg.cn/blog_migrate/53425309de9a12657d418de80d42f770.jpeg)
正确的样本多个标签
![e7f943b5b67f351ee129678de41b8c98.png](https://i-blog.csdnimg.cn/blog_migrate/1a99126db943dccfe86818c160ac6817.jpeg)
带有稀有标签的 正确样本:脂质液滴
![c102e4094ac8b464cd086b8a50a041a4.png](https://i-blog.csdnimg.cn/blog_migrate/e68392e5c7975c27a24795b02d3e97e9.jpeg)
带有稀有标签的正确样品:棒和戒指
![004c56c6d5f25183ee567646258c3177.png](https://i-blog.csdnimg.cn/blog_migrate/f2d51142e74b68351a74927b7ec89f11.jpeg)
错过标签
![20bc21929008cd3f31b9afcf535d8178.png](https://i-blog.csdnimg.cn/blog_migrate/13f9d48f86c94bbe788d7510b0ace5c7.jpeg)
错误地添加标签
![f32fbb847e36c72716cb8f5573fe15fc.png](https://i-blog.csdnimg.cn/blog_migrate/c4e34286a8ad4683c6b5bf0cdce555c8.jpeg)
由于top1精度> 0.9,我想我可以使用度量学习结果来设置测试集的标签。但是我发现测试集与V18略有不同,有些样本在火车组和V18中找不到最近邻居。所以我设置了一个阈值并用找到的样本替换标签。幸运的是,阈值对阈值不敏感。替换测试集中的1000个样本与替换1300个样本的评分几乎相同。通过这样做,我的得分可以提高0.03 +,这是本次比赛的一个巨大进步。
我认为我的方法很重要,不仅可以提高分数,还可以通过以下方式帮助HPA及其用户:1。当有人想要标记或学习标记图像或检查质量时,他可以获取最近的图像以供参考。2.我们可以按度量对图像进行聚类,找到标签噪声,然后提高标签的质量。我们可以通过可视化预测来解释为什么模型是好的。
合奏: 为了保持解决方案简单,我不在这里讨论合奏,单个模型甚至单个折叠+度量学习结果足以获得第一名。
LB上的分数:
![086145b9a5387450431fe61381f0a474.png](https://i-blog.csdnimg.cn/blog_migrate/d272831659e1893fd5ca007453ab4175.jpeg)
对不起,我现在无法描述这部分的细节,正如我之前提到的,鲸鱼鉴定比赛仍在进行中。
自省:在我参加本次比赛之前,我从没想过我能找到出路,很难建立稳定的简历,而且得分对稀有班级的分布非常敏感。金牌是我最大的期望。
我觉得参加比赛变得越来越难。老实说,没有秘密,只有努力工作。我把每一场比赛视为推动我前进的力量。我强迫自己不要学习和使用太多的竞争技巧,而是需要解决的知识真正的问题。
很幸运我在本次比赛中找到了一个相对较好的解决方案,因为我未能在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
由于我在这场比赛后非常忙碌(并且将会持续一段时间),我使用了几乎相同的模型来完成鲸鱼比赛并且获胜者的模型非常好,所以我想我不需要写出那场比赛的总结。我重新认识相关论文和解决方案是鲸鱼比赛的不错选择。
谢谢你的耐心阅读!