基于机器学习的情感分析问题

基于机器学习的情感分析问题

weibo_senti_100k 数据集 说明

  1. 数据概览: 10 万多条,带情感标注 新浪微博,正负向评论约各 5 万条
  2. 推荐实验: 情感/观点/评论 倾向性分析
  3. 数据来源: 新浪微博
  4. 原数据集: 新浪微博,情感分析标记语料共12万条
  5. 加工处理:
    1. 将原来的 2 份文档,整合成 1 份 csv 文件
    2. 编码统一为 UTF-8
    3. 去重

加载数据

导入必要的库函数
import pandas
import numpy as np
import matplotlib.pyplot as plt
from time import time
import seaborn as sns
sns.set()
from sklearn import model_selection as MS

pd_all = pd.read_csv('weibo_senti_100k.csv')
print(f'评论数目(总体):{pd_all.shape[0]}')
print(f'评论数目(正向):{pd_all[pd_all.label==1].shape[0]}')
print(f'评论数目(负向):{pd_all[pd_all.label==0].shape[0]}')

数据预处理

import re
#删除用户名
def delete_user(text):
    patt = re.compile(r'@.*?[:: ]')   #正则表达式匹配
    line = re.sub(patt,"",text)
    return line

#删除非必要字符的方法
def preprocessor(text):
    text = re.sub('<[^>]*>', '', text)
    emoticons = re.findall('(?::|;|=)(?:-)?(?:\)|\(|D|P)',
                           text)
    text = (re.sub('[\W]+', ' ', text.lower()) +
            ' '.join(emoticons).replace('-', ''))
    return text

#删除非中文字符
def remain_character(String):
    blog_new = u""
    for i in range(0,len(String)):
        if(String[i] >=u'\u4e00' and String[i]<=u'\u9fa5'):  #在中文范围内的保存
            blog_new = blog_new + String[i]
    return blog_new

pd_all['review'] = pd_all['review'].apply(delete_user)
pd_all['review'] = pd_all['review'].apply(preprocessor)
pd_all['review'] = pd_all['review'].apply(remain_character)

import jieba

#将所有的空格删去,方便分词
pd_all['review'] = pd_all['review'].str.replace(" ","")
测试分词后的效果
st = time()
word = jieba.cut(pd_all.loc[50000,'review'])
for i in word:
    print(i,end = ' ')
print()
print("耗时为:",time()-st)

马总说 你 太累 了 也 该 歇歇 嘞 哈哈
耗时为: 0.6213374137878418

对停用词的处理
#因为已经有了文件,为了节省时间,这个直接注释掉
#对停用词进行处理,并保存为npy文件
data = open('stop_words.txt','r').read()
words = jieba.cut(data)

data_words = []
for word in words:
    if word == '\n':
        continue
    data_words.append(word)

print(data_words)
data_np = np.array(data_words)
data_np.shape
np.save("stop_data",data_np)
导入提前处理好的停用词
stop_data = np.load('stop_data.npy')

stop_words = list(stop_data)
去除停用词
#这个比较费时
st = time()
#将所有的评论进行分词,放进words列表中
words = []   #最后存放结果的列表
for i, row in pd_all.iterrows():
    content = []        #保存当前那一行的内容
    tmp = jieba.cut(row['review'])    #对当前行进行分词
    for word in tmp:
        if word not in stop_words:   #如果不在停用词当中,就保存下来
            content.append(word)
    words.append(content)       #将当前行加入到总的当中
print("去除停用词耗时为:",time()-st,"秒")

这部分耗时了77秒。

接下来保存上面运行的结果,避免每次运行都要进行数据预处理

#将每一行合并,因为分词之后上述的words是一个二维列表,利用空格将它们合并起来
st = time()
word_all = []
for i in range(len(words)):
    hang = " ".join(words[i])   #用空格将它们串起来
    word_all.append(hang)

print("耗时为:",time()-st,'秒')

word_all_npy = np.save("word_all",word_all)

