FashionAI服装属性标签大赛参赛经验

我在初赛结束前一个月从论坛上看到了帖子实战阿里天池竞赛——服饰属性标签识别,正好当时并无太多事情要忙,于是下载了代码和数据集,准备小试一下。

两个月后,比赛终于落下帷幕。尽管最后只取得了初赛78,复赛52的成绩,但在比赛的过程中的亲身经历还是让我对于参数的修改,超参的选择和网络结构的选择等诸多内容有了更深的了解。所以准备写个帖子总结一下,方便以后自己回顾,顺便也希望借此能够和大家多多交流。

初赛

赛题介绍

不管是初赛还是复赛,服装属性标签的比赛当中所需要完成的任务共两大类,即length和design;又进一步细分为八小种,分别是 pant_length,coat_length,skirt_length,sleeve_length,collar_design,neck_design,neckline_design 和 label_design。尽管对于所有的任务都可以使用相同的模型进行分类,但是由于两大类任务之间存在着明显的不同,因而我在初赛中分别对它们做了特殊处理。

首先,在离线训练评估的过程中发现,比赛的八种任务在准确率和mAP上存在着显著的差异,具体数据如下所示:

  collar_design neckline_design neck_design lapel_design pant_length skirt_length sleeve_length coat_length
acc 0.874 0.878 0.844 0.894 0.893 0.961 0.929 0.885
mAp 0.889 0.888 0.864 0.915 0.902 0.966 0.929 0.885

从上表中的数据中可以看出,design相关的任务的准确率和mAP明显要低于length任务的数据,因此我在初赛阶段将提高design任务的结果作为了比赛重点。

参数固定

hetong007 的样例代码选择了 resnet50_v2 作为分类的模型,并利用微调进行模型的训练;在之前的 kaggle dog breed 比赛当中,ypw 分享的代码则是选择了将模型的卷积层部分作为特征提取器,对得到的向量训练一个 MLP 作为分类模型。那么,在训练的过程中对卷积层的参数进行固定与否会带来差别吗?

试验结果表明,将卷积层的参数固定后再进行训练,得到的结果会稍好一些。(因忘记记录相关数据,故此处未列出相关具体数据,大约有1%左右的提升。)不管是 resnet50_v2 还是其他的模型,MXNet 当中提供的预训练的模型都是在 ImageNet 这样的巨型数据集上进行训练的结果,因此其应该学习到了足够多的特征。而本次比赛所使用的服饰数据和 ImageNet 中的数据差别并非巨大,因此使用预训练的模型并对卷积层参数固定的方式比从头开始学习或者微调的结果都要好。

P.S. 然而在其他的数据上,例如医疗相关的数据,采用此种方式是否能够得到类似的结果暂未可知。

P.P.S. 在通过设定卷积层参数梯度为 null 后进行训练的过程中发现,似乎此种方式训练的速度更快。大概 MXNet 对此进行了优化。既然卷积层参数的梯度被设定为 null ,那么在反向传播的过程中卷积层的梯度便不会用来更新对应权重。

外部数据

在使用 MXNet 提供的预训练模型之前,我曾尝试过使用外部数据帮助训练。最初的设想为,既然使用在 ImageNet 上预训练的模型能够很好的提取相关特征,那么使用服饰相关的数据集训练一个模型,并在此模型的基础上进行固定卷积层参数训练是否会更好呢?于是我选择使用 DeepFashion 数据对 resnet50_v2 等模型进行训练,并在其基础上进行 FashionAI 模型的训练。

但是最终的结果表明,个人训练得到的模型性能不如 MXNet 提供的预训练模型。我个人的猜测,一方面可能是 DeepFashion 的数据量太小(只有几十万张图片)导致了过拟合,另一方面则可能是因为在单卡(对,我只有一块 1080Ti)上训练得到的模型的性能远未达到模型的理想性能。故最终这一设想被舍弃。

P.S. 与之类似的一个想法有,在 pant_length 这样的任务上训练好一个模型后将其作为后一个模型的预训练参数,再进行微调,最后结果证明效果不佳。

More data, better model

在提到深度学习时,常常会听到这样一种说法,数据、算力和算法是深度学习的三驾马车。在本次比赛中,通过试验也确实能证明这一点。在离线的训练当中,通常会选择9/10的数据作为训练集,1/10的数据作为验证集。但是在最终对测试集进行预测之前会选择在全部的数据上进行训练,这样一来使用的数据更多,模型的过拟合程度也会更低。

比赛中我分别使用训练集数据和全部数据进行训练,并在测试集上进行预测,提交后的得分结果表明,使用全体数据训练得到的结果相比只使用训练集数据约有0.5%的提升。

