基于朴素贝叶斯和逻辑回归中文外卖评论情感分类

  1. 数据集介绍
    1. 数据集来源

本文用到的数据集是来自爱数科(http://www.idatascience.cn/)的中文外卖评论数据集。该数据集包含大约12000条中文外卖评论及对应的情感标签,因此我们将其用于情感分析任务。

    1. 数据集内容介绍
      1. 字段描述

数据集的保存形式为.xlsx格式。字段描述如下:

表 1‑1数据集字段描述

字段名称

字段类型

字段说明

Label

整型

情感标签(1为正面,0为负面)

Review

字符型

评论内容

      1. 数据预览

数据预览如下:

 

图 1‑1 外卖评论数据集预览

      1. 字段诊断信息

关于label的信息:

表 1‑2数据集label信息

样本数

11987

缺失值个数

0

缺失值占比

0

不同取值个数

2

众数

0

众数的频数

7987

关于review的信息:

表 1‑3数据集comments信息

样本数

共11987条文本

缺失值个数

0

缺失值占比

0

    1. 数据集综述

该数据集包含大约4000条正向评论和8000条负向评论。数据集存放在Takeaway_data.xlsx文件中。其中评论内容中包含符号、繁体中文、简体中文以及标点等。

  1. 数据挖掘与分析的目标和任务

随着外卖行业的不断发展,外卖的各项功能也在不断的趋于完善和创新,其中就包括评价系统。既然有购物,自然就会有买完之后的感受了。一般评价有以下几点作用:抒发顾客的情感、给商家提供反馈、给其他买家提供可靠的经验参考。当然,美团外卖、饿了么等外卖平台均是以星级评定进行打分,我们这里为了简化模型,仅仅将系统分成了两个评价指标,好评和差评。有的用户可能打的星级比较高,但是他的评论可能是负面的。这会给商家和顾客产生很大的误导。

本次数据挖掘的任务为构建合适的数据挖掘模型,使之在输入文本评价的情况下,我们能够尽可能准确地判断出该未知标签的评论属于正面评论还是负面评论。本报告基于外卖用户评价数据,对中文文本执行了中文切词、停用词处理、中文文本向量化、朴素贝叶斯分类、PCA降维、基于PCA降维后的数据进行逻辑回归预测和分类指标可视化分析等实际操作,整个过程基于python实现。在数据集本身的挖掘过程中,绘制了关于好评常用100词的词云图以及差评常用100词的词云图,而且从原始评价中利用KNN抽象出了评价指标。

  1. 数据预处理
    1. 数据类型转化

由于下载的初始数据集中的评论包含繁体中文,因为繁体中文转化成的简体中文意义相同。为了降低后面数据计算的复杂度,我们在这里选择将数据转化成简体中文。

这里我们调用了python 内部的opencc工具包。转化函数如下:

from opencc import OpenCC
def t2s(text):
    """
        text  输入的繁体文本
    """
    output_text = OpenCC("t2s").convert(text)
    return output_text

    1. 噪声处理

因为我在第一次进行模型训练完之后,在测试集上面验证的正确率只有80%。后面我仔细观察分类错误的样本,并把他们写入csv文件中。通过分析发现,这些错误分类的数据中,有一些是数据本身具有问题。举个简单的例子,就是说一个评论输入的评价文本是好评,但是它的标签是差评0。为了初步筛选出这些一言看上去就不合理的数据,我们调用了python情感分析库SnowNLP。对于情感值大于0.95但是标签为0以及情感值小于0.05但是标签为1的数据,我们选择从初始数据集中除去。实现代码如下。

 

图 3‑1异常值处理部分代码展示

    1. 留出法划分训练集和测试集

前面我们已经介绍过数据集,数据集包括共12000条数据,其中4000条为正向评价另外的8000条为负面评价。经过处理之后,处理过的数据集包括8850条评价数据,其中1016条正向评价,其余均为负面评价。样本极度不均衡,我们会在后面进行模型训练的时候处理这个情况。赋予类别数量少的样本更高的权重。我们在这里采用留出法进行初始数据集的划分。

因为初始数据集在excel中是存在序号的,而且前1016条为正面评价,后7834条为负面评价。我们选择将800条正向评价和5600条负面评价的数据作为训练集,其他数据作为测试集。即生成包含5600条负面评价和800条正面评价的训练集。同时为了后面编码的方便,我们选择保存的excel文件格式为csv,编码方式为encoding='gb18030'。生成训练集和测试集的代码如图3‑2留出法部分代码展示。

 

图 3‑3留出法部分代码展示

    1. 中文切词

我们为什么要拆分句子为词语那?在回答这个问题之前,我们先介绍自然语言向量化。

我们对自然语言文本做向量化的主要原因,是计算机看不懂自然语言。假如这里有两句话:I love the game. I hate the game. 那么我们就可以简单粗暴地提取出如下特征。‘I,love,hate,the,game’。统计一下特征个数就会得到下列表格。

表 3‑1特征向量化示例展示

I

love

hate

the

Game

1

1

0

1

1

1

0

1

1

1

按照句子为单位,从左到右读数字,第一句表示为[1, 1, 0, 1, 1],第二句就成了[1, 0, 1, 1, 1]。这就叫向量化。

但是在处理中文的时候,会更加的麻烦一些。因为不同于英文、法文等拉丁语系文字,中文天然没有空格作为词语之间的分割符号。我们要先将中文分割成空格连接的词语。比如将“我喜欢这个课程”变成“我 喜欢 这个 课程”。这样我们就可以仿照英文句子的向量化,来进行中文句子的向量化了。

为了做特征向量化,我们利用jieba分词工具来拆分句子为词语。拆分函数如下:

def chinese_word_cut(mytext):
    	return " ".join(jieba.cut(mytext))

拆分前后的结果如下:

​​​​​​​

 

图 3‑4jieba分词效果展示

  1. 朴素贝叶斯模型介绍
    1. 多项式分布

多项式分布是指满足类别的实验,连续做n次后,每种类别出现的特定次数组合的概率分布情况。不妨,假设xi 表示类别i出现的次数,pi 表示类别i在单次实验中出现的概率。当满足前提条件i=1kxi=n 时,由随机变量xi 构成的随机向量X=[x1,…xk ]满足以下分布函数:

二项式分布和多项式分布结合朴素贝叶斯算法,经常被用来实现文章分类算法。不妨假设我们建立的词库大小为k,则文章中出现的某个词可以看成是一次满足k个类别的类别分布实验。我们假设一篇评论是由n个词组成的,因此一篇文章可以看成是n次符合类别分布实验后的产物。由此得知,一篇评论文章服从多项式分布,它是词库里的所有词语出现的次数组合构成的随机向量。一般情况下,词库比较大,评论文章只是由少量词组成,所以这个随机向量很稀疏,即大部分元素为0。

通过分析词库,我们容易统计出每个词出现差评及好评里的概率,即pi 的值。同时,针对待预测的评论文章,我们可以统计出词库里的所有词在这篇文章里的出现次数,即xi 的值及评论文章的词语个数n。代入多项式分布的概率质量函数即可求出概率。

  1. 朴素贝叶斯模型分析
    1. 模型简化
      1. 停用词介绍

无论什么自然语言的词汇,都只是某种特定组合的字符串而已。不管是处理中文还是英文,我们都需要处理一种词汇叫做停用词。比如还是拿刚才的例子,‘I hate the game’。直觉告诉我们,定冠词the应该是停用词,它是虚词没有什么特殊意义。所以为了简化模型,我们应该将停用词从特征里面剔除出去。

在中文维基百科中,停用词是这样定义的“在信息检索中,为节省存储空间和提高搜索效率,在处理自然语言数据(或文本)之前或之后会自动过滤掉某些字或词,这些字或词即被称为Stop Words(停用词)。”停用词是一种虚词,没有什么特殊的意义,甚至会干扰我们把握文本的特征,所以我们应该把停用词从特征里面剔除出去。

      1. 停用词处理

在scikit-learn中,英语停用词是自带的。只需要指定语言为英文,机器会帮助你自动处理它们。但是scikit-learn中并没有中文停用词,所以我们需要一个停用词表。这里我在github项目里面找到了一个哈工大停用词表。

首先定义一个函数,从中文停用词表里面,将停用词作为列表格式保存并返回。该函数如下:

def get_custom_stopwords(stop_words_file):
    with open(stop_words_file) as f:
        stopwords = f.read()
    stopwords_list = stopwords.split('\n')
    custom_stopwords_list = [i for i in stopwords_list]
    return custom_stopwords_list

指定使用的停用词表,为我们下载好的哈工大停用词表文件。

      1. 特殊词语和平凡词语规定

大部分评论语句中只有几个到几十个词语而已。但是如果我们把每一个出现的词语都当做特征,那样特征就太多了。因此选择有些词汇作为特征就值得我们注意一下了。其中包括那些过于普遍的词汇以及过于特殊的词汇。所以设置了特征词汇过滤。

stop_words_file = "stopwordsHIT.txt"
stopwords = get_custom_stopwords(stop_words_file)
    1. 中文语句向量化

读入CountVectorizer向量化工具,它依据词语出现频率转化向量。

CountVectorizer是特征数值计算类,是一个文本特征提取方法。对于每一个训练文本,它只考虑每种词汇在该训练文本中出现的频率。CountVectorizer会将文本中的词语转换为词频矩阵,它通过fit_transform函数计算各个词出现的频率。这里我们由于篇幅仅仅展现出了前五个数据样本的分析。

        1. 为了说明停用词的作用,我们使用默认参数建立vect

然后我们用向量化工具转换已经分词的训练集语句,并且将其转化为一个数据框,起名为term_matrix。

vect = CountVectorizer()
term_matrix = pd.DataFrame(vect.fit_transform(X_train.cutted_comments).toarray(), columns=vect.get_feature_names())

此时我们来看建立起的数据矩阵的前五行。

 

图 5‑1特征矩阵前五行展示

一共建立起了6612个特征。

        1. 然后我们测试加上停用词之后的效果。

 

图 5‑2特征矩阵前五行(去掉停用词)

列数变成了6474列,减少了一百多个特征,特征下降的还是不够明显,对于模型复杂度的下降也不够优秀。说明,这种停用词表的写法,依旧会有不少漏网之鱼。

        1. 首先就是前面那些显眼的数字,它们在情感分类中作为特征毫无意义。如果没有单位,没有具体语境,数字都是没有意义的。我们可以通过设定token_pattern来完成这个目标。加上我们对于普遍词语和特殊词语的约束,我们通过如下代码来实现。
vect = CountVectorizer(max_df=max_df, min_df=min_df,token_pattern=u'(?u)\\b[^\\d\\W]\\w+\\b',stop_words=frozenset(stopwords))
term_matrix = pd.DataFrame(vect.fit_transform(X_train.cutted_comments).toarray(), columns=vect.get_feature_names())

此时我们再观察生成的词汇矩阵。

 

图 5‑3特殊值处理后的矩阵前五行

那些数字全部消失,并且特征数量从六千多个降低成了1805个,大大降低了模型的复杂度。可能你会觉得这有点可惜,好不容易分出来的词就给扔掉了。其实,特征多一定是好事。在大量噪声混入时,会显著降低模型效能。

    1. 朴素贝叶斯分类
      1. MultinomialNB()函数介绍

调用sklearn.naive_bays.MultinomialNB()函数,该函数全称为先验为多项式分布的朴素贝叶斯。除了MultinomialNB之外,还有GaussianNB就是先验为高斯分布的朴素贝叶斯,BernoulliNB就是先验为伯努利分布的朴素贝叶斯。这里我们经过测试选择先验为多项式的朴素贝叶斯。

PXj=xjlY=Ck=xjl+ λmk+n λ

其中,PXj=xjlY=Ck 是第k个类别的第j维特征的第l个取值条件概率。mk 是训练集中输出为第 k类的样本个数。λ为一个大于0的常数,取值为1,即拉普拉斯平滑,也可以取其他值。代码实现如下:

      1. Pipeline管道简化学习过程

数据处理流程大概分成两个部分:1)特征向量化2)朴素贝叶斯分类

