「情感分析」是对带有感情色彩的主观性文本进行分析、处理、归纳和推理的过程。按照处理文本的类别不同,可分为基于新闻评论的情感分析和基于产品评论的情感分析。其中,前者多用于舆情监控和信息预测,后者可帮助用户了解某一产品在大众心目中的口碑。
目前常见的情感极性分析方法主要是两种:基于情感词典的方法和基于机器学习的方法。
这里的数据为大众点评上的评论数据,主要就是评论文字和打分。我们首先读入数据,看下数据的情况:
import numpy as np
import pandas as pd
data = pd.read_csv('comment_dazhongdianping.csv',encoding="utf-8-sig")
data.head()
print(data)
对star字段看唯一值,打分有1,2,4,5。
中文文本情感分析属于我们的分类问题(也就是消极和积极),这里是分数,那我们设计代码,让分数小于3的为消极(0),大于3的就是积极(1)。
定义一个函数,然后用apply方法,这样就得到了一个新列(数据分析里的知识点)
#分数小于3的为消极(0),大于3的就是积极(1)
def make_label(star):
if star > 3:
return 1
else:
return 0
#结果写入一个新列sentiment
data['sentiment'] = data.star.apply(make_label)
print(data)
我们首先不用机器学习方法,我们用一个第三库(snownlp),这个库可以直接对文本进行情感分析(记得安装),使用方法也是很简单。返回的是积极性的概率。
from snownlp import SnowNLP
text1 = '这个东西不错'
text2 = '这个东西很垃圾'
s1 = SnowNLP(text1)
s2 = SnowNLP(text2)
#显示分词
print(",".join(s1.words))
print(",".join(s2.words))
#显示情感值
print(s1.sentiments,s2.sentiments)
# result 0.8623218777387431 0.21406279508712744
这样,我们就定义大于0.6,就是积极的,同样的方法,就能得到结果。
# 用snownlp进行情感分析,定义大于0.6,就是积极的
def snow_result(comemnt):
s = SnowNLP(comemnt)
if s.sentiments >= 0.6:
return 1
else:
return 0
#结果写入一个新列snlp_result
data['snlp_result'] = data.comment.apply(snow_result)
print(data)
上面前五行的结果看上去很差(5个就2个是对的),那到底有多少是对的了?我们可以将结果与sentiment字段对比,相等的我就计数,这样在除以总样本,就能看大概的精度了。
#计算用snownlp进行情感分析的准确率
counts = 0
for i in range(len(data)):
if data.iloc[i,2] == data.iloc[i,3]:
counts+=1
print(counts/len(data))
# result 0.763
前面利用第三库的方法,结果不是特别理想(0.763),而且这种方法存在一个很大的弊端:针对性差。
什么意思了?我们都知道,不同场景下,语言表达都是不同的,例如这个在商品评价中有用,在博客评论中可能就不适用了。
所以,我们需要针对这个场景,训练自己的模型。本文将使用sklearn实现朴素贝叶斯模型(原理在后文中讲解)。slearn小抄先送上(下文有高清下载地址)。
大概流程为:
导入数据
切分数据
数据预处理
训练模型
测试模型
首先,我们对评论数据分词。为什么要分词了?中文和英文不一样,例如:i love python,就是通过空格来分词的;我们中文不一样,例如:我喜欢编程,我们要分成我/喜欢/编程(通过空格隔开),这个主要是为了后面词向量做准备。
import jieba
def chinese_word_cut(mytext):
return " ".join(jieba.cut(mytext))
data['cut_comment'] = data.comment.apply(chinese_word_cut)
print(data)
分类问题需要x(特征),和y(label)。这里分词后的评论为x,情感为y。按8:2的比例切分为训练集和测试集。
X = data['cut_comment']
y = data.sentiment
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=22)
电脑是没法识别文字的,只能识别数字。那文本怎么处理了,最简单的就是词向量。什么是词向量,我们通过一个案例来说明下,下面是我们的文本:
I love the dog
I hate the dog
词向量处理后就是这样的:
按照句子为单位,从左到右读数字,第一句表示为[1, 1, 0, 1, 1],第二句就成了[1, 0, 1, 1, 1]。
这就叫向量化。
这个例子里面,特征的数量叫做维度。于是向量化之后的这两句话,都有5个维度。
你一定要记住,此时机器依然不能理解两句话的具体含义。但是它已经尽量在用一种有意义的方式来表达它们。
注意这里我们使用的,叫做“一袋子词”(bag of words)模型。
下面这张图(来自 https://goo.gl/2jJ9Kp ),形象化表示出这个模型的含义。
一袋子词模型不考虑词语的出现顺序,也不考虑词语和前后词语之间的连接。每个词都被当作一个独立的特征来看待。
你可能会问:“这样不是很不精确吗?充分考虑顺序和上下文联系,不是更好吗?”
没错,你对文本的顺序、结构考虑得越周全,模型可以获得的信息就越多。
但是,凡事都有成本。只需要用基础的排列组合知识,你就能计算出独立考虑单词,和考虑连续n个词语(称作 n-gram),造成的模型维度差异了。
为了简单起见,咱们这里还是先用一袋子词吧。有空我再给你讲讲……
简单的说,词向量就是我们将整个文本出现的单词一一排列,然后每行数据去映射到这些列上,出现的就是1,没出现就是0,这样,文本数据就转换成了01稀疏矩阵(这也是上文中文分词的原因,这样一个词就是一个列)。
好在,sklearn中直接有这样的方法给我们使用。CountVectorizer方法常用的参数:
max_df:在超过这一比例的文档中出现的关键词(过于平凡),去除掉。
min_df:在低于这一数量的文档中出现的关键词(过于独特),去除掉。
token_pattern:主要是通过正则处理掉数字和标点符号。
stop_words:设置停用词表,这样的词我们就不会统计出来(多半是虚拟词,冠词等等),需要列表结构,所以代码中定义了一个函数来处理停用词表。
from sklearn.feature_extraction.text import CountVectorizer
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
stop_words_file = '哈工大停用词表.txt'
stopwords = get_custom_stopwords(stop_words_file)
vect = CountVectorizer(max_df = 0.8,
min_df = 3,
token_pattern=u'(?u)\\b[^\\d\\W]\\w+\\b',
stop_words=frozenset(stopwords))
如果想看到底出来的是什么数据,可通过下面代码查看。
test = pd.DataFrame(vect.fit_transform(X_train).toarray(), columns=vect.get_feature_names())
test.head()
print(test)
训练模型,很简单,用的是朴素贝叶斯算法,结果为0.899,比之前的snownlp好很多了。
from sklearn.naive_bayes import MultinomialNB
nb = MultinomialNB()
X_train_vect = vect.fit_transform(X_train)
nb.fit(X_train_vect, y_train)
train_score = nb.score(X_train_vect, y_train)
print(train_score)
# result 0.899375
当然,我们需要测试数据来验证精确度了,结果为0.8275,精度还是不错的。
X_test_vect = vect.transform(X_test)
print(nb.score(X_test_vect, y_test))
# result 0.8275
当然,我们也可以将结果放到data数据中:
X_vec = vect.transform(X)
nb_result = nb.predict(X_vec)
data['nb_result'] = nb_result
print(data)
样本量少
模型没调参
没有交叉验证
代码下载:https://github.com/panluoluo/crawler-analysis,下载完整数据和代码。
完整代码
#import numpy as np
import pandas as pd
from snownlp import SnowNLP
text1 = '这个东西不错'
text2 = '这个东西很垃圾'
s1 = SnowNLP(text1)
s2 = SnowNLP(text2)
#显示分词
print(",".join(s1.words))
print(",".join(s2.words))
#显示情感值
print(s1.sentiments,s2.sentiments)
data = pd.read_csv('comment_dazhongdianping.csv',encoding="utf-8-sig")
data.head()
print(data)
#分数小于3的为消极(0),大于3的就是积极(1)
def make_label(star):
if star > 3:
return 1
else:
return 0
#结果写入一个新列sentiment
data['sentiment'] = data.star.apply(make_label)
print(data)
# 用snownlp进行情感分析,定义大于0.6,就是积极的
def snow_result(comemnt):
s = SnowNLP(comemnt)
if s.sentiments >= 0.6:
return 1
else:
return 0
#结果写入一个新列snlp_result
data['snlp_result'] = data.comment.apply(snow_result)
print(data)
#计算用snownlp进行情感分析的准确率
counts = 0
for i in range(len(data)):
if data.iloc[i,2] == data.iloc[i,3]:
counts+=1
print(counts/len(data))
#######################################################
#用朴素贝叶斯进行情感分析
#######################################################
#jieba分词
import jieba
def chinese_word_cut(mytext):
return " ".join(jieba.cut(mytext))
data['cut_comment'] = data.comment.apply(chinese_word_cut)
print(data)
#划分数据集
X = data['cut_comment']
y = data.sentiment
#词向量(数据处理)
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=22)
from sklearn.feature_extraction.text import CountVectorizer
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
stop_words_file = '哈工大停用词表.txt'
stopwords = get_custom_stopwords(stop_words_file)
vect = CountVectorizer(max_df = 0.8,
min_df = 3,
token_pattern=u'(?u)\\b[^\\d\\W]\\w+\\b',
stop_words=frozenset(stopwords))
test = pd.DataFrame(vect.fit_transform(X_train).toarray(), columns=vect.get_feature_names())
test.head()
print(test)
#训练模型
from sklearn.naive_bayes import MultinomialNB
nb = MultinomialNB()
X_train_vect = vect.fit_transform(X_train)
nb.fit(X_train_vect, y_train)
train_score = nb.score(X_train_vect, y_train)
print(train_score)
# result 0.899375
#测试数据
X_test_vect = vect.transform(X_test)
print(nb.score(X_test_vect, y_test))
# result 0.8275
#将结果放到data数据中
X_vec = vect.transform(X)
nb_result = nb.predict(X_vec)
data['nb_result'] = nb_result
print(data)