P.S. 尽管数据越多,得到的结果越好,但是想要引入更多的数据是相当困难的。根据数据集的说明,在网络上进行数据爬取需要付出较高的成本,得到的数据还需要进行数据清洗才能够真正使用。故对于个人参赛的选手而言,这个选项基本是不可行的。

The “m label”

在 FashionAI 的数据中,每一件衣物所属的类别并非是全然确定的。通过对标签数据的统计发现,其中包含了少量的 “m label” (大约2%,具体数据已经忘了)。所谓 m label ,即预测成标记为 “m” 的类别是不计入准确率计算。详细的官方的比赛评分说明如下:

MaxAttrValue 对应的标注位是 ‘y’ 时,记为正确:PRED_COUNT++,PRED_CORRECT_COUNT++; MaxAttrValue 对应的标注位是 ‘m’ 时,不记入准确率评测:无操作; MaxAttrValue对应的标注位是’n’时,记为错误:PRED_COUNT++。

为此想要利用 m label 帮助模型训练是相对较为困难的,因此在多次尝试(将图片放入 m 对应的 label 文件夹,并在 y 对应的文件夹中将图片复制若干倍)而评分并不能增加后放弃了 m label 。

类别减少

在官方的数据解释当中,sleeve_length(袖长)数据中有 9 个类别需要区分。但是通过对实际数据的观察可以发现,sleeve_length 中 label=0 的数据极少(不足其他类别的1%),因此考虑在模型训练时可否将类别减少为 8 个,而将 label=0 的概率强制设定为 0.0。由于 FC 层的参数数量和最终的类别数目密切相关,因此从理论上来讲,通过减少类别可以提升模型的性能;但与此同时,由于强制将 label=0 的概率设置为 0.0,最终部分数据肯定会得到错误的结果。因此最终两者谁会占据主导需要试验来说明,结果如下表所示。

  acc mAP loss
9类 0.854 0.921 0.533
8类 0.862 0.924 0.534

可以看出减少类别后模型的准确率有 0.8% 的提升,而 mAP 有 0.3% 的提升,所以最终参数减少带来的优势占据了主导,因此在随后的任务中将 sleeve_length 的类别减少为 8。

但是在复赛阶段,sleeve_length 中 label=0 的数据比例约占全部数据的 2.5% ,此时则应选用 9 个类别进行训练。

模型选择

在选择了固定卷积层参数进行模型训练的方式后,一个很自然的想法就是:既然卷积层的参数固定,那么实际上模型需要训练的参数只有最后的 FC 层的参数;那么一个更加复杂的模型和一个相对简单的模型在参数相同的情况下,显然前者的性能会更好,因此对 resnet50_v2,resnet101_v2 和 resnet_152_v2 进行了对比。得到的结论是在固定卷积层参数进行训练的前提下,更为的复杂的模型带来了更强的特征提取能力,因而得到的结果也就越好。图表结果如下

model weight decay fixed? Val-mAP Val-los Val-acc
resnet101_v2 1.00E-04 non-fixed 0.897 0.546 0.815
resnet101_v2 1.00E-04 fixed 0.900 0.510 0.820
resnet152_v2 1.00E-04 fixed 0.906 0.623 0.830
resnet152_v2 5.00E-04 fixed 0.915 0.553 0.847

从上表中可以得到的结论有:

  1. 卷积层参数固定好于不固定;
  2. 在卷积层参数固定的前提下,模型越复杂越高;
  3. 对于 resnet152_v2 而言,存在一个最佳的 weight decay 参数。

P.S. weight decay 的大小,sgd/sgd+momentum 的选择等超参都需要根据模型本身进行,本身似乎并没有普适的规则。

P.P.S. 但是需要注意的是,在比赛中可以为了追求更高的准确率和 mAP 而选择非常复杂的模型,但是在实际的项目中,考虑到复杂模型带来的计算量的大幅增加,有时候需要在两者之间进行权衡取舍。

P.P.P.S. 比赛中了解到,有其他的选手使用了 resnext 的模型,但由于 MXNet 并未提供此模型的结构文件和预训练模型,因此并未使用此模型。所以其性能究竟如何也无法判断。

更小的模型,更好的结果?

由于过拟合的问题始终存在,因此我考虑是否采用较小的模型能够解决这一问题呢?考虑到对于服饰而言,即便将其从 rgb 三通道图像变成灰度的单通道图像,人仍能够区分(颜色是相对不重要的属性)其款式如何。因此在 resnet50_v2 的基础上进行通道数的缩减,将模型的全部通道数缩减为原来的三分之一左右。 最后的离线训练数据现实,此时模型的训练速度极快,验证集准确率和原始 resnet50_v2 fine tuning 的结果近似,但不如固定参数后的 resnet50_v2。因此最后选择放弃这一思路。

