基于情感词典的情感分析应该是最简单传统的情感分析方法。
本文中使用情感词典进行情感分析的思路为:
- 对文档分词,找出文档中的情感词、否定词以及程度副词
- 然后判断每个情感词之前是否有否定词及程度副词,将它之前的否定词和程度副词划分为一个组
- 如果有否定词将情感词的情感权值乘以-1,如果有程度副词就乘以程度副词的程度值
- 最后所有组的得分加起来,大于0的归于正向,小于0的归于负向。(得分的绝对值大小反映了积极或消极的程度)
主要参考文章:
https://blog.csdn.net/lom9357bye/article/details/79058946
https://blog.csdn.net/qq_36489878/article/details/103772714
目录
1.准备数据
1.1 BosonNLP情感词典
https://kexue.fm/usr/uploads/2017/09/1922797046.zip
从下载的文件里,随便粘了几个正向的情感词,词后面的数字表示的是情感词的情感分值,一般正向的都是正数,负向的是负数:
居家 2.08857470795
畅想 2.08875837264
2AM 2.08890952167
萌萌 2.08890952167
霍思燕 2.08890952167
阳光 2.08902889821
知足 2.08909186445
注:由于BosonNLP是基于微博、新闻、论坛等数据来源构建的情感词典,因此拿来对其他类别的文本进行分析效果可能不好
也有一种将所有情感词的情感分值设为1的方法来计算,想要详细了解可参考此文章:文本情感分类(一):传统模型
1.2 否定词词典
文本情感分类(一):传统模型中提供了一个情感极性词典的下载包,包中带了一个否定词的txt。
https://kexue.fm/usr/uploads/2017/09/1922797046.zip
1.3 程度副词词典
从程度级别词语.txt中选取了一部分程度副词,可以看到只有程度词,没有程度值。
这里做了一个简单的程度副词标记,大于1,表示情感加强,小于1,表示情感弱化,下面主要按照极其1.8,超1.6,很1.5,较1,稍0.7,欠0.5进行了一个简单的标记,如下所示。也可以根据自己的需求及及进行修改。
百分之百,1.8
倍加,1.8
备至,1.8
不得了,1.8
不堪,1.8
不可开交,1.8
不亦乐乎,1.8
不折不扣,1.8
彻头彻尾,1.8
充分,1.8
到头,1.8
地地道道,1.8
非常,1.8
极,1.8
极度,1.8
极端,1.8
极其,1.8
极为,1.8
截然,1.8
尽,1.8
惊人地,1.8
绝,1.8
绝顶,1.8
绝对,1.8
绝对化,1.8
刻骨,1.8
酷,1.8
满,1.8
满贯,1.8
满心,1.8
莫大,1.8
奇,1.8
入骨,1.8
甚为,1.8
十二分,1.8
十分,1.8
十足,1.8
死,1.8
滔天,1.8
痛,1.8
透,1.8
完全,1.8
完完全全,1.8
万,1.8
万般,1.8
万分,1.8
万万,1.8
无比,1.8
无度,1.8
无可估量,1.8
无以复加,1.8
无以伦比,1.8
要命,1.8
要死,1.8
已极,1.8
已甚,1.8
异常,1.8
逾常,1.8
贼,1.8
之极,1.8
之至,1.8
至极,1.8
卓绝,1.8
最为,1.8
佼佼,1.8
郅,1.8
綦,1.8
齁,1.8
最,1.8
不过,1.5
不少,1.5
不胜,1.5
惨,1.5
沉,1.5
沉沉,1.5
出奇,1.5
大为,1.5
多,1.5
多多,1.5
多加,1.5
多么,1.5
分外,1.5
格外,1.5
够瞧的,1.5
够戗,1.5
好,1.5
好不,1.5
何等,1.5
很,1.5
很是,1.5
坏,1.5
可,1.5
老,1.5
老大,1.5
良,1.5
颇,1.5
颇为,1.5
甚,1.5
实在,1.5
太,1.5
太甚,1.5
特,1.5
特别,1.5
尤,1.5
尤其,1.5
尤为,1.5
尤以,1.5
远,1.5
着实,1.5
曷,1.5
碜,1.5
大不了,0.8
多,0.8
更,0.8
更加,0.8
更进一步,0.8
更为,0.8
还,0.8
还要,0.8
较,0.8
较比,0.8
较为,0.8
进一步,0.8
那般,0.8
那么,0.8
那样,0.8
强,0.8
如斯,0.8
益,0.8
益发,0.8
尤甚,0.8
逾,0.8
愈,0.8
愈 ... 愈,0.8
愈发,0.8
愈加,0.8
愈来愈,0.8
愈益,0.8
远远,0.8
越 ... 越,0.8
越发,0.8
越加,0.8
越来越,0.8
越是,0.8
这般,0.8
这样,0.8
足,0.8
足足,0.8
点点滴滴,0.7
多多少少,0.7
怪,0.7
好生,0.7
还,0.7
或多或少,0.7
略,0.7
略加,0.7
略略,0.7
略微,0.7
略为,0.7
蛮,0.7
稍,0.7
稍稍,0.7
稍微,0.7
稍为,0.7
稍许,0.7
挺,0.7
未免,0.7
相当,0.7
些,0.7
些微,0.7
些小,0.7
一点,0.7
一点儿,0.7
一些,0.7
有点,0.7
有点儿,0.7
有些,0.7
半点,0.5
不大,0.5
不丁点儿,0.5
不甚,0.5
不怎么,0.5
聊,0.5
没怎么,0.5
轻度,0.5
弱,0.5
丝毫,0.5
微,0.5
相对,0.5
不为过,1.6
超,1.6
超额,1.6
超外差,1.6
超微结构,1.6
超物质,1.6
出头,1.6
多,1.6
浮,1.6
过,1.6
过度,1.6
过分,1.6
过火,1.6
过劲,1.6
过了头,1.6
过猛,1.6
过热,1.6
过甚,1.6
过头,1.6
过于,1.6
过逾,1.6
何止,1.6
何啻,1.6
开外,1.6
苦,1.6
老,1.6
偏,1.6
强,1.6
溢,1.6
忒,1.6
1.4 停用词词典
参考的是https://github.com/isnowfy/snownlp/blob/master/snownlp/normal/stopwords.txt(为什么高兴成停用词了?_?)
要注意一下需要将否定词或者是程度副词的词典过滤掉,不然否定词在去除停用词的时候都过滤掉了,就缺少了一些程度副词或者否定词。使用以下方法进行过滤:
(代码复制过来缩进怎么成这样,强迫症看着难受。。。)
#生成stopword表,需要去除一些否定词和程度词汇
stopwords = set()
fr = open('停用词.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.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. 数据预处理
2.1 使用jieba分词并去除停用词
#jieba分词后去除停用词
def seg_word(sentence):
seg_list = jieba.cut(sentence)
seg_result = []
for i in seg_list:
seg_result.append(i)
stopwords = set()
with open('stopwords.txt','r') as fr:
for i in fr:
stopwords.add(i.strip())
return list(filter(lambda x :x not in stopwords,seg_result))
3.分数计算
3.1 找出文本中的情感词,否定词和程度副词
如句子:我今天很高兴也非常开心,去除停用词后得到:
['很', '高兴', '非常', '开心']
情感词:高兴、开心,key为单词的索引,value为情感权值:
sen_word:{1: '1.48950851679', 3: '2.61234173173'}
否定词:没有出现否定词,所以否定词为空:
not_word:{}
程度副词:很、非常degree_word:{0: '1.5', 2: '1.8'}
#找出文本中的情感词、否定词和程度副词
def classify_words(word_list):
#读取情感词典文件
sen_file = open('BosonNLP_sentiment_score.txt','r+',encoding='utf-8')
#获取词典文件内容
sen_list = sen_file.readlines()
#创建情感字典
sen_dict = defaultdict()
#读取词典每一行的内容,将其转换成字典对象,key为情感词,value为其对应的权重
for i in sen_list:
if len(i.split(' '))==2:
sen_dict[i.split(' ')[0]] = i.split(' ')[1]
#读取否定词文件
not_word_file = open('否定词.txt','r+',encoding='utf-8')
not_word_list = not_word_file.readlines()
#读取程度副词文件
degree_file = open('程度副词.txt','r+')
degree_list = degree_file.readlines()
degree_dict = defaultdict()
for i in degree_list:
degree_dict[i.split(',')[0]] = i.split(',')[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_word_list and word not in degree_dict.keys():
# 找出分词结果中在情感字典中的词
sen_word[i] = sen_dict[word]
elif word in not_word_list and word not in degree_dict.keys():
# 分词结果中在否定词列表中的词
not_word[i] = -1
elif word in degree_dict.keys():
# 分词结果中在程度副词中的词
degree_word[i] = degree_dict[word]
#关闭打开的文件
sen_file.close()
not_word_file.close()
degree_file.close()
#返回分类结果
return sen_word,not_word,degree_word
3.2 计算情感词的分数
采用的规则如下:
遍历所有的情感词,查看当前情感词的前面是否有否定词和程度副词,如果没有否定词,就对当前情感词乘以1,如果有否定词或者有多个否定词,可以乘以(-1)^否定词的个数;如果有程度副词,就在当前情感词前面乘以程度副词的程度等级。
伪代码:
finalSentiScore = (-1) ^ (num of notWords) * degreeNum * sentiScore
#计算情感词的分数 def score_sentiment(sen_word,not_word,degree_word,seg_result): #权重初始化为1 W = 1 score = 0 #情感词下标初始化 sentiment_index = -1 #情感词的位置下标集合 sentiment_index_list = list(sen_word.keys()) #遍历分词结果 for i in range(0,len(seg_result)): #如果是情感词 if i in sen_word.keys(): #权重*情感词得分 score += W*float(sen_word[i]) #情感词下标加一,获取下一个情感词的位置 sentiment_index += 1 if sentiment_index < len(sentiment_index_list)-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]) #定位到下一个情感词 if sentiment_index < len(sentiment_index_list)-1: i = sentiment_index_list[sentiment_index+1] return score
‘我今天很高兴也非常开心’分词去停用词得到:['很', '高兴', '非常', '开心']
最开始W=1,score=0
第一个情感词是高兴,高兴的情感权值为1.48950851679,score=W*情感权值=1.48950851679
高兴和下一个情感词开心之间出现了非常,程度值为1.8,因此W=W*1.8=1.8
然后获取下一个情感词
下一个情感词是开心,此时W=1.8,score=score+1.8*2.61234173173= 6.191723633904
遍历结束
问题:这样写漏掉了第一个情感词前的否定词和程度副词。。。(这里漏掉了“很”)
4.完整代码
from collections import defaultdict
import os
import re
import jieba
import codecs
#生成stopword表,需要去除一些否定词和程度词汇
stopwords = set()
fr = open('停用词.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.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')
#jieba分词后去除停用词
def seg_word(sentence):
seg_list = jieba.cut(sentence)
seg_result = []
for i in seg_list:
seg_result.append(i)
stopwords = set()
with open('stopwords.txt','r') as fr:
for i in fr:
stopwords.add(i.strip())
return list(filter(lambda x :x not in stopwords,seg_result))
#找出文本中的情感词、否定词和程度副词
def classify_words(word_list):
#读取情感词典文件
sen_file = open('BosonNLP_sentiment_score.txt','r+',encoding='utf-8')
#获取词典文件内容
sen_list = sen_file.readlines()
#创建情感字典
sen_dict = defaultdict()
#读取词典每一行的内容,将其转换成字典对象,key为情感词,value为其对应的权重
for i in sen_list:
if len(i.split(' '))==2:
sen_dict[i.split(' ')[0]] = i.split(' ')[1]
#读取否定词文件
not_word_file = open('否定词.txt','r+',encoding='utf-8')
not_word_list = not_word_file.readlines()
#读取程度副词文件
degree_file = open('程度副词.txt','r+')
degree_list = degree_file.readlines()
degree_dict = defaultdict()
for i in degree_list:
degree_dict[i.split(',')[0]] = i.split(',')[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_word_list and word not in degree_dict.keys():
# 找出分词结果中在情感字典中的词
sen_word[i] = sen_dict[word]
elif word in not_word_list and word not in degree_dict.keys():
# 分词结果中在否定词列表中的词
not_word[i] = -1
elif word in degree_dict.keys():
# 分词结果中在程度副词中的词
degree_word[i] = degree_dict[word]
#关闭打开的文件
sen_file.close()
not_word_file.close()
degree_file.close()
#返回分类结果
return sen_word,not_word,degree_word
#计算情感词的分数
def score_sentiment(sen_word,not_word,degree_word,seg_result):
#权重初始化为1
W = 1
score = 0
#情感词下标初始化
sentiment_index = -1
#情感词的位置下标集合
sentiment_index_list = list(sen_word.keys())
#遍历分词结果
for i in range(0,len(seg_result)):
#如果是情感词
if i in sen_word.keys():
#权重*情感词得分
score += W*float(sen_word[i])
#情感词下标加一,获取下一个情感词的位置
sentiment_index += 1
if sentiment_index < len(sentiment_index_list)-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])
#定位到下一个情感词
if sentiment_index < len(sentiment_index_list)-1:
i = sentiment_index_list[sentiment_index+1]
return score
#计算得分
def sentiment_score(sentence):
#1.对文档分词
seg_list = seg_word(sentence)
#2.将分词结果转换成字典,找出情感词、否定词和程度副词
sen_word,not_word,degree_word = classify_words(seg_list)
#3.计算得分
score = score_sentiment(sen_word,not_word,degree_word,seg_list)
return score
print("我今天很高兴也非常开心 ",sentiment_score("我今天很高兴也非常开心"))
print('天灰蒙蒙的,路上有只流浪狗,旁边是破旧不堪的老房子 ',sentiment_score('天灰蒙蒙的,路上有只流浪狗,旁边是破旧不堪的老房子'))
print('愤怒、悲伤和埋怨解决不了问题 ',sentiment_score('愤怒、悲伤和埋怨解决不了问题'))
print('要每天都开心快乐 ',sentiment_score('要每天都开心快乐'))
print('我不喜欢这个世界,我只喜欢你 ',sentiment_score('我不喜欢这个世界,我只喜欢你'))
实验结果:
随机测试了5个句子,可以看出整体上情感分析的结果不错,越积极的文本评分越高,消极文本同理。
当然这种方法受很多因素的影响,不可控性大,比如情感字典的选择(里面情感词的权重赋值)、停用词表的选择、分数计算规则的设计。而基于机器(深度)学习的方法无疑是优于基于情感字典的方法的。