61

CCF BDCI新闻情感分类初赛A榜4/2735,复赛1%题解报告

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=MjM5ODkzMzMwMQ%3D%3D&%3Bmid=2650411919&%3Bidx=2&%3Bsn=082a95afe6a906b393129a8e9a403852
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

写在前面

本文将带来CCF BDCI新闻情感分类的题解报告,该方案在初赛A榜获得了4/2735,复赛成绩1%。希望可以给大家提供一些思路,互相交流学习

比赛代码已经开源在 https://github.com/linhaow/TextClassify

赛题说明

比赛的链接在这里:

https://www.datafountain.cn/competitions/350

iAnmi2q.png!web

比赛的内容是互联网的新闻情感分析。给定新闻标题和新闻的内容,然后需要我们设计一个方案对新闻的情感进行分类,判断新闻是消极的,积极的还是中立的。

训练数据集的字段如下:

id:新闻的唯一标识。

title:新闻的题目。

content:新闻的内容。

label:情感分类的标签。

数据分析

一个好的数据分析可以给比赛带来很大的提升。所以数据分析的过程不能忽视。对训练数据集分析后,可以发现训练集有如下一些特征。

(1)训练数据集中 0标签 的数据量比较少,只有几百。

(2)训练集中 1和2的标签 比较平衡,都是几千,相差不大。

(3)此外新闻的 文章内容 很长 ,很多有上千个字。

(4)相比于新闻内容, 新闻标题较短 ,很少有上百字的标题。

针对上述特征,可以看出标签中存在一定的数据不均衡,此外如何处理过长的文章内容也是一个核心任务。对于常用的bert模型,只能接收512个token,所以需要一个能处理过千字的文章内容的方法。

baseline

我刚参加比赛的时候初赛已经过去了一段时间了,当时有一个郭大开源的baseline,我的baseline是基于这个开源baseline修改的。baseline的结构图如下:

ayAjUba.png!web

整个模型由两个部分组成。

(1)第一部分是最下方的split_num个bert模型(这里可以使用bert全家桶中的任意一个),现在基本文本分类比赛的前排都是bert模型。毕竟bert模型在预训练的时候就加入了很多比赛外的数据。所以相对来说效果也不会太差。

为了同时利用标题和文章内容信息, 在bert模型的 输入端。 我选用了 两个sentence加[SEP]做为模型的输入,其中sentence1是该新闻的标题,而sentence2则是该新闻的内容的一部分。为了让新闻内容可以覆盖到整篇文章,我首先将文章分成split_num段,然后在每一段选择maxlen的长度,分别做为split_num个bert模型的sentence2的输入。

举个例子,如果下面的长方形代表的是一个文章的内容,而split_num是3,则三颗五角星的地方是三个bert的sentence2的输入。sentence1均是文章标题。

UFnEnyZ.png!web

(2)第二部分是上方的biGRU模块。该模块将bert对文章理解的不同部分串起来,最后给出综合考虑的分类输出。在这种RNN结构中, 双向 的效果往往比单向更好,所以使用了双向的GRU。

上述结构有如下的优点:

(1)减少了显存的使用,经过split后你可以在同样显存下处理更长的长度。

(2)另一个就是解决了长度上千的句子塞进bert的问题。上文bert模型处我使用的是中文的roberta-large模型。

提升模型

使用完上述baseline,并且调参后成绩就可以达到100名左右了。接下来就是如何提升模型的效果了。

(1)首先我发现郭大最初的代码好像有bug,当gru的层数大于1的时候维度不对。于是我查看源代码后把它fix了,然后在gru中加了几层layers,调整参数后,名次就到了前50了,大概线上是81.6左右。

具体的代码在pytorch_transformers文件夹中的modeling_bert.py第976行,修改后的代码如下:

self.gru.append(nn.GRU(config.hidden_sizeif i==0else config.lstm_hidden_size*2, config.lstm_hidden_size,num_layers=1,bidirectional=True,batch_first=True).cuda() )

这里增加layers的层数是为了让BiGRU更好地学习一篇长文章的不同部分。一层layer明显是 欠拟合 的,实际上实验结果也证明这里layers增大后成绩上去了不少。

(2)我尝试去清洗数据,除去一些url,img之类的数据,发现成绩不但没有提升,还有下降(其实这一点早就有心理准备了)。

因为在bert的使用中去除停用词等经常不能带来性能的提升。毕竟bert在预训练时使用的就是完成的带有噪声的文本信息,所以去停用词,清洗数据等不一定能带来效果的提升。

