历时两个月,Shopee比赛终于落下帷幕,我们队伍ID为Team Name,队员为“兰恒强”,在Private leadboard取得44名成绩,排名top2%,非常感谢队友小白Lan和zhengheng的强力付出与贡献,另外我们也很感谢那些分享了他们的想法和知识的参赛选手,特别是@chirs 和@ragnar。
1 比赛简介
- 比赛名称:Shopee - Price Match Guarantee
- 比赛任务:通过产品的图像和文本确定两个产品是否相同
- 比赛链接:https://www.kaggle.com/c/shopee-product-matching/overview
2 方案总结
我们的总体思路为:
- 图像特征: CNN Arcface
- 标题文本: TF-IDF 、基于ArcFace微调的Bert/Indonesian Bert
- 后处理:将预测结果数量为1的结果扩大为2
- 投票选择结果:根据“少数服从多数”对不同模型的预测结果进行筛选
3 图像匹配
- 余弦相似度:Efficient-B1,Efficient-B3,Efficient-B4,Efficient-B5,Efficient-B7,Desnet 101,eca_nfnet_l1
- 损失函数: ArcFace
- 优化器: Adam
- 组合:通过组合不同图像模型的特征,输入到KNN计算目标商品与候选商品的相似度
4 文本匹配
- 余弦相似度:基于Bert-en和Indonesian(马来语) Bert提取文本的特征向量,基于TfidfVectorizer提取词袋向量,输入到KNN获取目标商品与候选商品的相似度
- Bert微调:基于ArcFace 损失函数利用Bert进行商品类别识别训练,获取模型权重
5 后处理与投票
5.1 重新召回结果
前期根据最优lb分数的模型预测结果,我们统计结果发现存在大量没有匹配的样本,也就是匹配结果只有它,自己匹配到了自己,所以可以降低匹配标准(增加匹配距离阈值或者调小相似度阈值)
代码语言:javascript复制def combine_init_recall(row):
x = (row['matches_init'] ' ' row['matches_recl']).split()
# 初始预测匹配数为1再召回,否则不召回
if 1 == len(row['matches_init'].split()):
return ' '.join( np.unique(x) )
else:
return row['matches_init']
df['matches_recl'] = df.apply(vote_recall_predictions, axis=1)
df['matches'] = df.apply(combine_init_recall, axis=1)
最开始结果数量为1的样本数为:
代码语言:javascript复制train data matches none recall one match shape: (5283, 24)
后期可以减少到
代码语言:javascript复制train data recalled twice, only one match shape: (592, 24)
提分有:0.01
5.2 投票选择结果
因为不同模型的预测结果不一样或者存在噪音,那么可以根据模型性能设置投票的顺序,首选利用效果好的模型得到第一次结果,如果没有匹配到再使用次优模型的投票结果,合并之后得到最后的结果。
代码语言:javascript复制from collections import Counter
def vote_recall_predictions(row):
x = np.concatenate([ row['image_recall1'] , row['image_recall3'] , row['image_recall3']
, row['image_recall13'], row['image_recall17'], row['image_recall37']
, row['image_recall137']
])
collection_matches = Counter(x)
# 存在两个以上召回两个频次最大的,否则返回自身
try:
new_x = collection_matches.most_common(2)[0][0] ' ' collection_matches.most_common(2)[1][0]
except:
new_x = collection_matches.most_common(1)[0][0]
new_x = new_x.split()
new_x = np.concatenate([ row['oof_hash'], row['text_recall_660'], new_x ])
# bert 放在最后做召回
if len( np.unique(new_x) ) < 2:
new_x = np.concatenate([ row['text_bert1'] , row['text_bert3'] ])
return ' '.join( np.unique(new_x) )
6 做过的尝试
成功的尝试有:
- concat embedding:拼接不同模型的向量和组合不同模型的向量
- vote ensemble:投票选择结果
- post process:重新召回数量为1的结果
试过没用的:
- tta
- meta emb
- ....
另外“二分类”没有提升很多,其实第二名的第二阶段用到了lgb模型。
7 比赛总结
比赛已经结束,看到了前排大佬们的方案,有些想法还是不谋而合,有些想法是想到了但是没有尝试成功(需要坚持~),还有些想法之前没有听说过,学了很多名词,还有些想法非常细,很微妙。有遗憾有收获,最后再次感谢Team Mate:Lan和zhengheng。