每次修改一个参数或者换成测试集的话,我们都要重新运行上面那么多的函数,会大大降低我们的效率。Scikit-learn提供了一个功能,叫做管道(pipeline),可以很方便解决这个问题。尤其是其中的make_pipeline()函数,能够根据给定的估算器构造一条管道。大大提高了编码效率。Pipe内部的步骤如下:

 

刚才所做的工作都在管道里面了。因此我们可以把管道当成一个整体模型来调用。

    1. 测试集测试

首先我们用训练集将模型拟合出来,并对测试集进行测试。采用以下代码:

pipe.fit(X_train.cutted_comments, Y_train)
pipe.predict(X_test.cutted_comments)
y_predicted = pipe.predict(X_test.cutted_comments)
print(metrics.accuracy_score(Y_test,y_predicted))
print(metrics.confusion_matrix(Y_test, y_predicted))

可以得到预测准确率为95.796%

    1. 朴素贝叶斯结果分析
      1. ROC曲线

 

图 5‑4朴素贝叶斯ROC曲线

朴素贝叶斯分类器的ROC曲线如上图所示,通过ROC曲线我们可以很容易得出分类器对样本的识别能力。越靠近左上角的ROC曲线越优秀。AUC面积为0.8,还是比较优秀的。

      1. 混淆矩阵热力图