(3)我尝试了不同的split_num和maxlen,发现在roberta模型中,split_num越大的时候效果越好(我在7,8左右时效果最好),这是因为更大的split_num可以让模型更好地获取长句子的信息,更多的split_num可以让模型 捕捉的 密度 更高 ,从而对长篇章的把握更加准群。给一个极端的例子,如果你的 split_num 比较小,maxlen也比较小,刚好把文章先抑后扬的抑的部分全捕捉了,那准确性肯定会下降。 与此同时也需要增加 weigt_dec ay去防 止过拟合。通过这个方法线上就可以到81.7了

AzaAzmb.png!web

(4)使用多折交叉验证,多折交叉验证的效果会明显好于单折。 多折交叉和融合往往都能带来一定的稳定性和性能的提升。不过多折也 会增加 训练的时长 ,所以往往在先调参确定好模型后再进行多折交叉。通过这个方法,线上就可以到达81.8了, 当时已经在a榜前15了。

(5)使用roberta-wwm-ext模型, roberta-wwm-ext是一个基于全词遮罩(Whole Word Masking)技术的预训练模型。   简单来说,原有基于WordPiece的分词方式会把一个完整的词切分成若干个子词,在生成训练样本时,这些被分开的子词会随机被mask。 在全词Mask中,如果一个完整的词的部分WordPiece子词被mask,则同属该词的其他部分也会被mask,即全词Mask。 这个模型在复赛的a榜上效果比较好。这个应该是初赛和复赛训练数据集分布情况不同导致的。不过多样化的模型对于模型融合是很有帮助的。repo的链接如下:

https://github.com/ymcui/Chinese-BERT-wwm

(6)All data训练,在之前的训练中会使用一部分作为验证集,但是当你确定了所有超参数后你就可以尝试把所以数据拿过来盲跑,这样训练数据会多一点,不过缺点是拿不到本地的开发集测试结果。

模型融合

在得到两个模型的多个不同split_num,maxlen值后,就可以对模型的结果进行融合了,在融合中我使用的是分类问题中常用 投票法

总的来说就是用不同的模型输出结果进行投票,哪个结果的票多就选择哪个结果,如果票数一样,有0 label就选0,没有的话就选单模最好的模型。这里选0是因为之前的0样本在训练的时候比较少。

对于候选融合结果的选择我们采用的方法如下:

au6bIrq.jpg!web

首先我们计算不同输出文件之间的相关性,然后选择相关性最小的几个输出结果,在相关性和得分上需要对不同的输出结果进行一个trade off。即最好的情况就是几个得分较高的模型相关性比较低。相关系数可以使用pandas中的 corr()计算。

接着对选出的几个模型进行融合即可得到最后的输出,投票融合结果的代码框架如下:

import pandas as pd

import numpy as np

submits =['0.818.csv','0.816.csv']

#需要融合的文件放submits中

files = []

data = []

for f in submits:

if 'csv' in f:

files.append(f)

data.append(pd.read_csv(f).values)

print(len(files))


weight = [2,1]

#融合的权重

result = np.zeros([len(data[0]), 3])


for i in range(len(data)):

for j in range(len(data[0])):

if data[i][j][1] == 0:

result[j][0] += weight[i]

elif data[i][j][1] == 1:

result[j][1] += weight[i]

elif data[i][j][1] == 2:

result[j][2] += weight[i]


np.argmax(result, axis = 1)

submit = pd.read_csv('submit_example.csv')

submit['label'] = np.argmax(result, axis = 1)

submit.to_csv('result.csv',index=None)

总结

其实比赛中还有很多方法可以尝试,不过中间有点事耽误了很多时间所以就尝试了两个模型,有点可惜,文章中有很多地方都还是可以调整的,比如融合方法等,大家有空可以继续探究。

本文转载在公众号: 纸鱼AI,作者: linhw

推荐阅读

Transformer详解《attention is all your need》论文笔记

深度学习笔记系列(一):导数,梯度与方向导数

BERT源码分析PART I

BERT源码分析PART II

BERT源码分析PART III

站在BERT肩膀上的NLP新秀们(PART I)

站在BERT肩膀上的NLP新秀们(PART II)

站在BERT肩膀上的NLP新秀们(PART III)

Nvidia League Player:来呀比到天荒地老

关于AINLP

AINLP 是一个有趣有AI的自然语言处理社区,专注于 AI、NLP、机器学习、深度学习、推荐算法等相关技术的分享,主题包括文本摘要、智能问答、聊天机器人、机器翻译、自动生成、知识图谱、预训练模型、推荐系统、计算广告、招聘信息、求职经验分享等,欢迎关注!加技术交流群请添加AINLP君微信(id:AINLP2),备注工作/研究方向+加群目的。

qIR3Abr.jpg!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK