情感分析是大数据时代常见的一种分析方法,多用于对产品评论的情感挖掘,以探究顾客的满意度程度。在做情感分析时,有两种途径:一种是基于情感词典的分析方法,一种是基于机器学习的方法,两者各有利弊。
在此,笔者主要想跟大家分享基于python平台利用情感词典做情感分析的方法。本文主要参考这篇文章,在此文章中,博主用一句简单的语句“我今天很高兴也非常开心”向我们清楚的展示的利用情感词典做情感分析的方法,这篇文章对笔者很受用。
然而这篇文章博主也向我们抛出了几个问题,笔者就是基于此改写的算法。主要分以下几个步骤:
(1)过滤掉停用词表中的否定词和程度副词
有时候,停用词表中的词包括了否定词和程度副词,因此在做情感分析时首要先过滤掉停用词表中的否定词和程度副词,防止这些有意义的词被过滤掉。词表的下载见上述博主。
"""在停用词表中过滤否定词和程度副词"""
#生成stopword表,需要去除一些否定词和程度词汇
stopwords = set()
fr = open('stopwords.txt','r',encoding='utf-8')
for word in fr:
stopwords.add(word.strip())#Python strip() 方法用于移除字符串头尾指定的字符(默认为空格或换行符)或字符序列。
# #读取否定词文件
not_word_file = open('否定词.txt','r+',encoding='utf-8')
not_word_list = not_word_file.readlines()
not_word_list = [w.strip() for w in not_word_list]
#读取程度副词文件
degree_file = open('程度副词.txt','r+')
degree_list = degree_file.readlines()
degree_list = [item.split(',')[0] for item in degree_list]
#生成新的停用词表
with open('stopwords_new.txt','w',encoding='utf-8') as f:
for word in stopwords:
if(word not in not_word_list) and (word not in degree_list):
f.write(word+'\n')
(2)分词,过滤停用词
def seg_word(sentence):
jieba.load_userdict("./dict.txt")
seg_list = jieba.cut(sentence)
seg_result = []
# 读取停用词文件
stopwords = [line.strip() for line in open('stopwords_new.txt', encoding='UTF-8').readlines()]
# 去停用词
for word in seg_list: # 读取每一行分词
if word not in stopwords: # 如果分词不在停用词列表中
if word != '\t':
seg_result.append(word)
print(seg_result)
return seg_result
(3)找出分词结果中的情感词、否定词和程度副词
在博主的文章中,这里出现了一个错误就是如果句子中出现两个相同的情感词、程度副词和否定词的话,前面的就会被后面的覆盖掉了,这是因为博主将分词结果转换成了以词作为key,索引作为value的字典形式,但如果我们只对分词结果以列表的形式进行遍历,则可避免这种情况的发生。
def classify_words( word_list):
"""词语分类,找出情感词、否定词、程度副词"""
# 读取情感字典文件
sen_file = open('BosonNLP_sentiment_score.txt', 'r+', encoding='utf-8')
# 获取字典文件内容
sen_list = sen_file.readlines() # sen_list是一个二维列表
# 创建情感字典
sen_dict = defaultdict()
# 读取字典文件每一行内容,将其转换为字典对象,key为情感词,value为对应的分值
for s in sen_list:
# 每一行内容根据空格分割,索引0是情感词,索引1是情感分值(情感词典文件中有一行是空行,因此执行的时候会报错,注意处理一下空行,这里没有处理)
# print(s)
sen_dict[s.split(' ')[0]] = s.split(' ')[1] # 字典的键值对形式
# 读取否定词文件
not_word_file = open('否定词.txt', 'r+', encoding='utf-8')
# 由于否定词只有词,没有分值,使用list即可
not_word_list = not_word_file.readlines()
not_dict = defaultdict()
for n in not_word_list:
not_dict[n.split(',')[0]] = n.split(',')[1]
# 读取程度副词文件
degree_file = open('程度副词.txt', 'r+', encoding='ANSI')
degree_list = degree_file.readlines()
degree_dict = defaultdict()
# 程度副词与情感词处理方式一样,转为程度副词字典对象,key为程度副词,value为对应的程度值
for d in degree_list:
degree_dict[d.split(',')[0]] = d.split(',')[1]
# 分类结果,词语的index作为key,词语的分值作为value,否定词分值设为-1
sen_word = dict()
not_word = dict()
degree_word = dict()
# 分类
for i in range(len(word_list)):
word = word_list[i]
if word in sen_dict.keys() and word not in not_dict.keys() and word not in degree_dict.keys():
# 找出分词结果中在情感字典中的词
sen_word[i] = sen_dict[word]
elif word in not_dict.keys() and word not in degree_dict.keys():
# 分词结果中在否定词列表中的词
not_word[i] = -1
elif word in degree_dict.keys():
# 分词结果中在程度副词中的词
degree_word[i] = degree_dict[word]
# 将分类结果返回
print(sen_word,not_word,degree_word)
return sen_word, not_word, degree_word
(4)计算情感值
在计算情感值的过程中,博主提出了两个问题,第一是对第一个情感词之前的程度副词和否定词的判断情况,第二个就是权重W没有初始化,被累乘的情况。笔者将它改成下面的代码形式,完美解决了这两个问题。
#1.用来判断第一个情感词之前的程度副词和否定词
def get_init_weight(sen_word, not_word, degree_word):
# 权重初始化为1
W = 1
# 将情感字典的key转为list
sen_word_index_list = list(sen_word.keys())
if len(sen_word_index_list) == 0:
return W
# 获取第一个情感词的下标,遍历从0到此位置之间的所有词,找出程度词和否定词
for i in range(0, sen_word_index_list[0]):
if i in not_word.keys():
W *= -1
elif i in degree_word.keys():
#更新权重,如果有程度副词,分值乘以程度副词的程度分值
W *= float(degree_word[i])
return W
#2.计算得分
def socre_sentiment(sen_word, not_word, degree_word, seg_result):
W = get_init_weight(sen_word, not_word, degree_word)
print(W)
score = 0
# 情感词下标初始化
sentiment_index = -1
# 情感词的位置下标集合
sentiment_index_list = list(sen_word.keys())
#print(sentiment_index_list)
# 遍历分词结果(遍历分词结果是为了定位两个情感词之间的程度副词和否定词)
for i in range(0, len(seg_result)):
# 如果是情感词(根据下标是否在情感词分类结果中判断)
if i in sen_word.keys():
# 权重*情感词得分
score += W * float(sen_word[i])
print(score)
# 情感词下标加1,获取下一个情感词的位置
sentiment_index += 1
print("sentiment_index:",sentiment_index)
if sentiment_index < len(sentiment_index_list) - 1: #总的情感词的个数
# 判断当前的情感词与下一个情感词之间是否有程度副词或否定词
"""这个是解决W累乘情况的"""
W=1 #防止第二轮的权重出现累乘的情况
for j in range(sentiment_index_list[sentiment_index], sentiment_index_list[sentiment_index + 1]):
# 更新权重,如果有否定词,取反
if j in not_word.keys():
W *= -1
elif j in degree_word.keys():
# 更新权重,如果有程度副词,分值乘以程度副词的程度分值
W *= float(degree_word[j])
print(W)
"""这里又出现了一个问题,就是后一个没有否定词和程度副词的权重会将前一个覆盖掉,所以初始化权重W=1不能放在for循环中"""
# 定位到下一个情感词
if sentiment_index < len(sentiment_index_list) - 1:
i = sentiment_index_list[sentiment_index + 1]
return score
(5)测试
测试过程中,笔者用了一句和博主测试语句情感相反的语句,“我很不高兴也非常不开心”
seg_list = seg_word("我很不高兴也非常不开心")
sen_word, not_word, degree_word = classify_words(seg_list)
score = socre_sentiment(sen_word, not_word, degree_word, seg_list)
print(score)
测试结果如下图所示:
其中,第一行表示的是分词结果,第二行表示的是分词结果中情感词、否定词和程度副词词典
第三行表示的就是第一个情感词之前的否定词和程度副词所代表的权重,其中“很”的权重是1.6,“不”的权重是-1,那么第一次循环中的得分score=(-1)1.6score(“高兴”),第二次循环求得的是第一个情感词“高兴”和第二个情感词“开心”之间的权重,也就是“非常”=1.8,"不“=-1,第二次求得的分数score=上一次循环中求得的分值+(-1)1.8score(”开心“)。由于没有下一个情感词了,所以这也是最终句子的情感值得分。