数据预处理到此就结束了
之后运行模型,只需要调用上述保存的数据即可

加载保存的数据
word_all_npy = np.load("word_all.npy")
word_all = list(word_all_npy)

文字转数字

通过建立词频矩阵构造特征变量

词袋(bag-of-words)模型

词袋模型(Bag-of-words model)是个在自然语言处理和信息检索(IR)下被简化的表达模型。此模型下,一段文本(比如一个句子或是一个文档)可以用一个装着这些词的袋子来表示,这种表示方式不考虑文法以及词的顺序。

  • 使用机器学习算法解决文本数据分析问题,必需将文本数据转化为数字形式。

    可通过CountVectorizer的fit_transform方法构建词袋模型的词汇表,词汇表存储在Python字典,该字典将每个独立的单词映射为整数索引。

特征向量的每个索引位置对应于词汇存储在CountVectorizer字典项中的整数值。特征向量也被称为原词频率:tf(t,d) ,即 t 项在 d 文档中出现的次数。

通过词频-逆文本频率评估相关性

在分析文本数据时,经常会发现好的和坏的词在多个文档中出现。经常出现的单词基本上不包含有用或者判断性的信息。 词频-逆文本频率(tf-idf) 一算法,用于减少特征向量中频繁出现的词。tf-idf 可以定义为词频与逆反文档频率的乘积:

tf-idf ( t , d ) = tf (t,d) × idf ( t , d ) \text{tf-idf}(t,d)=\text{tf (t,d)}\times \text{idf}(t,d) tf-idf(t,d)=tf (t,d)×idf(t,d)

  • tf(t, d) 词频 (term frequency)
  • 逆文档频率 idf(t, d) 计算过程为:

idf ( t , d ) = log n d 1 + df ( d , t ) , \text{idf}(t,d) = \text{log}\frac{n_d}{1+\text{df}(d, t)}, idf(t,d)=log1+df(d,t)nd,

这里 n d n_d nd 是文档总数 ; df(d, t) 是包含单词 t 的文档数目.

或者通俗理解为:

         词频 ( T F ) = 某个词在文章中的出现次数 该文出现次数最多的词的出现次数 \text{词频}(TF) = \frac{某个词在文章中的出现次数}{该文出现次数最多的词的出现次数} 词频(TF)=该文出现次数最多的词的出现次数某个词在文章中的出现次数

         逆文档频率 ( I D F ) = log ( 语料库的文档总数 包含该词的文档数 + 1 ) \text{逆文档频率}(IDF) = \text{log}(\frac{语料库的文档总数}{包含该词的文档数 + 1}) 逆文档频率(IDF)=log(包含该词的文档数+1语料库的文档总数)

为分母添加常数1为可选,目的在于为所有训练样本中出现的单词赋予非零值,用对数来确保低文档频率的权重不会过大。

可以看到,TF-IDF与一个词在文档中的出现次数成正比,与该词在整个语言中的出现次数成反比。

此外scikit-learn实现了另外一个转换器TfidfTransformer类,它以来自于CountVectorizer类,以原始词频为输入,然后转换为tf-idfs格式:

scikit-learn实现的逆文档频率计算公式如下:

idf ( t , d ) = l o g 1 + n d 1 + df ( d , t ) \text{idf} (t,d) = log\frac{1 + n_d}{1 + \text{df}(d, t)} idf(t,d)=log1+df(d,t)1+nd

在scikit-learn中计算tf-idf公式为:

tf-idf ( t , d ) = tf ( t , d ) × ( idf ( t , d ) + 1 ) \text{tf-idf}(t,d) = \text{tf}(t,d) \times (\text{idf}(t,d)+1) tf-idf(t,d)=tf(t,d)×(idf(t,d)+1)

在调用 TfidfTransformer 类直接归一化并计算tf-idf之前,归一化原始单词的词频更具代表性。定义默认参数 norm=‘l2’,用scikit-learn的
TfidfTransformer 进行L2归一化,返回长度为1的向量,

