这个比赛可以说是一波三折,本来应该早早就结束了,结果因为数据泄露更换了数据,中途还更换过评价指标,不过好在最后还是顺利结果,我们队伍拿到了前1%的成绩(21/4551),属于不是很好但是也不是很坏的结果,这是我第一次在kaggle上完整的参与一个NLP比赛,因此我感觉有必要将这次的比赛经历写下来,做一个总结。比赛链接:点击打开链接
1)查看数据,训练数据159571 条,测试数据153164条,比例大概是在51:49的样子。数据格式如下:
id | comment_text | toxic | severe_toxic | obscene | threat | insult | identity_hate
第一列为文本id,第二列为文本内容,后面的6列是需要预测的值,评价指标使用的是6列的AUC值的平均数。
进一步进行数据探索
countdata = train_data.iloc[:,2:].apply(pd.Series.value_counts)
print (countdata)
toxic severe_toxic obscene threat insult identity_hate
0 144277 157976 151122 159093 151694 158166
1 15294 1595 8449 478 7877 1405
可以看到大部分的结果都是0,少部分是1,也就是说训练集里面大部分的评论不是toxic的评论。检查空值:
print("Check for missing values in Train dataset")
null_check=train_data.isnull().sum()
print(null_check)
print("Check for missing values in Test dataset")
null_check=test_data.isnull().sum()
可以看到打印结果是没有空值。
看一下多标签的个数(同时有多个标签的样本数)
rowsums=train_data.iloc[:,2:].sum(axis=1)
x=rowsums.value_counts()
#plot
plt.figure(figsize=(8,4))
ax = sns.barplot(x.index, x.values, alpha=0.8,color=color[2])
plt.title("Multiple tags per comment")
plt.ylabel('# of Occurrences', fontsize=12)
plt.xlabel('# of tags ', fontsize=12)
#adding the text labels
rects = ax.patches
labels = x.values
for rect, label in zip(rects, labels):
height = rect.get_height()
ax.text(rect.get_x() + rect.get_width()/2, height + 5, label, ha='center', va='bottom')
plt.show()
更多的EDA参考这里吧,点击打开链接
我的预处理部分比较简单,包含以下几个步骤:
0)去掉一些特殊字符,包括标点和stopwords我也去掉了。
1) 把数字0-9全部换成英文zero-nine
2)把& ,@之类的符号换成了and和at
3)找了一些表情符号比如:) ,:p之类的替换成smile,把:(之类的替换成sad
4)在网上找了个正则表达式代码把诸如fuckkkkkkk和fuccccck之类的词语替换成fuck
5)找了一些正则表达式代码,把日期替换成date,把电话替换成phonenumber,把网址替换成url
总之,就是尽量进行规范和统一。
6)用TextBlob改正了一些错误的词语(其实主要是在下面的embedding的时候用的,如果在fasttext和glove的预训练词典中第一次找不到则先搜索一下改正后的词语)。
模型部分主要分为了两种模型,一种是传统的比如LR、GBM的模型,一种是NN模型。
其中,NN模型主要是用到了以下的手段:
1)Embedding 部分:使用了Glove预训练embedding和fasttext预训练embedding,还有二者一块使用的复合embedding(在embedding过后concatenate到一块),然后自己也用Word2vec/Glove训练了一个embedding作为辅助训练,即在二者预训练embedding都没有找到这个词的时候,就用自己训练的embedding作为初始化。
2)使用了CNN:一种是CNN,包括TextCNN和DPCNN,但是我的DPCNN效果一直很烂。(word-level)
3)使用了RNN:主要是bi-GRU,bi-LSTM,中间包括了各种参数不同或者微小变形,比如多接一层,少接一层,第一层LSTM第二层GRU(包括第一层bi-LSTM,第二层bi-GRU,然后第一层和第二层结果参考ResNet做concatenate等等),有些加上了attention机制。(word-level)
4)使用CNN-RNN/RNN-CNN模型:先用CNN,再使用RNN;或者先使用RNN再使用CNN。(word-level)
5)使用了char-word-rnn模型:word-level的使用RNN,char-level的使用CNN,然后二者concatenate到一块输入下一层dense层
6)从kernel上面抄了capsulenet的模型代码进行训练。
总体上来讲,上面的模型里面,RNN的效果比CNN的效果好一丢丢,RNN-CNN比CNN-RNN的效果好,然后char-word-rnn的效果也比较好(我个人猜测是因为CNN作用在char-level上可以提取出类似于词根的东西作为特征)。
此外,还尝试了一下其他的模型:
lightgbm:使用wordlevel的tfidf和charlevel的tfidf进行了训练
LR:同上
FM:同上
MLP: 搞了个全连接层网络(DNN),使用embedding的输出做训练,但是接输出前的部分部分是使用了Relu,tanh和不激活的输出做concatenate,想的是把MLP当做一个半线性拟合器。
总结一下我用到的主要的神经网络模型以及一点点建模时候的想法吧:
模型融合这部分没什么好讲的,主要的策略有两个:
1)NN模型的CV结果和SUB结果,使用LGB模型进行stacking,首先按照模型分类做stacking,也就是CNN结果做stacking,RNN结果做stacking,CNN-RNN/RNN-CNN结果做stacking。
2)其他的非NN模型结果,用NN做stacking。
3)stacking的结果再按照相关性以及线下/线上得分做blending。中间还尝试过不按模型做stacking,直接按结果的高低做,即分高的结果stacking,分低的结果做stacking,效果不如按模型来做的。
4)将没有stacking的所有结果做了个大的blending,这个blending再和stacking的blending结果再做blending作为最后结果提交。
最后选择的两个文件,一个是线上得分最高的结果,一个按线下计分比较高,线上计分也还可以的结果。
以上就是我这次的手段,基本上提分靠的是模型多,而且stacking和blending做的多,骚方法比较少。
后来1st和top5的队伍分享了他们的策略,发现他们的模型上面跟我其实差异不大,但是在数据的增强和训练方法上比我出彩,我跟他们不足的地方有以下几个地方:
1.没有增强数据,第一名队伍和第五名队伍都是用了先将语言翻译为其他语言再翻译回来的方式做数据增强,类似于EN-DE-EN这种。
2.没有使用BPEm
3.第一名使用了伪标签的半监督学习方法(Pseudo labeling),简单来说就是在模型比较强的时候,将模型预测出的测试集结果加入到训练集中继续训练,很大程度上能将一个本身很强的模型提升得更强。
其他我尝试过但是没什么用的方法:label-chain,即多标签学习里面先预测一个标签,再预测下一个标签结果时将上一个标签结果作为训练特征,完全没什么用;分开训练6个模型预测6个标签,也没有什么用;
其他还未尝试的方法包括:stacking的时候加入原有的训练特征或者embedding结果,还没有来得及尝试。
我的用到的代码已经基本放到了github上面,链接