大众点评----评论情感分析

版权声明:本文为博主原创文章,转载请注明出处 https://blog.csdn.net/jingshuiliushen_zj/article/details/83090314

一、数据获取

1、爬虫评论一次之后就被屏蔽(好像是网站被一个IP频繁访问会让你输验证码),解决办法:先试了用代理IP,大众点评好像不能用代理IP访问,然后加入了time.sleep(random.uniform(1,10)),让它访问不要太频繁。
2、爬完数据写入csv文件乱码问题:out = codecs.open(’./data/Stu_csv.csv’, ‘a’, encoding=“gbk”)
3、爬下来的评论详情,显示乱七八糟:蛮久之座,提周预周末位,都。次国庆,估计队出游了,然易到了。发现,大众点评上的评论是字和图片混着来的,而且也不能复制。数据不好,后期做出来效果肯定也不好,于是去github上找了个数据集。
4、csv文件用excel打开乱码,原因:csv用UTF-8编码,而excel默认编码是ANSI,解决办法:将csv文件用TXT打开,另存为ANSI格式,再用excel打开就好了。

二、数据预处理

1、数据查看

import pandas as pd
def loaddata():
    #显示所有列
    pd.set_option('display.max_columns', None)
    pd.set_option('display.width',200)
    data=pd.read_csv('data/data.csv')
    print(data.head())

结果:
在这里插入图片描述
查看数据集的规模:data.shape:(32483, 14)
也可以查看数据集的信息:data.info()
在这里插入图片描述
可以看到,没有缺失值(其实贝叶斯对缺失值没有很敏感)。
因为是对评论进行情感分析,因此我们用到的数据也就是cus_comment(特征)和stars(类别)两列数据,这里的stars是评论的类别,1、2是差评,3是中评,4、5是好评,我们把1-2记为0,4-5记为1,3为中评,对我们的情感分析作用不大,丢弃掉这部分数据,但是可以用来作为语料。我们的情感评分可以转化为标签值为1的概率值,这样我们就把情感分析问题转为文本分类问题了。

def getLabel(score): 
    if score > 3: 
        return 1 
    elif score < 3:
        return 0 
    else: 
        return None
def createLabel(data):
    data['target'] = data['stars'].map(lambda x:getLabel(x))
    dataSelect=data[['cus_comment','target']]#选择评论列和类别列。 
    return dataSelect.dropna()#删除掉中评的数据

现在我们还剩21691条数据。
将数据集切分为训练集和测试集:

from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(data_model['cus_comment'], data_model['target'], random_state=3, test_size=0.25)#random_state是随机数的种子,不同的种子会造成不同的随机采样结果,相同的种子采样结果相同。
data['target'].value_counts()

输出:

1.0    14920
0.0     1348

从输出结果可以看出,训练集的样本分布不平衡。正样本是14920条,而负样本只有1348条。

在样本不均衡的情况下,我们应该选择合适的评估标准,比如ROC或者F1,而不是准确度(accuracy)。处理样本不均衡问题的方法,首先可以选择调整阈值,使得模型对于较少的类别更为敏感,或者另外一种方法就是通过采样(sampling)来调整数据的不平衡。其中欠采样抛弃了大部分正例数据,从而弱化了其影响,可能会造成偏差很大的模型,同时,数据总是宝贵的,抛弃数据是很奢侈的。另外一种是过采样。
这里我们用过采样的方式来解决样本不平衡的问题。但如果是单纯复制反例,因此会过分强调已有的反例。如果其中部分点标记错误或者是噪音,那么错误也容易被成倍的放大。容易造成对反例的过拟合,因此,我们采用SMOTE算法来人工合成数据实现过采样。

后续我们会加入过采样。

三、特征工程

接下来对训练集进行文本特征处理,首先用jieba分词进行中文分词:

def fenci(data):
    data=data.apply(lambda x:' '.join(jieba.cut(x)))
    return data

载入停用词:

