算法介绍
- 属于无标签学习
- 需要归一化处理
算法原理
- 首先随机选择k个中心作为初始聚类中心
- 接着把每个数据都按照选定的距离策略,分配给最近的中心
- 对每个簇,计算数据点的坐标平均值,并将其作为新的中心
- 不断循环重复2.3步,直到前后两次的中心不变或者达到最大迭代次数
- 将分簇结果保存
算法流程
变量准备
/**
* Manhattan distance.
*/
public static final int MANHATTAN = 0;
/**
* Euclidean distance.
*/
public static final int EUCLIDEAN = 1;
/**
* The distance measure.
*/
public int distanceMeasure = EUCLIDEAN;
/**
* A random instance;
*/
public static final Random random = new Random();
/**
* The data.
*/
Instances dataset;
/**
* The number of clusters.
*/
int numClusters = 2;
//用二位数组的形式存放聚类后的数据,每一行代表一个中心,一行的一列就表示该中心下的具体某一个数据
int[][] clusters;
这里同样设置了距离策略、随机数种子以及聚类结果变量clusters,clusters是用二位数组的形式存放聚类后的数据,每一行代表一个中心,一行的一列就表示该中心下的具体某一个数据
读取数据
/**
*******************************
* 构造方法1:读取数据集
*
* @param paraFilename
* The data filename.
*******************************
*/
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);
} // Of try
}// Of the first constructor
这里使用构造方法读取数据集
获得随机序列
/**
*********************
* 与kNN类似,得到一个指定长度的打乱序列
*
* @param paraLength
* The length of the sequence.
* @return An array of indices, 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;
} // Of for i
// 随机交换来打乱
int tempFirst, tempSecond, tempValue;
for (int i = 0; i < paraLength; i++) {
// Generate two random indices.
tempFirst = random.nextInt(paraLength);
tempSecond = random.nextInt(paraLength);
// Swap.
tempValue = resultIndices[tempFirst];
resultIndices[tempFirst] = resultIndices[tempSecond];
resultIndices[tempSecond] = tempValue;
} // Of for i
return resultIndices;
}// Of getRandomIndices
这个部分同kNN,也是输入一个长度N,获得一个1–N的随机序列
计算距离
/**
*********************
* 计算第i条数据与该中心的距离
*
* @param paraI
* 表示第i条数据
* @param paraArray
* 一个数组,表示特征空间的一个位置
* @return The distance.
*********************
*/
public double distance(int paraI, double[] paraArray) {
int resultDistance = 0;
double tempDifference;
switch (distanceMeasure) {
case MANHATTAN:
// dataset.numAttributes()-1为非标签的属性总数
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;
} // Of if
} // Of for i
break;
case EUCLIDEAN:
for (int i = 0; i < dataset.numAttributes() - 1; i++) {
tempDifference = dataset.instance(paraI).value(i) - paraArray[i];
resultDistance += tempDifference * tempDifference;
} // Of for i
break;
default:
System.out.println("Unsupported distance measure: " + distanceMeasure);
}// Of switch
return resultDistance;
}// Of distance
这里计算一条数据与一个中心间的距离
聚类
聚类分为很多个部分,这里我分点解释
随机设置聚类中心
// 随机设置聚类中心
int[] tempRandomOrders = getRandomIndices(dataset.numInstances());
//把前k个数据的坐标直接当成聚类中心来用,实现随机设置聚类中心的作用
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);
} // Of for j
} // Of for i
把随机序列前k个数据的坐标直接当成聚类中心来用,实现随机设置聚类中心的作用
循环更新
这一步是核心,不断迭代,每次迭代都会计算在该情况下每个数据的所属中心,后根据均值来选择新的聚类中心,这样不断重复,直到聚类中心不再变化或者达到最大迭代次数
为每个数据匹配最近的中心
//用i遍历所有的数据
for (int i = 0; i < dataset.numInstances(); i++) {
tempNearestCenter = -1;
tempNearestDistance = Double.MAX_VALUE;
//对数据i遍历所有的聚类中心,选择最近的
for (int j = 0; j < numClusters; j++) {
//计算i与当前中心的距离,用tempDistance表示
tempDistance = distance(i, tempCenters[j]);
//每次遍历都会比较,如果更近,那就赋值给tempNearestDistance,并把当前中心的序号j赋值给tempNearestCenter
if (tempNearestDistance > tempDistance) {
tempNearestDistance = tempDistance;
tempNearestCenter = j;
} // Of if
} // Of for j
//在得到最近的中心后,将结果交给temClusterArray保存
tempClusterArray[i] = tempNearestCenter;
} // Of for i
利用簇内数据的均值选择新的中心
// Step 2.2 Mean. Find new centers.
//把一个中心的所有数据进行均值处理,把该均值作为新的中心
//tempClusterLengths用于存放每个中心的数据数量
tempClusterLengths = new int[numClusters];
Arrays.fill(tempClusterLengths, 0);
//tempNewCenters则是存放新的中心的位置信息
double[][] tempNewCenters = new double[numClusters][dataset.numAttributes() - 1];
// Arrays.fill(tempNewCenters, 0);
//在循环中累加,得到新的中心的坐标累加值与中心数据数量
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);
} // Of for j
tempClusterLengths[tempClusterArray[i]]++;
} // Of for i
// Step 2.3 Now average
//上面得到的新的中心的坐标累加值与中心数据数量,作除法,得到新的中心坐标
for (int i = 0; i < tempNewCenters.length; i++) {
for (int j = 0; j < tempNewCenters[0].length; j++) {
tempNewCenters[i][j] /= tempClusterLengths[i];
} // Of for j
} // Of for i
分簇结果保存
//得到最终的聚类中心,将结果写入clusters中并保存
clusters = new int[numClusters][];
int[] tempCounters = new int[numClusters];
for (int i = 0; i < numClusters; i++) {
//这里每个中心的数据数量是不同的,所以分别为不同的中心分配不同的空间
clusters[i] = new int[tempClusterLengths[i]];
} // Of for i
for (int i = 0; i < tempClusterArray.length; i++) {
//tempClusterArray是每条数据的所属中心,所以tempClusterArray[i]就是当前数据的所属中心
//tempCounters
clusters[tempClusterArray[i]][tempCounters[tempClusterArray[i]]] = i;
tempCounters[tempClusterArray[i]]++;
} // Of for i
详细注释
package knn_nb;
import java.io.FileReader;
import java.util.Arrays;
import java.util.Random;
import weka.core.Instances;
public class KMeans {
/**
* Manhattan distance.
*/
public static final int MANHATTAN = 0;
/**
* Euclidean distance.
*/
public static final int EUCLIDEAN = 1;
/**
* The distance measure.
*/
public int distanceMeasure = EUCLIDEAN;
/**
* A random instance;
*/
public static final Random random = new Random();
/**
* The data.
*/
Instances dataset;
/**
* The number of clusters.
*/
int numClusters = 2;
//用二位数组的形式存放聚类后的数据,每一行代表一个中心,一行的一列就表示该中心下的具体某一个数据
int[][] clusters;
/**
*******************************
* 构造方法1:读取数据集
*
* @param paraFilename
* The data filename.
*******************************
*/
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);
} // Of try
}// Of the first constructor
/**
*******************************
* A setter.
*******************************
*/
public void setNumClusters(int paraNumClusters) {
numClusters = paraNumClusters;
}// Of the setter
/**
*********************
* 与kNN类似,得到一个指定长度的打乱序列
*
* @param paraLength
* The length of the sequence.
* @return An array of indices, 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;
} // Of for i
// 随机交换来打乱
int tempFirst, tempSecond, tempValue;
for (int i = 0; i < paraLength; i++) {
// Generate two random indices.
tempFirst = random.nextInt(paraLength);
tempSecond = random.nextInt(paraLength);
// Swap.
tempValue = resultIndices[tempFirst];
resultIndices[tempFirst] = resultIndices[tempSecond];
resultIndices[tempSecond] = tempValue;
} // Of for i
return resultIndices;
}// Of getRandomIndices
/**
*********************
* 计算第i条数据与该中心的距离
*
* @param paraI
* 表示第i条数据
* @param paraArray
* 一个数组,表示特征空间的一个位置
* @return The distance.
*********************
*/
public double distance(int paraI, double[] paraArray) {
int resultDistance = 0;
double tempDifference;
switch (distanceMeasure) {
case MANHATTAN:
// dataset.numAttributes()-1为非标签的属性总数
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;
} // Of if
} // Of for i
break;
case EUCLIDEAN:
for (int i = 0; i < dataset.numAttributes() - 1; i++) {
tempDifference = dataset.instance(paraI).value(i) - paraArray[i];
resultDistance += tempDifference * tempDifference;
} // Of for i
break;
default:
System.out.println("Unsupported distance measure: " + distanceMeasure);
}// Of switch
return resultDistance;
}// Of distance
/**
*******************************
* Clustering.
*******************************
*/
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());
//把前k个数据的坐标直接当成聚类中心来用,实现随机设置聚类中心的作用
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);
} // Of for j
} // Of for i
//不断迭代(循环条件是两次聚类中心是不同的),每次迭代都会计算在该情况下每个数据的所属中心,后根据均值来选择新的聚类中心,这样不断重复,直到聚类中心不再变化或者达到最大迭代次数
int[] tempClusterLengths = null;
while (!Arrays.equals(tempOldClusterArray, tempClusterArray)) {
System.out.println("New loop ...");
//用于记录上次循环每条数据的所属中心
tempOldClusterArray = tempClusterArray;
//用于记录当前循环每条数据的所属中心
tempClusterArray = new int[dataset.numInstances()];
// Step 2.1 Minimization. Assign cluster to each instance.
//在当前中心的情况下,为每条数据选择最近的中心,并将结果保存
int tempNearestCenter;
double tempNearestDistance;
double tempDistance;
//用i遍历所有的数据
for (int i = 0; i < dataset.numInstances(); i++) {
tempNearestCenter = -1;
tempNearestDistance = Double.MAX_VALUE;
//对数据i遍历所有的聚类中心,选择最近的
for (int j = 0; j < numClusters; j++) {
//计算i与当前中心的距离,用tempDistance表示
tempDistance = distance(i, tempCenters[j]);
//每次遍历都会比较,如果更近,那就赋值给tempNearestDistance,并把当前中心的序号j赋值给tempNearestCenter
if (tempNearestDistance > tempDistance) {
tempNearestDistance = tempDistance;
tempNearestCenter = j;
} // Of if
} // Of for j
//在得到最近的中心后,将结果交给temClusterArray保存
tempClusterArray[i] = tempNearestCenter;
} // Of for i
// Step 2.2 Mean. Find new centers.
//把一个中心的所有数据进行均值处理,把该均值作为新的中心
//tempClusterLengths用于存放每个中心的数据数量
tempClusterLengths = new int[numClusters];
Arrays.fill(tempClusterLengths, 0);
//tempNewCenters则是存放新的中心的位置信息
double[][] tempNewCenters = new double[numClusters][dataset.numAttributes() - 1];
// Arrays.fill(tempNewCenters, 0);
//在循环中累加,得到新的中心的坐标累加值与中心数据数量
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);
} // Of for j
tempClusterLengths[tempClusterArray[i]]++;
} // Of for i
// Step 2.3 Now average
//上面得到的新的中心的坐标累加值与中心数据数量,作除法,得到新的中心坐标
for (int i = 0; i < tempNewCenters.length; i++) {
for (int j = 0; j < tempNewCenters[0].length; j++) {
tempNewCenters[i][j] /= tempClusterLengths[i];
} // Of for j
} // Of for i
System.out.println("Now the new centers are: " + Arrays.deepToString(tempNewCenters));
tempCenters = tempNewCenters;
} // Of while
// Step 3. Form clusters.
//得到最终的聚类中心,将结果写入clusters中并保存
clusters = new int[numClusters][];
int[] tempCounters = new int[numClusters];
for (int i = 0; i < numClusters; i++) {
//这里每个中心的数据数量是不同的,所以分别为不同的中心分配不同的空间
clusters[i] = new int[tempClusterLengths[i]];
} // Of for i
for (int i = 0; i < tempClusterArray.length; i++) {
//tempClusterArray是每条数据的所属中心,所以tempClusterArray[i]就是当前数据的所属中心
//tempCounters
clusters[tempClusterArray[i]][tempCounters[tempClusterArray[i]]] = i;
tempCounters[tempClusterArray[i]]++;
} // Of for i
System.out.println("The clusters are: " + Arrays.deepToString(clusters));
}// Of clustering
/**
*******************************
* Clustering.
*******************************
*/
public static void testClustering() {
KMeans tempKMeans = new KMeans("C:\\Users\\hp\\Desktop\\deepLearning\\src\\main\\java\\resources\\iris.arff");
tempKMeans.setNumClusters(3);
tempKMeans.clustering();
}// Of testClustering
/**
*************************
* A testing method.
*************************
*/
public static void main(String arags[]) {
testClustering();
}// Of main
}// Of class KMeans