java写质心,JAVA实现K-means聚类

本文重点介绍K-means聚类算法,阐述其基本思想和具体步骤,包括选取初始质心、样本分类、计算新质心及判断终止条件。还详细说明了用Java实现该算法的过程,涵盖基础属性设置、初始质心选择、一轮聚类及质心移动判断等,最后给出二维数聚类的具体实现和测试结果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

重点介绍下K-means聚类算法。K-means算法是比较经典的聚类算法,算法的基本思想是选取K个点(随机)作为中心进行聚类,然后对聚类的结果计算该类的质心,通过迭代的方法不断更新质心,直到质心不变或稍微移动为止,则最后的聚类结果就是最后的聚类结果。下面首先介绍下K-means具体的算法步骤。

K-means算法

在前面已经大概的介绍了下K-means,下面就介绍下具体的算法描述:

1)选取K个点作为初始质心;

2)对每个样本分别计算到K个质心的相似度或距离,将该样本划分到相似度最高或距离最短的质心所在类;

3)对该轮聚类结果,计算每一个类别的质心,新的质心作为下一轮的质心;

4)判断算法是否满足终止条件,满足终止条件结束,否则继续第2、3、4步。

在介绍算法之前,我们首先看下K-means算法聚类平面200,000个点聚成34个类别的结果(如下图)

c38581b515cbeab2cf8147efd3fe3638.png

算法实现

K-means聚类算法整体思想比较简单,下面 就分步介绍如何用Java来实现K-means算法。

一、K-means算法基础属性

在K-means算法中,有几个重要的指标,比如K值、最大迭代次数等,对于这些指标,我们统一把它们设置为类的属性,如下:

private List dataArray;//待分类的原始值

private int K = 3;//将要分成的类别个数

private int maxClusterTimes = 500;//最大迭代次数

private List> clusterList;//聚类的结果

private List clusteringCenterT;//质心

二、初始质心的选择

K-means聚类算法的结果很大程度收到初始质心的选取,这了为了保证有充分的随机性,对于初始质心的选择这里采用完全随机的方法,先把待分类的数据随机打乱,然后把前K个样本作为初始质心(通过多次迭代,会减少初始质心的影响)。

List centerT = new ArrayList(size);

//对数据进行打乱

Collections.shuffle(dataArray);

for (int i = 0; i 

centerT.add(dataArray.get(i));

}

三、一轮聚类

在K-means算法中,大部分的时间都在做一轮一轮的聚类,具体功能也很简单,就是对每一个样本分别计算和所有质心的相似度或距离,找到与该样本最相似的质心或者距离最近的质心,然后把该样本划分到该类中,具体逻辑介绍参照代码中的注释。

private void clustering(List preCenter, int times) {

if (preCenter == null || preCenter.size() 

return;

}

//打乱质心的顺序

Collections.shuffle(preCenter);

List> clusterList =  getListT(preCenter.size());

for (T o1 : this.dataArray) {

//寻找最相似的质心

int max = 0;

double maxScore = similarScore(o1, preCenter.get(0));

for (int i = 1; i 

if (maxScore 

maxScore = similarScore(o1, preCenter.get(i));

max = i;

}

}

clusterList.get(max).add(o1);

}

//计算本次聚类结果每个类别的质心

List nowCenter = new ArrayList ();

for (List list : clusterList) {

nowCenter.add(getCenterT(list));

}

//是否达到最大迭代次数

if (times >= this.maxClusterTimes || preCenter.size() 

this.clusterList = clusterList;

return;

}

this.clusteringCenterT = nowCenter;

//判断质心是否发生移动,如果没有移动,结束本次聚类,否则进行下一轮

if (isCenterChange(preCenter, nowCenter)) {

clear(clusterList);

clustering(nowCenter, times + 1);

} else {

this.clusterList = clusterList;

}

}

四、质心是否移动

在第三步中,提到了一个重要的步骤:每轮聚类结束后,都要重新计算质心,并且计算质心是否发生移动。对于新质心的计算、样本之间的相似度和判断两个样本是否相等这几个功能由于并不知道样本的具体数据类型,因此把他们定义成抽象方法,供子类来实现。下面就重点介绍如何判断质心是否发生移动。

private boolean isCenterChange(List preT, List nowT) {

if (preT == null || nowT == null) {

return false;

}

for (T t1 : preT) {

boolean bol = true;

for (T t2 : nowT) {

if (equals(t1, t2)) {//t1在t2中有相等的,认为该质心未移动

bol = false;

break;

}

}

//有一个质心发生移动,认为需要进行下一次计算

if (bol) {

return bol;

}

}

return false;

}

从上述代码可以看到,算法的思想就是对于前后两个质心数组分别前一组的质心是否在后一个质心组中出现,有一个没有出现,就认为质心发生了变动。

完整代码

上面四步已经完整的介绍了K-means算法的具体算法思想,下面就看下完整的代码实现。

/**

*@Description:  K-means聚类

*/

package com.lulei.datamining.knn;

import java.util.ArrayList;

import java.util.Collections;

import java.util.List;