def load_stopwords():
    stop_file=open("data/stopwords.txt", encoding='utf-8')
    stopwords_list=stop_file.readlines()
    stopwords=[x.strip() for x in stopwords_list]
    return stopwords

文本转向量,有词库表示法、TF-IDF、word2vec等, 这里我们使用sklearn库的TF-IDF工具进行文本特征提取:

def feature_extraction(data,stopwords):
    #TF-IDF构建文本向量    sklearn库中可以指定stopwords
    tf = TfidfVectorizer(stop_words=stopwords, max_features=3000)
    tf.fit(data)
    return tf #返回一个适配器

四、模型训练

特征和标签准备好之后,接下来就是建模了。这里我们使用文本分类的经典算法朴素贝叶斯算法,而且朴素贝叶斯算法的计算量较少。特征值是评论文本经过TF-IDF处理的向量,标签值评论的分类共两类,好评是1,差评是0。情感评分为分类器预测分类1的概率值,概率值越大,情感评分越高。

#模型训练--贝叶斯分类器
def train_model(x_train,y_train,x_test,y_test):
    classifier=MultinomialNB()
    classifier.fit(x_train,y_train)
    print(classifier.score(x_test,y_test))
    return classifier #返回模型

对测试集同样要进行分词、去停用词、转TF-IDF,然后测试模型的准确率、ROC值:

if __name__ == '__main__':
    data=loaddata()
    data=preprocess(data)
    x_train,x_test,y_train,y_test=train_test_split(data['cus_comment'],data['target'],random_state=3,test_size=0.25)
    stopwords=load_stopwords()
    x_train_fenci=fenci(x_train)
    tf=feature_extraction(x_train_fenci, stopwords)
    model=train_model(tf.transform(x_train_fenci), y_train,tf.transform(fenci(x_test)),y_test)

输出:

accuracy 0.9302968836437396
roc-score 0.5657246489975608

可以看出来,虽然准确率可以,但是ROC却很低。
从大众点评网找两条评论来测试一下

def predict(model,comment,tf):
    return model.predict_proba(tf.transform(fenci((comment))))# 返回预测属于某标签的概率

comment1="一如既往的好。已经快成了陆家嘴上班的我的食堂了。满减活动非常给力,上次叫了八样东西,折扣下来居然就六十左右,吃得好爽好爽。南瓜吃过几次,就一次不够酥烂,其他几次都很好。烤麸非常入味,适合上海人。鱼香肉丝有点辣,下饭刚好。那个蔬菜每次都点。总体很好吃。"
comment2="糯米外皮不绵滑,豆沙馅粗躁,没有香甜味。12元一碗不值。"
print(predict(model,pd.Series([comment1]) , tf))
print(predict(model,pd.Series([comment2]), tf))

输出:

[[0.01838771 0.98161229]]
[[0.17337369 0.82662631]]

predict_proba返回的是一个 n 行 k 列的数组, 第 i 行 第 j 列上的数值是模型预测 第 i 个预测样本为某个标签的概率,并且每一行的概率和为1。

可看到,5星好评模型预测出来了,1星差评却预测错误。这种情况就是样本不平衡,我们查看一下混淆矩阵

 y_predict=model.predict(tf.transform(fenci((x_test))))
 print(confusion_matrix(y_test,y_predict))

输出:

[[  57  374]
 [   4 4988]]

第一行是负样本,第二行是正样本,负类样本中有57个被预测为正类(15%),这是由于数据的不平衡导致的,模型的默认阈值为输出值的中位数。

SMOTE算法实现过采样:
原理:对少量类别的每一个样本o计算其K近邻,根据采样倍率N从其K近邻中随机选取N个样本x,根据公式: x n e w = x + r a n d ( 0 , 1 ) × ( x o ) x_{new}=x+rand(0,1)\times(x-o) 计算新样本。
代码:

