## 군집(Clustering)?
- 패턴 공간에 주어진 유한 개의 패턴들이 서로 가깝게 모여서 무리를 이루고 있는 패턴 집합을 묶는 과정.
## K-MEANS (KMEANS)란?
- [K-평균알고리즘 - WIKI](https://ko.wikipedia.org/wiki/K-평균_알고리즘)
- 주어진 데이터를 k개의 군집(클러스터:Clustering)로 묶는 알고리즘.
- 각 클러스터와 거리 차이의 분산을 최소화하는 방식으로 동작.
- 거리에 기반을 둔 clustering 기법
- 기준점에 가까운 곳의 데이터들을 하나의 군집으로 묶는 방법.
- 비지도학습 : Unsupervised Learning - [참고](https://ko.wikipedia.org/wiki/비_지도_학습)
## K-MEANS 수행과정1
1. 임의의 "K개 중심값" 설정.
2. 전체 데이터와 "K개 중심값"을 비교, 가장 가까운 군집(K)에 소속.
3. 군집된 데이터를 기준으로 군집중앙의 위치를 제 설정.
4. 새롭개 구한 "k개 중심값"이 기존과 동일하면 알고리즘 종료.
:: 이 과정을 통하여 K개의 군집으로 데이터를 구분.
# 수행과정 - javascript
###**1. 초기 중심값(init Centroid) 초기화.**
* 알고리즘 : https://ko.wikipedia.org/wiki/K-평균_알고리즘#초기화_기법
- Random Partition
- Forgy, MacQueen
- Kaufman
###**2. Data간 거리 계산 및 분류**
**1. 초기 중심값(init Centroid) 과 Data간 거리 계산.**
* [유클리드 거리(Euclidean distance)](https://ko.wikipedia.org/wiki/유클리드_거리)
```JavaScript
/* 초기 중심값과 Data의 거리 비교.
- 초기 중심값 : center
- Data : dataset
>> distance(dataset[n], center[k]);
*/
let c = [];
for(let n = 0 ; n < dataset.length ; n++) {
let x = dataset[n];
let minDist = -1, rn = 0;
for(let k = 0 ; k < center.length ; k++) {
let dist = distance(dataset[n], center[k]);
if(minDist === -1 || minDist > dist) {
minDist = dist;
cn = k;
}
}
c[n] = cn;
}
// c : 클러스터링 분류정보 (0~K)
```
**2. 거리가 가까운 군집에 Data를 분류.**
```JavaScript
c[n] = rn;
```
**3. 중심점 최적화 및 왜곡측정.**
- 왜곡측정 : 거리의 합을 비교.
1. 중심점 계산 :centroid();
2. 왜곡측정 : 중심점과 Data간 최소거리의합과 이전최소거리의 합의 변화를 비교함.
```javaScript
let preJ = 0;
while (true) {
let c = centroid();
// 왜곡측정 : 거리의 합을 이용.
let J = 0;
for (let n = 0; n < dataset.length; n++) {
let x = dataset[n];
let minDist = -1;
for (let k = 0; k < center.length; k++) {
let dist = distance(dataset[n], center[k]);
if (minDist === -1 || minDist > dist) {
minDist = dist;
}
}
J += minDist;
}
// 이전값과 비교하여 차이가 없으면 종료
let diff = Math.abs(preJ - J);
if (diff <= 0) {
console.info("last-cluster", c);
break;
} else {
//debugger;
//console.info(diff,preJ,J);
}
preJ = J;
};
function centroid() {
let c = [];
for (let n = 0; n < dataset.length; n++) {
let x = dataset[n];
let minDist = -1,
cn = 0;
for (let k = 0; k < center.length; k++) {
let dist = distance(dataset[n], center[k]);
if (minDist === -1 || minDist > dist) {
minDist = dist;
cn = k;
}
}
c[n] = cn;
}
//center null 초기화
center = Array.apply(null, Array(center.length));
let clusterCount = Array.apply(null, Array(center.length)).map(Number.prototype.valueOf, 0);
for (let n = 0; n < dataset.length; n++) {
let k = c[n] * 1;
let x = dataset[n];
if (!center[k]) center[k] = {};
//for(let key in x) {
for (let key = 0; key < x.length; key++) {
if (!center[k][key]) center[k][key] = 0;
center[k][key] += x[key] * 1;
}
//console.info("clusterCount["+k+"]",clusterCount[k]);
clusterCount[k]++;
}
for (let k = 0; k < center.length; k++) {
for (let _key in center[k]) {
center[k][_key] = center[k][_key] / clusterCount[k * 1];
//console.info("center["+k+"]["+_key+"]",center[k][_key]);
}
}
//console.info("re---center",center);
return c;
}
```
**4. 왜곡이 있는동안 "3.중심점 최적화 및 왜곡측정." 반복 수행.**
## Language별 구현체
* Java
- [Apache Commons Math](http://commons.apache.org/proper/commons-math/download_math.cgi) - [Overview](http://commons.apache.org/proper/commons-math/userguide/ml.html)
- [WEKA 데이터마이닝 분석패키지 ( GNU )](https://www.cs.waikato.ac.nz/ml/weka/) : https://blog.naver.com/zxy826/220732514990
- [Java-ML](http://java-ml.sourceforge.net/content/installing-library) : Java Machine Learning Library
- [xetorthio/kmeans](https://github.com/xetorthio/kmeans/tree/master/src)
* R
kmeans() 함수 이용.
- 첫 번째 인자: 군집에 사용할 데이터
- 두 번째 인자: 클러스터의 수(k)
* Javascript
- nodejs : [nodeml](https://www.npmjs.com/package/nodeml) : node machine learning
- 그외 참고 : [javascript kmeans](https://github.com/mlehman/kmeans-javascript)
## 기타 참고:
- https://github.com/xetorthio/kmeans/tree/master/src
- http://action713.tistory.com/entry/K평균-알고리즘Kmeans-algorithm
- http://datamining.uos.ac.kr/wp-content/uploads/2016/09/10장-클러스터링.pdf
- https://blog.naver.com/pjc1349/20057343166
- EM Clustering : https://blog.naver.com/pjc1349/20064718100
- https://www.nextobe.com/single-post/2018/02/26/데이터-과학자가-알아야-할-5가지-클러스터링-알고리즘
## K-Means (5) comparison with EM
- 원본 : http://ai-times.tistory.com/158
- K-Means
- Hard Clustering : A instance belong to only one Cluster.
- Based on Euclidean distance.
- Not Robust on outlier, value range.
- EM
- Soft Clustering : A instance belong to several clusters with
- membership probability.
- Based on density probability.
- Can handle both numeric and nominal attributes.
## source - javascript 구현소스 (원본 : https://proinlab.com/archives/2134)
1. [kmeans-sample-test-01-Find Centroid](https://scrimba.com/c/cPwp3hZ)
2. [kmeans-sample-test-02-Euclidean Distance](https://scrimba.com/c/cb3ZJHa)
3. [kmeans-sample-test-03-Expectation](https://scrimba.com/c/cvLVvsn)
4. [kmeans-sample-test-04-Maximazation](https://scrimba.com/c/czZV4Hd)
5. [kmeans-sample-test-05-왜곡 측정 및 Iteration](https://scrimba.com/c/cEaDPuK)
```javascript
function kmeans(k,dataset) {
let center = [];
let preRand = {}; // 중복된 center가 존재하지 않도록 점검
while(true) { // k개의 데이터가 선택될 때까지 실행
let rand = Math.floor(Math.random() * dataset.length);
if(preRand[rand]) continue;
if(dataset[rand]) {
center.push(dataset[rand]);
preRand[rand] = true;
}
if(center.length == k) break;
}
//center.sort();
// console.log("init-center",center);
center[5,15,25];
let distance = (x, y)=> {
let sum = 0;
let keys = {};
for(let key in x) keys[key] = true;
for(let key in y) keys[key] = true;
//console.info("keys",keys);
for(let key in keys) {
let xd = x[key] ? x[key] * 1 : 0;
let yd = y[key] ? y[key] * 1 : 0;
sum += (xd - yd) * (xd - yd);
}
return Math.sqrt(sum);
};
//console.info(distance([1,2,3],[1]));
//console.info("init-cluster-let r",r)
function centroid() {
let c = [];
for(let n = 0 ; n < dataset.length ; n++) {
let x = dataset[n];
let minDist = -1, cn = 0;
for(let k = 0 ; k < center.length ; k++) {
let dist = distance(dataset[n], center[k]);
if(minDist === -1 || minDist > dist) {
minDist = dist;
cn = k;
}
}
c[n] = cn;
}
//center null 초기화
center = Array.apply(null, Array(center.length));
let clusterCount = Array.apply(null, Array(center.length)).map(Number.prototype.valueOf, 0);
for(let n = 0 ; n < dataset.length ; n++) {
let k = c[n] * 1;
let x = dataset[n];
if(!center[k]) center[k] = {};
//for(let key in x) {
for ( let key = 0;key dist) {
minDist = dist;
}
}
J += minDist;
}
// 이전값과 비교하여 차이가 없으면 종료
let diff = Math.abs(preJ - J);
if(diff <= 0) {
console.info("last-cluster",c);
break;
} else {
//debugger;
//console.info(diff,preJ,J);
}
preJ = J;
};
return c;
}
let dataset = [
[ 1 , ] ,
[ 2 , ] ,
[ 3 , ] ,
[ 4 , ] ,
[ 5 , ] ,
[ 6 , ] ,
[ 14 , ] ,
[ 15 , ] ,
[ 16 , ] ,
[ 17 , ] ,
[ 18 , ] ,
[ 19 , ] ,
[ 20 , ] ,
[ 21 , ] ,
[ 22 , ] ,
[ 23 , ] ,
[ 24 , ] ,
[ 25 , ] ,
[ 26 , ] ,
[ 27 , ] ,
[ 28 , ] ,
[ 7 , ] ,
[ 8 , ] ,
[ 9 , ] ,
[ 10 , ] ,
[ 11 , ] ,
[ 12 , ] ,
[ 13 , ] ,
[ 29 , ] ,
[ 30 ]
];
for (var i=0;i<1;i++) {
let c = kmeans(3,dataset);
let v0 = c.map(function(r,idx){
//console.info(r,idx);
if ( r == 0 ) {
return dataset[idx][0];
}
}).filter(function(data) {return data !=null} );
console.info("cluster 0", v0);
let v1 = c.map(function(r,idx){
//console.info(r,idx);
if ( r == 1 ) {
return dataset[idx][0];
}
}).filter(function(data) {return data !=null} );
console.info("cluster 1", v1);
let v2 = c.map(function(r,idx){
//console.info(r,idx);
if ( r == 2 ) {
return dataset[idx][0];
}
}).filter(function(data) {return data !=null} );
console.info("cluster 2", v2);
}
//kmeans(3,dataset);
```
## source - nodeml (nodejs)
```javascript
'use strict';
const {sample, kMeans, evaluate} = require('nodeml');
const bulk = sample.iris();
let kmeans = new kMeans();
kmeans.train(bulk.dataset, {k: 3});
result = knn.test(bulk.dataset);
console.log(result);
```
## source - nodeml (nodejs2)
```JavaScript
const {sample, kMeans, evaluate} = require('nodeml');
const bulk = sample.iris();
//console.info(bulk.dataset);
let kmeans = new kMeans();
//
// kmeans.train(bulk.dataset, {k: 2});
// result = kmeans.test(bulk.dataset);
var dataset1 = [
[ '5.1', '4.2', '1.4', '0.2' ], // 0
[ '1.5', '4.2', '1.4', '0.2' ], // 2
[ '2.5', '4.2', '1.4', '0.2' ], // 2
[ '3.5', '4.2', '1.4', '0.2' ], // 2
[ '10.5', '4.2', '1.4', '0.2' ], // 0
[ '0.5', '4.2', '1.4', '0.2' ], // 1
[ '4.5', '4.2', '1.4', '0.2' ], // 0
[ '5.5', '4.2', '1.4', '0.2' ], // 0
[ '0.1', '4.2', '1.4', '0.2' ], // 1
[ '5.5', '4.2', '1.4', '0.2' ], // 0
[ '4.9', '3.1', '1.5', '0.1' ] // 0
];
var dataset2 = [
['5.1' , '1' ], // 2
['1.5' , '1' ], // 0
['2.5' , '1' ], // 0
['3.5' , '1' ], // 2
['10.5' , '1' ], // 1
['0.5' , '1' ], // 0
['4.5' , '1' ], // 2
['5.5' , '1' ], // 2
['0.1' , '1' ], // 0
['5.5' , '1' ], // 2
['4.9' , '1' ] // 2
];
kmeans.train(dataset2, {k: 3});
var result2 = kmeans.test(dataset2);
console.log(result2);
var dataset3 = [
['5.1' , '1' ], // 2
['1.5' , '0' ], // 0
['2.5' , '1' ], // 0
['3.5' , '3' ], // 2
['10.5' , '5' ], // 1
['0.5' , '0' ], // 0
['4.5' , '8' ], // 2
['5.5' , '6' ], // 2
['0.1' , '5' ], // 0
['5.5' , '4' ], // 2
['4.9' , '3' ] // 2
];
kmeans.train(dataset3, {k: 3});
var result3 = kmeans.test(dataset3);
console.log(result3);
```
## source - java
- [Apache Commons Math](http://commons.apache.org/proper/commons-math/download_math.cgi) - [Overview](http://commons.apache.org/proper/commons-math/userguide/ml.html)
import org.apache.commons.math3.ml.clustering.Cluster;
import org.apache.commons.math3.ml.clustering.Clusterer;
import org.apache.commons.math3.ml.clustering.DoublePoint;
import org.apache.commons.math3.ml.clustering.KMeansPlusPlusClusterer;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
public class TestClusterer
{
@Test
public void test1() throws Exception {
for (int i=0;i<10;i++) {
Clusterer<DoublePoint> clusterer = new KMeansPlusPlusClusterer<DoublePoint>(3);
List<DoublePoint> list = new ArrayList<DoublePoint>();
list.add(new DoublePoint(new double[]{ 1 }));
list.add(new DoublePoint(new double[]{ 2 }));
list.add(new DoublePoint(new double[]{ 3 }));
list.add(new DoublePoint(new double[]{ 4 }));
list.add(new DoublePoint(new double[]{ 5 }));
list.add(new DoublePoint(new double[]{ 6 }));
list.add(new DoublePoint(new double[]{ 7 }));
list.add(new DoublePoint(new double[]{ 8 }));
list.add(new DoublePoint(new double[]{ 9 }));
list.add(new DoublePoint(new double[]{ 10 }));
list.add(new DoublePoint(new double[]{ 11 }));
list.add(new DoublePoint(new double[]{ 12 }));
list.add(new DoublePoint(new double[]{ 13 }));
list.add(new DoublePoint(new double[]{ 14 }));
list.add(new DoublePoint(new double[]{ 15 }));
list.add(new DoublePoint(new double[]{ 16 }));
list.add(new DoublePoint(new double[]{ 17 }));
list.add(new DoublePoint(new double[]{ 18 }));
list.add(new DoublePoint(new double[]{ 19 }));
list.add(new DoublePoint(new double[]{ 20 }));
list.add(new DoublePoint(new double[]{ 21 }));
list.add(new DoublePoint(new double[]{ 22 }));
list.add(new DoublePoint(new double[]{ 23 }));
list.add(new DoublePoint(new double[]{ 24 }));
list.add(new DoublePoint(new double[]{ 25 }));
list.add(new DoublePoint(new double[]{ 26 }));
list.add(new DoublePoint(new double[]{ 27 }));
list.add(new DoublePoint(new double[]{ 28 }));
list.add(new DoublePoint(new double[]{ 29 }));
list.add(new DoublePoint(new double[]{ 30 }));
System.out.println(list);
List<? extends Cluster<DoublePoint>> res = clusterer.cluster(list);
System.out.println("!!!");
System.out.println(res.size());
System.out.println(res.toString());
int seq = 0;
for (Cluster<DoublePoint> re : res) {
System.out.print(re.getPoints() + " / ");
seq++;
if ( seq % 3 == 0) System.out.print("\n");
}
}
}
## source - java : https://github.com/xetorthio/kmeans
## source - java : http://ai-times.tistory.com/158
출처: http://ai-times.tistory.com/158 [ai-times]
package xminer.mining.clustering;
import xminer.core.*;
import java.util.Random;
public class KMeans {
Dataset m_dataset;
int m_clusterSize;
int m_maxIteration;
int m_recordCount;
int m_fieldCount;
int m_recordClusterIndex[]; // 각 레코드에 대하여 소속 군집번호
int m_clusterCount[]; // 각 클러스터별 소속 개수
Record m_cetroids[];
public KMeans(Dataset ds, int clusterSize, int maxIteration) {
m_dataset = ds;
this.m_clusterSize = clusterSize;
this.m_maxIteration = maxIteration;
this.m_recordCount = ds.getRecordCount();
this.m_fieldCount = ds.getAttrCount();
this.m_recordClusterIndex = new int[ ds.getRecordCount() ];
this.m_cetroids = new Record[ this.m_clusterSize ];
this.m_clusterCount = new int[ this.m_clusterSize ];
}
public void learn(){
// 초기 랜덤 시드 결정
int i=0;
init_centroid();
this.print_centroid();
while(true){
//System.out.println( i + "번재 수행결과");
reAssign_Step();
findNewCentroid_Step();
// System.out.println();
// this.print_centroid();
// this.print_state();
// 최대반복횟수에 의한 학습 종료
i++;
if( i >= this.m_maxIteration){
System.out.println("최대반복횟수에 도달하여 종료합니다. 반복횟수 : " + i);
break;
}
// 중심점(Centroid)의 고정에 의한 학습 종료
// -- 새로운 중심점의 계산
// -- 이전 중심점과의 차이를 계산
// -- 만약 중심점의 변화가 없으면 끝
}
System.out.println( i + "번재 수행결과");
System.out.println();
this.print_centroid();
this.print_state();
}
/**
* 초기에 클러스터 개수만큼의 레코드를 선택하여 이들을 초기 군집 중심으로 합니다.
* 이때 같은 레코드가 중복해서 다른 군집의 중심점이 되지 않도록 합니다.
*/
public void init_centroid(){
Random random = new Random();
for(int c=0; c<this.m_clusterSize; c++){
this.m_cetroids[c] = this.m_dataset.getRecord( random.nextInt(m_recordCount-1)).copy();
}
}
/**
* 군집의 중심을 새롭게 계산합니다.
* 모든 레코드의 소속값을 고려하여 평균값을 정합니다.
*/
public void findNewCentroid_Step(){
// 초기화
for(int c=0; c<this.m_clusterSize; c++){
this.m_clusterCount[c] = 0;
for(int f=0; f<this.m_fieldCount; f++){
this.m_cetroids[c].add(f, 0.0);
}
}
int c_num;
// 클러스터별 소속 레코드 개수를 계산합니다.
for(int r=0; r<this.m_recordCount; r++){
c_num = this.m_recordClusterIndex[r];
this.m_clusterCount[c_num]++;
}
// 클러스터별 중심을 계산합니다.
for(int r=0; r<this.m_recordCount; r++){
c_num = this.m_recordClusterIndex[r];
Record record = this.m_dataset.getRecord(r).copy();
for(int f=0; f<this.m_fieldCount; f++){
this.m_cetroids[c_num].addOnPrevValue(f, record.getValue(f));
}
}
for(int c=0; c<this.m_clusterSize; c++){
//System.out.println("군집 " + c + "의 개수 : " + this.m_clusterCount[c]);
this.m_cetroids[c].multiply( 1.0/(double)this.m_clusterCount[c] );
}
}
/**
* 주어진 중심에 대하여 모든 레코드들을 지정(Assign)합니다.
* 레코드와 각 군집중심과의 거리를 계산해보고 가장 거리가 가까운 군집에 지정합니다.
*/
public void reAssign_Step(){
int c_num;
double min_dist = Double.POSITIVE_INFINITY;
double distance;
for(int r=0; r<this.m_recordCount; r++){
Record record = this.m_dataset.getRecord(r).copy();
c_num = 0;
min_dist = 10000; //Double.POSITIVE_INFINITY;
for(int c=0; c<this.m_clusterSize; c++){
distance = this.m_dataset.getDistanceOfUclideanP(record, this.m_cetroids[c]);
// 해당 레코드와 군집중심과의 거리를 계산합니다.
if(distance < min_dist){ // 최소
min_dist = distance;
c_num = c;
}
}
this.m_recordClusterIndex[r] = c_num;
}
}
/**
* 현재 중심점(Centroid)의 값을 출력합니다.
*/
public void print_centroid() {
for (int c = 0; c < this.m_clusterSize; c++) {
System.out.println("군집[" + (c) + "]의 중심점 : " + this.m_cetroids[c].toString());
}
}
public void print_state(){
for(int r=0; r<this.m_recordCount; r++){
System.out.print("번호 "+ (r+1) + " : " );
for(int f=0; f<this.m_fieldCount; f++){
System.out.print( this.m_dataset.getRecord(r).getValue(f) + ", " );
}
System.out.println( this.m_recordClusterIndex[r] );
}
}
public static void main(String[] args) {
// 아이리스 원본 데이터
Dataset ds = new Dataset("아이리스","D:\\ai-miner-test-data\\iris.csv",
Dataset.FIRSTLINE_ATTR_NO_INFO, true);
// 수치 필드만 있는 아이리스 데이터
// Dataset ds = new Dataset("D:\\work\\(01) (입력모듈) Dataset\\datafile\\iris_4.csv",
Dataset.HAS_NOT_FIELD_NAME_LINE);
ds.printDataInfo();
KMeans km = new KMeans(ds, 3, 200); // 3개 군집, 최대 10번 반복, 종료변화값 0.01
km.learn();
}
}
'java' 카테고리의 다른 글
Spring Boot internationalization (0) | 2019.01.30 |
---|---|
상관관계 분석(Correlation Analysis)? (0) | 2019.01.29 |
spring_sqlmap_selectkey (0) | 2019.01.29 |
String to Date format string For java (0) | 2015.12.28 |
java generic classes (0) | 2015.12.28 |
댓글