B事件发生后,A事件发生的概率可以如下表示:
p ( A ∣ B ) = p ( A ∩ B ) P ( B ) (1) p(A|B)=\frac{p(A\cap B)}{P(B)}\tag{1} p(A∣B)=P(B)p(A∩B)(1)
A事件发生后,B事件发生的概率可以如下表示:
p ( B ∣ A ) = p ( A ∩ B ) P ( A ) (2) p(B|A)=\frac{p(A\cap B)}{P(A)}\tag{2} p(B∣A)=P(A)p(A∩B)(2)
二者做比:
P ( A ∣ B ) P ( B ∣ A ) = P ( A ) P ( B ) (3) \frac{P(A|B)}{P(B|A)}=\frac{P(A)}{P(B)}\tag{3} P(B∣A)P(A∣B)=P(B)P(A)(3)
把 P ( B ∣ A ) P(B|A) P(B∣A) 乘到等式右边后,我们就叨叨了如下贝叶斯定理:
P ( A ∣ B ) = P ( B ∣ A ) P ( A ) P ( B ) (4) P(A|B)=\frac{P(B|A)P(A)}{P(B)}\tag{4} P(A∣B)=P(B)P(B∣A)P(A)(4)
将贝叶斯定理的变量名称稍作变换,我们就得到了贝叶斯公式:
P ( c ∣ x ) = P ( x ∣ c ) P ( c ) P ( x ) (5) P(c|\bm{x})=\frac{P(\bm{x}|c)P(c)}{P(\bm{x})}\tag{5} P(c∣x)=P(x)P(x∣c)P(c)(5)
其中, P ( c ) P(c) P(c) 表示数据集中 l a b e l label label 为 c c c 类样本的概率, x \bm{x} x 是输入属性, P ( x ) P(\bm{x}) P(x) 表示输入 x \bm{x} x 发生的概率, P ( x ∣ c ) P(\bm{x}|c) P(x∣c) 是 c c c 发生条件下 x \bm{x} x 发生的概率。
我们通过公式5,来表示我们把输入 x \bm{x} x 分为 c c c 类的概率,这就是贝叶斯分类。
进一步理解, P ( c ) P(c) P(c)叫做先验概率, P ( x ∣ c ) P(x|c) P(x∣c)叫做似然概率,二者相乘,最终的结果就可以很好的表征样本为某个类别的可能性大小。
从上面的式5可以看出,我们如果想要预测一个输入 x \bm{x} x的类别,我们只需要得到训练数据集中的 P ( c ) P(c) P(c) 和 P ( x ∣ c ) P(\bm{x}|c) P(x∣c) 就可以了, P ( x ) P(x) P(x)不需要。
因为我们的预测过程是,给出一个样本输入 x x x,假设这个样本可能的类别为 C = { c ∣ c 0 , c 1 , . . . , c n } C=\{c|c_0,c_1,...,c_n\} C={c∣c0,c1,...,cn};
我们根据贝叶斯公式,计算 P ( c 0 ) P(c_0) P(c0), P ( c 1 ) P(c_1) P(c1),……, P ( c n ) P(c_n) P(cn) ;
最后,我们选择一个最大的 P ( c ) P(c) P(c)作为我们最终的预测类别 c ′ c' c′,模型预测结束。
而在这个过程中,我们的输入 x x x 是相同的,因此 P ( x ) P(\bm{x}) P(x) 也是相同的,所以我们不需要管它,就选择分子最大的就可以了。
这个很好计算,我们直接统计一下,数据集中不同类别的数据的频率就可以了,大数定律告诉我们,当数据集足够大的时候,我们可以使用频率来逼近概率。
这个思路也很简单,同样是统计,统计数据集中,label属于 c c c 的,同时输入属性为 x \bm{x} x 的数据的频率,在数据量比较大的情况下,同样使用频率逼近概率。
但是,这里的 x \bm{x} x 是由不同的输入属性组合到一起最终合成的,它具有非常多种的情况,是组合问题,这种问题统计起来是会出现复杂度爆炸的情况的,无法在多项式的时间内完成程序的运算,换言之为一种 NP
难问题。
为了解决这种 NP
难问题,我们采用了属性条件独立假设,进而就诞生了朴素贝叶斯。
朴素贝叶斯分类通过属性独立假设,近似求解了 P ( x ∣ c ) P(\bm{x}|c) P(x∣c)这个NP难问题,而且近似的效果非常好,可以取得很棒的分类效果。
假设所有属性为独立分布的,我们可以得到如下式子:
P ( x ∣ c ) = ∏ i = 0 d P ( x i ∣ c ) (6) P(\bm{x}|c)=\prod_{i=0}^dP(x_i|c)\tag{6} P(x∣c)=i=0∏dP(xi∣c)(6)
其中d为属性数目, x i x_i xi 为 x \bm{x} x 在第 i i i 个属性上的取值。
将式(6)带入式(5)可以得到如下结果:
P ( c ∣ x ) = P ( x ∣ c ) P ( c ) P ( x ) = P ( c ) P ( x ) ∏ i = 0 d P ( x i ∣ c ) (7) P(c|\bm{x})=\frac{P(\bm{x}|c)P(c)}{P(\bm{x})}=\frac{P(c)}{P(\bm{x})}\prod_{i=0}^dP(x_i|c)\tag{7} P(c∣x)=P(x)P(x∣c)P(c)=P(x)P(c)i=0∏dP(xi∣c)(7)
这里面的 P ( x i ∣ c ) P(x_i|c) P(xi∣c) 是很有限的,它的数量可以表示为 ∑ i = 1 n n u m A t t r i b u t e s V a l u e s ( i ) × n u m C l a s s V a l u e s \sum_{i=1}^n numAttributesValues(i)\times numClassValues ∑i=1nnumAttributesValues(i)×numClassValues。在程序设计中我们可以采用一个二维的List数组、三维double数组或者二维数组表示三维数组等多种方法来存储这个变量。(具体可以看下面代码,为了代码的可读性和简洁性,我是采用二维List来进行表示的)
其中 n n n表示输入 x \bm{x} x的属性数量, n u m A t t r i b u t e s V a l u e s ( i ) numAttributesValues(i) numAttributesValues(i) 表示第i个属性的可能取值的数量, n u m C l a s s V a l u e s numClassValues numClassValues 表示 label
有多少个类别。
从上面也可以看出,贝叶斯分类器天然是用来处理名词性属性的,如果我们遇到了数值型属性,就需要进行一下数据离散化处理,才能采用贝叶斯进行分类。数据离散化处理方法有很多,比如:等高分箱、等宽分享,以及基于概率分布的划分等。
package weka.classifiers.myf;
import weka.classifiers.Classifier;
import weka.core.*;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
/**
* @author YFMan
* @Description 朴素贝叶斯 分类器
* @Date 2023/5/14 18:48
*/
public class myNaiveBayes extends Classifier {
// 用于存储 朴素贝叶斯 属性参数
protected List<Integer>[][] m_Distributions;
// 用于存储 朴素贝叶斯 类别参数
protected List<Integer> m_ClassDistribution;
// 类别参数 的 种类数量
protected int m_NumClasses;
// 存储训练数据
protected Instances m_Instances;
/*
* @Author YFMan
* @Description 训练分类器,初始化 属性参数 和 类别参数
* @Date 2023/5/14 21:42
* @Param [instances 训练数据]
* @return void
**/
public void buildClassifier(Instances instances) throws Exception {
// 初始化训练数据
m_Instances = instances;
// 初始化类别参数 的 种类数量
m_NumClasses = instances.numClasses();
// 初始化 属性参数
m_Distributions = new List[instances.numAttributes() - 1][m_NumClasses];
for(int i=0;i<instances.numAttributes() - 1;i++){
for(int j=0;j<m_NumClasses;j++){
m_Distributions[i][j] = new ArrayList<>();
}
}
// 初始化 类别参数
m_ClassDistribution = new ArrayList<>();
for(int i=0;i<m_NumClasses;i++){
m_ClassDistribution.add(0);
}
// 获取属性参数的枚举类型
Enumeration attributeEnumeration = instances.enumerateAttributes();
// 遍历属性参数
while (attributeEnumeration.hasMoreElements()) {
// 获取属性参数
Attribute attribute = (Attribute) attributeEnumeration.nextElement();
// 获取属性参数的索引
int attributeIndex = attribute.index();
// 获取属性参数的值的枚举类型
Enumeration attributeValueEnumeration = attribute.enumerateValues();
// 遍历属性参数的值
while (attributeValueEnumeration.hasMoreElements()) {
// 获取属性参数的值
String attributeValue = (String) attributeValueEnumeration.nextElement();
// 遍历类别参数
for (int classIndex = 0; classIndex < m_NumClasses; classIndex++) {
// 初始化 属性参数 的 某个值 的 某个类别参数 的 计数
m_Distributions[attributeIndex][classIndex].add(0);
}
}
}
// 遍历训练数据
for (int instanceIndex = 0; instanceIndex < instances.numInstances(); instanceIndex++) {
// 获取训练数据的实例
Instance instance = instances.instance(instanceIndex);
// 获取训练数据的类别参数的值
int classValue = (int) instance.classValue();
// 遍历属性参数
for (int attributeIndex = 0; attributeIndex < instances.numAttributes() - 1; attributeIndex++) {
// 获取训练数据的属性参数的值
int attributeValue = (int) instance.value(attributeIndex);
// 计数
m_Distributions[attributeIndex][classValue].set(attributeValue,
m_Distributions[attributeIndex][classValue].get(attributeValue) + 1);
}
// 计数
m_ClassDistribution.set(classValue, m_ClassDistribution.get(classValue) + 1);
}
}
/*
* @Author YFMan
* @Description 根据给定的实例,预测其类别
* @Date 2023/5/14 21:43
* @Param [instance 给定的实例]
* @return double[]
**/
public double[] distributionForInstance(Instance instance)
throws Exception {
// 初始化预测概率数组
double[] predictionProbability = new double[m_NumClasses];
// 遍历类别参数
for (int classIndex = 0; classIndex < m_NumClasses; classIndex++) {
// 初始化预测概率
double prediction = 1;
// 遍历属性参数
for (int attributeIndex = 0; attributeIndex < m_Instances.numAttributes() - 1; attributeIndex++) {
// 获取属性参数的值
int attributeValue = (int) instance.value(attributeIndex);
// 获取 当前属性 可能的取值数
int attributeValueCount = m_Distributions[attributeIndex][classIndex].size();
// 计算条件概率P(x|c) (当前属性值在当前类别下占的比例) (拉普拉斯平滑)
double p_x_c = (double) (m_Distributions[attributeIndex][classIndex].get(attributeValue) + 1) /
(m_ClassDistribution.get(classIndex) + attributeValueCount);
// 计算预测概率
prediction *= p_x_c;
}
// 计算先验概率P(c) (当前类别占总类别的比例) (拉普拉斯平滑)
double p_c = (double) (m_ClassDistribution.get(classIndex) + 1) /
(m_Instances.numInstances() + m_NumClasses);
// 计算预测概率
predictionProbability[classIndex] = prediction * p_c;
}
// 归一化
Utils.normalize(predictionProbability);
// 返回预测概率数组
return predictionProbability;
}
/*
* @Author YFMan
* @Description 主函数
* @Date 2023/5/14 21:54
* @Param [argv 命令行参数]
* @return void
**/
public static void main(String[] argv) {
runClassifier(new myNaiveBayes(), argv);
}
}