在测试集中我们并没有改变样本,所以测试集中类别为0的样本数量较多。

 

图 5‑5混淆矩阵热力图

上图为混淆矩阵热力图,从该混淆矩阵中我们可以得到以下数据:

查准率P

P=TP/TP+FP

0.962

查全率R

R=TP/TP+FN

0.993

真正例率TPR

TPR=TP/TP+FN

0.993

假正例率FPR

FPR=FP/TN+FP

0.402

      1. 中文词云图

这里对所有评论中出现次数最多的前五十个词做了一个词云图。来展现外卖评论中的高频关键词,通过文字、色彩、图形的搭配产生更好的视觉效果。

 

图 5‑6中文词云图

  1. PCA降维+logistic逻辑回归
    1. 降维原因

在前面我们提到了,采用朴素贝叶斯分类的话。尽管我们经过了层层简化(停用词处理,特殊词和常见词),但是我们依旧需要面对1800多个词语也就是1800多个属性。对于如此之多的属性,处理起来运行速度是个很大的问题。所以我就在想可不可以利用PCA对原来的属性进行降维,这样就可以减少计算量,从而加快运行速度。

    1. PCA降维
      1. 降维前的处理

在进行PCA降维之前,我们需要重复一些前面的步骤,比如停用词处理,中文分词以及中文语句向量化。这些方法在上面都已经介绍过了,这里就不在赘述。经过上面的步骤之后,我们得到一个矩阵如下所示。

 

      1. PCA降维

