说道分类算法-NB,我要从两个方面入手,第一什么是分类问题?第二什么是NB算法?
分类问题:
1.1 二值问题:就是 0,1是与否的问题和回归(概率,分数值)问题。前者我们很好理解,我们用淘宝举个例子,买东西我们要搜索对吧,那么这个商品能不能推荐给你是不是只有两种可能,推荐或者不推荐嘛。可如果符合你胃口的商品有很多件,实际情况也是这样,那么要把最适合你的这类商品推荐给你,就存在一个拟合度,涉及到回归问题和概率的问题了。回归问题这里比如有一个域值:0.8,我们对商品进行预测只有大于0.8说明这商品多半是你想要的,我就推荐给你放在上面,如果小于0.8我就不推荐出来或者放在后面。应该懂了吧。
1.2 多值问题:前面的Knn中花的种类就是多值问题。还有比如一部电影的种类:爱情,治愈,励志,科幻,动漫等类别,现在有一部新的电影要判断它属于哪一个类别,就要从它的性质来分出它的类别。
有监督问题和无监督问题就在于是否含有数据的标签,不多介绍了,进入正题。
分类算法——NB (朴素贝叶斯)
贝叶斯分类算法是统计学的一种概率分类方法,朴素贝叶斯分类是贝叶斯分类中最简单的一种。其分类原理就是利用贝叶斯公式根据某特征的先验概率计算出其后验概率,然后选择具有具有最大后验概率的类作为该特征所属的类。之所以称为朴素是因为贝叶斯只做最原始、最简单的假设:所有特征之间是统计独立的。
假设某样本 X 有 a1,a2,...,an 个属性,那么有 P(X) = P(a1,a2,a3,...,an) = P(a1)* P(a2)*...* P(an).满足这样的公式就说明特征统计独立。
1.条件概率公式
条件概率(conditional probability),就是指在事件B发生的情况下,事件A发生的概率,用P(A|B)来表示。
根据文氏图可知:在事件B发生的情况下事件A发生的概率就是P(A∩B)除以P(B).
接着我们来看全概率公式,如果事件 A1,A2,...,An 构成一个完备事件且都有正概率,那么对于任意一个事件B都有:
P(B) = P(BA1) + P(BA2) + ... + P(BAn)
= P(B|A1)P(A1) + P(B|A2)P(A2) + ... + P(B|An)P(An)
=
2.贝叶斯公式推断
根据条件概率公式和全概率公式,可以得到贝叶斯公式如下:
P(A)其实就是先验概率,在事件B发生之前我们对事件A做一个简单的判断。左边的P(A|B)其实就是我们要求的后验概率,意思是在事件B发生之后我们对事件A发生的概率重新评估。而 P(A|B)/P(B)就是调正因子,它的作用便是使预估概率更接近我们的真实概率,调整因子又叫做可能性函数。这样我们可以理解为 后验概率 = 先验概率 * 调整因子。
这里我们需要注意的是:
- 调整因子如果它大于1,也就是说先验概率被放大了,那么事件A发生的概率就增大了。
- 调整因子如果 = 1,说明 B 事件无助于判断事件A的可能性。
- 调整因子如果 < 1,说明先验概率变小了,事件A的可能性变小了。
3.朴素贝叶斯种类
3.1GaussianNB
GaussianNB就是先验为高斯分布(正态分布)的朴素贝叶斯,假设每个标签的数据服从简单的正态分布。
其中Ck为Y的第k类别, 和
为需要从训练集估计的值。
3.2MultinomialNB
MultinomialNB就是先验为多项式分布的朴素贝叶斯。它假设特征是由一个简单多项式分布生成的。多项分布可以描述各种类型样本出现次数的概率,因此多项式朴素贝叶斯非常适用于描述出现次数或者出现次数比例的特征。该模型常用于文本分类,特征表示的是次数,例如某个单词出现的次数。多项式分布公式如下:
其中,P(Xj= |Y =Ck)是第k个类别的第j维特征的第I个取值条件概率。
是训练集中输出为第k类的样本数。入为一个大于0的常数,常常取为1,即拉普拉斯平滑。也可以取其他值。
3.BernoulliNB
BernoulliNB就是先验为伯努利分布的朴素贝叶斯。假设特征的先验概率为二元伯努利分布,即如下式:
此时l只有两种取值,只能取0或1。
在伯努利模型中,每个取值都是布尔型的,即 ture 和 false。在文本中,就是一个特征有没有在文档中出现。
小结
- 样本特征的分布大部分是连续值,使用GaussianNB会比较好。
- 样本分布大部分是多元离散,使用MultinomialNB比较合适
- 样本特征是二元离散值或者很稀疏的多元离散值,应该使用BernoulliNB。
这里附上代码,里面是细致的注释,方便我们更深入的学习。
package machinelearning.bayes;
import java.io.FileReader;
import java.util.Arrays;
import weka.core.*;
public class NaiveBayes {
/**
*************************
* 用于存储参数的内部类。
*************************
*/
private class GaussianParamters {
double mu;
double sigma;
// 内部类的构造方法,给mu sigma 赋值
public GaussianParamters(double paraMu, double paraSigma) {
mu = paraMu;
sigma = paraSigma;
}// Of the constructor
public String toString() {
return "(" + mu + ", " + sigma + ")";
}// Of toString
}// Of GaussianParamters
/**
* 数据
*/
Instances dataset;
/**
* 类的数量. 对于二元分类器它是2,鸢尾花就是 3 种花,天气就是 P N
*/
int numClasses;
/**
* 事例的数量
*/
int numInstances;
/**
* 条件属性的数量。
*/
int numConditions;
/**
* 预测,包括查询标签和预测标签。
*/
int[] predicts;
/**
* 类分布
*/
double[] classDistribution;
/**
* 拉普拉斯光滑的类分布.
*/
double[] classDistributionLaplacian;
/**
* 计算所有值上所有属性上所有类的条件概率。
*
*/
double[][][] conditionalCounts;
/**
* 拉普拉斯光滑的条件概率。
*/
double[][][] conditionalProbabilitiesLaplacian;
/**
* 高斯参数
*/
GaussianParamters[][] gaussianParameters;
/**
* 数据类型
*/
int dataType;
/**
* nominal(0,1)
*/
public static final int NOMINAL = 0;
/**
* 数值的
*/
public static final int NUMERICAL = 1;
/**
********************
* 传递数据
*
* @param paraFilename
* The given file.
********************
*/
public NaiveBayes(String paraFilename) {
dataset = null;
try {
FileReader fileReader = new FileReader(paraFilename);
dataset = new Instances(fileReader);
fileReader.close();
} catch (Exception ee) {
System.out.println("Cannot read the file: " + paraFilename + "\r\n" + ee);
System.exit(0);
} // Of try
dataset.setClassIndex(dataset.numAttributes() - 1);
// 去除了花名,留下了花的特征,五个特征,就只有四个
numConditions = dataset.numAttributes() - 1;
// 花的总数
numInstances = dataset.numInstances();
// 获得最后一个决策属性个数,P 和 N,数组0开始,numcondition便是最后一个属性
numClasses = dataset.attribute(numConditions).numValues();
}// Of the constructor
/**
********************
* 设置数据类型。
********************
*/
public void setDataType(int paraDataType) {
dataType = paraDataType;
}// Of setDataType
/**
********************
* 用拉普拉斯平滑法计算类分布。
********************
*/
public void calculateClassDistribution() {
// 累的分布,一维数组,它的空间是numclasses,决策的种类
classDistribution = new double[numClasses];
// 拉普拉斯分布。
classDistributionLaplacian = new double[numClasses];
// 生成一个double 型数组,它的长度是数据的数目
double[] tempCounts = new double[numClasses];
// 这里给了一个循环,统计0,1决策的总数。
for (int i = 0; i < numInstances; i++) {
// tempClassValue 用来存储第 i 个决策属性
int tempClassValue = (int) dataset.instance(i).classValue();
tempCounts[tempClassValue]++;
} // Of for i
for (int i = 0; i < numClasses; i++) {
classDistribution[i] = tempCounts[i] / numInstances;
classDistributionLaplacian[i] = (tempCounts[i] + 1) / (numInstances + numClasses);
} // Of for i
System.out.println("Class distribution: " + Arrays.toString(classDistribution));
System.out.println(
"Class distribution Laplacian: " + Arrays.toString(classDistributionLaplacian));
}// Of calculateClassDistribution
/**
********************
* 用拉普拉斯计算条件概率。 只遍历一遍数据集就可以了
* 这里有一个更简单的, 我删除它是因为时间复杂度更高。
********************
*/
public void calculateConditionalProbabilities() {
conditionalCounts = new double[numClasses][numConditions][];
conditionalProbabilitiesLaplacian = new double[numClasses][numConditions][];
// 分配空间 a[决策数目][属性值]
for (int i = 0; i < numClasses; i++) {
for (int j = 0; j < numConditions; j++) {
int tempNumValues = (int) dataset.attribute(j).numValues();
conditionalCounts[i][j] = new double[tempNumValues];
conditionalProbabilitiesLaplacian[i][j] = new double[tempNumValues];
} // Of for j
} // Of for i
// 计算成员占决策的数目,比如yes 50 人,no 20人。
int[] tempClassCounts = new int[numClasses];
// 求每一个条件下yes,no的数目
for (int i = 0; i < numInstances; i++) {
int tempClass = (int) dataset.instance(i).classValue();
tempClassCounts[tempClass]++;
for (int j = 0; j < numConditions; j++) {
int tempValue = (int) dataset.instance(i).value(j);
conditionalCounts[tempClass][j][tempValue]++;
} // Of for j
} // Of for i
// 现在来看看拉普拉斯的真实概率
for (int i = 0; i < numClasses; i++) {
for (int j = 0; j < numConditions; j++) {
int tempNumValues = (int) dataset.attribute(j).numValues();
for (int k = 0; k < tempNumValues; k++) {
conditionalProbabilitiesLaplacian[i][j][k] = (conditionalCounts[i][j][k] + 1)
/ (tempClassCounts[i] + tempNumValues);
} // Of for k
} // Of for j
} // Of for i
System.out.println("Conditional probabilities: " + Arrays.deepToString(conditionalCounts));
}// Of calculateConditionalProbabilities
/**
********************
* Calculate the conditional probabilities with Laplacian smooth.
********************
*/
public void calculateGausssianParameters() {
// gaussianParameters [决策数目][决策属性]
gaussianParameters = new GaussianParamters[numClasses][numConditions];
// 这个数组,空间是数据的数目
double[] tempValuesArray = new double[numInstances];
int tempNumValues = 0;
double tempSum = 0;
// 外层是yes,no,内层是决策属性 天气温度等
for (int i = 0; i < numClasses; i++) {
for (int j = 0; j < numConditions; j++) {
tempSum = 0;
// 从这个类获取值
tempNumValues = 0;
for (int k = 0; k < numInstances; k++) {
// 1 代表 yes , 0 代表 no,第一次循环就是收集 no
if ((int) dataset.instance(k).classValue() != i) {
continue;
} // Of if
// tempValuesArray[tempValues] 索引 0 处存放 第 i 行 第 j 个属性值
tempValuesArray[tempNumValues] = dataset.instance(k).value(j);
// 再把这个属性值加起来乘以数组长度
tempSum += tempValuesArray[tempNumValues];
// 这个是索引,继续加
tempNumValues++;
} // Of for k
// Obtain parameters.
double tempMu = tempSum / tempNumValues;
double tempSigma = 0;
for (int k = 0; k < tempNumValues; k++) {
tempSigma += (tempValuesArray[k] - tempMu) * (tempValuesArray[k] - tempMu);
} // Of for k
tempSigma /= tempNumValues;
tempSigma = Math.sqrt(tempSigma);
gaussianParameters[i][j] = new GaussianParamters(tempMu, tempSigma);
} // Of for j
} // Of for i
System.out.println(Arrays.deepToString(gaussianParameters));
}// Of calculateGausssianParameters
/**
********************
* 对所有数据分类,结果储存在 predicts[].
********************
*/
public void classify() {
predicts = new int[numInstances];
for (int i = 0; i < numInstances; i++) {
predicts[i] = classify(dataset.instance(i));
} // Of for i
}// Of classify
/**
********************
* 对事例进行分类
********************
*/
public int classify(Instance paraInstance) {
if (dataType == NOMINAL) {
return classifyNominal(paraInstance);
} else if (dataType == NUMERICAL) {
return classifyNumerical(paraInstance);
} // Of if
return -1;
}// Of classify
/**
********************
* 使用符号数据对实例进行分类。
********************
*/
public int classifyNominal(Instance paraInstance) {
// 找到一个最大的
double tempBiggest = -10000;
int resultBestIndex = 0;
for (int i = 0; i < numClasses; i++) {
// 公式的技巧性处理
double tempClassProbabilityLaplacian = Math.log(classDistributionLaplacian[i]);
// 申明tempPseudoProbability它的值是tempClassProbabilityLaplacian
double tempPseudoProbability = tempClassProbabilityLaplacian;
for (int j = 0; j < numConditions; j++) {
int tempAttributeValue = (int) paraInstance.value(j);
// 拉普拉斯平滑,这里也有技巧处理,注意,每一项和整体
tempPseudoProbability += Math.log(conditionalCounts[i][j][tempAttributeValue])
- tempClassProbabilityLaplacian;
} // Of for j
if (tempBiggest < tempPseudoProbability) {
tempBiggest = tempPseudoProbability;
resultBestIndex = i;
} // Of if
} // Of for i
return resultBestIndex;
}// Of classifyNominal
/**
********************
* 使用数字数据对实例进行分类。
********************
*/
public int classifyNumerical(Instance paraInstance) {
// Find the biggest one
double tempBiggest = -10000;
int resultBestIndex = 0;
for (int i = 0; i < numClasses; i++) {
double tempClassProbabilityLaplacian = Math.log(classDistributionLaplacian[i]);
double tempPseudoProbability = tempClassProbabilityLaplacian;
for (int j = 0; j < numConditions; j++) {
double tempAttributeValue = paraInstance.value(j);
double tempSigma = gaussianParameters[i][j].sigma;
double tempMu = gaussianParameters[i][j].mu;
// 这里也要技巧处理
tempPseudoProbability += -Math.log(tempSigma) - (tempAttributeValue - tempMu)
* (tempAttributeValue - tempMu) / (2 * tempSigma * tempSigma);
} // Of for j
if (tempBiggest < tempPseudoProbability) {
tempBiggest = tempPseudoProbability;
resultBestIndex = i;
} // Of if
} // Of for i
return resultBestIndex;
}// Of classifyNumerical
/**
********************
* Compute accuracy.
********************
*/
public double computeAccuracy() {
double tempCorrect = 0;
for (int i = 0; i < numInstances; i++) {
if (predicts[i] == (int) dataset.instance(i).classValue()) {
tempCorrect++;
} // Of if
} // Of for i
double resultAccuracy = tempCorrect / numInstances;
return resultAccuracy;
}// Of computeAccuracy
/**
*************************
* Test nominal data.
*************************
*/
public static void testNominal() {
System.out.println("Hello, Naive Bayes. I only want to test the nominal data.");
String tempFilename = "D:/data/mushroom.arff";
NaiveBayes tempLearner = new NaiveBayes(tempFilename);
tempLearner.setDataType(NOMINAL);
tempLearner.calculateClassDistribution();
tempLearner.calculateConditionalProbabilities();
tempLearner.classify();
System.out.println("The accuracy is: " + tempLearner.computeAccuracy());
}// Of testNominal
/**
*************************
* Test numerical data.
*************************
*/
public static void testNumerical() {
System.out.println(
"Hello, Naive Bayes. I only want to test the numerical data with Gaussian assumption.");
// String tempFilename = "D:/data/iris.arff";
String tempFilename = "D:/data/iris-imbalance.arff";
NaiveBayes tempLearner = new NaiveBayes(tempFilename);
tempLearner.setDataType(NUMERICAL);
tempLearner.calculateClassDistribution();
tempLearner.calculateGausssianParameters();
tempLearner.classify();
System.out.println("The accuracy is: " + tempLearner.computeAccuracy());
}// Of testNominal
/**
*************************
* Test this class.
*
* @param args
* Not used now.
*************************
*/
public static void main(String[] args) {
testNominal();
testNumerical();
}// Of main
}// Of class NaiveBayes