点击上方“Datawhale”,选择“星标”公众号
第一时间获取价值内容
作者:GaryLIU 来源:Cver https://zhuanlan.zhihu.com/p/93806755 本文已授权,未经允许,不得二次转载
Kaggle profile: kaggle.com/garybios
rank: 60/125547
个人其实从入坑kaggle到最近拿到了GM,其实可以分成三个阶段。
去年9月中的时候,刚上大四不久,之前一直对热衷于DL的我,其实都只是在自学看书,学习一些理论知识,但动手实践非常少,框架也只是会一些Tensorflow/Keras/sklearn之类的。某一天无意只是想查一些项目的开源code,自己对着学习一下,就查到了kaggle,之前在kaggle也是有账号的,但只是为了下载Dog vs Cat的数据?。当时入坑的是TGS Salt Identification,一开始只能尝试着用keras去把开源kernel的代码跑一下,最后发现自己连改Unet换backbone都不会(之前都是白学了),就死皮赖脸的在讨论区发帖/水贴(参加过这个比赛的老师估计对我当时都有点印象),但幸运的是得到了许多非常热心的回答。慢慢地,对LB上分上了瘾。从一点都不会用pytorch到学会用pytorch魔改Unet模型,自己一路上分到top20,真的是非常刺激。GPU机器都是自己花在校生活费在vast.ai上租的单卡1080ti。
有了第一次在TGS获取的top2%银牌,开始在一些老师脑海中留下了些印象,到了第二个比赛Doodle Recognition Challenge,当时就和杜老师在知乎上认识了,并且和吴老师也一起组上了队伍。当然,当时的我还是对很多都非常不懂的,只是在队伍里打打下手。拿下第二个银牌升级为Expert title后,进入了华人Kagglers大群(当时expert是进群门槛,现在已经是master了),里面全是GM/Master/Expert大神,在群里向各位老师学习。后面寒假实习面试(鹅厂),遇到了Chen Joya师兄,虽然当时不match实习岗位,但是师兄挺欣赏我(师兄真的非常非常好人,感谢),私下给我提供4 * p40+4 * v100和我一起打比赛。这个时候就是在Human Protein Atlas Image Classification和action(国内第一位本科应届生Grand Master)他们一起打了,拿下第一枚金牌,我们的solution: kaggle Human Protein Atlas 比赛总结。
自己当时完全想不到自己有机会拿下金牌和Master title,在比赛里也认识到了涛哥(史上最快晋升GM的男人),杨老师等。在此阶段,和涛哥/action他们继续组队,或者和群里一些老师的讨论,主要是吸收/学习他们身上的一些调参和对数据的理解的经验。同时,自己也在相关岗位实习,做research,每天更是阅读许多论文,自己的能力在此阶段成长了许多。也在此阶段拿下了两块金牌,不过时间不等人,这个时候我刚好大学毕业了。
当觉得自己学习到一个程度,认为自己能够独当一面,独立解决大部分问题的时候,这时我选择了solo,但发现solo确实是比组队辛苦很多的,无队友讨论,需要自己去挖掘idea等等。幸运的是,在此阶段拿下了一枚solo金和一枚team金,成功在一年后晋升自己以前想都不敢想的Grand Master。
1. 以下所有的一些方法,都是一些top选手常用的,如果你使用得当,银牌是非常稳的,然后自己再加把劲点,金牌都非常有机会的。可能不够全面或者有错误的,希望各位小伙伴一起观摩观摩,如有补充的,麻烦留下评论,或者私信我,我修改补充上去。 2. 基本都是CV任务下的经验,其它类型的任务可适当借鉴下。
编程语言:python(⭐⭐⭐⭐⭐)
炼丹框架:Pytorch(⭐⭐⭐⭐⭐)、Keras(⭐⭐⭐⭐)、Mxnet(⭐⭐⭐)、Tensorflow(⭐)
必备框架:Apex(⭐⭐⭐⭐⭐)、Numpy、Opencv、PIL、Scikit-learn、albumentations、imgaug等
1. 个人看来,Pytorch的易用性其实是已经超越了另外几个框架,搭建/魔改模型都非常方便; 2. Apex可以让你只写几行代码,就可以轻松使用float16或者混合精度来训练模型,显存减少将近一半的情况下,训练速度也得到大幅度提升。 3. 自写数据增强库,推荐使用Opencv;如果是封装好的数据增强库,推荐albumentations或imgaug(或torchvison.transforms),基本想得到的transform方式都包含。
apex使用example
from apex import amp
import apex
...
model = resnet18()
optimizer = Adam(....)
model, optimizer = amp.initialize(model, optimizer, opt_level="O1", verbosity=0)
...
logits = model(inputs)
train_loss = criterion(logits, truth)
with amp.scale_loss(train_loss, optimizer) as scaled_loss:
scaled_loss.backward()
optimizer.step()
对于参赛者而言,往往具有一个好的baseline,或有着一套属于自己风格的pipeline,其实是已经成功了一半。好的baseline等价于一个好的起点,后面的改进遇到的阻碍相对也会少非常多。但是,要把baseline写好,其实是需要不少场比赛下来的经验或者是已经有过相关项目的工作经验。
调参是比赛环节里最重要的一步(日常工作也都是一样离不开调参的),好的learning rate和learning rate schedule对比不合适的,得到的结果也是千差万别,optimizer的选取或许多比较fancy的finetune技巧也是类似的结果。
2. lr schedule
3. finetune,微调也是有许多比较fancy的技巧,在这里不做优劣比较,针对分类任务说明。
optimizer = torch.optim.Adam([{'params': model.backbone.parameters(), 'lr': 3e-5},
{'params': model.fc.parameters(), 'lr': 3e-4}, ])
4. Find the best init_lr,前面说到3e-4在Adam是较优的init_lr,那么如何寻找最好的init_lr?
5. learing rate warmup,理论解释可以参 zhihu.com/question/3380
6. 如果模型太大的同时你的GPU显存又不够大,那么设置的batch size就会太小,如何在有限的资源里提升多一点?
分类任务的标签是one-hot形式,交叉熵会不断地去拟合这个真实概率,在数据不够充足的情况下拟合one-hot容易形成过拟合,因为one-hot会鼓励正确类别与所属类别之间的差异性尽可能大,但其实有不少类别之间是极为相似的。label smoothing的做法其实就是将hard label变成soft label。
2. topk-loss(OHEM)
OHEM最初是在目标检测上提出来的,但其实思想是所有领域任务都通用的。意思就是提取当前batch里top k大的loss的均值作为当前batch的loss,进行梯度的计算和回传。其insight也很简单,就是一中hard mining的方法,一个batch里会有easy sample和hard sample,easy sample对网络的更新作用较小(loss值小,梯度也会小),而hard sample的作用会更大(loss值大,梯度值也会大),所以topk-loss就是提取hard sample。
loss = criterion(logits, truth)
loss,_ = loss.topk(k=..)
loss = loss.mean()
3. weighted loss
weighted loss其实也算是一种hard mining的方法,只不过这种是人为地认为哪种类别样本更加hard,哪种类别样本更加easy。也就是说人为对不同类别的loss进行进行一个权重的设置,比如0,1类更难,设置权重为1.2,2类更容易,设置权重为0.8。。
weights = [1.2, 1.2, 0.8]
class_weights = torch.FloatTensor(weights).to(device)
criterion = torch.nn.CrossEntropyLoss(weight=class_weights)
4. dual pooling
这种是在模型层进行改造的一种小trick了,常见的做法:global max/average pooling + fc layer,这里试concat(global max-pooling, global average pooling) + fc layer,其实就是为了丰富特征层,max pooling更加关注重要的局部特征,而average pooling试更加关注全局的特征。不一定有效,我试过不少次,有效的次数比较少,但不少人喜欢这样用。
class res18(nn.Module):
def __init__(self, num_classes):
super(res18, self).__init__()
self.base = resnet18(pretrained=True)
self.feature = nn.Sequential(
self.base.conv1,
self.base.bn1,
self.base.relu,
self.base.maxpool,
self.base.layer1,
self.base.layer2,
self.base.layer3,
self.base.layer4
)
self.avg_pool = nn.AdaptiveAvgPool2d(1)
self.max_pool = nn.AdaptiveMaxPool2d(1)
self.reduce_layer = nn.Conv2d(1024, 512, 1)
self.fc = nn.Sequential(
nn.Dropout(0.5),
nn.Linear(512, num_classes)
)
def forward(self, x):
bs = x.shape[0]
x = self.feature(x)
x1 = self.avg_pool(x).view(bs, -1)
x2 = self.max_pool(x).view(bs, -1)
x = torch.cat([avgpool_x, maxpool_x], dim=1)
x = self.reduce_layer(x)
logits = self.fc(x)
return logits
5. margin-based softmax
6. Lovasz loss
class res50(torch.nn.Module):
def __init__(self, num_classes):
super(res50, self).__init__()
resnet = resnet50(pretrained=True)
self.backbone = torch.nn.Sequential(
resnet.conv1,
resnet.bn1,
resnet.relu,
resnet.layer1,
resnet.layer2,
resnet.layer3,
resnet.layer4
)
self.pool = torch.nn.AdaptiveMaxPool2d(1)
self.bnneck = nn.BatchNorm1d(2048)
self.bnneck.bias.requires_grad_(False) # no shift
self.classifier = nn.Linear(2048, num_classes, bias=False)
def forward(self, x):
x = self.backbone(x)
x = self.pool(x)
feat = x.view(x.shape[0], -1)
feat = self.bnneck(feat)
if not self.training:
return nn.functional.normalize(feat, dim=1, p=2)
x = self.classifier(feat)
return x
2. margin-based softmax(上面已经说到)
3. triplet loss + softmax loss,结合metric learning,对feature进行多个loss的优化,triplet loss也是可以有很多的花样,Batch Hard Triplet Loss,是针对triplet loss的一种hard mining方法。
4. IBN,切换带有IBN block的backbone,搜图(open-set)往往test和train是不同场景下的数据,IBN block当初提出是为了提高针对不同场景下的模型泛化性能,提升跨域(cross domain)能力,在reid下的实验,IBN表现优异。
5. center loss
6. Gem,generalized mean pooling,出自Fine-tuning CNN Image Retrieval with No Human Annotation,提出的是一种可学习的pooling layer,可提高检索性能。
class GeM(nn.Module):
def __init__(self, p=3, eps=1e-6):
super(GeM,self).__init__()
self.p = Parameter(torch.ones(1)*p)
self.eps = eps
def forward(self, x):
return LF.gem(x, p=self.p, eps=self.eps)
def __repr__(self):
return self.__class__.__name__ + '(' + 'p=' + '{:.4f}'.format(self.p.data.tolist()[0]) + ', ' + 'eps=' + str(self.eps) + ')'
7. global feature + local features 将全局特征和多个局部特征一起融合,其实就是一种暴力融合特征的方法,对提升精度有一定的帮助,就是耗时相对只使用global feature来说很多点,此种方法可参考在reid常用的PCB(Beyond Part Models: Person Retrieval with Refined Part Pooling)或MGN(Learning Discriminative Features with Multiple Granularities for Person Re-Identification)方法
8. re-ranking,是一种在首次获取检索图的候选图里做一次重新排序,获得更加精准的检索,相对比较耗时间,不适合现实场景,适合比赛刷精度。
出自蛙神-Heng CherKeng
import segmentation_models_pytorch as smp
class Res34_UNET(nn.Module):
def __init__(self, num_classes):
super(Res34_UNET, self).__init__()
self.model = smp.Unet(encoder_name='resnet34', encoder_weights='imagenet', classes=num_classes, activation=None)
self.avgpool = nn.AdaptiveAvgPool2d((1,1))
self.cls_head = nn.Linear(512, num_classes, bias=False)
def forward(self, x):
global_features = self.model.encoder(x)
cls_feature = global_features[0]
cls_feature = self.avgpool(cls_feature)
cls_feature = cls_feature.view(cls_feature.size(0), -1)
cls_feature = self.cls_head(cls_feature)
seg_feature = self.model.decoder(global_features)
return seg_feature, cls_feature
3. lovasz loss 之前在TGS Salt Identification的适合,lovasz对分割的效果的表现真的是出类拔萃,相比bce或者dice等loss可以提高一个档次。但是最近的分割赛这个loss的表现就一般,猜测是优化不同metric,然后不同loss就会带来不同的效果,又或者是数据的问题。
4. dice loss for postive,bce loss for negtive 主要就是将分割任务划分两个任务:1. 分割任务,2. 分类任务 dice loss可以很好的优化模型的dice score,而bce loss训练出来的分类器可以很好地找出negtive sample,两者结合可以达到一种非常好效果,详细解说可以参考我之前的一个solution: Kaggle Understanding Clouds 7th place总结
大部分都是牺牲推断速度来提高精度的方法,适合比赛,不适合现实场景。
4. 设计metric loss 许多小伙伴会有这样一个疑惑,比赛的评测metric往往和自己训练时所使用的loss优化方向不是那么一致。比如多标签分类里的metric是fbeta_score,但训练时是用了bce loss,经常可以看到val loss再收敛后会有一个反弹增大的过程,但此时val fbeta_score是还在继续提升的。这时就可以针对metric来自行设计loss,比如fbeta loss就有。
5. semi-supervised learning
from seutao
数据增强往往都是调出来的,可以先在本地里对图像施加不同的变换方式,用肉眼观察其是否有效(肉眼观察和模型学习到的不一定对等),之后再扔进去网络里训练验证其是否有效。