因为存在太多的0数据,我们通过对降低的目标维度进行分析,通过计算explained_variance_ratio_,它代表降维后的各主成分的方差值占总方差值的比例,这个比例越大,则越是重要的主成分,绘制出了下图。理论上我们应该选择大于0.9比例的维数。但是我们经过测试发现即使降低到20维,对于训练集和测试集也有着较高的准确率。为了运行简便,我们选择将维度从2519维降低至20维。代码展示如下:

 

图 6‑1explained variance ratio

def print_PCA(term_matrix,y_label):
    pca = PCA()
    pca.fit(term_matrix)
    cumsum = np.cumsum(pca.explained_variance_ratio_)
    plt.plot(cumsum)
    plt.ylim(0, 1.1)
    plt.xlabel('num of principals', fontsize=16)
    plt.ylabel('explained variance ratio', fontsize=16)
    plt.show()
    1. 类别不均衡处理

我们发现训练集中的数据仅仅只有1000多个正样本,但是负样本却有5000多个。类别十分不均衡。因此我们选择从负样本中挑选出1000多个数据和正样本共同组成训练集。这是因为我们将维度降低至了20位,2000多个样本已经足够多进行训练了

    1. 属性对比分析
      1. 相关矩阵热力图绘制

 

图 6‑2相关矩阵热力图

上图即为通过降维至20个属性的相关矩阵热力图。上面那个是类之间高度不平衡的数据绘制的,下面这个图是我们平衡类别之后绘制出来的热力图。可见,原始数据中类别的不平衡对结果影响很大。从图中我们可以看到Attribute_1和Attribute_8是负相关的,这些值的程度越低,最终结果的可能性越大。并且,没有高度正相关的属性。

      1. 箱式图绘制

我们绘制了Attribute_1和Attribute_8箱式图,其中多余的点点是异常点。

 

图 6‑3 Attribute1箱式图

 

图 6‑4 Attribute8箱式图

    1. 模型测试与选择
      1. 默认超参数模型测试

我们选择了四个模型对数据进行训练。包括“逻辑回归模型”、“k临近模型”、“支持向量机模型”以及决策树模型。在测试集上面展示的效果如下:

 

可见逻辑回归在测试集上面有着更好的效果。

      1. GridSearchCV(网格搜索)——超参数调节

GridSearchCV的名字其实可以拆分为两部分,GridSearch和CV,即网格搜索和交叉验证。这两个名字都非常好理解。网格搜索,搜索的是参数,即在指定的参数范围内,按步长依次调整参数,利用调整的参数训练学习器,从所有的参数中找到在验证集上精度最高的参数,这其实是一个训练和比较的过程。GridSearchCV可以保证在指定的参数范围内找到精度最高的参数,但是这也是网格搜索的缺陷所在,他要求遍历所有可能参数的组合,在面对大数据集和多参数的情况下,非常耗时。

下面展示我们使用网格搜索之后的超参数在训练集上面进行交叉验证的结果:

 

可见,在这四个模型里面依旧是逻辑回归模型有着更好的效果。

      1. ROC曲线绘制

我们通过计算得到上面所述四个模型的AUC值如下:

 

根据四个模型绘制出来的ROC曲线如下图:

 

图 6‑5 四种算法训练模型ROC曲线图

因此最终选择逻辑回归模型。

    1. 结果分析与展示
      1. 混淆矩阵

混淆矩阵绘制如下所示,一些重要参数均可从混淆矩阵中获得。

 

图 6‑6逻辑回归算法混淆矩阵

      1. 逻辑回归ROC曲线

 

图 6‑7逻辑回归算法ROC曲线

  1. 数据集挖掘
    1. 词云图绘制
      1. 全体评论词云图

 

      1. 好评词云图

 

      1. 差评词云图

 

  • 8
    点赞
  • 57
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值