本文使用朴素贝叶斯算法实现 豆瓣Top250电影评价的情感分析与预测。python
最近在学习天然语言正负面情感的处理问题,可是绝大部分能搜索到的实践都是Kggle上IMDB影评的情感分析。git
因此在这里我就用最基础的朴素贝叶斯算法来对豆瓣的影评进行情感分析与预测。程序员
在这里我参考了 github.com/aeternae/IM…,万分感谢。github
贝叶斯分类是一类分类算法的总称,这类算法均以贝叶斯定理为基础,故统称为贝叶斯分类。算法
这种算法经常使用来作文章分类,垃圾邮、件垃圾评论分类,朴素贝叶斯的效果不错而且成本很低。bash
已知某条件几率,如何获得两个事件交换后的几率,也就是在已知P(A|B)的状况下如何求得P(B|A)。markdown
P(B|A)表示事件A已经发生的前提下,事件B发生的几率,叫作事件A发生下事件B的条件几率。app
朴素贝叶斯的公式dom
一个通俗易懂的视频教程函数
Youtube www.youtube.com/watch?v=Aqo…
举个不太恰当的例子
咱们想知道作程序员与秃头之间的关系,咱们就能够用朴素贝叶斯公式来进行计算。
咱们如今想求 P(秃头|作程序员) 的几率, 也就是作程序员就会秃头的几率
我这辈子都不会秃头 (((o(゚▽゚)o))) !!!
代入朴素贝叶斯公式
已知数据以下表
姓名 | 职业 | 是否秃头 |
---|---|---|
奎托斯 | 战神 | 是 |
杀手47号 | 杀手 | 是 |
埼玉 | 超人 | 是 |
灭霸 | 计生办主任 | 是 |
杰森 斯坦森 | 硬汉 | 是 |
某某996程序员 | 程序员 | 是 |
我 | 程序员 | 否 |
基于朴素贝叶斯公式,由以上这张表咱们能够求出:
上面这个例子就简单的描述了朴素贝叶斯公式的基本用法。
接下来我就使用豆瓣Top250排行榜的影评来使用朴素贝叶斯进行好评与差评的训练与预测。
首先须要豆瓣Top250影评的语料,我用Scrapy抓取了5w份语料,用于训练与验证。
豆瓣影评爬虫 github.com/3inchtime/d…
有了语料以后咱们就能够开始实际开发了。
这里建议使用jupyter来开发操做。
如下代码所有在个人Github上能够看到,欢迎你们提出建议。
首先加载语料
# -*- coding: utf-8 -*- import random import numpy as np import csv import jieba file_path = './data/review.csv' jieba.load_userdict('./data/userdict.txt') # 读取保存为csv格式的语料 def load_corpus(corpus_path): with open(corpus_path, 'r') as f: reader = csv.reader(f) rows = [row for row in reader] review_data = np.array(rows).tolist() random.shuffle(review_data) review_list = [] sentiment_list = [] for words in review_data: review_list.append(words[1]) sentiment_list.append(words[0]) return review_list, sentiment_list 复制代码
在训练以前,通常均会对数据集作shuffle,打乱数据之间的顺序,让数据随机化,这样能够避免过拟合。因此使用random.shuffle()
方法打乱数据。
jieba.load_userdict('./data/userdict.txt')
这里我本身作了一个词典,防止部分结巴分词的不许确,能够提升约1%左右的准确率。
好比不是很喜欢这句,jieba会分红’不是‘,’很喜欢‘两个词,这样致使这句话很大几率会被预测为好评。
因此这里我在自定义的词典中分好了不少相似这样的词,提升了一点点准确率。
而后将所有的语料按1:4分为测试集与训练集
n = len(review_list) // 5 train_review_list, train_sentiment_list = review_list[n:], sentiment_list[n:] test_review_list, test_sentiment_list = review_list[:n], sentiment_list[:n] 复制代码
分词
使用jieba分词,将语料进行分词,而且去除stopwords。
import re import jieba stopword_path = './data/stopwords.txt' def load_stopwords(file_path): stop_words = [] with open(file_path, encoding='UTF-8') as words: stop_words.extend([i.strip() for i in words.readlines()]) return stop_words def review_to_text(review): stop_words = load_stopwords(stopword_path) # 去除英文 review = re.sub("[^\u4e00-\u9fa5^a-z^A-Z]", '', review) review = jieba.cut(review) # 去掉停用词 if stop_words: all_stop_words = set(stop_words) words = [w for w in review if w not in all_stop_words] return words # 用于训练的评论 review_train = [' '.join(review_to_text(review)) for review in train_review_list] # 对于训练评论对应的好评/差评 sentiment_train = train_sentiment_list # 用于测试的评论 review_test = [' '.join(review_to_text(review)) for review in test_review_list] # 对于测试评论对应的好评/差评 sentiment_test = test_sentiment_list 复制代码
TF*IDF与词频向量化
TF-IDF(是一种经常使用于信息处理和数据挖掘的加权技术。根据词语的在文本中出现的次数和在整个语料中出现的文档频率来计算一个词语在整个语料中的重要程度。
它的优势是能过滤掉一些常见的却可有可无本的词语,同时保留影响整个文本的重要字词。
使用Countvectorizer()
将一个文档转换为向量,计算词汇在文本中出现的频率。
CountVectorizer
类会将文本中的词语转换为词频矩阵,例如矩阵中包含一个元素a[i] [j],它表示j词在i类文本下的词频。它经过fit_transform函数计算各个词语出现的次数。
TfidfTransformer用于统计vectorizer中每一个词语的TF-IDF值。
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer from sklearn.pipeline import Pipeline from sklearn.naive_bayes import MultinomialNB count_vec = CountVectorizer(max_df=0.8, min_df=3) tfidf_vec = TfidfVectorizer() # 定义Pipeline对所有步骤的流式化封装和管理,能够很方便地使参数集在新数据集(好比测试集)上被重复使用。 def MNB_Classifier(): return Pipeline([ ('count_vec', CountVectorizer()), ('mnb', MultinomialNB()) ]) 复制代码
max_df
这个参数的做用是做为一个阈值,当构造语料库的关键词集的时候,若是某个词的词频大于max_df
,这个词不会被看成关键词。
若是这个参数是float,则表示词出现的次数与语料库文档数的百分比,若是是int,则表示词出现的次数。
min_df
相似于max_df
,不一样之处在于若是某个词的词频小于min_df
,则这个词不会被看成关键词
这样咱们就成功的构造出了用于训练以及测试的Pipeline
而后用 Pipeline.fit()对训练集进行训练
再直接用 Pipeline.score() 对测试集进行预测并评分
mnbc_clf = MNB_Classifier() # 进行训练 mnbc_clf.fit(review_train, sentiment_train) # 测试集准确率 print('测试集准确率: {}'.format(mnbc_clf.score(review_test, sentiment_test))) 复制代码
这样咱们就完成了整个从训练到测试的所有流程。
基本上测试集的正确率在79%-80%左右。
由于电影评论中有很大一部分好评中会有负面情感的词语,例如在纪录片《海豚湾》中
我以为大部分看本片会有感的人,都不知道,中国的白暨豚已经灭绝8年了,也不会知道,长江里的江豚也仅剩1000左右了。与其感慨,咒骂日本人如何捕杀海豚,不如作些实际的事情,保护一下长江里的江豚吧,没几年,也将绝迹了。中国人作出来的事情,也不会比小日本好到哪儿去。
因此说若是将这种相似的好评去除,则能够提升准确率。
保存训练好的模型
# 先转换成词频矩阵,再计算TFIDF值 tfidf = tfidftransformer.fit_transform(vectorizer.fit_transform(review_train)) # 朴素贝叶斯中的多项式分类器 clf = MultinomialNB().fit(tfidf, sentiment_train) with open(model_export_path, 'wb') as file: d = { "clf": clf, "vectorizer": vectorizer, "tfidftransformer": tfidftransformer, } pickle.dump(d, file) 复制代码
使用训练好的模型进行影评情感预测
这里我直接贴上所有的源代码,代码很是简单,我将整个处理逻辑封装为一个类,这样就很是方便使用了。
有须要直接能够在个人Github上clone
# -*- coding: utf-8 -*- import re import pickle import numpy as np import jieba class SentimentAnalyzer(object): def __init__(self, model_path, userdict_path, stopword_path): self.clf = None self.vectorizer = None self.tfidftransformer = None self.model_path = model_path self.stopword_path = stopword_path self.userdict_path = userdict_path self.stop_words = [] self.tokenizer = jieba.Tokenizer() self.initialize() # 加载模型 def initialize(self): with open(self.stopword_path, encoding='UTF-8') as words: self.stop_words = [i.strip() for i in words.readlines()] with open(self.model_path, 'rb') as file: model = pickle.load(file) self.clf = model['clf'] self.vectorizer = model['vectorizer'] self.tfidftransformer = model['tfidftransformer'] if self.userdict_path: self.tokenizer.load_userdict(self.userdict_path) # 过滤文字中的英文与无关文字 def replace_text(self, text): text = re.sub('((https?|ftp|file)://)?[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|].(com|cn)', '', text) text = text.replace('\u3000', '').replace('\xa0', '').replace('”', '').replace('"', '') text = text.replace(' ', '').replace('↵', '').replace('\n', '').replace('\r', '').replace('\t', '').replace(')', '') text_corpus = re.split('[!。?;……;]', text) return text_corpus # 情感分析计算 def predict_score(self, text_corpus): # 分词 docs = [self.__cut_word(sentence) for sentence in text_corpus] new_tfidf = self.tfidftransformer.transform(self.vectorizer.transform(docs)) predicted = self.clf.predict_proba(new_tfidf) # 四舍五入,保留三位 result = np.around(predicted, decimals=3) return result # jieba分词 def __cut_word(self, sentence): words = [i for i in self.tokenizer.cut(sentence) if i not in self.stop_words] result = ' '.join(words) return result def analyze(self, text): text_corpus = self.replace_text(text) result = self.predict_score(text_corpus) neg = result[0][0] pos = result[0][1] print('差评: {} 好评: {}'.format(neg, pos)) 复制代码
使用时只要实例化这个分析器,并使用analyze()
方法就能够了。
# -*- coding: utf-8 -*- from native_bayes_sentiment_analyzer import SentimentAnalyzer model_path = './data/bayes.pkl' userdict_path = './data/userdict.txt' stopword_path = './data/stopwords.txt' corpus_path = './data/review.csv' analyzer = SentimentAnalyzer(model_path=model_path, stopword_path=stopword_path, userdict_path=userdict_path) text = '倍感失望的一部诺兰的电影,感受更像是盗梦帮的一场大杂烩。虽然看以前就知道确定是一部没法超越前传2的蝙蝠狭,但真心没想到能差到这个地步。节奏的把控的失误和角色的定位模糊绝对是整部影片的硬伤。' analyzer.analyze(text=text) 复制代码
以上所有代码均push到了个人Github上,欢迎你们提出建议。