概述
这个学期有幸在王老师的实验室实习,期间参加了京东的AI时尚挑战赛,我负责的是比赛的第一题-时尚风格识别,简单来说这是一个多标签分类的问题,最后的名次为第12名,本文记录并介绍此次比赛中我使用的主要思路和方法。
比赛详情
题目内容
本题是一个多标签分类问题,要求根据服饰单品/搭配的整体或局部设计判断服饰所属的一种或多种时尚风格。任务是给定一张人物的照片$I$,参赛算法需要预测$ s=f(I) \subseteqq S $,其中$S$是目标风格的集合,$s$是目标图片所所类型的集合,可能不止含有一个元素,因此是一个多标签的分类问题,
$S=\{运动,休闲,通勤,日系,韩版,欧美,英伦,少女,名媛淑女,简约,自然,街头朋克,民族\}$。
比赛提供了一个容量约为5万的数据集,评价的指标为F2-score的评价值:
数据预处理
数据预处理应该是比赛中很重要的一个环节,也是我们工作做的比较多的环节之一。
我们首先统计了京东官方给的数据集,主要是统计13个标签分别含有样本的数量,发现的问题是这个数据集中标签是非平衡的,即13个标签中有的标签所含样本图片数目较多,有的则含有的样本图片较少,其中,数量多的可以达到1万多张,而少的仅几百张。因此首先需要改善的是训练样本不平衡的问题。
我们的一个思路是,因为比赛方允许添加数据,所以我们添加新的数据来平衡数据集。因为老师之前有一个项目中使用了类似服装风格分类的样本集,但是标签不一样。我们选择其中和本次比赛标签比较类似的图片进行归类,最后添加的数据约为1万6。
添加之后有两个问题。第一个问题是虽然添加的数据可以缓解一下数据标签不平衡的问题,但是这个问题依旧比较明显。第二个问题是添加的数据和原本的数据有较大的差距。因为官方给的数据其实和服装网店中的图片比较相近,即人物的背景比较干净,背景多为纯色。但是扩充的数据人物的背景多比较杂乱。在未处理后直接放入,跑完后发现mAP不升反降。
第一个问题从数据来说,没有太好的解决方法了,我们在训练的时候有改进的方法。对于第二个问题,我解决的方法是,跑了一个Mask R-CNN。因为人总是照片中的主体,识别率基本在99.0%以上,因此是物体识别率最高的。我利用这个特点,把人和人的边框提取出来,将这个区域中除人外的区域的像素点填充为白色,用来代替原本样本。这样处理后,保证扩充的数据集背景是白色的。背景去除的过程如下:
数据预处理还有一个工作是,官方给的是图片的URL,有的图像下载下来之后是损坏的,因此我们先对于图片用openCV加载了一遍,把无法正常写入的图片去掉。
模型设计
问题求解的整体思路如下:
在数据预处理完之后,先用tf中给出的imageNet上训练好的模型进行预训练,我们使用的主要模型是inception_v4和inception-resnet_v2。之后加入处理之后的数据进行训练.但是在训练完之后发现mAP仅达到45%左右,观察每个标签的AP发现,10个标签的表现尚可,但是有3个标签的效果较低,不到10%,这三个标签是样本数量较少的三个标签,因此考虑可能的原因是在训练的过程中,由于标签的样本数目较少,因此有的batch中没有此标签的训练样本,因此训练的结果较差。所以我们为这三个表现较差的标签单独设计了一个size为64的batch,其中32个是有此标签的样本,32个是不含此标签的样本,这样就保证了这三个单独训练的模型在训练的过程中,batch是balance的,训练的结果整个,三个单独的模型中对应标签的AP是提高的,因此对于每一个网络模型,我们先训练好一个基础的模型,然后分别训练好三个单独的模型,之后将单独模型对应标签的结果替换到基础模型中。最后再做一个ensemble。
模型训练
在未提交的时候,官方的数据集中我们划分4万用来train,1万作为val,由于扩充的数据集还是和最后的val有一些区别的,因此我们把扩充的数据集全部作为train。在模型确定之后,我们将val中的数据也拿来进行train,提高样本的数量。需要说明的是,train和val中每个标签所占的比例应该是基本符合的,以免交叉验证受到数据不平衡的影响。
在基础模型训练的时候,batch是随机的;在三个单独的模型训练的时候,batch按照上面balance的方法进行填充。
代码实现
Mask R-CNN及背景抠除
Mask R-CNN实现参考github上的开源demo。
背景抠除实现如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55for loop1 in range(1,len_files):
image = skimage.io.imread(os.path.join(IMAGE_DIR, file_names[loop1]))
results = model.detect([image], verbose=1)
# Visualize results
r = results[0]
print ('--------------------')
print (file_names[loop1])
print ('--------------------')
print (np.shape(file_names))
#skimage.io.imsave('test.jpg',r)
#plt.savefig("examples.jpg")
# visualize.display_instances(image, r['rois'], r['masks'], r['class_ids'],class_names, r['scores'])
boxes = r['rois'];
masks = r['masks'];
scores = r['scores'];
class_ids = r['class_ids'];
# Number of instances
N = boxes.shape[0];
N = 1;
row = image.shape[1];
col = image.shape[0];
for i in range(N):
# Bounding box
if not np.any(boxes[i]):
continue;
y1, x1, y2, x2 = boxes[i];
# Label
class_id = class_ids[i];
score = scores[i] if scores is not None else None
label = class_names[class_id];
# Mask
mask = masks[:, :, i];
masked_image = np.zeros((col, row, 3), dtype=np.uint8);
masked_image = apply_maskX(masked_image, mask);
frontImage = image.copy();
for m in range(row):
for n in range(col):
if(masked_image[n, m, 0]<254):
frontImage[n,m,0] = 255;
frontImage[n, m, 1] = 255;
frontImage[n, m, 2] = 255;
roiImg = frontImage[y1:y2, x1:x2];
roiImg = cv2.cvtColor(roiImg, cv2.COLOR_BGR2RGB);
fileName = file_names[loop1]
fileName = "/home/jdai/datasets/jd-fashion/style-recog-samples/sample_img/hyq2_img/laber2/"+fileName
cv2.imwrite(fileName, roiImg);
平衡的batch实现
其中这个实现也比较简单,先把样本集按照指定标签是否1划分为两部分,然后每个batch从指定标签为1的集合中random出32张图片,然后再从指定标签为0的样本集中random出32张图片,组成训练用的batch即可,实现如下:1
2
3
4
5
6
7
8
9
10
11# for Binary classification
for i in range(0,len(train_data)):
if (train_labels[i][12] == 1):
train_example_one_true.append(train_examples[i])
train_labels_one_true.append(train_labels[i])
else:
train_example_one_false.append(train_examples[i])
train_labels_one_false.append(train_labels[i])
len_label_one_true = len(train_example_one_true)
len_label_one_false = len(train_example_one_false)
总结
其实这个任务中,我们主要解决的是训练集合不balance的问题。解决的思路主要有两个,一个是补充训练样本缓解不平衡;一个是为三个样本数过少的标签设计了balance的batch。在比赛过程中还用到了一些常用的trick。总之这次比赛经历对我来说很有意义。