多模型融合

既然选择复杂的模型能够带来较大的提升,那么更多复杂的模型是否会带来更好的结果呢?毕竟在 kaggle 的诸多比赛当中,很多 TOP1 选手就选择了大量模型进行融合。在本比赛中,最终选择使用了 resnet152_v2,inception_v3 和 densenet201 三个模型进行训练,在预测结果得到相应的结果后取其平均值得到最终提交的结果。 离线训练的结果显示,使用多模型融合后,mAP能有接近1%的提升,而准确率则有2%左右的提升。

P.S. 即便是简单的对结果进行取平均,在实际中仍然可以通过细微的变动来略微改善相应的结果(毕竟在比赛中,万分之一的提升都能够反超)。具体而言,对于不同模型得到的结果进行加和求平均时并非一定具有相同的权重。具体来讲,可以通过对模型赋予不同的权重(保持权重之和为1)在验证集上进行评估进而得到最佳组合。但在本次比赛中,因时间限制,最终(忘了)未对权重进行调整。

MLP

多模型融合的方式并未只有上述的方式,另外一种方式就是 ypw 曾经使用的,即将模型作为 feature extractor 提取特征,拼接后训练一个 MLP。尽管此种方法对于 GPU 要求不高,但是其缺陷在于无法使用更多的 data augmentation 带来的好处。因此曾经尝试在训练的过程中,对 data batch 使用三个不同的模型进行实时的特征提取和拼接,并在其上进行 MLP 的训练。

需要注意的是,由于 resnet152_v2 和 densenet201 要求输入图片的尺寸(常为224)和 inception_v3(常为299)不同,因此需要增加一个额外的函数对 data batch 进行处理。但是由于时间限制(初赛后期还有其他工作要做),并未对这一想法进行验证。

P.S. 将特征进行组合的方式并非只有拼接一种方式,在 VQA 等任务中将不同维度的向量整合还可以使用 FFT 重采样等更为复杂的方式,可能效果会更好。

目标检测

由于 design 任务的低准确率拖累了最终结果的提升,因此比赛后期将重点集中在了 design 任务之上。通过对原始图片的分析发现,原始图像大多为 512*512 像素的模特图片。在 design 任务中所要预测的 collar,neck,neckline 和 lapel(领子,立领,翻领,领线等)在图像中只占据较小的一部分区域,即图片中超过 8/9 的部分包含的信息都是无效的噪声信息。因此考虑使用目标检测模型将脖子的部分从原始图像中剪裁出来再进行进一步的分类。

在本次比赛中虽然可以使用 ssd 或者 yolo 等相对简单的模型,但由于初赛中 gluoncv 工具包尚未放出,且需要制作 rec 文件进行训练,因此我最后选择了相对更简单的 faster rcnn(可以直接使用类似以VOC2007数据集的xml文件进行训练)。考虑到四个任务当中所需要剪裁的目标尽管有相似性,但仍然略有不同,例如翻领在图片中的比例要大于普通的领子占据的比例。所以想要使用一个检测器对四类任务进行检测在标注的过程中就需要更大的宽容度,即选择较大的范围以保证真正重要的信息不被遗漏。于是在标注过程中,选择了唇部以下,胸部以上,两肩之间的区域作为检测目标。最后人工标记的图片数目约2000张。

data-preprocess task model fixed train-acc train-mAP train-loss val-acc val-mAP val-loss
norm collar_design_labels resnet50_v2 no 0.9966 0.9982 0.0177 0.8296 0.9042 0.6669
croped collar_design_labels resnet50_v2 no 0.9957 0.9978 0.0217 0.8854 0.9358 0.4556
croped collar_design_labels resnet50_v2 yes 0.9805 0.9896 0.0654 0.8866 0.9364 0.4519
croped collar_design_labels resnet152_v2 yes 0.9903 0.995 0.0312 0.9069 0.9462 0.44

从上表的结果可以看出,在使用了目标检测预处理之后不管是acc还是mAP均有了较大幅度的提升。 P.S.当然,如果想要分别训练得到一个检测器,那么可以选择更细致的标注方式,这样得到的结果也会更好。

采用了上述的这些手段之后,最终我在初赛取得了第 78 名的成绩,勉强进入复赛。

复赛

