0.前言
二月份上旬参加了一个关于景区评分预测的竞赛,即根据游客对某景区的评论,预测该游客给该景区打几分。比赛还在继续,感兴趣的朋友点这里。
这是我第一次接触中文文本处理,半个月来最大的收获是了解了很多和NLP相关的知识,并且在各种试错中学到了一些对个人而言比较有用的建模经验。
虽然目前的模型和排在前列的队伍还有很大差距,但能够在2周时间里将模型得分提升近10%,并且一度冲进排行榜前8%(排名时刻在变,现在大概已经不是这个名次),我已经很开心了。
比赛还未结束,但我决定就此打住了。一来我参赛的主要目的是学习,而现在我已经学到了很多东西;二来我已经看到了自己目前的局限以及和大神之间的差距,就算再继续下去模型性能也难以有质的飞跃;三来是心疼自己的电脑,模型再跑下去我怕本子会爆炸。
但,在此之前我需要做个总结。
这篇文章主要记录自己的建模思路,以及在数据处理、建模过程中得到的一些经验。文章结构如下:
- 建模总结,包括:1)使用的库,2)特征工程,3)模型结构,4)反思
- 数据处理和建模过程中学到的一些小tip
- 对评论进行探索性分析时发现的一些有趣的小事
1. 建模总结
1.1 使用的库
- 使用软件:
Python 3.6
- 基本数据处理:
pandas
,numpy
- 中文分词:
jieba
,re
- Word Embeddings:
sklearn
,gensim
- 算法调用:
sklearn
,keras
,xgboost
,lightgbm
1.2 特征工程
原始训练集中的数据一共只有2列:游客评论(文本)和其对应的打分(1-5分)。
基于这些数据,通过特征工程和Stacking,我的模型里一共用到了75个特征,包括10个人工特征、64个基于Word Embeddings的特征、1个二元分类预测特征。(实际构建的特征不止75个,但最终用到的一共是75个。)
1.2.1 人工特征
这部分的特征提取于文本本身,我用“人工特征”这个词主要是为了和后面的特征做区分。
10个特征具体说明如下:
- word count:文本里的词数,比如“我喜欢你”这句话里共有“我”、“喜欢”、“你”3个词。
- noun ratio:文本里的名词数量占比。
- verb ratio:文本里的动词数量占比。
- punctuation ratio:文本里的标点符号数量占比。
- char count:文本字数(不包括标点符号)。
- mean word length:文本里每个词的平均字数。
- unique word ratio:文本里非重复词的数量占比,比如“我超级超级喜欢你”这句话里共有“我”、“超级”、“喜欢”、“你”4个非重复词,那么这句话的非重复词数量占比为0.8(4/5)。
- pos word ratio:文本里的积极词数量占比,这里引入了第三方积极词词典。
- neg word ratio:文本里的消极词数量占比,这里引入了第三方消极词词典。
- neg pos ratio:文本里的积极词和消极词数量比例。
1.2.2 基于Word Embeddings的特征
说到文本处理,Word Embeddings(即“词嵌入”)是绕不过去的,这部分的特征主要建立在Word Embeddings上。
所使用的Word Embeddings如下:
- 基于词的Count Vectors
- 基于字符的Count Vectors
- 基于词的TF-IDF Vectors
- 基于字符的TF-IDF Vectors
- Word2Vec
由于Word Embeddings的结果往往维度过高,不利于做计算,所以我会先用一些简单算法将其“降维”,将这些算法得到的低维矩阵或预测值作为新特征加入到后续模型中。
这里用到的算法有:
- 朴素贝叶斯(多分类)
- 逻辑回归(多分类)
- 岭回归
- SVD
- LSTM
- fastText
1.2.3 二元分类预测特征
- binary prediction:用上述所有特征和light gbm构建的二元分类预测值。
思路:因为数据集里各个分值的评论分布很不均匀,打分为1或2的评论分别只占了0.5%和1%,在这种数据极度不平衡的情况下,少数类的预测准确率不会太高,于是我想干脆把目标特征简化成两类——分数1或2的评论为少数类、其他为多数类——并对这个二元分类做预测,然后将预测结果(评论为少数类的概率)作为新特征加入到后续模型中,这样也许能改善情况。
1.3 模型结构
从第一个模型到最后一个模型,我大概改了30个版本,以下是最终版本:
顺带说一句,岭回归属于回归算法,得出的结果为实数,然而目标特征实际上为整数(1-5分),所以最终还需要对预测值进行四舍五入,将实数化整。(当然,四舍五入不一定是最优的处理方式,我这里只是贪图方便。)
1.4 反思
我的模型得分在排行榜里虽然属于中等偏上,但和排在前几位的团队比起来差距还是不小,思考之后觉得有以下几个方面需要改进:
文本处理做得不够精细。因为这个预测模型完全是基于文本之上的,所以文本处理的好坏与否,直接决定特征和模型的质量。在中文文本处理中,(我个人认为)最重要的环节是分词,这部分我用了结巴分词,结巴分词速度很快,但对口语化的文本处理得不是很理想,需要引入第三方词典(词典是另一个关键点)。因为是第一次接触中文分词,经验不足,所以在分词部分我没有做太复杂的处理,因此有可能导致一些信息丢失。
深度学习做得不好。在构建基于Word Embeddings的特征时,我用到了LSTM和fastText,这两者都属于深度学习框架下的算法。我个人目前对深度学习的了解还不深,调用算法的时候基本属于“照着葫芦画瓢”,模型框架搭得比较粗糙(心态上也只是“试试看”),所以得出来得结果也不是非常理想。之后还需要花时间对深度学习进行一个系统的学习。
模型结构较乱。仔细看我的模型结构,就会发现整个结构其实挺乱的,因为我并不是一开始就设计好结构,而是边走边搭建模型、想到什么做什么,这么做的后果是:当预测结果不理想的时候,回溯找原因会比较费时,改造模型也会比较麻烦,此外,不恰当的特征、算法组合可能会产生不必要的性能损失。所以建模之前,应该要先设计好大致的结构框架。
2. 数据处理与建模小Tip
以下是我在处理数据、建模的过程中,脑子里出现“噢,原来如此!”或者“要是能这样就好了!”的瞬间,现在总结出来做Tips:
做Word Embeddings的时候,python中无论是
sklearn
、keras
还是别的库,全都是基于英文文本的,也就是说所输入的文本应该(或者说最好)是像英文那样词与词之间用空格隔开的。这种情况下,如果想用这些库给中文文本做Word Embeddings,可以先做分词,然后把每个词按顺序用空格隔开来模拟英文文本形式,再调用这些库做进一步的处理。在做中文分词的时候要考虑好中英混合的情况,中文和英文在分词处理上有很大的不同,一旦出现中英混合的文本,分词复杂度就大大增加了。
用
sklearn
做Count Vectors或TF-IDF Vectors的时候,有几点可以尝试:1)去掉stop words和不去掉stop words的都来一遍,看哪个效果更好;2)word和char都试试,有时候char比word效果要好;3)n-grams的范围可以多试试,有时候(1, 1)比较好,有时候(1, n)比较好;4)如果设置max features,数值不要设置得太小。在提升模型分数方面,比起调参,效果更显著的是改进特征工程和做Stacking。
一定一定要做Stacking,且最后一层的组合模型选用简单算法效果较好。
Light GBM比XGBoost速度快太多,如果数据量很大,推荐前者而不是后者。
如果预测目标是整数型,且数值范围不大(比如1-5),分类和回归都试试,或者二者结合,可能有意想不到的收获。
对于需要重复调用的代码,请务必先写好def。
数据量很大的时候,多线程和分布式是个好东西。(这一点还在努力学习中……)
3. 一些关于评论的有趣小发现
NLP的一个有趣之处在于,通过对文本进行处理,可以让一些隐藏在文本中的、一眼望过去难以发现的小秘密浮出水面。(这也是我为什么喜欢数据分析的原因。)
比如可以发现一些无效评论:
再比如,我们可以对比一下最高分和最低分评论的关键词(基于TF-IDF):
有时候做预测,反过来还可以发现一些更有趣的事情。
比如发现一些虽然给了高分但其实对景区并不满意的游客:
对于这些评论,虽然其打分为最高分5分,但根据语境,这些游客大多对景区印象不好。(我的模型也给出了较低分值,说明它对语义的理解还是比较不错的。)
还有一些迷之低分:
有趣的发现还有不少,感兴趣的可以参加比赛自己分析。
4. 结束语
这篇文章总结了我自己参加预测竞赛的建模过程和心得,以及分析文本之后得到的一些有趣的小发现。
总的来说,这是一次有趣的尝试,希望以后遇到类似情况的时候可以少走一些弯路。
以上。