笨妞很少做文本分类,因为工作中文本分类确实不怎么用得到,唯一一个项目用到短文本分类,验证集acc和f1都到90%以上,所以在笨妞印象中文本分类应该是很简单的分类问题,都不属于NLP问题。偶然碰到DC竞赛中“达观杯”文本分类大赛,尝试了一下新闻类文本的分类。其实并没有那么简单。
“达观杯”的主题是19类新闻分类,数据包含4个字段:id(文章索引)、article(字级别文章)、word_seg(词级别文章)、class(类别),其中字和词全部用数字代替。
以词度量文章的长度,其分布式这样的:
总体来说,算是长文本吧,且长度分布比较分散。
各类别的分布是这样的:
{'14': 6740, '3': 8313, '12': 5326, '13': 7907, '1': 5375, '10': 4963, '19': 5524, '18': 7066, '7': 3038, '9': 7675, '4': 3824, '17': 3094, '2': 2901, '8': 6972, '6': 6888, '11': 3571, '15': 7511, '5': 2369, '16': 3220}。
之前做短文本分类的时候用了两个模型——bilstm和cnn,两个模型的验证acc都在90%以上,所有首先考虑这两个算法。回过头去看,有这么高的精度主要还是分类的文章规则性比较强,且属于特定领域,词量不多,类别差异可能比较明显。
搜了一下别人对“文本分类”的总结,貌似中文,基于词的效果会好于字。因此用第三个字段(词级别文章)作为训练数据。首先训练词向量,分别训练了200维和100维的词向量做备用。
模型很快搭好,接下来设定输入长度,一般是以最长的文本长度作为输入长度,然而这个数据中,最长的文本达到几万字,不太适合,于是设定最长为1000(80%满足)。
首先跑bilstm,没有GPU,200维词向量且词向量在模型中不参与训练,跑一轮需要20个小时左右,第一轮验证准确率达到0.74的样子,没有勇气跑第二轮了。
换上TextCNN,和bilstm相比,速度快到飞起来,30分钟便可跑一轮,看别人说Textcnn做文本分类容易过拟合,于是采用了变换batch_size和lr,batch_size变化范围(32, 64, 128, 256), lr从0.0005开始,val_loss连续2次不下降,则lr变为原来的0.2倍。这样训练后,验证集f1到了0.8,测试集预测后提交,f1只有0.74。CNN并联和串联都尝试过,其中并联比串联更容易过拟合。
doc2vec+mlp,这个没什么好说的,充数,验证集f1为0.7019。
keras版本的fasttext。fasttext之前没有接触过,算是一个新收获。fasttext也是mikolov大神的杰作,原生版的原理基本上和word2vec类似,采用cbow和skgram两种模型,同样是一个输入层+一个隐藏层(映射)+softmax。不同的是,输入不仅仅是词,采用了ngram。原生的还需要安装,于是自组了keras版本的,后来发现,不用原生的是大失误。keras版本的,ngram设为2,16G的内存就很吃紧了,原因在于,按照ngram=2遍历,embedding层最大有几百万的节点,句子长度为1000,算一算确实比较可怕。用ngram=1,跑下来,f1只能到0.72,放弃。
4. 机器学习经典文本分类算法尝试
tfidf+svm,选错了svm的kernel,选了非线性kernel,tfidf特征有100多万维,用非线性kernel速度太慢了,且效果不好,即便压缩到5000维,速度还是跟不上。后来别人给的baseline,用linearsvc,速度快,且测试集f1能到0.77。这就是经验呀。
tfidf+nb: 原本觉得朴素贝叶斯效果应该不会比mlp差的,但我做出来f1只有 0.59。
5. 文本inception:这是“看山杯”文本分类大赛第一名自创的模型,训练过程中过拟合严重,训练acc到0.92,验证集val_acc只有0.75.
6. 原生版fasttext: 认真读了一下fasttext的论文,并看了官网,发现原生版和自己的keras版本还是很不同的,原生版采用C语言,读入文本没有最大长度之分。所有数据全部读进去,且可分bucket,不像一般的深度学习框架,需要对句子进行padding。同时,原生版处理学习率是从0.1开始,然后按照训练步数来折算学习率的变化值,这也是keras不好操作的。不太明白的一点是:为什么word2vec和fasttext用0.1这么大的学习率,loss也并不震荡。
用原生版fasttext分别训练了字级别文章和词级别文章,验证集准确率分别是0.761和0.77,词级别测试集f1也达到0.774。好悲伤,搞了这么久才和人家的baseline同分。
想了一下,tfidf+svm和fasttext都有一个共同点——句子的长度对模型没有影响。而这个数据集中,句子长度分布太广了,很难一个模型照顾到短的文本和长文本。那么要不要做了个多输入的CNN什么的呢?想法虽好,但多通路选择性输入,训练的时候怎么反向传播呢?好吧,暂时到这儿吧。
在cnn和lstm中,尝试了embedding层训练和不训练两种,参与训练速度会慢一些,效果也并没有很好的提升。
复杂的模型未必会有很好的结果,简单模型效果未必不理想,没必要一味追求深度学习、复杂模型什么的。选什么样的模型还是要根据数据来的。
同一类问题,不同的数据效果差异很大,不要小看任何一类问题,例如分类,我们通常觉得它很简单,但有些数据并非你所想。本次“达观杯”到目前为止,第一名最高也只有0.80。