public abstract class KMeansClustering {

private List dataArray;//待分类的原始值

private int K = 3;//将要分成的类别个数

private int maxClusterTimes = 500;//最大迭代次数

private List> clusterList;//聚类的结果

private List clusteringCenterT;//质心

public int getK() {

return K;

}

public void setK(int K) {

if (K 

throw new IllegalArgumentException("K must greater than 0");

}

this.K = K;

}

public int getMaxClusterTimes() {

return maxClusterTimes;

}

public void setMaxClusterTimes(int maxClusterTimes) {

if (maxClusterTimes 

throw new IllegalArgumentException("maxClusterTimes must greater than 10");

}

this.maxClusterTimes = maxClusterTimes;

}

public List getClusteringCenterT() {

return clusteringCenterT;

}

/**

* @Author:lulei

* @Description: 对数据进行聚类

*/

public List> clustering() {

if (dataArray == null) {

return null;

}

//初始K个点为数组中的前K个点

int size = K > dataArray.size() ? dataArray.size() : K;

List centerT = new ArrayList(size);

//对数据进行打乱

Collections.shuffle(dataArray);

for (int i = 0; i 

centerT.add(dataArray.get(i));

}

clustering(centerT, 0);

return clusterList;

}

/**

* @param preCenter

* @param times

* @Author:lulei

* @Description: 一轮聚类

*/

private void clustering(List preCenter, int times) {

if (preCenter == null || preCenter.size() 

return;

}

//打乱质心的顺序

Collections.shuffle(preCenter);

List> clusterList =  getListT(preCenter.size());

for (T o1 : this.dataArray) {

//寻找最相似的质心

int max = 0;

double maxScore = similarScore(o1, preCenter.get(0));

for (int i = 1; i 

if (maxScore 

maxScore = similarScore(o1, preCenter.get(i));

max = i;

}

}

clusterList.get(max).add(o1);

}

//计算本次聚类结果每个类别的质心

List nowCenter = new ArrayList ();

for (List list : clusterList) {

nowCenter.add(getCenterT(list));

}

//是否达到最大迭代次数

if (times >= this.maxClusterTimes || preCenter.size() 

this.clusterList = clusterList;

return;

}

this.clusteringCenterT = nowCenter;

//判断质心是否发生移动,如果没有移动,结束本次聚类,否则进行下一轮

if (isCenterChange(preCenter, nowCenter)) {

clear(clusterList);

clustering(nowCenter, times + 1);

} else {

this.clusterList = clusterList;

}

}

/**

* @param size

* @return

* @Author:lulei

* @Description: 初始化一个聚类结果

*/

private List> getListT(int size) {

List> list = new ArrayList>(size);

for (int i = 0; i 

list.add(new ArrayList());

}

return list;

}

/**

* @param lists

* @Author:lulei

* @Description: 清空无用数组

*/

private void clear(List> lists) {

for (List list : lists) {

list.clear();

}

lists.clear();

}

/**

* @param value

* @Author:lulei

* @Description: 向模型中添加记录

*/

public void addRecord(T value) {

if (dataArray == null) {

dataArray = new ArrayList();

}

dataArray.add(value);

}

/**

* @param preT

* @param nowT

* @return

* @Author:lulei

* @Description: 判断质心是否发生移动

*/

private boolean isCenterChange(List preT, List nowT) {

if (preT == null || nowT == null) {

return false;

}

for (T t1 : preT) {

boolean bol = true;

for (T t2 : nowT) {

if (equals(t1, t2)) {//t1在t2中有相等的,认为该质心未移动

bol = false;

break;

}

}

//有一个质心发生移动,认为需要进行下一次计算

if (bol) {

return bol;

}

}

return false;

}

/**

* @param o1

* @param o2

* @return

* @Author:lulei

* @Description: o1 o2之间的相似度

*/

public abstract double similarScore(T o1, T o2);

/**

* @param o1

* @param o2

* @return

* @Author:lulei

* @Description: 判断o1 o2是否相等

*/

public abstract boolean equals(T o1, T o2);

/**

* @param list

* @return

* @Author:lulei

* @Description: 求一组数据的质心

*/

public abstract T getCenterT(List list);

}

二维数聚类实现

在算法描述中,介绍了一个200,000个点聚成34个类别的效果图,下面就针对二维坐标数据实现其具体子类。

一、相似度

对于二维坐标的相似度,这里我们采取两点间聚类的相反数,具体实现如下:

@Override

public double similarScore(XYbean o1, XYbean o2) {

double distance = Math.sqrt((o1.getX() - o2.getX()) * (o1.getX() - o2.getX()) + (o1.getY() - o2.getY()) * (o1.getY() - o2.getY()));

return distance * -1;

}

二、样本/质心是否相等

判断样本/质心是否相等只需要判断两点的坐标是否相等即可,具体实现如下:

@Override

public boolean equals(XYbean o1, XYbean o2) {

return o1.getX() == o2.getX() && o1.getY() == o2.getY();

}

三、获取一个分类下的新质心

对于二维坐标数据,可以使用所有点的重心作为分类的质心,具体如下:

@Override

public XYbean getCenterT(List list) {

int x = 0;

int y = 0;

try {

for (XYbean xy : list) {

x += xy.getX();

y += xy.getY();

}

x = x / list.size();

y = y / list.size();

} catch(Exception e) {

}

return new XYbean(x, y);

}

四、main方法

对于具体二维坐标的源码这里就不再贴出来,就是实现前面介绍的抽象类,并实现其中的3个抽象方法,下面我们就随机产生200,000个点,然后聚成34个类别,具体代码如下:

public static void main(String[] args) {

int width = 600;

int height = 400;

int K = 34;

XYCluster xyCluster = new XYCluster();

for (int i = 0; i 

int x = (int)(Math.random() * width) + 1;

int y = (int)(Math.random() * height) + 1;

xyCluster.addRecord(new XYbean(x, y));

}

xyCluster.setK(K);

long a = System.currentTimeMillis();

List> cresult = xyCluster.clustering();

List center = xyCluster.getClusteringCenterT();

System.out.println(JsonUtil.parseJson(center));

long b = System.currentTimeMillis();

System.out.println("耗时:" + (b - a) + "ms");

new ImgUtil().drawXYbeans(width, height, cresult, "d:/2.png", 0, 0);

}

对于这随机产生的200,000个点聚成34类,总耗时5485ms。(计算机配置:i5 + 8G内存)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值