程序猿成长之路之数据挖掘篇——Kmeans聚类算法

news2024/12/24 11:40:03

Kmeans 是一种可以将一个数据集按照距离(相似度)划分成不同类别的算法,它无需借助外部标记,因此也是一种无监督学习算法。

什么是聚类

用官方的话说聚类就是将物理或抽象对象的集合分成由类似的对象组成的多个类的过程。用自己的话说聚类是根据不同样本数据间的相似度进行种类划分的算法。这种划分可以基于我们的业务需求或建模需求来完成,也可以单纯地帮助我们探索数据的自然结构和分布。

什么是K-means聚类

用官方的话说:k均值聚类算法(k-means clustering algorithm)是一种迭代求解的聚类分析算法,其步骤是,预将数据分为K组,则随机选取K个对象作为初始的聚类中心,然后计算每个对象与各个种子聚类中心之间的距离,把每个对象分配给距离它最近的聚类中心。聚类中心以及分配给它们的对象就代表一个聚类。每分配一个样本,聚类的聚类中心会根据聚类中现有的对象被重新计算。这个过程将不断重复直到满足某个终止条件。终止条件可以是没有(或最小数目)对象被重新分配给不同的聚类,没有(或最小数目)聚类中心再发生变化,误差平方和局部最小。

K-means聚类实现流程在这里插入图片描述

K-means聚类聚类的优劣性

优点:

  1. K-means聚类可以支持无监督学习,无需人工标记即可进行分类
  2. K-means聚类有处理不同类型数据的能力,如二元、序数、标称、数值等类型数据都可以处理。
  3. K-means聚类算法基于欧几里得或者曼哈顿距离度量来决定聚类。基于这样的距离度量的算法趋向于发现具有相近尺度和密度的球状簇。但是,一个簇可能是任意形状的。提出能发现任意形状簇的算法是很重要的。

缺点:

  1. 需要提前确定几何中心的数量
  2. 设置初始几何中心需要考虑尽可能选取差异较大的数据作为初始几何中心
  3. 适用于有明显中心的数据样本,对于相对分散的数据样本处理效果欠佳。

典型案例

学校A有若干不同年龄分布的学生,并且性别也不一样,想要依据这两个参数对学生进行分类。

学生类

import java.util.List;

public class Student{
	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + ", gender=" + gender + ", myHobby=" + myHobby
				+ ", myDream=" + myDream + "]";
	}
	public List<MyHobby> getMyHobby() {
		return myHobby;
	}
	public Student setMyHobby(List<MyHobby> myHobby) {
		this.myHobby = myHobby;
		return this;
	}
	public String getName() {
		return name;
	}
	public Student setName(String name) {
		this.name = name;
		return this;
	}
	public int getAge() {
		return age;
	}
	public Student setAge(int age) {
		this.age = age;
		return this;
	}
	public String getGender() {
		return gender;
	}
	public Student setGender(String gender) {
		this.gender = gender;
		return this;
	}
	String name;
	
	@Elem(type = ElemType.NUMBER)
	int age;
	
	@Elem(type = ElemType.XUSHU,list={"男","女"})
	String gender;
	
	@Elem()
	List<MyHobby> myHobby;
	
	@Elem()
	List<String> myDream;
	
	public Student(String name, int age, String gender) {
		super();
		this.name = name;
		this.age = age;
		this.gender = gender;
	}
	
	public Student(String name, int age, String gender,List<MyHobby> myHobby) {
		this(name,age,gender);
		this.myHobby = myHobby;
	}
	
	public Student(String name, int age, String gender,List<MyHobby> myHobby, List<String> myDreams) {
		this(name,age,gender);
		this.myHobby = myHobby;
		this.myDream = myDreams;
	}
}

配置类

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Elem {
	ElemType type() default ElemType.BASIC; //属性类型
	String[] list() default {}; //选择项
}

