总是有废话要先说~~
二妹原创,转载请注明出处,大家讨论~
上次面试,写自我评价的缺点,我写的比较胖~~然后拿offer了,人啊 就是要看得见的实诚!
----------------------------------------我是废话分割线 -------------------------------------------------------------
web上大量的数据,希望对这些数据进行聚类,而事先并不知道该怎么聚类,k-均值算法则是将大数据聚为k类。
关键要素:
1:用户事先要确定K的值,这个可能需要大量的测试优化k值。k值代表将数据聚为k类,那么将会产生k个簇的质心(均值所在的位置)。
2:聚类的的数据节点之间必须是可以计算平均值。K均值的思想就是某一堆数据都在一个均值的附近,那么将这一堆数据划为一簇。
3:适用于球状分布的数据。例如如果数据是环形分布的,直观上应该将同一环形的数据归为一类,而用k均值则可能分为扇形簇。
4:初始的质心选择得不同,则得到的聚类也会不同。
例如如下图,当k为3的时候,下面红黄蓝是正确的分类,使用k均值算法可以正确的分类第一类,而第二类则会出现错误。
以Person为例,现在有一堆人的集合,将他们分为三类,按年龄相近来分类。
Person对象,相当于集合中的节点。
package com.zyp.learn.datamining.algorithm;
public class Person {
private int age;
private String name;
public Person(String name,int age){
this.name = name;
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
算法实现类:
package com.zyp.learn.datamining.algorithm;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* K均值算法
* @author 卓二妹 2012/10/31
*
*/
public class KAverageAlgorithm {
public static void main(String[] args){
//生成集合,年龄随机生成0-99的数字
// Set<Person> personSet= initSet(5);
//生成固定值集合,便于调试
Set<Person> personSet= initSet();
System.out.print("集合元素:");
for(Person p : personSet){
System.out.print(p.getAge()+" ");
}
System.out.println();
// 调用无监督学习的k均值算法,将这50人按年龄分为3类,如老、中、青
Person[] centerArray = cluster(3,personSet);
System.out.print("质心:");
for(Person center : centerArray){
System.out.print(center.getAge()+" ");
}
//标记元素所属质心
Map<String,List> map = new HashMap();
//集合中的某个元素所属的质心,加到该质心对应的数组上
for(Person p: personSet){
int index = nearestIndex(centerArray,p);//p属于centerArray[index]质心
List<Person> lst = map.get(centerArray[index].getAge()+"");
if(lst==null){
lst = new ArrayList();
map.put(centerArray[index].getAge()+"",lst);
}
lst.add(p);
}
System.out.println("\n质心包含的元素:");
for(Person center : centerArray){
System.out.print(center.getAge()+" :");
List<Person> lst = map.get(center.getAge()+"");
if(lst!=null){
for(Person p : lst){
System.out.print(p.getAge()+" ");
}
}
System.out.println("\n");
}
}
/**
* 将person集合分类,按照person的年龄维度来分类。
* 分为k个年龄段,而每个年龄段是事前并不知道。
* 用k-均值算法的硬盘版本,只需要遍历一次全集。
* @param k:簇的数量
* @param personSet:要划分簇的全集
* @return 质心的数组
*/
public static Person[] cluster(int k,final Set<Person> personSet){
Person[] centerArray = new Person[k];
//由于集合是无序的,因此在集合中选前k个点做初始的质心(任意k个点)
int i = 0;
for(Person p:personSet){
centerArray[i++] = p;
if(i==k){
break;
}
}
//进行一遍执行的调整,本次调整中,改变了所属簇的节点的数量为chanedPointNum
int chanedPointNum = -1;
//直到没有任何person所属簇都不再变化后,结束分类
boolean isExit = false;
while(isExit){
Person[] newCenterArray = adjustCenteroid(centerArray,personSet);
isExit = exit(newCenterArray,centerArray,personSet);
}
return centerArray;
}
/**
* 进行一遍质心的调整
* @param centerArray 质心数组
* @param personSet 全集
* return 返回本轮调整质心,改变了所属簇的Person的个数
*/
public static Person[] adjustCenteroid(final Person[] centerArray,final Set<Person> personSet){
int changedPointNum = 0;//本次调整质心,改变了所属的簇的节点的个数
int oldIndex = -1;
int newIndex = -1;
int k = centerArray.length;
//distArray[i]代表该划划分到第i类的年龄的总和
int[] distArray = new int[k];
//numberArray[i]代表划分到第i类的年龄有多少个
int[] numberArray = new int[k];
//待返回的新质心
Person[] newCenterArray = new Person[k];
//将数组元素初始化为0
initArray(distArray);
initArray(numberArray);
for(Person tempPerson:personSet){
//计算该tempPerson到centerArray的第index质心最近
int index = nearestIndex(centerArray,tempPerson);
//则该tempPerson被划为第index质心代表的簇中
distArray[index] = distArray[index] + tempPerson.getAge();
numberArray[index] ++;
}
//已将每个元素分到k个质心中,重新计算每个簇的均值,得到新的质心
for(int index = 0; index<k;index++){
newCenterArray[index] = new Person("",distArray[index]/numberArray[index]);
}
return newCenterArray;
}
/**
* 质心调整的退出标准
* @param centerArray当前的质心
* @param oldCenterArray 调整前的质心
* @param personSet 数据集合
* @return 数据集合的簇在质心调整前后无变化,则返回true
*/
public static boolean exit(final Person[] centerArray,final Person[] oldCenterArray,final Set<Person> personSet){
boolean isExit = false;
int changedPointNum = 0;
//遍历person集合,看质心调整后,person的所属簇是否改变
for(Person tempPerson:personSet){
int newIndex = nearestIndex(centerArray,tempPerson);
int oldIndex = nearestIndex(oldCenterArray,tempPerson);
if(oldIndex!=newIndex){
changedPointNum++;
}
}
if(changedPointNum==0){
isExit = true;
}
return isExit;
}
public static void initArray(int array[]){
for(int i = 0;i<array.length;i++){
array[i] = 0;
}
}
public static int nearestIndex(final Person[] centerArray,final Person tempPerson){
int minIndex = 0;//与该点距离最近的质心的index
int minValue = 100;//与最近的质心的距离
for(int i = 0;i<centerArray.length;i++){
int centerAge = centerArray[i].getAge();
int temp = Math.abs(tempPerson.getAge()-centerAge);//数据点的年龄与质心距离
if(temp<=minValue){
minValue = temp;//
minIndex = i;
}
}
return minIndex;
}
/**
* 初始化集合中的数据
* @param count:需要生成的集合的元素个数
* @return
*/
public static Set<Person> initSet(int count){
Set<Person> set = new HashSet();
// System.out.print("初始集合:");
for(int i=0;i<count;i++){
String name = "person"+i;
//随即生成0--99的人的年龄
int age = (int)(Math.random()*100);
// System.out.print(age+" ");
set.add(new Person(name,age));
}
System.out.println();
return set;
}
/**
* 初始化集合中的数据,指定一些出错的数据,便于调试
* @return
*/
public static Set<Person> initSet(){
Set<Person> set = new HashSet();
set.add(new Person("", 69));
set.add(new Person("", 30));
set.add(new Person("", 75));
set.add(new Person("", 12));
set.add(new Person("", 14));
return set;
}
}
运行一下main方法,随机生成5个人,按年龄分为3类,打印结果如下:
集合元素:75 69 14 12 30
质心:75 69 14
质心包含的元素:
75 :75
69 :69
14 :14 12 30
二天空了再来解释算法,敬请期待~