import random
from sklearn.neighbors import NearestNeighbors
import numpy as np
class Smote:
    def __init__(self,samples,N=10,k=5):
        self.n_samples,self.n_attrs=samples.shape
        self.N=N
        self.k=k
        self.samples=samples
        self.newindex=0
       # self.synthetic=np.zeros((self.n_samples*N,self.n_attrs))

    def over_sampling(self):
        N=int(self.N/100)
        self.synthetic = np.zeros((self.n_samples * N, self.n_attrs))
        neighbors=NearestNeighbors(n_neighbors=self.k).fit(self.samples)
        print("neighbors",neighbors)
        for i in range(len(self.samples)):
            nnarray=neighbors.kneighbors(self.samples[i].reshape(1,-1),return_distance=False)[0]
            self._populate(N,i,nnarray)
        return self.synthetic


    # for each minority class samples,choose N of the k nearest neighbors and generate N synthetic samples.
    def _populate(self,N,i,nnarray):
        for j in range(N):
            nn=random.randint(0,self.k-1)
            dif=self.samples[nnarray[nn]]-self.samples[i]
            gap=random.random()
            self.synthetic[self.newindex]=self.samples[i]+gap*dif
            self.newindex+=1

训练集中的样本分布:

1.0    14920
0.0     1348

负样本只有1348条,所以利用SMOTE算法根据这1348条数据人工合成为原来的10倍,加入到训练集中进行模型的训练。

#下面的代码是为了解决引用同目录下py文件中的类时报错的问题。
current_dir = os.path.abspath(os.path.dirname(__file__))
sys.path.append(current_dir)
sys.path.append("..")
import smote
#将负样本中的词向量传入SMOTE算法进行过采样
x_train_tf=tf.transform(x_train_fenci).toarray()
samples0=[]
for i, label in enumerate(y_train):
     if label==0:
         samples0.append(x_train_tf[i])
s=smote.Smote(np.array(samples0),N=100)
over_samplings_x=s.over_sampling()
#下面是矩阵的合并,组成新的训练集
total_samplings_x=np.row_stack((x_train_tf,over_samplings_x))
total_samplings_y=np.concatenate((y_train,np.zeros(len(over_samplings_x))),axis=0)

再次训练模型:

采样倍率N=100(多一倍)
accuracy 0.9382260741287111
roc-score 0.6410470209411625
[[0.03223564 0.96776436]]
[[0.30446218 0.69553782]]

采样倍率N=500(多5倍)
accuracy 0.9280840862990964
roc-score 0.7828688314295913
[[0.0898476 0.9101524]]
[[0.59244416 0.40755584]]

采样倍率N=600(多6倍)
accuracy 0.9203392955928453
roc-score 0.8041004818847046
[[0.08742767 0.91257233]]
[[0.60816768 0.39183232]]

因为采样倍率再大的话np好像有MemoryError的错误,所以先到6倍,但是结果已经优化了好多,ROC值提升了很多,差评也能预测出来。

这个项目基本已经结束,通过SMOTE算法也实现了不错的效果。后续优化方向:

使用更复杂的机器学习模型如神经网络、支持向量机等
模型的调参
行业词库的构建
增加数据量
优化情感分析的算法
增加标签提取等

过程总结:
1、简单的爬虫操作,爬取点评网站上的评论数据。
2、怎么对数据进行分析、预处理。用pandas的info或describe方法或其他方法对数据做统计分析,发现数据的缺失值,样本的平衡性,并使用过采样(SMOTE算法)解决这个问题。
3、特征处理。这里就是基本的文本特征处理:分词–>去停用词–>转向量
4、标签类别处理。将几个评分类别转化为二分类问题。
5、模型训练。使用sklearn的贝叶斯进行训练并使用准确率和ROC来评估模型分类效果。
6、使用混淆矩阵查看预测情况,对模型做出评价。

知识点总结:

  • TF-IDF的参数,调用,原理
  • 朴素贝叶斯的原理
  • 朴素贝叶斯的调用。
  • smote算法的基本思想
  • 模型评价指标:PR、ROC、混淆矩阵

猜你喜欢

转载自blog.csdn.net/jingshuiliushen_zj/article/details/83090314
今日推荐