package kmeans;
/**
 * 元素属性类型(标称属性、序数属性、数值属性、二元属性)
 * @author zygswo
 *
 */
public enum ElemType {
	BASIC("标称属性"),
	XUSHU("序数属性"),
	NUMBER("数值属性"),
	ERYUAN("二元属性");
	
	private String name;
	
	private ElemType(String name) {
		this.setName(name);
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

package kmeans;

public enum DistanceType {
	EUCLID("欧几里得距离"),
	MANHATTAN("曼哈顿距离"),
	QIEBIXUEFU("切比雪夫距离");
	
	private String name;
	
	private DistanceType(String name) {
		this.setName(name);
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

主方法

package kmeans;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * kmeans聚类工具类
 * @author zygswo
 *
 */
public class KmeansUtils<T> {
	private int initKNodeNb; //kmeans初始几何中心数量
	
	private List<T> trainData; //kmeans训练数据
	
	private DistanceType distanceType;
	
	/**
	 * kmeans构造方法(默认为欧式距离公式)
	 * @param initKNodeNb kmeans初始几何中心数量
	 * @param trainData	训练数据
	 */
	public KmeansUtils(List<T> trainData, int initKNodeNb) {
		this.initKNodeNb = initKNodeNb;
		this.trainData = trainData;
		this.distanceType = DistanceType.EUCLID;
	}
	
	/**
	 * kmeans构造方法(默认为欧式距离公式)
	 * @param initKNodeNb kmeans初始几何中心数量
	 * @param trainData	训练数据
	 * @param distanceType 距离公式
	 */
	public KmeansUtils(List<T> trainData, int initKNodeNb, DistanceType distanceType) {
		this.initKNodeNb = initKNodeNb;
		this.trainData = trainData;
		this.distanceType = distanceType;
	}
	
	/**
	 * kmeans模型训练
	 */
	public void fit(){
		//计算距离
		List<Map<String,Double>> initKNodeDistanceVal = Collections.synchronizedList(
				new ArrayList<>()
		);
		//初始化几何列表
		List<List<T>> resList = Collections.synchronizedList(
				new ArrayList<>()
		);
		if (this.trainData == null || this.trainData.isEmpty()) {
			throw new IllegalArgumentException("训练集为空");
		}
		if (this.initKNodeNb <=0) {
			throw new IllegalArgumentException("几何中心数量小于0");
		}
		if (this.initKNodeNb > this.trainData.size()) {
			throw new IllegalArgumentException("几何中心数量超过数组数量");
		}
		if (this.distanceType == null) {
			throw new IllegalArgumentException("距离类型为空");
		}
		//1.获取前initKNodeNb个数据放入initKNodeList列表中
		//初始化的几何中心,需要选择差异较大的
		this.trainData.sort((T item1,T item2)-> {
			return (int)(calcDiff(item1,this.trainData.get(0)) - calcDiff(item2,this.trainData.get(0)));
		});
		int step = this.trainData.size() / initKNodeNb;
		//选择从小到大的initKNodeNb个元素作为初始几何
		for (int i = 0; i < this.trainData.size() && resList.size() < initKNodeNb; i+=step) {
			List<T> temp = Collections.synchronizedList(
					new ArrayList<>()
			);
			temp.add(this.trainData.get(i));
			resList.add(temp); //多个几何列表设置初始结点
		}
		//2.计算所有变量到不同的几何中心距离,如果稳定了(几何中心固定了),就退出循环
		while(true) {
			boolean balanced = true; //是否已经平衡
			for (T item: this.trainData) {
				double distance, minDistance = Double.MAX_VALUE; //求最小距离
				int preIndex = 0,afterIndex = 0; //preIndex-原位置
				initKNodeDistanceVal.clear();
//				for (List<T> list : resList) {
//					System.out.println(list.toString());
//				}
				//计算几何中心
				for (int i = 0; i < initKNodeNb; i++) {
					if (resList.get(i).size() > 0)
						initKNodeDistanceVal.add(calc(resList.get(i))); //计算初始结点距离
				}
				//计算原来的位置
				for (int i = 0; i < initKNodeNb; i++) {
					if(resList.get(i).contains(item)) {
						preIndex = i;
						break;
					}
				}
//				System.out.println("item = " + item.toString());
				//计算不同变量到不同的几何中心距离
				for (int i = 0; i < initKNodeNb; i++) {
					if (resList.get(i).size() > 0 && i < initKNodeDistanceVal.size()) {
						distance = calcDistance(item, initKNodeDistanceVal.get(i));
//						System.out.println("distance = " + distance);
//						System.out.println("minDistance = " + minDistance);
						if (distance < minDistance) {
							minDistance = distance;
							afterIndex = i;
						}
					}					
				}
//				System.out.println("preIndex = " + preIndex);
//				System.out.println("afterIndex = " + afterIndex);
				//位置替换,如果替换就还没结束
				if (preIndex != afterIndex) {
					resList.get(preIndex).remove(item);
					resList.get(afterIndex).add(item);
					balanced = false;
				} 
				if (preIndex == afterIndex) {
					//如果新增就还没结束
					if (!resList.get(preIndex).contains(item)) {
						resList.get(preIndex).add(item);
						balanced = false;
					}
				}
			}
			if (balanced){
				break;
			}
		}
//		//打印结果
		for (List<T> list : resList) {
			System.out.println(list.toString());
		}
	}
	
	/**
	 * 计算距离
	 * @param item1 item1
	 * @param item2 item2
	 * @return
	 */
	private double calcDiff(T item1, T item2) {
		List<T> list = Collections.synchronizedList(new ArrayList<>());
		list.add(item2);
		Map<String, Double> map = calc(list);
		double dist = calcDistance(item1, map);
		return dist;
	}
/**
	 * 计算距离
	 * @param item 当前对象
	 * @param map 几何中心
	 * @return
	 */
	private double calcDistance(T item, Map<String, Double> map) {
		double distance = 0.0;//距离
		int level = 0;//根据距离公式判断距离计算等级
		Class<?> cls = item.getClass();
		Field[] fs = cls.getDeclaredFields();
		for (Field f : fs) {
			double dist1 = 0.0, dist2 = 0.0;
			f.setAccessible(true);
			//获取需要计算的参数
			Elem el = f.getAnnotation(Elem.class);
			if (el == null) {
				continue;
			}
			try {
				switch(el.type()) {
				case BASIC: break;
				case XUSHU:
					//获取数组
					String[] arr = el.list();
					if (arr == null) {
						throw new IllegalArgumentException("序数属性需配置属性集合数组");
					}
					//数组排序
					Arrays.sort(arr);
					List<String> list = Arrays.asList(arr);
					//计算差距步长
					Double diffStep = 1 / (list.size() * 1.0);
					//获取当前对象序数属性的值
					Object value = f.get(item);
					dist1 = list.indexOf(value) * diffStep;
					break;
				case NUMBER: 
					//获取当前对象数值属性的值
					Object value1 = f.get(item); 
					//数据转换
					Double intVal = Double.parseDouble(String.valueOf(value1));
					dist1 = intVal;
					break;
				case ERYUAN:
					//获取数组
					String[] arr1 = el.list();
					if (arr1 == null) {
						arr1 = new String[]{"0","1"};
					} else {
						//数组排序
						Arrays.sort(arr1);
					}
					//转列表
					List<String> list1 = Arrays.asList(arr1);
					//计算差距步长
					Double diffStep1 = 1 / (list1.size() * 1.0);
					Object value2 = f.get(item);
					int ind = list1.indexOf(value2);
					dist1 = ind * diffStep1;
					break;
				}
				//获取当前几何中心属性的值
				dist2 = map.get(f.getName());
				//计算距离
				switch(distanceType) {
					case EUCLID: level = 2; break;
					case MANHATTAN: level = 1;break;
					case QIEBIXUEFU: level = 100;break;
				}
				distance += Math.pow(Math.abs(dist1 - dist2),level);
			} catch(Exception ex) {
				throw new RuntimeException(ex.getMessage());
			}
			distance = Math.pow(distance, 1/(level * 1.0));
		}	
		return distance;
	}

	/**
	 * 计算几何中心坐标
	 * @param kNodeList
	 * @return 几何中心坐标map
	 */
	private Map<String, Double> calc(List<T> kNodeList) {
		if (kNodeList == null || kNodeList.size() <= 0) {
			throw new IllegalArgumentException("几何中心列表数组为空");
		}
		//反射获取参数,形成数值数组
		Map<String, Double> result = new ConcurrentHashMap<>();
		T item = kNodeList.get(0);
		Class<?> cls = item.getClass();
		Field[] fs = cls.getDeclaredFields();
		for (Field f: fs) {
			//获取需要计算的参数
			Elem el = f.getAnnotation(Elem.class);
			if (el == null) {
				continue;
			}
			//将数据转换成数值
			Double dist = 0.0;
			switch(el.type()) {
				case BASIC: break;
				case XUSHU: 
					//获取数组
					String[] arr = el.list();
					if (arr == null) {
						throw new IllegalArgumentException("序数属性需配置属性集合数组");
					}
					//数组排序
					Arrays.sort(arr);
					//转列表
					List<String> list = Arrays.asList(arr);
					//计算差距步长
					Double diffStep = 1 / (list.size() * 1.0);
					for (T kNode : kNodeList) {
						try {
							//获取当前对象序数属性的值
							Object value = f.get(kNode);
							int ind = list.indexOf(value);
							//求和
							dist += ind * diffStep;
						} catch (IllegalArgumentException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						} catch (IllegalAccessException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}
					break;
				case NUMBER: 
					for (T kNode : kNodeList) {
						try {
							//获取当前对象数值属性的值
							Object value = f.get(kNode);
							//数据转换
							Double intVal = Double.parseDouble(String.valueOf(value));
							dist += intVal;
						} catch (IllegalArgumentException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						} catch (IllegalAccessException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}
					break;
				case ERYUAN:
					//获取数组
					String[] arr1 = el.list();
					if (arr1 == null) {
						arr1 = new String[]{"0","1"};
					} else {
						//数组排序
						Arrays.sort(arr1);
					}
					//转列表
					List<String> list1 = Arrays.asList(arr1);
					//计算差距步长
					Double diffStep1 = 1 / (list1.size() * 1.0);
					for (T kNode : kNodeList) {
						try {
							//获取当前对象二元属性的值
							Object value = f.get(kNode);
							int ind = list1.indexOf(value);
							//求和
							dist += ind * diffStep1;
						} catch (IllegalArgumentException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						} catch (IllegalAccessException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}
					break;
			}
			dist /= (kNodeList.size() * 1.0); //求平均值
			result.put(f.getName(), dist);
		}
		return result;
	}
	
	public static void main(String[] args) {
		List<Student> trainData = new ArrayList<>();
		trainData.add(new Student("zyl",28,"男"));
		trainData.add(new Student("sjl",28,"女"));
		trainData.add(new Student("xxx",27,"男"));
		trainData.add(new Student("stc",30,"男"));
		trainData.add(new Student("wxq",30,"女"));
		trainData.add(new Student("zzz",27,"男"));
		trainData.add(new Student("sss",27,"女"));
		trainData.add(new Student("mmm",20,"男"));
		trainData.add(new Student("qqq",20,"女"));
		trainData.add(new Student("666",30,"男"));
//		trainData.add(new Student("mmm",19,"男"));
		KmeansUtils<Student> utils = new KmeansUtils<>(trainData, 4);
		utils.fit();
	}
}

运行结果
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2072883.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VSCode插件 live Server

普通打开 安装live Server 包含端口 说明内置了服务器

改造小蚁摄像头支持免费无限容量云储存(Samba挂载篇)

为什么要改造&#xff1f; 插卡摄像头最大的一个问题就是频繁的读写会导致内存卡寿命急速下降&#xff0c;哪怕是市面上支持NAS转存的摄像头也是先录制到SD卡里&#xff0c;然后把SD卡上的视频再转存到NAS。同样对内存卡和NAS硬盘寿命都是损耗巨大。而这类监控视频绝大多数情况…

重磅!小米将对外公开超 1000 万行的 Xiaomi Vela 开源代码

点击上方关注 “终端研发部” 设为“星标”&#xff0c;和你一起掌握更多数据库知识 如果说接下来的澎湃OS系统会带来很强的吸引力&#xff0c;那么第二个惊喜也是随之而来&#xff0c;那就是小米Vela开源大动作。 早在2017年起&#xff0c;小米就活跃于 NuttX 社区&#xff0c…

Reinforcement-Learning 2.State Value and Bellman Equation

目录 0.Outline 1.Motivating examples Motivating example 1: Why return is important? Motivating example 2: How to calculate return? 2.State value 3.Bellman equation: Derivation Deriving the Bellman equation An illustrative example Exercise 4.Be…

FreeSWITCH 1.10.10 简单图形化界面28 - 麒麟V10 SP3服务器系统X86和ARM版本安装FreeSWITCH

FreeSWITCH 1.10.10 简单图形化界面28 - 麒麟V10 SP3 服务器系统X86和ARM版本安装FreeSWITCH 界面预览00、先看使用手册01、 麒麟服务器v10 sp3 x86版本1、安装操作系统2、下载安装脚本3、安装 02、麒麟服务器v10 sp3 arm版本1、安装操作系统2、下载安装脚本3、安装 03、登录网…

搭建一个私有的知识库mm-wiki

文章目录 前言一、mm-wiki二、安装步骤下载安装 总结 前言 一般公司内部想要记录一些东西,都需要一个共享文档,当然可以选择类似比较简单易用的,有道云笔记,腾讯文档,语雀等,但是肯定有些公司是保密的,所以不希望这些数据被泄露,当然选择本地存储是最安全的~ 一、mm-wiki 对于…

vue3+vite配置环境变量实现开发、测试、生产的区分

文章目录 一、为什么需要区分 (dev)、测试 (test) 和生产 (prod) 环境二、vue3的项目如何通过配置方式区分不同的环境1、创建不同环境的.env文件2、在不同的.env文件中配置相应的环境变量1&#xff09;.env.develoment2&#xff09;.env.test3&#xff09;.env.production 3、在…

查找2

树表的查找 1&#xff09;二叉排序树 I)二叉排序树的插入 II)二叉排序树的生成 III)二叉排序树的删除 2&#xff09;平衡二叉树 I&#xff09;平衡二叉树调整 、

MMOE+ESSM

MMOE 动机 多个任务之间的相关性并不是很强&#xff0c;这个时候如果再用过去那种共享底座embedding的结构&#xff0c;往往会导致『跷跷板』现象。 当前学术界已经有很多工作意识到1中描述的问题并且尝试去解决&#xff0c;但大多数工作的套路都是『大力出奇迹』的路子&…

zigbee笔记、十五、组播通信原理

一、zigbee四种通讯 1、单播&#xff08;略&#xff09; 2、广播&#xff08;略&#xff09; 3、组播&#xff1a;在zigbee网络中&#xff0c;模块可以用分组来标记&#xff0c;发送的模块如果发送的组号和网络里面标记接收模块的组号相对应&#xff0c;那么这些模块就可以拿到…

深度剖析渗透测试:流程、规范与实战全指南

一、引言 在当今数字化时代&#xff0c;网络安全问题日益凸显。渗透测试作为一种主动的安全评估方法&#xff0c;能够帮助企业和组织发现潜在的安全漏洞&#xff0c;提高系统的安全性。本文将详细介绍渗透测试的实施流程、规范、不同类型的测试方法以及相关的 checklist 和报告…

Matlab处理H5文件

1.读取h5文件 filenamexxx.h5; h5disp(filename) 2.h5文件保存为mat文件 读取 HDF5 文件中的数据 % 指定 HDF5 文件的路径 filename xxx.h5;% 读取 HDF5 文件中的各个数据集 A241_P h5read(filename, /A241_P); A241_W h5read(filename, /A241_W); A242_P h5read(filen…

ensp 中 wlan 的配置过程和示例

一、拓朴&#xff1a; 要求&#xff1a;vlan20 用于笔记本上网&#xff0c;使用Huawei信号&#xff0c;vlan30 用于手机上网&#xff0c;使用 Huawei-5G 信号 二、配置过程&#xff1a; 1、SW1 基本配置&#xff1a; 起 vlan batch 10 20 30&#xff0c;10 为管理 vlan&#…

Acrobat Pro DC 2024 for mac/Win:跨平台PDF编辑与管理的巅峰之作

Adobe Acrobat Pro DC 2024是一款专为Mac和Windows用户设计的全面PDF解决方案软件&#xff0c;它集成了创建、编辑、转换、共享和签署PDF文件的强大功能&#xff0c;为用户带来前所未有的高效与便捷体验。 强大的PDF编辑功能 Acrobat Pro DC 2024在PDF编辑方面表现出色。用户…

JavaScript初级——DOM增删改

1、 document.createElement&#xff08;&#xff09; —— 可以用于创建一个元素节点对象&#xff0c;他需要一个标签名作为参数&#xff0c;将会根据该标签名创建元素节点对象&#xff0c;并将创建好的对象作为返回值返回。 2、 document.createTextNode&#xff08;&#…

职场达人必备!MyComputerManager助你轻松管理快捷方式

前言 你是否还在为硬盘管理界面上那一堆乱糟糟的快捷方式头疼不已&#xff1f;是不是每次打开‘此电脑’都像是在玩寻宝游戏&#xff0c;寻找那个被深埋的文件夹&#xff1f;想象一下&#xff0c;如果能在此电脑页面一键启动程序&#xff0c;是不是觉得整个人都轻松了许多&…

使用Tabs组件提升页面内容的聚焦与分类效率

当页面信息量较大时&#xff0c;为了提高用户的浏览效率&#xff0c;我们需要对页面内容进行有效的分类和展示。HarmonyOS提供的Tabs组件是一个理想的解决方案&#xff0c;可以在一个页面内快速切换视图内容&#xff0c;提升用户查找信息的效率&#xff0c;同时减少用户在单次操…

CSS定位与布局

一、display属性&#xff08;元素如何显示&#xff09; 网页上的每个元素都是一个​盒模型​。​display​属性决定了盒模型的​行为方式​&#xff0c;设置元素如何被显示。 display常用的属性共有​4个​值&#xff1a; ​display: none;​ -- 让标签消失(隐藏元素并脱离文档…

Mac M1Pro 安装Java性能监控工具VisualVM 2.1.9

本地已经安装了java8&#xff0c;在终端输入jvisualvm提示没有安装 zhiniansara ~ % jvisualvm The operation couldn’t be completed. Unable to locate a Java Runtime that supports jvisualvm. Please visit http://www.java.com for information on installing Java.官网…

Kafka事件(消息、数据)的存储

1、查看有关kafka日志配置文件的信息 2、查看kafka全部主题的日志文件 3、查看每个主题的日志文件 4、__consumer_offsets-xx文件夹的作用 package com.power;public class Test {public static void main(String[] args) {int partition Math.abs("myTopic".hashCo…