Java实现基于朴素贝叶斯的情感词分析
朴素贝叶斯(Naive Bayesian
)是一种基于贝叶斯定理和特征条件独立假设的分类方法,它是基于概率论的一种有监督学习方法,被广泛应用于自然语言处理,并在机器学习领域中占据了非常重要的地位。在之前做过的一个项目中,就用到了朴素贝叶斯分类器,将它应用于情感词的分析处理,并取得了不错的效果,本文我们就来介绍一下朴素贝叶斯分类的理论基础和它的实际使用。
在学习朴素贝叶斯分类以及正式开始情感词分析之前,我们首先需要了解一下贝叶斯定理的数学基础。
贝叶斯定理
贝叶斯定理是关于随机事件A和B的条件概率的定理,公式如下:
在上面的公式中,每一项表示的意义如下:
P(A)
:先验概率(prior probability
),是在没有任何条件限制下事件A发生的概率,也叫基础概率,是对A事件概率的一个主观判断P(A|B)
:在B发生的情况下A发生的可能性,也被称为A的后验概率(posterior probability
)P(B|A)
:似然性,也被称为条件似然(conditional likelihood
)P(B)
:不论A是否发生,在所有情况下B发生的概率,它被称为整体似然或归一化常量(normalizing constant
)
按照上面的解释,贝叶斯定理可以表述为:
后验概率 = 先验概率 * 似然性 / 归一化常量
通俗的来说,可以理解为当我们不能确定某一个事件发生的概率时,可以依靠与该事件本质属性相关的事件发生的概率去推测该事件发生的概率。用数学语言来表达就是,支持某项属性的事件发生得愈多,则该事件发生的的可能性就愈大,这个推理过程也被叫做贝叶斯推理。
在查阅的一些文档中,P(B|A)/P(B)
可以被称为可能性函数,它作为一个调整因子,表示新信息B对事件A带来的调整,作用是将先验概率(主观判断)调整到更接近真实的概率。那么,贝叶斯定理也可以理解为:
新信息出现后A的概率 = A的先验概率 * 新信息带来的调整
举一个例子,方便大家更直观的理解这一过程。假设统计了一段时间内天气和气温对于运动情况的影响,如下所示:
天气 气温 运动
晴天 非常高 游泳
晴天 高 足球
阴天 中 钓鱼
阴天 中 游泳
晴天 低 游泳
阴天 低 钓鱼
现在请计算在晴天,气温适中的情况下,去游泳的概率是多少?根据贝叶斯定理,计算过程如下:
P(游泳|晴天,中温)=P(晴天,中温|游泳)*P(游泳)/P(晴天,中温)
=P(晴天|游泳)*P(中温|游泳)*P(游泳)/[P(晴天)*P(中温)]
=2/3 * 1/3 *1/2 / (1/2 *1/3 )
=2/3
最终得出去游泳的概率时2/3,上面就是基于贝叶斯定理,根据给定的特征,计算事件发生概率大小的过程。
贝叶斯分析的思路对于由证据的积累来推测一个事物的发生的概率具有重大作用,当我们要预测一个事物,首先会根据已有的经验和知识推断一个先验概率,然后在新证据不断的积累的情况下调整这个概率。整个通过累积证据来得到一个事件发生概率的过程我们称为贝叶斯分析。这样,贝叶斯底层的思想就可以概括为,如果能够掌握一个事情的全部信息,就能够计算出它的一个客观概率。
另外,在贝叶斯公式的基础上进行变形,可以得到下面的公式:
其中B1,B2,…,Bj
是一个完备事件组,上面的公式可以表示在事件A已经发生的条件下,寻找导致A发生的各种“原因”的Bi
的概率。
朴素贝叶斯
在学习朴素贝叶斯之前,首先需要对贝叶斯分类进行一下了解,贝叶斯分类通过预测一个对象属于某个类别的概率,通过比较不同类别概率的大小预测其最可能从属的类别,是基于贝叶斯定理而构成出来的。在处理大规模数据集时,贝叶斯分类器表现出较高的分类准确性。
贝叶斯分类在处理一个未知类型的样本X时,可以先算出X属于每一个类别Ci的概率 P(Ci|X)
,然后选择其中概率最大的类别。假设有两个特征变量x和y,并且存在两个分类类别C1和C2,结合贝叶斯定理:
- 如果
P(C1|x,y) > P(C2|x,y)
,说明在x和y发生的条件下,C1比C2发生的概率要大,那么它应该属于类别C1 - 反之如果
P(C1|x,y) < P(C2|x,y)
,那么它应该属于类别C2
而朴素贝叶斯模型(Naive Bayesian Model
)作为一种强大的预测建模算法,它在贝叶斯定理的基础上进行了简化,假定了目标的特征属性之间相互独立,这也是它被形容为“朴素”的原因。在实际情况中如果属性之间存在关联,那么分类准确率会降低,不过对于解决绝大部分的复杂问题非常有效。
设在样本数据集D上,样本数据的特征属性集为X={x1,x2,…,xd}
,类变量可被分为 Y={y1,y2,…,ym}
,即数据集D可以被分为ym
个类别。我们假设x1,x2,…,xd
相互独立,那么由贝叶斯定理可得:
对于相同的测试样本,分母P(X)
的大小是固定不变的,因此在比较后验概率时,我们可以只比较分子的大小即可。
在这里解释一下贝叶斯定理、贝叶斯分类和朴素贝叶斯之间的区别,贝叶斯定理作为理论基础,解决了概率论中的逆概率问题,在这个基础上人们设计出了贝叶斯分类器,而朴素贝叶斯是贝叶斯分类器中的一种,也是最简单和常用的分类器,可以使用下面的图来表示它们之间的关系:
在实际应用中,朴素贝叶斯有广泛的应用,在文本分类、垃圾邮件过滤、情感预测及钓鱼网站的检测方面都能够起到良好的效果。为了训练朴素贝叶斯模型,我们需要先在训练集的基础上对分类好的数据进行训练,计算出先验概率和每个属性的条件概率,计算完成后,概率模型就可以使用贝叶斯原理对新数据进行预测。
贝叶斯推断与人脑的工作机制很像,这也是它为什么能够成为机器学习的基础,大脑的决策过程就是先对事物进行主观判断,然后搜集新的信息,优化主观判断,如果新的信息符合这个主观判断,那就提高主观判断的可信度,如果不符合,就降低主观判断的可信度。
代码实现
在对理论有了基本的了解后,我们开始分析怎样将朴素贝叶斯应用于我们文本处理的情感词分析中。主要步骤如下:
- 对训练集和测试集完成文本分词,并通过主观判断标注所属的分类
- 对训练集进行训练,统计每个词汇出现在分类下的次数,计算每个类别在训练样本中的出现频率、及每个特征属性对每个类别的条件概率(即似然概率)
- 将训练好的模型应用于测试集的样本上,根据贝叶斯分类计算样本在每个分类下的概率大小
- 比较在各个分类情况下的概率大小,推测文本最可能属于的情感分类
使用流程图表示:
1、准备阶段
首先准备数据集,这里使用了对某酒店的评论数据,根据主观态度将其分为“好评”或“差评”这两类待分类项,对每行分词后的语句打好了情感标签,并且已经提前对完整语句完成了对分词,数据格式如下:
在每行的数据的头部,是添加的“好评”或“差评”标签,标签与分词采用tab
分割,词语之间使用空格分割。按照比例,将数据集的80%作为训练集,剩余20%作为测试集,分配过程尽量保证随机原则。
2、训练阶段
在训练阶段,主要完成词频的统计工作。读取训练集,统计出每个词属于该分类下出现的次数,用于后续求解每个词出现在各个类别下的概率,即词汇与主观分类情感之间的关系:
private static void train(){
Map<String,Integer> parameters = new HashMap<>();
try(BufferedReader br = new BufferedReader(new FileReader(trainingData))){ //训练集数据
String sentence;
while(null!=(sentence=br.readLine())){
String[] content = sentence.split("\t| "); //以tab或空格分词
parameters.put(content[0],parameters.getOrDefault(content[0],0)+1);
for (int i = 1; i < content.length; i++) {
parameters.put(content[0]+"-"+content[i], parameters.getOrDefault(content[0]+"-"+content[i], 0)+1);
}
}
}catch (IOException e){
e.printStackTrace();
}
saveModel(parameters);
}
将训练好的模型保存到文件中,可以方便在下次使用时不用重复进行模型的训练:
private static void saveModel(Map<String,Integer> parameters){
try(BufferedWriter bw =new BufferedWriter(new FileWriter(modelFilePath))){
parameters.keySet().stream().forEach(key->{
try {
bw.append(key+"\t"+parameters.get(key)+"\r\n");
} catch (IOException e) {
e.printStackTrace();
}
});
bw.flush();
}catch (IOException e){
e.printStackTrace();
}
}
查看保存好的模型,数据的格式如下:
好评-免费送 3
差评-真烦 1
好评-礼品 3
差评-脏乱差 6
好评-解决 15
差评-挨宰 1
……
这里对训练的模型进行保存,所以如果后续有同样的分类任务时,可以直接在训练集的基础上进行计算,对于分类速度要求较高的任务,能够有效的提高计算的速度。
3、加载模型
加载训练好的模型:
private static HashMap<String, Integer> parameters = null; //用于存放模型
private static Map<String, Double> catagory=null;
private static String[] labels = {"好评", "差评", "总数","priorGood","priorBad"};
private static void loadModel() throws IOException {
parameters = new HashMap<>();
List<String> parameterData = Files.readAllLines(Paths.get(modelFilePath));
parameterData.stream().forEach(parameter -> {
String[] split = parameter.split("\t");
String key = split[0];
int value = Integer.parseInt(split[1]);
parameters.put(key, value);
});
calculateCatagory(); //分类
}
对词进行分类,统计出好评及差评的词频总数,并基于它们先计算得出先验概率:
//计算模型中类别的总数
public static void calculateCatagory() {
catagory = new HashMap<>();
double good = 0.0; //好评词频总数
double bad = 0.0; //差评的词频总数
double total; //总词频
for (String key : parameters.keySet()) {
Integer value = parameters.get(key);
if (key.contains("好评-")) {
good += value;
} else if (key.contains("差评-")) {
bad += value;
}
}
total = good + bad;
catagory.put(labels[0], good);
catagory.put(labels[1], bad);
catagory.put(labels[2], total);
catagory.put(labels[3],good/total); //好评先验概率
catagory.put(labels[4],bad/total); //差评先验概率
}
查看执行完后的统计值:
“好评”对应的词汇出现的总次数是46316个,“差评”对应的词汇出现的总次数是77292个,训练集词频总数为123608个,并可基于它们计算出它们的先验概率:
该文档属于某个类别的条件概率= 该类别的所有词条词频总数 / 所有词条的词频总数
4、测试阶段
测试阶段,加载我们提前准备好的测试集,对每一行分词后的评论语句进行主观情感的预测:
private static void predictAll() {
double accuracyCount = 0.;//准确个数
int amount = 0; //测试集数据总量
try (BufferedWriter bw = new BufferedWriter(new FileWriter(outputFilePath))) {
List<String> testData = Files.readAllLines(Paths.get(testFilePath)); //测试集数据
for (String instance : testData) {
String conclusion = instance.substring(0, instance.indexOf("\t")); //已经打好的标签
String sentence = instance.substring(instance.indexOf("\t") + 1);
String prediction = predict(sentence); //预测结果
bw.append(conclusion + " : " + prediction + "\r\n");
if (conclusion.equals(prediction)) {
accuracyCount += 1.;
}
amount += 1;
}
//计算准确率
System.out.println("accuracyCount: " + accuracyCount / amount);
} catch (Exception e) {
e.printStackTrace();
}
}
在测试中,调用下面的predict
方法进行分类判断。在计算前,再来回顾一下上面的公式,在程序中进行简化运算:
对于同一个预测样本,分母相同,所以我们可以只比较分子的大小。对分子部分进行进一步简化,对于连乘预算,我们可以对其进行对数操作,变成各部分相加:
这样对于概率的大小比较,就可以简化为比较 先验概率和各个似然概率分别取对数后相加的和。先验概率我们在之前的步骤中已经计算完成并保存,所以这里只计算各词汇在分类条件下的似然概率即可。predict
方法的实现如下:
private static String predict(String sentence) {
String[] features = sentence.split(" ");
String prediction;
//分别预测好评和差评
double good = likelihoodSum(labels[0], features) + Math.log(catagory.get(labels[3]));
double bad = likelihoodSum(labels[1], features) + Math.log(catagory.get(labels[4]));
return good >= bad?labels[0]:labels[1];
}
在其中调用likelihood
方法计算似然概率的对数和:
//似然概率的计算
public static double likelihoodSum(String label, String[] features) {
double p = 0.0;
Double total = catagory.get(label) + 1;//分母平滑处理
for (String word : features) {
Integer count = parameters.getOrDefault(label + "-" + word, 0) + 1;//分子平滑处理
//计算在该类别的情况下是该词的概率,用该词的词频除以类别的总词频
p += Math.log(count / total);
}
return p;
}
在计算似然概率的方法中,如果出现在训练集中没有包括的词汇,那么会出现它的似然概率为0的情况,为了防止这种情况,对分子分母进行了分别加一的平滑操作。
最后在主函数中调用上面的步骤,最终如果计算出基于样本的好评概率大于等于差评概率,那么将它分类划入“好评”,反之划入“差评”类别,到此就完成了训练和测试的全过程:
public static void main(String[] args) throws IOException {
train();
loadModel();
predictAll();
}
执行全部代码,结果如下,可以看到获取了93.35%的准确率。
对比最后输出的文档中的标签与预测结果,可以看到,预测结果的准确度还是非常高的。
5、总结
在上面的例子中,还有一些可以进行改进的地方,例如可以在前期建立情感词库,在特征值提取的过程中只提取情感词,去除其余无用词汇(如介词等)对分类的影响,只选取关键的代表词作为特征提取,达到更高的分类效率。另外,可以在建立词库时,将测试集的情感词也放入词库,避免出现在某个分类条件下似然概率为零的情况,简化平滑步骤。
此外,朴素贝叶斯的分类对于追加训练集的情况有很好的应用,如果训练集不断的增加,可以在现有训练模型的基础上添加新的样本值、或对已有的样本值的属性进行修改,在此基础上,可以实现增量的模型修改。
如果文章对您有所帮助,欢迎关注公众号 码农参上
Java实现基于朴素贝叶斯的情感词分析的更多相关文章
- 详解基于朴素贝叶斯的情感分析及 Python 实现
相对于「 基于词典的分析 」,「 基于机器学习 」的就不需要大量标注的词典,但是需要大量标记的数据,比如: 还是下面这句话,如果它的标签是: 服务质量 - 中 (共有三个级别,好.中.差) ╮(╯-╰ ...
- 【Coursera】基于朴素贝叶斯的中文多分类器
一.算法说明 为了便于计算类条件概率\(P(x|c)\),朴素贝叶斯算法作了一个关键的假设:对已知类别,假设所有属性相互独立. 当使用训练完的特征向量对新样本进行测试时,由于概率是多个很小的相乘所得, ...
- 数据算法 --hadoop/spark数据处理技巧 --(13.朴素贝叶斯 14.情感分析)
十三.朴素贝叶斯 朴素贝叶斯是一个线性分类器.处理数值数据时,最好使用聚类技术(eg:K均值)和k-近邻方法,不过对于名字.符号.电子邮件和文本的分类,则最好使用概率方法,朴素贝叶斯就可以.在某些情况 ...
- 朴素贝叶斯算法源码分析及代码实战【python sklearn/spark ML】
一.简介 贝叶斯定理是关于随机事件A和事件B的条件概率的一个定理.通常在事件A发生的前提下事件B发生的概率,与在事件B发生的前提下事件A发生的概率是不一致的.然而,这两者之间有确定的关系,贝叶斯定理就 ...
- spark MLlib实现的基于朴素贝叶斯(NaiveBayes)的中文文本自动分类
1.自动文本分类是对大量的非结构化的文字信息(文本文档.网页等)按照给定的分类体系,根据文字信息内容分到指定的类别中去,是一种有指导的学习过程. 分类过程采用基于统计的方法和向量空间模型可以对常见的文 ...
- R 基于朴素贝叶斯模型实现手机垃圾短信过滤
# 读取数数据, 查看数据结构 df_raw <- read.csv("sms_spam.csv", stringsAsFactors=F) str(df_raw) leng ...
- 朴素贝叶斯算法下的情感分析——C#编程实现
这篇文章做了什么 朴素贝叶斯算法是机器学习中非常重要的分类算法,用途十分广泛,如垃圾邮件处理等.而情感分析(Sentiment Analysis)是自然语言处理(Natural Language Pr ...
- C#编程实现朴素贝叶斯算法下的情感分析
C#编程实现 这篇文章做了什么 朴素贝叶斯算法是机器学习中非常重要的分类算法,用途十分广泛,如垃圾邮件处理等.而情感分析(Sentiment Analysis)是自然语言处理(Natural Lang ...
- 利用朴素贝叶斯算法进行分类-Java代码实现
http://www.crocro.cn/post/286.html 利用朴素贝叶斯算法进行分类-Java代码实现 鳄鱼 3个月前 (12-14) 分类:机器学习 阅读(44) 评论(0) ...
随机推荐
- 官网GitLab CI/CD英文文档翻译
在查阅GitLab官网的CI/CD功能说明时,全是英文看起来不方便,通过翻译软件自动翻译后"内容失真",看起来很变扭.查阅了百度上的资料发现很多翻译很老旧,有些甚至是挂羊头卖狗肉. ...
- sqlserver日期时间格式转换
Select CONVERT(varchar(100), GETDATE(), 0): 05 16 2006 10:57AM Select CONVERT(varchar(100), GETDATE( ...
- 为什么我们在定义HashMap的时候,就指定它的初始化大小呢
在当我们对HashMap初始化时没有设置初始化容量,系统会默认创建一个容量为16的大小的集合.当HashMap的容量值超过了临界值(默认16*0.75=12)时,HashMap将会重新扩容到下一个2的 ...
- 微信小程序:数据绑定
data中的数据不仅仅可以当成文本来显示,还可以当成属性来显示. 注意:属性值要用单引号或双引号引起来. 在微信开发者工具的控制台中点击Wxml会看到 使用Boolean类型充当属性的时候,字符串和花 ...
- 用Vue3构建企业级前端应用,TS能让你更轻松点
摘要:Vue 3已经发布有一段时间了,到底有哪些新特性值得关注,如何用它构建企业级前端项目,怎样快速上手Vue 3?本篇文章将对此进行详细讲解. 前言 工欲善其事,必先利其器 --<论语> ...
- Newbe.Claptrap 框架入门,第二步 —— 创建项目
接上一篇 Newbe.Claptrap 框架入门,第一步 -- 开发环境准备 ,我们继续了解如何创建一个 Newbe.Claptrap 项目. Newbe.Claptrap 是一个用于轻松应对并发问题 ...
- Java并发之ThreadPoolExecutor源码解析(三)
Worker 先前,笔者讲解到ThreadPoolExecutor.addWorker(Runnable firstTask, boolean core),在这个方法中工作线程可能创建成功,也可能创建 ...
- Django 自定义标签与过滤器报错 No module named 'templatetags'
Django 自定义标签与过滤器报错 按照网上的教程如果想使用自定义的标签与过滤器就得往settings.py中添加下列数据 TEMPLATES = [ { 'BACKEND': 'django.te ...
- PAT-1099(Build A Binary Search Tree)Java实现+二叉排序树的中序遍历和层次遍历
Build A Binary Search Tree PAT-1099 本题有意思的一个点就是:题目已经给出了一颗排序二叉树的结构,需要根据这个结构和中序遍历序列重构一棵二叉排序树. 解法:可以根据中 ...
- 面试必备——Java多线程与并发(二)
1.synchroized相关(锁的是对象,不是代码) (1)线程安全问题的主要原因 存在共享数据(也称临界资源) 存在多线程共同操作这些共享数据 解决:同一时刻有且只有一个线程在操作共享数据,其他线 ...