v norm = v ∣ ∣ v ∣ ∣ 2 = v v 1 2 + v 2 2 + ⋯ + v n 2 = v ( ∑ i = 1 n v i 2 ) 1 2 v_{\text{norm}} = \frac{v}{||v||_2} = \frac{v}{\sqrt{v_{1}^{2} + v_{2}^{2} + \dots + v_{n}^{2}}} = \frac{v}{\big (\sum_{i=1}^{n} v_{i}^{2}\big)^\frac{1}{2}} vnorm=∣∣v2v=v12+v22++vn2 v=(i=1nvi2)21v

导入模型
#将之前所有的新闻标题进行文本向量化
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfVectorizer

因为原数据量过于庞大,使得电脑无法运转
经过反复试验之后,发现max_features参数为120的时候效果最好

st = time()
model = CountVectorizer(max_features=120)

X = model.fit_transform(word_all)
X = X.toarray()
print("耗时为:",time()-st)

耗时为: 1.9630837440490723

st = time()
#Tfidf = TfidfVectorizer(min_df=20,max_df=25)
Tfidf = TfidfVectorizer(token_pattern=r"(?u)\b\w+\b", max_features=120, ngram_range=(1,2), stop_words=stop_words)
X2 = Tfidf.fit_transform(word_all)
X2 = X2.toarray()
print("耗时为:",time()-st)

耗时为: 7.643373489379883

原数据的特征如果不经筛选,会有20万之多,现在仅仅取其中出现频率最高的前120个

print(X2.shape) #此时的数据规模为(119988,120)
words_bag = Tfidf.get_feature_names()  # 第二种查看词袋的方法
data_all = pd.DataFrame(X2, columns=words_bag)
data_all.head()

在这里插入图片描述

观察上述运行结果可知,已经将用户名,数字,符号,英文都删除了。

将数据划分为训练数据和验证数据
st = time()
from sklearn.model_selection import train_test_split
Xtrain,Xtest,ytrain,ytest = train_test_split(data_all,pd_all['label'],test_size=0.3)

print("耗时为:",time()-st)

检查数据是否按照类别分类

print("训练数据中类别为1的比例为:",sum(ytrain)/ytrain.shape[0])
print("测试数据中类别为1的比例为:",sum(ytest)/ytest.shape[0])

训练数据中类别为1的比例为: 0.49955352359181343
测试数据中类别为1的比例为: 0.5010139733866711

至此,数据预处理部分全部结束,可以导入模型开始训练

利用模型进行语义分类

Logistic模型

st = time()
from sklearn.metrics import accuracy_score
from sklearn.linear_model import LogisticRegression  #导入Logistic模型
LR = LogisticRegression(penalty='l2',C=1000)   #偏置设置为True,即有偏置
LR.fit(Xtrain,ytrain)                     #模型训练
pred_LR = LR.predict(Xtest)              #模型预测
print("logistic分类所花费的时间为:",time()-st,"秒")

print("accuracy_score = ",accuracy_score(ytest,pred_LR))   #分类准确率
print(metrics.classification_report(ytest,pred_LR))        #分类报告  

运行结果如下图
在这里插入图片描述


#ROC曲线的面积
y_score_LR= MS.cross_val_predict(LR,Xtest,ytest,cv=3)
metrics.roc_auc_score(ytest,y_score_LR)
#绘制出ROC曲线
metrics.plot_roc_curve(LR,Xtest,ytest)

ROC曲线的面积为0.907
在这里插入图片描述

Logistic模型调参
#网格搜索最佳参数
penaltys = ['l1','l2']   #罚函数的类型
Cs = [0.001, 0.01, 0.1, 1, 10, 100, 1000]   #惩罚系数
tuned_parameters = dict(penalty = penaltys, C = Cs)  #做成字典
 