复赛的赛题和之前保持一致,不过在本阶段 design 任务保持不变,而 length 的任务难度大幅增加。在初赛阶段,所要进行预测的图片全部为真人模特的实拍图片;但是在复赛阶段,length 的任务中增加了大量(约 40%)的平铺图片。平铺图片的难度急剧增加(未经训练普通人都难以区分一条裤子到底是七分裤,九分裤或者更长),导致结果变差。

在本阶段延续了之前的一些思路,对四个 length 任务分别训练了四个不同的目标检测器,将外套,裤子,裙子和上身从原始图片中剪裁出来进行预测。除此之外,对图片预处理等尽心了细微的调整。

图片去重

由于初赛训练数据 + 一二阶段的评测数据 + 复赛的训练数据中存在着较多的重复,因此通过 md5 校验的方式将重复的图片删除以保证数据的准确。

图片预处理

在使用训练集中的图片进行模型训练之前,原始代码使用了 resize_short 的方式对图片进行缩放后随机剪裁。由于原始的图像大部分为 512*512 的正方向图片,因此 resize_short 的效果等同于 resize。

但是需要注意的是,由于目标检测算法对原始图像进行预处理,由此得到的剪裁后的图片长宽比发生了较大改变。例如,在 design 的图片中,有较多的图片长宽比在 2:1 左右。如果此时仍然采用之前的 resize_short 就会导致图片的宽度远远大于所需要剪裁的大小,这会导致有效信息的丢失。为了解决这一问题,对剪裁后的图片进行补齐处理,即为图片增加无信息的 margin 使其长宽比保持为 1:1。实际所使用的 margin 的像素值既非 0,也非 255,而是通过零中心时所使用的 mean+std 计算得到。

试验结果最终证明,这样的预处理操作也会增加最终结果的准确率(具体数据遗漏)。

数据增广

  1. hetong007 的代码中数据增广的使用相对较弱,因此在比赛中选用了更多的数据增广方式,例如色彩抖动和对比度抖动等,以帮助缓解过拟合(初赛中也使用了此策略)。
  2. 在训练阶段,hetong007 的原始代码将图片缩放至 256 后剪裁至 224,但是由于我对 design 任务采用了目标检测,过于激进的剪裁策略会导致有效信息的损失,因此更改为缩放至 234 后剪裁出 224 的图片进行训练。
  3. 在预测阶段,hetong007 对图片采用了 ten_crop 的处理(即镜像翻转 + 上下左右中心剪裁)来增加结果的可信度;在本次比赛中,个人采用 eighteen_crop 的方式,即在之前的剪裁基础上增加更多的剪裁处理(镜像翻转 + 上中下各三次剪裁),对比之前的结果有微弱的提升(初赛中也使用了此策略)。

图片分离

由于真人模型和平铺图片具有不同的数据分布,因此将其合并在一起进行训练可能并不好。为此训练了一个较小的网络区分一件衣服是平铺图片还是真人图片。将两类数据分离后分别使用之前的策略进行独立的模型训练,在预测过程中首先利用小模型进行类型判断,然后调取对应的模型进行预测。

关键点监测

在 FashionAI 的比赛中,除了服装标签属性比赛之外还有关键点检测的任务。对于平铺服饰的检测,个人认为使用关键点检测得到的数据进行 ML 建模分析可能会有较好的结果。但是由于之前并未写过相关代码,而网络公开的代码大多基于 tf 等,因此最终并未尝试这一思路。 如果有小伙伴这样做了,欢迎分享。

最后我在复赛阶段取得了第 52 名的成绩

总结

总之,参加这场耗时两个月的比赛消耗了大量的精力和空闲时间,但也由此对计算机视觉的任务更加熟悉。除了调参和 debug 更加熟练之外,代码之外对于深度学习和项目落地等也有了一些感觉。

  1. 在复赛二阶段,组委会邮件通知所有人为了比赛更加有意义,因此限制了复赛所使用的模型不能超过两个,总大小不能超过 600M(貌似是这么大)。由此可见,在项目的具体落地实施上并非准确率越高越高;为了取得 1% 的准确率提升需要付出多少代价是需要权衡的。
  2. 深度学习的局限性。归根到底,深度学习也算是流形学习,也不是万能的。例如对原始数据观察后发现了一些数据是超出 DL 能力范围的。大部分情况下,一张照片中的目标是完整的,但是存在部分数据存在着遮掩等等。尽管如此,人能够通过逻辑推理进行判断(有些图片中有着裸露的脚踝和裤脚,是长裤;有些露着大腿,那就只能是短裤等等),但如果此类数据数量较少,那么 DL 大概率会出错。

此外,根据论坛小伙伴 willer透露,他们团队输入尺寸设为512,成绩为初赛46,复赛12。

讨论请点这里