原博文:minfanphd
任务计划
第51天:kNN 分类器
什么是kNN?
邻近算法,或者说K最近邻(KNN,K-NearestNeighbor)分类算法是数据挖掘分类技术中最简单的方法之一。所谓K最近邻,就是K个最近的邻居的意思,说的是每个样本都可以用它最接近的K个邻近值来代表。近邻算法就是将数据集合中每一个记录进行分类的方法。
什么是weka?weka - 百度百科
在weka.jar中,Instances是什么?
Instances对象实例化后,就存储了所有数据,同时也附带了很多数据属性,简单来说,你可以认为Instances就像一个电子表格,存储了你所加载的数据。
参考链接:使用Weka进行数据挖掘(Weka教程三)Weka数据之Instances和Instance
在weka.jar中,Instances.numInstances()有什么作用?
Instances. numInstances() – The number of instances with a class value
应该指的就是由类别的实例数量
在weka.jar中,Instances.numAttributes()有什么作用?
在手册里没看到,我认为应该是属性数量
在weka.jar中,Instances.classValue()有什么作用?
在手册里没看到,我认为应该是数据集中单行数据的序号
在weka.jar中,Instances.numClasses()有什么作用?
在手册里没看到,我认为应该是计算每一个类出现的次数
package machinelearning.knn;
import java.util.Arrays;
import java.io.FileReader;
import java.util.Random;
import weka.core.*;
/**
* @description: knn分类器
* @author: Qing Zhang
* @time: 2021/7/1
*/
public class KnnClassification {
//曼哈顿距离,|x|+|y|
public static final int MANHATTAN = 0;
//欧氏距离
public static final int EUCLIDEAN = 1;
//距离衡量方式
public int distanceMeasure = EUCLIDEAN;
//一个随机实例
public static final Random random = new Random();
//邻居数量
int numNeighbors = 7;
//存储整个数据集
Instances dataset;
//训练集。由数据索引表示
int[] trainingSet;
//测试集。由数据索引表示
int[] testingSet;
//预测结果
int[] predictions;
public KnnClassification(String paraFileName) {
try {
FileReader fileReader = new FileReader((paraFileName));
dataset = new Instances(fileReader);
//最后一个属性是类别
dataset.setClassIndex((dataset.numAttributes() - 1));
fileReader.close();
} catch (Exception e) {
System.out.println("Error occurred while trying to read \'" + paraFileName
+ "\' in KnnClassification constructor.\r\n" + e);
System.exit(0);
}
}
/**
* @Description: 获得一个随机的索引用于数据随机化
* @Param: [paraLength: 序列的长度]
* @return: int[] e.g., {4, 3, 1, 5, 0, 2} with length 6.
*/
public static int[] getRandomIndices(int paraLength) {
int[] resultIndices = new int[paraLength];
//第一步:初始化
for (int i = 0; i < paraLength; i++) {
resultIndices[i] = i;
}
//第二步:随机交换
int tempFirst, tempSecond, tempValue;
for (int i = 0; i < paraLength; i++) {
//产生两个随机的索引
tempFirst = random.nextInt(paraLength);
tempSecond = random.nextInt(paraLength);
//交换
tempValue = resultIndices[tempFirst];
resultIndices[tempFirst] = resultIndices[tempSecond];
resultIndices[tempSecond] = tempValue;
}
return resultIndices;
}
/**
* @Description: 将数据分割为训练集和测试集
* @Param: [paraTrainingFraction:训练集的占比]
* @return: void
*/
public void splitTrainingTesting(double paraTrainingFraction) {
//numInstances??获取数据集的样本数量。
int tempSize = dataset.numInstances();
int[] tempIndices = getRandomIndices(tempSize);
int tempTrainingSize = (int) (tempSize * paraTrainingFraction);
trainingSet = new int[tempTrainingSize];
testingSet = new int[tempSize - tempTrainingSize];
//将前tempTrainingSize的数据赋给测试集
for (int i = 0; i < tempTrainingSize; i++) {
trainingSet[i] = tempIndices[i];
}
//将剩余的数据赋给测试集
for (int i = 0; i < tempSize - tempTrainingSize; i++) {
testingSet[i] = tempIndices[tempTrainingSize + i];
}
}
/**
* @Description: 预测整个测试集。结果都存储再预测结果集中
* @Param: []
* @return: void
*/
public void predict() {
predictions = new int[testingSet.length];
for (int i = 0; i < predictions.length; i++) {
predictions[i] = predict(testingSet[i]);
}
}
/**
* @Description: 预测给定的实例
* @Param: [paraIndex]
* @return: int
*/
public int predict(int paraIndex) {
int[] tempNeighbors = computeNearests(paraIndex);
int resultPrediction = simpleVoting(tempNeighbors);
return resultPrediction;
}
/**
* @Description: 两个实例之间的距离
* @Param: [paraI, paraJ]
* @return: double
*/
public double distance(int paraI, int paraJ) {
int resultDistance = 0;
double tempDifference;
switch (distanceMeasure) {
case MANHATTAN:
//numAttributes??
for (int i = 0; i < dataset.numAttributes() - 1; i++) {
tempDifference = dataset.instance(paraI).value(i) - dataset.instance(paraJ).value(i);
if (tempDifference < 0) {
resultDistance -= tempDifference;
} else {
resultDistance += tempDifference;
}
}
break;
case EUCLIDEAN:
for (int i = 0; i < dataset.numAttributes() - 1; i++) {
tempDifference = dataset.instance(paraI).value(i) - dataset.instance(paraJ).value(i);
//这里为什么直接是平方??应该是为了效率,将开根省略了
resultDistance += tempDifference * tempDifference;
}
break;
default:
System.out.println("Unsupported distance measure: " + distanceMeasure);
}
return resultDistance;
}
/**
* @Description: 获得分类器的正确率
* @Param: []
* @return: double
*/
public double getAccuracy() {
//一个double除以一个int会得到另一个double
double tempCorrect = 0;
for (int i = 0; i < predictions.length; i++) {
//classValue??就是数据集中的序号
if (predictions[i] == dataset.instance(testingSet[i]).classValue()) {
tempCorrect++;
}
}
return tempCorrect / testingSet.length;
}
/**
* @Description: 计算最近的k个邻居。在每一轮扫描中选择一个邻居
* @Param: [paraIndex]
* @return: int[]
*/
public int[] computeNearests(int paraCurrent) {
int[] resultNearests = new int[numNeighbors];
boolean[] tempSelected = new boolean[trainingSet.length];
double tempDistance;
double tempMinimalDistance;
int tempMinimalIndex = 0;
//选择最近的k个索引
for (int i = 0; i < numNeighbors; i++) {
tempMinimalDistance = Double.MAX_VALUE;
for (int j = 0; j < trainingSet.length; j++) {
if (tempSelected[j]) {
continue;
}
tempDistance = distance(paraCurrent, trainingSet[j]);
if (tempDistance < tempMinimalDistance) {
tempMinimalDistance = tempDistance;
tempMinimalIndex = j;
}
}
resultNearests[i] = trainingSet[tempMinimalIndex];
tempSelected[tempMinimalIndex] = true;
}
System.out.println("The nearest of " + paraCurrent + " are: " + Arrays.toString(resultNearests));
return resultNearests;
}
/**
* @Description: 使用实例投票
* @Param: [tempNeighbors]
* @return: int
*/
public int simpleVoting(int[] paraNeighbors) {
//numClasses?? 计算每一个类出现的次数
int[] tempVotes = new int[dataset.numClasses()];
for (int i = 0; i < paraNeighbors.length; i++) {
tempVotes[(int) dataset.instance(paraNeighbors[i]).classValue()]++;
}
int tempMaximalVotingIndex = 0;
int tempMaximalVoting = 0;
for (int i = 0; i < dataset.numClasses(); i++) {
if (tempVotes[i] > tempMaximalVoting) {
tempMaximalVoting = tempVotes[i];
tempMaximalVotingIndex = i;
}
}
return tempMaximalVotingIndex;
}
public static void main(String[] args) {
KnnClassification tempClassifier = new KnnClassification("F:\\研究生\\研0\\学习\\Java_Study\\data_set\\iris.arff");
tempClassifier.splitTrainingTesting(0.8);
tempClassifier.predict();
System.out.println("The accuracy of the classifier is: " + tempClassifier.getAccuracy());
}
}
第52天:kNN 分类器 (续)
重新实现 computeNearests, 仅需要扫描一遍训练集, 即可获得 k k k 个邻居. 提示: 现代码与插入排序思想相结合.
/**
* @Description: 计算最近的k个邻居。在每一轮扫描中选择一个邻居
* @Param: [paraIndex]
* @return: int[]
*/
public int[] computeNearests(int paraCurrent) {
int[] resultNearests = new int[numNeighbors];
boolean[] tempSelected = new boolean[trainingSet.length];
double tempDistance;
double tempMinimalDistance;
int tempMinimalIndex = 0;
/*//选择最近的k个索引
for (int i = 0; i < numNeighbors; i++) {
tempMinimalDistance = Double.MAX_VALUE;
for (int j = 0; j < trainingSet.length; j++) {
if (tempSelected[j]) {
continue;
}
tempDistance = distance(paraCurrent, trainingSet[j]);
if (tempDistance < tempMinimalDistance) {
tempMinimalDistance = tempDistance;
tempMinimalIndex = j;
}
}
resultNearests[i] = trainingSet[tempMinimalIndex];
tempSelected[tempMinimalIndex] = true;
}*/
//使用直接插入排序
//创建一个临时二维数组去存储距离
double[][] tempDistanceArray = new double[trainingSet.length][2];
tempDistanceArray[0][0] = 0;
tempDistanceArray[0][1] = distance(paraCurrent, trainingSet[0]);
int j;
for (int i = 1; i < trainingSet.length; i++) {
tempDistance = distance(paraCurrent, trainingSet[i]);
for (j = i - 1; j >= 0; j--) {
if (tempDistance < tempDistanceArray[j][1]) {
tempDistanceArray[j + 1] = tempDistanceArray[j];
} else {
break;
}
}
tempDistanceArray[j + 1][0] = i;
tempDistanceArray[j + 1][1] = tempDistance;
}
for (int i = 0; i < numNeighbors; i++) {
resultNearests[i] = trainingSet[(int)tempDistanceArray[i][0]];
}
System.out.println("The nearest of " + paraCurrent + " are: " + Arrays.toString(resultNearests));
return resultNearests;
}
增加 setDistanceMeasure() 方法.
/**
* @Description: 选择距离计算方式
* @Param: [paraType:0 or 1]
* @return: void
*/
public void setDistanceMeasure(int paraType) {
if (paraType == 0) {
distanceMeasure = MANHATTAN;
} else if (paraType == 1) {
distanceMeasure = EUCLIDEAN;
} else {
System.out.println("Wrong Distance Measure!!!");
}
}
public static void main(String[] args) {
KnnClassification tempClassifier = new KnnClassification("F:\\研究生\\研0\\学习\\Java_Study\\data_set\\iris.arff");
tempClassifier.setDistanceMeasure(1);
tempClassifier.splitTrainingTesting(0.8);
tempClassifier.predict();
System.out.println("The accuracy of the classifier is: " + tempClassifier.getAccuracy());
}
增加 setNumNeighors() 方法.
/**
* @Description: 设置邻居数量
* @Param: [paraNumNeighbors]
* @return: void
*/
public void setNumNeighbors(int paraNumNeighbors) {
if (paraNumNeighbors > dataset.numInstances()) {
System.out.println("The number of neighbors is bigger than the number of dataset!!!");
return;
}
numNeighbors = paraNumNeighbors;
}
public static void main(String[] args) {
KnnClassification tempClassifier = new KnnClassification("F:\\研究生\\研0\\学习\\Java_Study\\data_set\\iris.arff");
tempClassifier.setDistanceMeasure(1);
tempClassifier.setNumNeighbors(8);
tempClassifier.splitTrainingTesting(0.8);
tempClassifier.predict();
System.out.println("The accuracy of the classifier is: " + tempClassifier.getAccuracy());
}
第53天:kNN 分类器 (续)
增加 weightedVoting() 方法, 距离越短话语权越大. 支持两种以上的加权方式。
第一尝试时,直接将距离作为判断,没有特意去设置权重的计算方式,因此造成准确率直线下降,达到了0.3😂
第二次尝试用用距离代入到反函数中去计算权重,结果还行,但是需要调整参数才能够达到理想的效果。
尝试调参,a越大,b越小,结果越好。
/**
* @Description: 距离权重投票
* 距离越短,话语权越大,直接设置个权值根据顺序来增强其关联性
* @Param: [paraCurrent, paraNeighbors]
* @return: int
*/
public int weightedVoting(int paraCurrent, int[] paraNeighbors) {
//numClasses?? 计算每一个类型出现的次数
double[] tempVotes = new double[dataset.numClasses()];
double tempDistance;
int a = 1, b = 1;
//这样距离越短则值就越大
for (int i = 0; i < paraNeighbors.length; i++) {
tempDistance = distance(paraCurrent, paraNeighbors[i]);
tempVotes[(int) dataset.instance(paraNeighbors[i]).classValue()]
+= getWeightedNum(a, b, tempDistance);
}
int tempMaximalVotingIndex = 0;
double tempMaximalVoting = 0;
for (int i = 0; i < dataset.numClasses(); i++) {
if (tempVotes[i] > tempMaximalVoting) {
tempMaximalVoting = tempVotes[i];
tempMaximalVotingIndex = i;
}
}
return tempMaximalVotingIndex;
}
/**
* @Description: 获取权重,利用反函数
* @Param: [a, b, paraDistance]
* @return: double
*/
public double getWeightedNum(int a, int b, double paraDistance) {
return b / (paraDistance + a);
}
实现 leave-one-out 测试.
/**
* @Description: 留一交叉验证
* 留一法交叉验证是一种用来训练和测试分类器的方法,会用到图像数据集里所有的数据,假定数据集有N个样本(N1、N2、...Nn),
* 将这个样本分为两份,第一份N-1个样本用来训练分类器,另一份1个样本用来测试,
* 如此从N1到Nn迭代N次,所有的样本里所有对象都经历了测试和训练。
* @Param: []
* @return: void
*/
public void leave_one_out() {
int tempSize = dataset.numInstances();
int[] tempIndices = getRandomIndices(tempSize);
double tempCorrect = 0;
for (int i = 0; i < tempSize; i++) {
trainingSet = new int[tempSize - 1];
testingSet = new int[1];
int tempIndex = 0;
for (int j = 0; j < tempSize; j++) {
if (j == i) {
continue;
}
trainingSet[tempIndex++] = tempIndices[j];
}
testingSet[0] = tempIndices[i];
this.predict();
if (predictions[0] == dataset.instance(testingSet[0]).classValue()) {
tempCorrect++;
}
}
System.out.println("正确率为:" + tempCorrect / tempSize);
}
public static void main(String[] args) {
KnnClassification tempClassifier = new KnnClassification("F:\\研究生\\研0\\学习\\Java_Study\\data_set\\iris.arff");
tempClassifier.setDistanceMeasure(1);
tempClassifier.setNumNeighbors(8);
tempClassifier.splitTrainingTesting(0.8);
tempClassifier.predict();
System.out.println("The accuracy of the classifier is: " + tempClassifier.getAccuracy());
System.out.println("\r\n-------leave_one_out-------");
tempClassifier.leave_one_out();
}
第54天:基于 M-distance 的推荐
package machinelearning.knn;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
/**
* @description:基于 M-distance 的推荐
* @author: Qing Zhang
* @time: 2021/7/3
*/
public class MBR {
//默认评分(1-5星)
public static final double DEFAULT_RATING = 3.0;
//用户总数
private int numUsers;
//个例总数(电影)
private int numItems;
//评分数量
private int numRatings;
//预测结果
private double[] predictions;
//压缩评分矩阵,User-item-rating 三元组
//item/user:m_1,m_2
//0 : 0 2
//1 : 3 4
private int[][] compressedRatingMatrix;
//用户的评价数量(他评价了多少项)
private int[] userDegrees;
//当前用户的平均评分
private double[] userAverageRatings;
//用户评分的数量
private int[] itemDegrees;
//当前项(例如电影)的平均评分
private double[] itemAverageRatings;
//第一个用户从0开始。若第一个用户有x个评分,第二个将从x开始
private int[] userStartingIndices;
//无邻居项的数量
private int numNonNeighbors;
//决定邻居的半径
private double radius;
/**
* @Description: 构造评分矩阵
* @Param: [paraFilename, paraNumUsers, paraNumItems, paraNumRatings]
* @return:
*/
public MBR(String paraFilename, int paraNumUsers, int paraNumItems, int paraNumRatings) throws Exception {
// 初始化数组
numItems = paraNumItems;
numUsers = paraNumUsers;
numRatings = paraNumRatings;
userDegrees = new int[numUsers];
userStartingIndices = new int[numUsers + 1];
userAverageRatings = new double[numUsers];
itemDegrees = new int[numItems];
compressedRatingMatrix = new int[numRatings][3];
itemAverageRatings = new double[numItems];
predictions = new double[numRatings];
System.out.println("Reading " + paraFilename);
// 读入数据文件
File tempFile = new File(paraFilename);
if (!tempFile.exists()) {
System.out.println("File " + paraFilename + " does not exists.");
System.exit(0);
}
BufferedReader tempBufReader = new BufferedReader(new FileReader(tempFile));
String tempString;
String[] tempStrArray;
int tempIndex = 0;
userStartingIndices[0] = 0;
//这里是末尾,直接设置为总数即可
userStartingIndices[numUsers] = numRatings;
while ((tempString = tempBufReader.readLine()) != null) {
// 每行有三个值
tempStrArray = tempString.split(",");
compressedRatingMatrix[tempIndex][0] = Integer.parseInt(tempStrArray[0]);
compressedRatingMatrix[tempIndex][1] = Integer.parseInt(tempStrArray[1]);
compressedRatingMatrix[tempIndex][2] = Integer.parseInt(tempStrArray[2]);
userDegrees[compressedRatingMatrix[tempIndex][0]]++;
itemDegrees[compressedRatingMatrix[tempIndex][1]]++;
if (tempIndex > 0) {
// 开始读入用户的数据
if (compressedRatingMatrix[tempIndex][0] != compressedRatingMatrix[tempIndex - 1][0]) {
userStartingIndices[compressedRatingMatrix[tempIndex][0]] = tempIndex;
}
}
tempIndex++;
}
tempBufReader.close();
//计算每个用户总评分
double[] tempUserTotalScore = new double[numUsers];
//计算每项的总评分
double[] tempItemTotalScore = new double[numItems];
for (int i = 0; i < numRatings; i++) {
tempUserTotalScore[compressedRatingMatrix[i][0]] += compressedRatingMatrix[i][2];
tempItemTotalScore[compressedRatingMatrix[i][1]] += compressedRatingMatrix[i][2];
}
//分别计算每个用户的平均评分和每项受到的平均评分
for (int i = 0; i < numUsers; i++) {
userAverageRatings[i] = tempUserTotalScore[i] / userDegrees[i];
}
for (int i = 0; i < numItems; i++) {
itemAverageRatings[i] = tempItemTotalScore[i] / itemDegrees[i];
}
}
/**
* @Description: 设置半径(delta)
* @Param: [paraRadius]
* @return: void
*/
public void setRadius(double paraRadius) {
if (paraRadius > 0) {
radius = paraRadius;
} else {
radius = 0.1;
}
}
/**
* @Description: 留一交叉验证
* @Param: []
* @return: void
*/
public void leaveOneOutPrediction() {
double tempItemAverageRating;
// Make each line of the code shorter.
int tempUser, tempItem, tempRating;
System.out.println("\r\nLeaveOneOutPrediction for radius " + radius);
numNonNeighbors = 0;
for (int i = 0; i < numRatings; i++) {
tempUser = compressedRatingMatrix[i][0];
tempItem = compressedRatingMatrix[i][1];
tempRating = compressedRatingMatrix[i][2];
// 重新计算当前项的平均分(把当前项的评分去除后的)
tempItemAverageRating = (itemAverageRatings[tempItem] * itemDegrees[tempItem] - tempRating)
/ (itemDegrees[tempItem] - 1);
// 重新计算邻居,同时获得邻居的评分
int tempNeighbors = 0;
double tempTotal = 0;
int tempComparedItem;
//根据该用户的评分去预测
for (int j = userStartingIndices[tempUser]; j < userStartingIndices[tempUser + 1]; j++) {
tempComparedItem = compressedRatingMatrix[j][1];
if (tempItem == tempComparedItem) {
continue;
}
if (Math.abs(tempItemAverageRating - itemAverageRatings[tempComparedItem]) < radius) {
tempTotal += compressedRatingMatrix[j][2];
tempNeighbors++;
}
}
// 根据邻居的平均值预测
if (tempNeighbors > 0) {
predictions[i] = tempTotal / tempNeighbors;
} else {
predictions[i] = DEFAULT_RATING;
numNonNeighbors++;
}
}
}
/**
* @Description: 根据每次留意交叉法计算平均绝对误差
* MAE,平均绝对误差(Mean Absolute Error),观测值与真实值的误差绝对值的平均值
* @Param: []
* @return: double
*/
public double computeMAE() throws Exception {
double tempTotalError = 0;
for (int i = 0; i < predictions.length; i++) {
tempTotalError += Math.abs(predictions[i] - compressedRatingMatrix[i][2]);
}
return tempTotalError / predictions.length;
}
/**
* @Description: 根据结果计算均方根误差
* RMSE:均方根误差(Root-mean-square error), 观测值与真值偏差的平方和与观测次数m比值的平方根
* @Param: []
* @return: double
*/
public double computeRSME() throws Exception {
double tempTotalError = 0;
for (int i = 0; i < predictions.length; i++) {
tempTotalError += (predictions[i] - compressedRatingMatrix[i][2])
* (predictions[i] - compressedRatingMatrix[i][2]);
}
double tempAverage = tempTotalError / predictions.length;
return Math.sqrt(tempAverage);
}
public static void main(String[] args) {
try {
MBR tempRecommender = new MBR("F:\\研究生\\研0\\学习\\Java_Study\\data_set\\movielens943u1682m.txt", 943, 1682, 100000);
for (double tempRadius = 0.2; tempRadius < 0.6; tempRadius += 0.1) {
tempRecommender.setRadius(tempRadius);
tempRecommender.leaveOneOutPrediction();
double tempMAE = tempRecommender.computeMAE();
double tempRSME = tempRecommender.computeRSME();
System.out.println("Radius = " + tempRadius + ", MAE = " + tempMAE + ", RSME = " + tempRSME
+ ", numNonNeighbors = " + tempRecommender.numNonNeighbors);
}
} catch (Exception ee) {
System.out.println(ee);
}
}
}
第55天:基于 M-distance 的推荐 (续)
昨天实现的是 item-based recommendation. 今天自己来实现一下 user-based recommendation. 只需要在原有基础上增加即可.
感觉上还有点问题,但由于没有正确答案,因此只有先按照自己的想法来实现。
/**
* @Description: 留一交叉验证
* @Param: []
* @return: void
*/
public void leaveOneOutPredictionBasedOnUsers() {
double tempUserAverageRating;
// Make each line of the code shorter.
int tempUser, tempItem, tempRating;
System.out.println("\r\nleaveOneOutPredictionBasedOnUsers for radius " + radius);
numNonNeighbors = 0;
for (int i = 0; i < numRatings; i++) {
tempUser = compressedRatingMatrix[i][0];
tempItem = compressedRatingMatrix[i][1];
tempRating = compressedRatingMatrix[i][2];
// 重新计算当前项的平均分(把当前项的评分去除后的)
tempUserAverageRating = (userAverageRatings[tempUser] * userDegrees[tempUser] - tempRating)
/ (userDegrees[tempUser] - 1);
// 重新计算邻居,同时获得邻居的评分
int tempNeighbors = 0;
double tempTotal = 0;
//根据该用户的评分去预测
for (int j = 0; j < numUsers; j++) {
if (tempUser == j) {
continue;
}
if (Math.abs(tempUserAverageRating - userAverageRatings[j]) < radius) {
tempTotal += userAverageRatings[j];
tempNeighbors++;
}
}
// 根据邻居的平均值预测
if (tempNeighbors > 0) {
predictions[i] = tempTotal / tempNeighbors;
} else {
predictions[i] = DEFAULT_RATING;
numNonNeighbors++;
}
}
}
public static void main(String[] args) {
try {
MBR tempRecommender = new MBR("F:\\研究生\\研0\\学习\\Java_Study\\data_set\\movielens943u1682m.txt", 943, 1682, 100000);
System.out.println("\r\n-------leave_one_out-------");
for (double tempRadius = 0.2; tempRadius < 0.6; tempRadius += 0.1) {
tempRecommender.setRadius(tempRadius);
tempRecommender.leaveOneOutPrediction();
double tempMAE = tempRecommender.computeMAE();
double tempRSME = tempRecommender.computeRSME();
System.out.println("Radius = " + tempRadius + ", MAE = " + tempMAE + ", RSME = " + tempRSME
+ ", numNonNeighbors = " + tempRecommender.numNonNeighbors);
}
System.out.println("\r\n-------leave_one_out_BasedOnUsers-------");
for (double tempRadius = 0.2; tempRadius < 0.6; tempRadius += 0.1) {
tempRecommender.setRadius(tempRadius);
tempRecommender.leaveOneOutPredictionBasedOnUsers();
double tempMAE = tempRecommender.computeMAE();
double tempRSME = tempRecommender.computeRSME();
System.out.println("Radius = " + tempRadius + ", MAE = " + tempMAE + ", RSME = " + tempRSME
+ ", numNonNeighbors = " + tempRecommender.numNonNeighbors);
}
} catch (Exception ee) {
System.out.println(ee);
}
}
第56天:kMeans 聚类
k均值聚类算法(k-means clustering algorithm)是一种迭代求解的聚类分析算法,其步骤是,预将数据分为K组,则随机选取K个对象作为初始的聚类中心,然后计算每个对象与各个种子聚类中心之间的距离,把每个对象分配给距离它最近的聚类中心。聚类中心以及分配给它们的对象就代表一个聚类。每分配一个样本,聚类的聚类中心会根据聚类中现有的对象被重新计算。这个过程将不断重复直到满足某个终止条件。终止条件可以是没有(或最小数目)对象被重新分配给不同的聚类,没有(或最小数目)聚类中心再发生变化,误差平方和局部最小。
getRandomIndices() 和 kMeans 的完全相同, 拷贝过来. 本来应该写在 SimpleTools.java 里面的, 代码不多, 为保证独立性就放这里了.
distance() 和 kMeans 的相似, 注意不要用决策属性, 而且参数不同. 第 2 个参数为实数向量, 这是类为中心可能为虚拟的, 而中心点那里并没有对象.
package machinelearning.knn;
import java.util.Random;
import java.util.Arrays;
import java.io.FileReader;
import java.util.Random;
import weka.core.*;
/**
* @description:kMeans聚类
* @author: Qing Zhang
* @time: 2021/7/5
*/
public class KMeans {
//曼哈顿距离,|x|+|y|
public static final int MANHATTAN = 0;
//欧氏距离
public static final int EUCLIDEAN = 1;
//距离衡量方式
public int distanceMeasure = EUCLIDEAN;
//一个随机实例
public static final Random random = new Random();
//存储整个数据集
Instances dataset;
//聚类个数
int numClusters = 2;
//聚类
int[][] clusters;
/**
* @Description: 构造函数
* @Param: [paraFilename]
* @return:
*/
public KMeans(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);
}
}
/**
* @Description: 设置聚类数量
* @Param: [paraNumClusters]
* @return: void
*/
public void setNumClusters(int paraNumClusters) {
numClusters = paraNumClusters;
}
/**
* @Description: 获得一个随机的索引用于数据随机化
* @Param: [paraLength: 序列的长度]
* @return: int[] e.g., {4, 3, 1, 5, 0, 2} with length 6.
*/
public static int[] getRandomIndices(int paraLength) {
int[] resultIndices = new int[paraLength];
//第一步:初始化
for (int i = 0; i < paraLength; i++) {
resultIndices[i] = i;
}
//第二步:随机交换
int tempFirst, tempSecond, tempValue;
for (int i = 0; i < paraLength; i++) {
//产生两个随机的索引
tempFirst = random.nextInt(paraLength);
tempSecond = random.nextInt(paraLength);
//交换
tempValue = resultIndices[tempFirst];
resultIndices[tempFirst] = resultIndices[tempSecond];
resultIndices[tempSecond] = tempValue;
}
return resultIndices;
}
/**
* @Description: 两个实例的距离
* @Param: [paraI, paraArray:表示空间中的一个点]
* @return: double
*/
public double distance(int paraI, double[] paraArray) {
int resultDistance = 0;
double tempDifference;
switch (distanceMeasure) {
case MANHATTAN:
//numAttributes??属性数量
for (int i = 0; i < dataset.numAttributes() - 1; i++) {
tempDifference = dataset.instance(paraI).value(i) - paraArray[i];
if (tempDifference < 0) {
resultDistance -= tempDifference;
} else {
resultDistance += tempDifference;
}
}
break;
case EUCLIDEAN:
for (int i = 0; i < dataset.numAttributes() - 1; i++) {
tempDifference = dataset.instance(paraI).value(i) - paraArray[i];
//这里为什么直接是平方??应该是为了效率,将开根省略了
resultDistance += tempDifference * tempDifference;
}
break;
default:
System.out.println("Unsupported distance measure: " + distanceMeasure);
}
return resultDistance;
}
public void clustering() {
int[] tempOldClusterArray = new int[dataset.numInstances()];
tempOldClusterArray[0] = -1;
int[] tempClusterArray = new int[dataset.numInstances()];
Arrays.fill(tempClusterArray, 0);
double[][] tempCenters = new double[numClusters][dataset.numAttributes() - 1];
// 初始化,设置中心
int[] tempRandomOrders = getRandomIndices(dataset.numInstances());
for (int i = 0; i < numClusters; i++) {
for (int j = 0; j < tempCenters[0].length; j++) {
tempCenters[i][j] = dataset.instance(tempRandomOrders[i]).value(j);
}
}
int[] tempClusterLengths = null;
//当当前聚类结果与上次相同时,说明已经拟合了
while (!Arrays.equals(tempOldClusterArray, tempClusterArray)) {
System.out.println("New loop ...");
tempOldClusterArray = tempClusterArray;
tempClusterArray = new int[dataset.numInstances()];
// 2.1 让每一个实例都被聚类
int tempNearestCenter;
double tempNearestDistance;
double tempDistance;
for (int i = 0; i < dataset.numInstances(); i++) {
tempNearestCenter = -1;
tempNearestDistance = Double.MAX_VALUE;
//与每个中心比较,取最近的作为聚类结果
for (int j = 0; j < numClusters; j++) {
tempDistance = distance(i, tempCenters[j]);
if (tempNearestDistance > tempDistance) {
tempNearestDistance = tempDistance;
tempNearestCenter = j;
}
}
tempClusterArray[i] = tempNearestCenter;
}
// 2.2 取每类的平均值,重新获得中心点
//每种类别的数量
tempClusterLengths = new int[numClusters];
Arrays.fill(tempClusterLengths, 0);
double[][] tempNewCenters = new double[numClusters][dataset.numAttributes() - 1];
// 将每个类别中每个实例的单个属性值加在一起
for (int i = 0; i < dataset.numInstances(); i++) {
for (int j = 0; j < tempNewCenters[0].length; j++) {
tempNewCenters[tempClusterArray[i]][j] += dataset.instance(i).value(j);
}
tempClusterLengths[tempClusterArray[i]]++;
}
// 2.3 计算平均中心
for (int i = 0; i < tempNewCenters.length; i++) {
for (int j = 0; j < tempNewCenters[0].length; j++) {
tempNewCenters[i][j] /= tempClusterLengths[i];
}
}
System.out.println("Now the new centers are: " + Arrays.deepToString(tempNewCenters));
tempCenters = tempNewCenters;
}
// 3. 形成最终的聚类
clusters = new int[numClusters][];
int[] tempCounters = new int[numClusters];
for (int i = 0; i < numClusters; i++) {
clusters[i] = new int[tempClusterLengths[i]];
}
for (int i = 0; i < tempClusterArray.length; i++) {
clusters[tempClusterArray[i]][tempCounters[tempClusterArray[i]]] = i;
tempCounters[tempClusterArray[i]]++;
}
System.out.println("The clusters are: " + Arrays.deepToString(clusters));
}
/**
* @Description: 聚类测试
* @Param: []
* @return: void
*/
public static void testClustering() {
KMeans tempKMeans = new KMeans("F:\\研究生\\研0\\学习\\Java_Study\\data_set\\iris.arff");
tempKMeans.setNumClusters(3);
tempKMeans.clustering();
}
public static void main(String arags[]) {
testClustering();
}
}
第57天:kMeans 聚类 (续)
获得虚拟中心后, 换成与其最近的点作为实际中心, 再聚类.
今天主要是想控制下节奏. 毕竟 kMeans 也值得两天的工作量.
我的思想:就是根据当前已经分好类的实例去与该类当前的平均中心比较距离,通过对所有实例遍历找到与各自所属类的平均中心最近的实际点作为类中心。
//当前临时实际中心点与平均中心点的距离
double[] tempNearestDistanceArray = new double[numClusters];
//当前距离平均中心最近的实际点
double[][] tempActualCenters = new double[numClusters][dataset.numAttributes() - 1];
Arrays.fill(tempNearestDistanceArray, Double.MAX_VALUE);
for (int i = 0; i < dataset.numInstances(); i++) {
//用当前数据去与其分类的中心比较距离
if (tempNearestDistanceArray[tempClusterArray[i]] > distance(i, tempCenters[tempClusterArray[i]])) {
tempNearestDistanceArray[tempClusterArray[i]] = distance(i, tempCenters[tempClusterArray[i]]);
//暂时存储当前距离平均中心最近的实际点
for (int j = 0; j < dataset.numAttributes() - 1; j++) {
tempActualCenters[tempClusterArray[i]][j] = dataset.instance((i)).value(j);
}
}
}
for (int i = 0; i < tempNewCenters.length; i++) {
tempNewCenters[i] = tempActualCenters[i];
}
第58天:符号型数据的 NB 算法
参考博文:
贝叶斯分类是一类分类算法的总称,这类算法均以贝叶斯定理为基础,故统称为贝叶斯分类
贝叶斯定理: P ( B ∣ A ) = P ( A ∣ B ) P ( B ) P ( A ) P(B|A) = \frac{P(A|B)P(B)}{P(A)} P(B∣A)=P(A)P(A∣B)P(B)
P ( A ∣ B ) P(A|B) P(A∣B)表示在B事件已经发生的情况下A事件发生的概率,称为条件概率, P ( A ∣ B ) = P ( A B ) P ( B ) P(A|B)=\frac{P(AB)}{P(B)} P(A∣B)=P(B)P(AB)。
朴素贝叶斯思想:对于给出的待分类项,求出在此项条件(特征)下各个类别出现的概率,那个最大就认为其属于哪个类别。就比如你在大街上看到黑人,让你去才他来自哪,那么很大概率会猜他来自非洲。为什么呢?因为黑人中非洲人的比率最高,但他也可能来自其它地区,不过概率较小,因此我们会下意识的选择概率最高的那个选择。这就是朴素贝叶斯的思想基础。
朴素贝叶斯分类正式定义如下:
- 设 x = { a 1 , a 2 , … , a m } x=\{a_1,a_2,\dots,a_m\} x={a1,a2,…,am}为一个待分类项(样本),而每个 a i a_i ai为 x x x的一个特征属性。
- 类别集合 C = { y 1 , y 2 , ⋯ , y n } C=\{y_1,y_2,\cdots,y_n\} C={y1,y2,⋯,yn}。
- 计算 P ( y 1 ∣ x ) , P ( y 2 ∣ x ) , ⋯ , P ( y n ∣ x ) P(y_1|x),P(y_2|x),\cdots,P(y_n|x) P(y1∣x),P(y2∣x),⋯,P(yn∣x)。
- 如果 P ( y k ∣ x ) = m a x { P ( y 1 ∣ x ) , P ( y 2 ∣ x ) , ⋯ , P ( y n ∣ x ) } P(y_k|x)=max\{P(y_1|x),P(y_2|x),\cdots,P(y_n|x)\} P(yk∣x)=max{P(y1∣x),P(y2∣x),⋯,P(yn∣x)},则 x ∈ y k x\in y_k x∈yk。
因此关键是第三步的计算。
根据贝叶斯定理来进行求解,步骤如下:
- 将已经分类的样本集合整理出来用作训练。
- 统计在各类别下各个特征属性的条件概率。即:
P ( a 1 ∣ y 1 ) , P ( a 2 ∣ y 1 ) , ⋯ , P ( a m ∣ y 1 ) ; P ( a 1 ∣ y 2 ) , P ( a 2 ∣ y 2 ) , ⋯ , P ( a m ∣ y 2 ) ⋯ P ( a 1 ∣ y n ) , P ( a 2 ∣ y n ) , ⋯ , P ( a m ∣ y n ) P(a_1|y_1),P(a_2|y_1),\cdots,P(a_m|y_1);P(a_1|y_2),P(a_2|y_2),\cdots,P(a_m|y_2)\cdots P(a_1|y_n),P(a_2|y_n),\cdots,P(a_m|y_n) P(a1∣y1),P(a2∣y1),⋯,P(am∣y1);P(a1∣y2),P(a2∣y2),⋯,P(am∣y2)⋯P(a1∣yn),P(a2∣yn),⋯,P(am∣yn)- 如果各个特征属性是条件独立的,则根据贝叶斯定理有如下推导:
P ( y i ∣ x ) = P ( x ∣ y i ) P ( y i ) P ( x ) P(y_i|x)=\frac{P(x|y_i)P(y_i)}{P(x)} P(yi∣x)=P(x)P(x∣yi)P(yi);
因为分母对于所有类别来说为常数,因此只要将分子最大化即可,又各特征属性是条件独立的,所以有:
P ( x ∣ y i ) P ( y i ) = P ( a 1 ∣ y i ) P ( a 2 ∣ y i ) ⋯ P ( a m ∣ y i ) P ( y i ) = P ( y i ) ∏ j = 1 m P ( a j ∣ y i ) P(x|y_i)P(y_i)=P(a_1|y_i)P(a_2|y_i)\cdots P(a_m|y_i)P(y_i)=P(y_i)\prod_{j=1}^m P(a_j|y_i) P(x∣yi)P(yi)=P(a1∣yi)P(a2∣yi)⋯P(am∣yi)P(yi)=P(yi)∏j=1mP(aj∣yi)
从上面的步骤可以看出 P ( a k ∣ y i ) P(a_k|y_i) P(ak∣yi)的求解是很重要的。
当特征属性为连续值时,通常假定其值服从高斯分布(正态分布)。即:
g ( x , μ , σ ) = 1 2 π σ e − ( x − μ ) 2 2 σ 2 g(x,\mu,\sigma) = \frac{1}{\sqrt{2\pi} \sigma}e^{-\frac{(x-\mu)^2}{2\sigma^2}} g(x,μ,σ)=2πσ1e−2σ2(x−μ)2;
P ( a k ∣ y i ) = g ( a k , μ y i , σ y i ) P(a_k|y_i)=g(a_k,\mu_{y_i, \sigma_{y_i}}) P(ak∣yi)=g(ak,μyi,σyi);
因此只要计算出训练样本中各个类别中此特征项划分的各均值和标准差,代入上述公式即可得到需要的估计值。
但是这里需要考虑一个问题,什么问题呢?当 P ( a k ∣ y i ) = 0 P(a_k|y_i)=0 P(ak∣yi)=0 时会严重影响分类器的准确率,因此这里引入了Laplace校准,就是对没类别下所有划分的计数加一,这样如果训练样本集数量充分时,不会对结果产生影响,并解决了上述频率为0的尴尬局面。
Exp:
帅? | 性格好? | 上进? | 嫁与否? |
---|---|---|---|
帅 | 不好 | 不上进 | 不嫁 |
帅 | 好 | 上进 | 嫁 |
不帅 | 不好 | 不上进 | 不嫁 |
不帅 | 好 | 上进 | 嫁 |
帅 | 不好 | 上进 | 不嫁 |
帅 | 好 | 不上进 | 嫁 |
不帅 | 不好 | 上进 | 嫁 |
不帅 | 好 | 不上进 | 不嫁 |
三个特征集合分别为:长相(帅、不帅)、性格(爆好、好、不好)、上进与否(上进、不上进)。
类别集合:嫁与否(嫁、不嫁)。
现在假设去求一个男生{帅、爆好、上进}的情况下,嫁与不嫁的概率谁大。
也就是要比较
P
(
嫁
∣
长
相
帅
,
性
格
爆
好
,
上
进
)
与
P
(
不
嫁
∣
长
相
帅
,
性
格
爆
好
,
上
进
)
P(嫁|长相帅,性格爆好,上进)与P(不嫁|长相帅,性格爆好,上进)
P(嫁∣长相帅,性格爆好,上进)与P(不嫁∣长相帅,性格爆好,上进)的概率大小。
根据朴素贝叶斯公式可以得出:
P ( 嫁 ∣ 长 相 帅 , 性 格 爆 好 , 上 进 ) = P ( 长 相 帅 , 性 格 爆 好 , 上 进 ∣ 嫁 ) P ( 嫁 ) P ( 长 相 帅 , 性 格 爆 好 , 上 进 ) P(嫁|长相帅,性格爆好,上进) = \frac{P(长相帅,性格爆好,上进|嫁)P(嫁)}{P(长相帅,性格爆好,上进)} P(嫁∣长相帅,性格爆好,上进)=P(长相帅,性格爆好,上进)P(长相帅,性格爆好,上进∣嫁)P(嫁)
P ( 不 嫁 ∣ 长 相 帅 , 性 格 爆 好 , 上 进 ) = P ( 长 相 帅 , 性 格 爆 好 , 上 进 ∣ 不 嫁 ) P ( 不 嫁 ) P ( 长 相 帅 , 性 格 爆 好 , 上 进 ) P(不嫁|长相帅,性格爆好,上进) = \frac{P(长相帅,性格爆好,上进|不嫁)P(不嫁)}{P(长相帅,性格爆好,上进)} P(不嫁∣长相帅,性格爆好,上进)=P(长相帅,性格爆好,上进)P(长相帅,性格爆好,上进∣不嫁)P(不嫁)
根据表格我们可以发现没有一个数据有性格爆好这个特点,那么 P ( 性 格 爆 好 ∣ 嫁 ) = 0 P(性格爆好|嫁)=0 P(性格爆好∣嫁)=0,则根据上述公式:
P ( 嫁 ∣ 长 相 帅 , 性 格 爆 好 , 上 进 ) = P ( 长 相 帅 , 性 格 爆 好 , 上 进 ∣ 嫁 ) P ( 嫁 ) P ( 长 相 帅 , 性 格 爆 好 , 上 进 ) = P ( 长 相 帅 ∣ 嫁 ) P ( 性 格 爆 好 ∣ 嫁 ) P ( 上 进 ∣ 嫁 ) P ( 嫁 ) P ( 长 相 帅 ) P ( 性 格 爆 好 ) P ( 上 进 ) P(嫁|长相帅,性格爆好,上进) = \frac{P(长相帅,性格爆好,上进|嫁)P(嫁)}{P(长相帅,性格爆好,上进)}= \frac{P(长相帅|嫁)P(性格爆好|嫁)P(上进|嫁)P(嫁)}{P(长相帅)P(性格爆好)P(上进)} P(嫁∣长相帅,性格爆好,上进)=P(长相帅,性格爆好,上进)P(长相帅,性格爆好,上进∣嫁)P(嫁)=P(长相帅)P(性格爆好)P(上进)P(长相帅∣嫁)P(性格爆好∣嫁)P(上进∣嫁)P(嫁)
由于 P ( 性 格 爆 好 ∣ 嫁 ) = 0 P(性格爆好|嫁)=0 P(性格爆好∣嫁)=0,因此使得上述结果也为0,这显示是不合理的。因此这里引入拉普拉斯平滑处理。
引入公式如下:
P
λ
(
X
(
j
)
=
a
j
l
∣
Y
=
c
k
)
=
∑
i
=
1
N
I
(
x
i
(
j
)
=
a
j
l
,
y
i
=
c
k
)
+
λ
∑
i
=
1
N
I
(
y
i
=
c
k
)
+
S
j
λ
;
\begin{aligned} P_{\lambda}(X^{(j)}=a_{jl}|Y=c_k)=\frac{\sum_{i=1}^N {I(x_i^{(j)}=a_{jl},y_i=c_k)}+\lambda}{\sum_{i=1}^N {I(y_i=c_k)+S_j\lambda}} \end{aligned} ;
Pλ(X(j)=ajl∣Y=ck)=∑i=1NI(yi=ck)+Sjλ∑i=1NI(xi(j)=ajl,yi=ck)+λ;
P
λ
(
Y
=
c
k
)
=
∑
i
=
1
N
(
y
i
=
c
k
)
+
λ
N
+
K
λ
;
\begin{aligned} P_{\lambda}(Y=c_k)=\frac{\sum_{i=1}^N {(y_i=c_k)}+\lambda}{N+K\lambda}\end{aligned} ;
Pλ(Y=ck)=N+Kλ∑i=1N(yi=ck)+λ;
其中 a j l a_{jl} ajl表示第 j j j个特征的第 l l l个选择, S j S_j Sj代表第 j j j个特征的个数, K K K代表类别个数。
λ \lambda λ 为1,加入拉普拉斯平滑后即避免了出现概率为0的情况,又保证了单概率范围0-1,且最终和为1。
接下来分别计算 P ( 长 相 帅 ∣ 嫁 ) , P ( 性 格 爆 好 ∣ 嫁 ) , P ( 上 进 ∣ 嫁 ) , P ( 嫁 ) P(长相帅|嫁),P(性格爆好|嫁),P(上进|嫁),P(嫁) P(长相帅∣嫁),P(性格爆好∣嫁),P(上进∣嫁),P(嫁)
帅? | 性格好? | 上进? | 嫁与否? |
---|---|---|---|
帅 | 不好 | 不上进 | 不嫁 |
帅 | 好 | 上进 | 嫁 |
不帅 | 不好 | 不上进 | 不嫁 |
不帅 | 好 | 上进 | 嫁 |
帅 | 不好 | 上进 | 不嫁 |
帅 | 好 | 不上进 | 嫁 |
不帅 | 不好 | 上进 | 嫁 |
不帅 | 好 | 不上进 | 不嫁 |
P ( 长 相 帅 ∣ 嫁 ) P(长相帅|嫁) P(长相帅∣嫁)
上图2, 6行数据满足。
长相特征的个数为帅,不帅,两种情况,那么 S j S_j Sj为2,则最终概率 P ( 长 相 帅 ∣ 嫁 ) = 3 6 P(长相帅|嫁) = \frac{3}{6} P(长相帅∣嫁)=63 (嫁的个数为4+特征个数为2)
P ( 性 格 爆 好 ∣ 嫁 ) P(性格爆好|嫁) P(性格爆好∣嫁)
上图没有任何一行数据满足,但是通过拉普拉斯平滑处理后可得:
性格特征的个数为爆好,好,不好三种情况,那么 S j S_j Sj为3,则最终概率 P ( 性 格 爆 好 ∣ 嫁 ) = 1 7 P(性格爆好|嫁) = \frac{1}{7} P(性格爆好∣嫁)=71 (嫁的个数为4+特征个数为3)
P ( 上 进 ∣ 嫁 ) P(上进|嫁) P(上进∣嫁)
上图第2, 4, 7行数据满足。
是否上进特征的个数为上进,不上进两种情况,那么 S j S_j Sj为2,则最终概率 P ( 上 进 ∣ 嫁 ) = 4 6 P(上进|嫁) = \frac{4}{6} P(上进∣嫁)=64 (嫁的个数为4+特征个数为2)
P ( 嫁 ) P(嫁) P(嫁)
上图第3, 4, 6, 7行数据满足。
是否嫁类别的个数为两种情况,那么 K K K为2,则最终概率 P ( 嫁 ) = 5 10 P(嫁) = \frac{5}{10} P(嫁)=105 (总个数为8+特征个数为2)
因此 P ( 嫁 ∣ 长 相 帅 , 性 格 爆 好 , 上 进 ) = 3 6 × 1 7 × 4 6 × 5 10 P(嫁|长相帅,性格爆好,上进) = \frac{3}{6}\times\frac{1}{7}\times\frac{4}{6}\times\frac{5}{10} P(嫁∣长相帅,性格爆好,上进)=63×71×64×105
接下来继续计算 P ( 不 嫁 ∣ 长 相 帅 , 性 格 爆 好 , 上 进 ) P(不嫁|长相帅,性格爆好,上进) P(不嫁∣长相帅,性格爆好,上进)的概率,然后与上面的数值进行比较即可,算法与上面完全一模一样。这里就直接省略具体步骤。给出结果:
P
(
长
相
帅
∣
不
嫁
)
=
3
6
P(长相帅|不嫁) = \frac{3}{6}
P(长相帅∣不嫁)=63
P
(
性
格
爆
好
∣
不
嫁
)
=
1
7
P(性格爆好|不嫁) = \frac{1}{7}
P(性格爆好∣不嫁)=71
P
(
上
进
∣
不
嫁
)
=
2
6
P(上进|不嫁) = \frac{2}{6}
P(上进∣不嫁)=62
P
(
不
嫁
)
=
5
10
P(不嫁) = \frac{5}{10}
P(不嫁)=105
P
(
不
嫁
∣
长
相
帅
,
性
格
爆
好
,
上
进
)
=
3
6
×
1
7
×
2
6
×
5
10
P(不嫁|长相帅,性格爆好,上进) = \frac{3}{6}\times\frac{1}{7}\times\frac{2}{6}\times\frac{5}{10}
P(不嫁∣长相帅,性格爆好,上进)=63×71×62×105
根据比较可以得出: P ( 嫁 ∣ 长 相 帅 , 性 格 爆 好 , 上 进 ) > P ( 不 嫁 ∣ 长 相 帅 , 性 格 爆 好 , 上 进 ) P(嫁|长相帅,性格爆好,上进)> P(不嫁|长相帅,性格爆好,上进) P(嫁∣长相帅,性格爆好,上进)>P(不嫁∣长相帅,性格爆好,上进),因此这样的男生贝叶斯告诉你可以嫁😝
根据这个例子能够清楚的理解朴素贝叶斯的整个分类过程。
代码运行截图:
代码:
package machinelearning.knn;
import weka.core.Instance;
import weka.core.Instances;
import java.io.FileReader;
import java.util.Arrays;
/**
* @description:朴素贝叶斯算法
* @author: Qing Zhang
* @time: 2021/7/6
*/
public class NaiveBayes {
//存储擦参数的内部类
private class GaussianParamters {
double mu;
double sigma;
public GaussianParamters(double paraMu, double paraSigma) {
mu = paraMu;
sigma = paraSigma;
}
public String toString() {
return "(" + mu + ", " + sigma + ")";
}
}
//数据
Instances dataset;
//类数量。例如二分类就是2
int numClasses;
//实例数量
int numInstances;
//属性数量
int numConditions;
//预测,包含被查询和预测的标签
int[] predicts;
//类分布
double[] classDistribution;
//具有拉普拉斯平滑的类分布
double[] classDistributionLaplacian;
//所有类的所有属性对所有值的条件概率
double[][][] conditionalProbabilities;
//具有拉普拉斯平滑的条件概率
double[][][] conditionalProbabilitiesLaplacian;
//高斯参数
GaussianParamters[][] gaussianParameters;
//数据类型
int dataType;
//符号型
public static final int NOMINAL = 0;
//数值型
public static final int NUMERICAL = 1;
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);
}
dataset.setClassIndex(dataset.numAttributes() - 1);
numConditions = dataset.numAttributes() - 1;
numInstances = dataset.numInstances();
numClasses = dataset.attribute(numConditions).numValues();
}
/**
* @Description: 设置数据类型
* @Param: [paraDataType]
* @return: void
*/
public void setDataType(int paraDataType) {
dataType = paraDataType;
}
/**
* @Description: 计算拉普拉斯平滑的类分布
* @Param: []
* @return: void
*/
public void calculateClassDistribution() {
classDistribution = new double[numClasses];
classDistributionLaplacian = new double[numClasses];
double[] tempCounts = new double[numClasses];
for (int i = 0; i < numInstances; i++) {
int tempClassValue = (int) dataset.instance(i).classValue();
tempCounts[tempClassValue]++;
}
for (int i = 0; i < numClasses; i++) {
classDistribution[i] = tempCounts[i] / numInstances;
classDistributionLaplacian[i] = (tempCounts[i] + 1) / (numInstances + numClasses);
}
System.out.println("Class distribution: " + Arrays.toString(classDistribution));
System.out.println(
"Class distribution Laplacian: " + Arrays.toString(classDistributionLaplacian));
}
/**
* @Description: 用拉普拉斯平滑法计算条件概率。只扫描一次数据集
* @Param: []
* @return: void
*/
public void calculateConditionalProbabilities() {
conditionalProbabilities = new double[numClasses][numConditions][];
conditionalProbabilitiesLaplacian = new double[numClasses][numConditions][];
// 分配空间
for (int i = 0; i < numClasses; i++) {
for (int j = 0; j < numConditions; j++) {
int tempNumValues = (int) dataset.attribute(j).numValues();
conditionalProbabilities[i][j] = new double[tempNumValues];
conditionalProbabilitiesLaplacian[i][j] = new double[tempNumValues];
}
}
// 计数
int[] tempClassCounts = new int[numClasses];
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);
conditionalProbabilities[tempClass][j][tempValue]++;
}
}
// 拉普拉斯函数的真实概率
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] = (conditionalProbabilities[i][j][k]
+ 1) / (tempClassCounts[i] + numClasses);
}
}
}
System.out.println(Arrays.deepToString(conditionalProbabilities));
}
/**
* @Description: 用拉普拉斯平滑法计算条件概率
* @Param: []
* @return: void
*/
public void calculateGausssianParameters() {
gaussianParameters = new GaussianParamters[numClasses][numConditions];
double[] tempValuesArray = new double[numInstances];
int tempNumValues = 0;
double tempSum = 0;
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++) {
if ((int) dataset.instance(k).classValue() != i) {
continue;
}
tempValuesArray[tempNumValues] = dataset.instance(k).value(j);
tempSum += tempValuesArray[tempNumValues];
tempNumValues++;
}
// 获得参数。
double tempMu = tempSum / tempNumValues;
double tempSigma = 0;
for (int k = 0; k < tempNumValues; k++) {
tempSigma += (tempValuesArray[k] - tempMu) * (tempValuesArray[k] - tempMu);
}
tempSigma /= tempNumValues;
tempSigma = Math.sqrt(tempSigma);
gaussianParameters[i][j] = new GaussianParamters(tempMu, tempSigma);
}
}
System.out.println(Arrays.deepToString(gaussianParameters));
}
/**
* @Description: 给所有实例分类,结果将存储在predict[]中
* @Param: []
* @return: void
*/
public void classify() {
predicts = new int[numInstances];
for (int i = 0; i < numInstances; i++) {
predicts[i] = classify(dataset.instance(i));
}
}
/**
* @Description: 给所有样本分类
* @Param: [paraInstance]
* @return: int
*/
public int classify(Instance paraInstance) {
if (dataType == NOMINAL) {
return classifyNominal(paraInstance);
} else if (dataType == NUMERICAL) {
return classifyNumerical(paraInstance);
}
return -1;
}
/**
* @Description: 符号型数据分类
* @Param: [paraInstance]
* @return: int
*/
public int classifyNominal(Instance paraInstance) {
// 找到最大的一个
double tempBiggest = -10000;
int resultBestIndex = 0;
for (int i = 0; i < numClasses; i++) {
double tempPseudoProbability = Math.log(classDistributionLaplacian[i]);
for (int j = 0; j < numConditions; j++) {
int tempAttributeValue = (int) paraInstance.value(j);
// 拉普拉斯平滑
tempPseudoProbability += Math
.log(conditionalProbabilities[i][j][tempAttributeValue]);
}
if (tempBiggest < tempPseudoProbability) {
tempBiggest = tempPseudoProbability;
resultBestIndex = i;
}
}
return resultBestIndex;
}
/**
* @Description: 数值型数据分类
* @Param: [paraInstance]
* @return: int
*/
public int classifyNumerical(Instance paraInstance) {
// 找到最大的一个
double tempBiggest = -10000;
int resultBestIndex = 0;
for (int i = 0; i < numClasses; i++) {
double tempPseudoProbability = Math.log(classDistributionLaplacian[i]);
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);
}
if (tempBiggest < tempPseudoProbability) {
tempBiggest = tempPseudoProbability;
resultBestIndex = i;
}
}
return resultBestIndex;
}
/**
* @Description: 计算准确率
* @Param: []
* @return: double
*/
public double computeAccuracy() {
double tempCorrect = 0;
for (int i = 0; i < numInstances; i++) {
if (predicts[i] == (int) dataset.instance(i).classValue()) {
tempCorrect++;
}
}
double resultAccuracy = tempCorrect / numInstances;
return resultAccuracy;
}
/**
* @Description: 符号型数据测试
* @Param: []
* @return: void
*/
public static void testNominal() {
System.out.println("Hello, Naive Bayes. I only want to test the nominal data.");
String tempFilename = "F:\\研究生\\研0\\学习\\Java_Study\\data_set\\mushrooms.arff";
NaiveBayes tempLearner = new NaiveBayes(tempFilename);
tempLearner.setDataType(NOMINAL);
tempLearner.calculateClassDistribution();
tempLearner.calculateConditionalProbabilities();
tempLearner.classify();
System.out.println("The accuracy is: " + tempLearner.computeAccuracy());
}
/**
* @Description: 数值型数据测试
* @Param: []
* @return: void
*/
public static void testNumerical() {
System.out.println(
"Hello, Naive Bayes. I only want to test the numerical data with Gaussian assumption.");
String tempFilename = "F:\\研究生\\研0\\学习\\Java_Study\\data_set\\iris.arff";
NaiveBayes tempLearner = new NaiveBayes(tempFilename);
tempLearner.setDataType(NUMERICAL);
tempLearner.calculateClassDistribution();
tempLearner.calculateGausssianParameters();
tempLearner.classify();
System.out.println("The accuracy is: " + tempLearner.computeAccuracy());
}
public static void main(String[] args) {
testNominal();
testNumerical();
}
}
第59天: 数值型数据的 NB 算法
这里直接贴出数值型数据处理的代码,工作在昨天就已经一起完成了。
代码:
/**
* @Description: 数值型数据分类
* @Param: [paraInstance]
* @return: int
*/
public int classifyNumerical(Instance paraInstance) {
// 找到最大的一个
double tempBiggest = -10000;
int resultBestIndex = 0;
for (int i = 0; i < numClasses; i++) {
double tempPseudoProbability = Math.log(classDistributionLaplacian[i]);
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);
}
if (tempBiggest < tempPseudoProbability) {
tempBiggest = tempPseudoProbability;
resultBestIndex = i;
}
}
return resultBestIndex;
}
/**
* @Description: 数值型数据测试
* @Param: []
* @return: void
*/
public static void testNumerical() {
System.out.println(
"Hello, Naive Bayes. I only want to test the numerical data with Gaussian assumption.");
String tempFilename = "F:\\研究生\\研0\\学习\\Java_Study\\data_set\\iris.arff";
NaiveBayes tempLearner = new NaiveBayes(tempFilename);
tempLearner.setDataType(NUMERICAL);
tempLearner.calculateClassDistribution();
tempLearner.calculateGausssianParameters();
tempLearner.classify();
System.out.println("The accuracy is: " + tempLearner.computeAccuracy());
}
第60天:小结
-
了解并使用了weka.jar。该工具是为了方便在java中编写机器学习以及数据挖掘的算法。
-
knn的大概思想。
-
knn优化思想就是使用权重投票来判断类别。
-
M-distance思想
-
首先需要确定根据哪种方式去预测,比如在该例中可以根据具体项目评分,也可以根据用户评分去预测。然后计算出所有样本的平均分,最后根据设置的范围去找到邻居,并根据邻居的平均分来预测。
-
KMeans聚类思想
-
其主要的思想就是找中心,然后划分,再找中心,再划分···
-
Naive Bayes(朴素贝叶斯)。其思想主要是根据概率公式推导出来的,其中需要注意的就是使用拉普拉斯平滑法处理,因为样本的概率可能不是连续的,所以若没有经过平滑法处理,会对最终的预测结果产生严重的影响。
-
编程实现的大概思想:首先是去计算所有类别以及特征相应的条件概率,先验概率,其次是根据公式计算出其概率,最后再通过比较来进行预测,最大概率的为最终结果。
-
通过编程可以将这些算法的原理理解的更加透彻,同时自己也可以试着举例子来巩固所了解的原理,也能检验自己是否理解的正确。