lr_penalty= LogisticRegression()   #选择模型
#网格搜索模型初始化
grid_log= GridSearchCV(lr_penalty,  #模型
                   tuned_parameters,   #网格搜索的参数
                   cv=10,   #10折交叉验证
                   scoring='accuracy')    #用准确率作为得分


grid_log.fit(Xtrain,ytrain)
#网格搜索的结果
print("网格搜索的结果为:")
print(grid_log.cv_results_)
#网格搜索的最好得分
print("GridSearchCV的最好得分为:",grid_log.best_score_)
print("GridSearchCV的最好参数为:",grid_log.best_params_)

以分类准确率为得分的网格搜索,在logistic回归的准确率上,最高得分为0.91,相比默认参数提高了1个百分点

朴素贝叶斯模型

st = time()
from sklearn.naive_bayes import GaussianNB
clf = GaussianNB(var_smoothing  =  1e-06)
clf.fit(Xtrain,ytrain)                     #模型训练
pred_clf = clf.predict(Xtest) 
print(time()-st)
print("accuracy_score = ",accuracy_score(ytest,pred_clf))   #分类准确率
print(metrics.classification_report(ytest,pred_clf))        #分类报告  

在这里插入图片描述

绘制ROC曲线及计算ROC曲线面积

#绘制出ROC曲线
metrics.plot_roc_curve(clf,Xtest,ytest)

#ROC曲线的面积
y_score_clf = MS.cross_val_predict(clf,Xtest,ytest,cv=3)
metrics.roc_auc_score(ytest,y_score_clf)

ROC曲线的面积为0.865
在这里插入图片描述

朴素贝叶斯调参
# 调参 GNB
param_grid ={}
param_grid['var_smoothing'] = [1e-5,1e-6,1e-7,1e-8,1e-9]
model = GaussianNB()
grid_GNB = GridSearchCV(estimator= model, param_grid = param_grid, scoring='accuracy', cv=10)
grid_GNB.fit(Xtrain,ytrain)
#网格搜索的结果
print("网格搜索的结果为:")
print(grid_GNB.cv_results_)
#网格搜索的最好得分
print("GridSearchCV的最好得分为:",grid_GNB.best_score_)
print("GridSearchCV的最好参数为:",grid_GNB.best_params_)

在这里插入图片描述

随机森林模型

st = time()
from sklearn.ensemble import RandomForestClassifier  #导入随机森林模型
RFC = RandomForestClassifier(n_estimators=100,max_depth=9,min_samples_leaf=10,min_samples_split=140,max_features=9)   #模型初始化:树的个数为100
RFC.fit(Xtrain,ytrain)          #模型训练
ypred_RFC = RFC.predict(Xtest)   #模型预测
print(time()-st)

print("accuracy_score = ",accuracy_score(ytest,ypred_RFC))
print(metrics.classification_report(ytest,ypred_RFC))

在这里插入图片描述

#绘制出ROC曲线
metrics.plot_roc_curve(RFC,Xtest,ytest)
#ROC曲线的面积
y_score_RFC = MS.cross_val_predict(RFC,Xtest,ytest,cv=3)
metrics.roc_auc_score(ytest,y_score_RFC)

ROC曲线面积为0.905
在这里插入图片描述

随机森林模型调参
#首先对n_estimators进行网格搜索:
param_test1 = {'n_estimators':[50,100,150,200]}  
gsearch1 = GridSearchCV(estimator = RandomForestClassifier(min_samples_split=100,
                                                            min_samples_leaf=20,
                                                            max_depth=8,  #最大深度
                                                            max_features='sqrt',random_state=10,n_jobs=-1), 
                        param_grid = param_test1, 
                        scoring='accuracy',
                        cv=5)
gsearch1.fit(Xtrain,ytrain)
#最佳参数,最佳得分
print(gsearch1.best_params_,  gsearch1.best_score_)

{‘n_estimators’: 150} 0.9068233201590468

