PaddleClas是PaddlePaddle最近开源的图像分类代码库,里面包括了目前比较常用的分类模型,ResNet、HRNet、DenseNet、ResNeXt等,配置文件里也提供了这些模型的训练脚本,总共大概是117个模型,当然pretrained model权重文件也都给出来了,github地址在这里:https://github.com/PaddlePaddle/PaddleClas
模型库其实还是比较丰富,不过里面最吸引人的可能还是ImageNet1k val数据集上**top1 acc 82.4%**的ResNet50-Vd预训练模型,这个是224的图像分类情况下得到的,也是目前开源的精度最高的ResNet50系列模型。
这个模型主要是基Paddle自研的SSLD这个知识蒸馏方案得到的,全称是:Simple Semi-supervised Label Distillation,当然仅仅用ImageNet1k数据是很难做到这个高的指标的,PaddleClas里面引入了ImageNet22k数据集,并且对数据进行了抽样选择训练,这里主要是说下82.4%的指标实现思路,主要参考了PaddleClas的蒸馏文档:https://github.com/PaddlePaddle/PaddleClas/blob/master/docs/zh_CN/advanced_tutorials/distillation/distillation.md
预训练模型可以从这里下载:https://github.com/PaddlePaddle/PaddleClas/blob/master/docs/zh_CN/models/models_intro.md
带ssld后缀的模型都是使用SSLD知识蒸馏方案得到的模型。
SSLD的流程图如下图所示。
数据还是毋庸置疑地有效,要想提升精度,额外数据是必须的。
首先从ImageNet22k中挖掘出了近400万张图片,与ImageNet-1k训练集整合在一起,得到了一个新的包含500万张图片的数据集。然后,将学生模型与教师模型组合成一个新的网络,该网络分别输出学生模型和教师模型的预测分布,与此同时,固定教师模型整个网络的梯度,而学生模型可以做正常的反向传播。最后,将两个模型的logits经过softmax激活函数转换为soft label,并将二者的soft label做JS散度作为损失函数,用于蒸馏模型训练。这里其实PaddleClas也开源了79%地MobileNetV3 large模型,主要也是以这个模型蒸馏为例,给出了一些消融实验结论。
教师模型的选择。在进行知识蒸馏时,如果教师模型与学生模型的结构差异太大,蒸馏得到的结果反而不会有太大收益。相同结构下,精度更高的教师模型对结果也有很大影响。相比于79.12%的ResNet50_vd教师模型,使用82.4%的ResNet50_vd教师模型可以带来0.4%的绝对精度收益(75.6%->76.0%
)。
改进loss计算方法。分类loss计算最常用的方法就是cross entropy loss,实验发现,在使用soft label进行训练时,相对于cross entropy loss,KL div loss对模型性能提升几乎无帮助,但是使用具有对称特性的JS div loss时,在多个蒸馏任务上相比cross entropy loss均有0.2%左右的收益(76.0%->76.2%
),SSLD中也基于JS div loss展开实验。
更多的迭代轮数。蒸馏的baseline实验只迭代了120个epoch。实验发现,迭代轮数越多,蒸馏效果越好,最终迭代了360epoch,精度指标可以达到77.1%(76.2%->77.1%
)。
无需数据集的真值标签,很容易扩展训练集。SSLD的loss在计算过程中,仅涉及到教师和学生模型对于相同图片的处理结果(经过softmax激活函数处理之后的soft label),因此即使图片数据不包含真值标签,也可以用来进行训练并提升模型性能。该蒸馏方案的无标签蒸馏策略也大大提升了学生模型的性能上限(77.1%->78.5%
)。
ImageNet1k蒸馏finetune。仅使用ImageNet1k数据,使用蒸馏方法对上述模型进行finetune,最终仍然可以获得0.4%的性能提升(75.8%->78.9%
)。
指标上升情况大概如下
ImageNet22k数据包含1400W张图像,如果全部用来训练的话,训练成本太高,因此PaddleClas里面参考了FaceBook之前的一个做法,用大模型去预测所有图片,这个大模型是包含1000类的图像分类模型(ImageNet 1k)对于每一个类别,都找到得分Top-k的图片,这样总共可以找到1000k张图片,再和ImageNet1k 训练集融合在一起,组成最后的500W张图片的训练集。
在这里需要注意的是,因为引入了新的数据,因此为了防止引入的新数据中包含ImageNet1k val的数据,因此需要对数据进行去重。PaddleClas里面是使用了SIFT特征相似度匹配的方法进行去重,防止添加的ImageNet22k数据集中包含ImageNet1k val的数据,但是这块去重的代码目前还没有开源。
大数据集训练+ImageNet1k蒸馏finetune
的策略。选择合适的教师模型,首先在挑选得到的500万数据集上进行训练,然后在ImageNet1k训练集上进行finetune,最终得到蒸馏后的学生模型。为了验证教师模型和学生模型的模型大小差异和教师模型的模型精度对蒸馏结果的影响,PaddleClas提供了几组实验。训练策略统一为:cosine_decay_warmup,lr=1.3, epoch=120, bs=2048
,学生模型均为从头训练。
Teacher Model | Teacher Top1 | Student Model | Student Top1 |
---|---|---|---|
ResNeXt101_32x16d_wsl | 84.2% | MobileNetV3_large_x1_0 | 75.78% |
ResNet50_vd | 79.12% | MobileNetV3_large_x1_0 | 75.60% |
ResNet50_vd | 82.35% | MobileNetV3_large_x1_0 | 76.00% |
从表中可以看出
教师模型结构相同时,其精度越高,最终的蒸馏效果也会更好一些。
教师模型与学生模型的模型大小差异不宜过大,否则反而会影响蒸馏结果的精度。
因此最终在蒸馏实验中,对于ResNet系列学生模型,使用ResNeXt101_32x16d_wsl
作为教师模型;对于MobileNet系列学生模型,使用蒸馏得到的ResNet50_vd
作为教师模型。
基于PaddleClas的蒸馏策略为大数据集训练+imagenet1k finetune
的策略。
针对从ImageNet22k挑选出的400万数据,融合imagenet1k训练集,组成共500万的训练集进行训练,具体地,在不同模型上的训练超参及效果如下。
Student Model | num_epoch | l2_ecay | batch size/gpu cards | base lr | learning rate decay | top1 acc |
---|---|---|---|---|---|---|
MobileNetV1 | 360 | 3e-5 | 4096/8 | 1.6 | cosine_decay_warmup | 77.65% |
MobileNetV2 | 360 | 1e-5 | 3072/8 | 0.54 | cosine_decay_warmup | 76.34% |
MobileNetV3_large_x1_0 | 360 | 1e-5 | 5760/24 | 3.65625 | cosine_decay_warmup | 78.54% |
MobileNetV3_small_x1_0 | 360 | 1e-5 | 5760/24 | 3.65625 | cosine_decay_warmup | 70.11% |
ResNet50_vd | 360 | 7e-5 | 1024/32 | 0.4 | cosine_decay_warmup | 82.07% |
ResNet101_vd | 360 | 7e-5 | 1024/32 | 0.4 | cosine_decay_warmup | 83.41% |
对于在大数据集上训练的模型,其学习到的特征可能与ImageNet1k数据特征有偏,因此在这里使用ImageNet1k数据集对模型进行finetune。finetune的超参和finetune的精度收益如下。
Student Model | num_epoch | l2_ecay | batch size/gpu cards | base lr | learning rate decay | top1 acc |
---|---|---|---|---|---|---|
MobileNetV1 | 30 | 3e-5 | 4096/8 | 0.016 | cosine_decay_warmup | 77.89% |
MobileNetV2 | 30 | 1e-5 | 3072/8 | 0.0054 | cosine_decay_warmup | 76.73% |
MobileNetV3_large_x1_0 | 30 | 1e-5 | 2048/8 | 0.008 | cosine_decay_warmup | 78.96% |
MobileNetV3_small_x1_0 | 30 | 1e-5 | 6400/32 | 0.025 | cosine_decay_warmup | 71.28% |
ResNet50_vd | 60 | 7e-5 | 1024/32 | 0.004 | cosine_decay_warmup | 82.39% |
ResNet101_vd | 30 | 7e-5 | 1024/32 | 0.004 | cosine_decay_warmup | 83.73% |
Teacher Model | Teacher Top1 | Student Model | Student Top1 |
---|---|---|---|
ResNet50_vd | 82.35% | MobileNetV3_large_x1_0 | 76.00% |
ResNet50_vd | 82.35% | MobileNetV3_large_x1_0 | 75.84% |
def net(self, input, class_dim=1000):
student = ResNet34_vd(postfix_name="_student")
out_student = student.net( input, class_dim=class_dim )
teacher = ResNet50_vd()
out_teacher = teacher.net( input, class_dim=class_dim )
out_teacher.stop_gradient = True
return out_teacher, out_student
cd model_final # enter model dir
for var in ./*_student; do cp "$var" "../student_model/${var%_student}"; done # batch copy and rename