#接着对决策树最大深度max_depth进行网格搜索。
param_test2 = {'max_depth':[3,5,7,9]}
gsearch2 = GridSearchCV(estimator = RandomForestClassifier(n_estimators=200, min_samples_split=100,
                                  min_samples_leaf=20,max_features='sqrt' ,oob_score=True, random_state=10,n_jobs=-1),
                        param_grid = param_test2,
                        scoring='accuracy',
                        cv=5)
gsearch2.fit(Xtrain,ytrain)
print( gsearch2.best_params_, gsearch2.best_score_)

{‘max_depth’: 9} 0.9072638374535147

#下面再对内部节点再划分所需最小样本数min_samples_split和叶子节点最少样本数min_samples_leaf一起调参。
param_test3 = {'min_samples_split':[80,100,120,140],'min_samples_leaf':[10,20,30,40,50]}
gsearch3 = GridSearchCV(estimator = RandomForestClassifier(n_estimators= 200, max_depth=11,
                                  max_features='sqrt' ,oob_score=True, random_state=10,n_jobs=-1),
                        param_grid = param_test3,
                        scoring='accuracy',
                        cv=5)
gsearch3.fit(Xtrain,ytrain)
print( gsearch3.best_params_, gsearch3.best_score_)

{‘min_samples_leaf’: 10, ‘min_samples_split’: 120} 0.9088354403646666

#最后再对最大特征数max_features做调参:
param_test4 = {'max_features':[3,5,7,9]}
gsearch4 = GridSearchCV(estimator = RandomForestClassifier(n_estimators= 200, max_depth=11, min_samples_split=80,
                                  min_samples_leaf=10 ,oob_score=True, random_state=10,n_jobs=-1),
                        param_grid = param_test4, 
                        scoring='accuracy',
                        cv=5)
gsearch4.fit(Xtrain,ytrain)
print( gsearch4.best_params_, gsearch4.best_score_)

{‘max_features’: 9} 0.9083472990617139

综上可得出结论:
经过数据预处理之后
随机森林 语义分类最优,其次是Logistic回归分类准确率,朴素贝叶斯分类准确率最低

以上仅仅是提取出现频率最高的前120个中文词汇进行语义分类的结果,主要还是由于个人电脑的内存原因,无法提取更多特征
理论上,如果能够提取更多的特征,分类准确率会有明显的提高
但是本文的研究方向主要是区分正向评论和负向评论,从词义的角度来看,一句话中仅仅有部分的词汇能够表明该评论是正向的还是负向的,如果数据量足够大,如本文中,12万的评论数据,那么一些能够体现评论种类的词语出现频率会非常高,因此这解释了为什么只取前120个词汇的分类准确率就可以达到90%的原因了

利用logistic模型来测试一下最高频率词汇数量与分类准确率的关系

vectors = [10,20,30,40,50,60,70,80,90,100,110,120]

from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.linear_model import LogisticRegression  #导入Logistic模型

def calcu(feature):
    model = CountVectorizer(max_features=feature)

    X = model.fit_transform(word_all)
    X = X.toarray()

    words_bag = model.get_feature_names() 
    data_all = pd.DataFrame(X, columns=words_bag)
    data_all.head()

    Xtrain,Xtest,ytrain,ytest = train_test_split(data_all,pd_all['label'],test_size=0.3)

    LR = LogisticRegression(penalty='l2',C=1000)   #偏置设置为True,即有偏置
    LR.fit(Xtrain,ytrain)                     #模型训练
    pred_LR = LR.predict(Xtest)              #模型预测
    print("特征数为:",feature,"时logistic分类所花费的时间为:",time()-st,"秒")

    return accuracy_score(ytest,pred_LR)   #分类准确率

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfVectorizer
accuracy = []
st = time()
for feature in vectors:
    accuracy.append(calcu(feature))
    
plt.plot(vectors,accuracy)
plt.xlabel("the number of high frequency words")
plt.ylabel("classification accuracy")

在这里插入图片描述因此本文所选取的最高频率词汇数量具有一定的可取性。

  • 6
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值