だいたいでええやろで書いたk-平均法

前提
k個のクラスタを作る

①全てのデータに対してランダムなクラスタを割り振る
②クラスタ毎に重心を作る
③重心との距離に応じて各データのクラスタを振り直す

④終了条件を満たすまで繰り返す
終了条件
クラスタの振り直しが無くなるまで繰り返す
なんらかの量の変位が既定値を下回るまで繰り返す
規定の回数繰り返す

ここでは入力された2Dベクトル座標群をクラスタリングします。
processingで書きます。

//n回ループ
HashMap<Integer, List<PVector>> kmeans_n(List<PVector> vectors, int k, int n)
{
  //ベクトル毎にインデックスを割り付けた辞書HashMap<PVector, Integer>を作る
  var map = setup_cluster(vectors, k);
    
  for(int i = 0; i<n; i++)
  {
    //現在のmap : HashMap<PVector, Integer>からクラスター : HashMap<Integer, List<PVector>>を作成する
    var clusters = get_clusters(map, k);
    //クラスタ毎の重心を作成する
    var centroids = setup_centroid(clusters, k);          
    //mapの更新    
    update_cluster(map,vectors,centroids);    
  }  
  
  return get_clusters(map, k);
}

①全てのデータに対してランダムなクラスタを割り振る

入力データにクラスタを割り振るため、なんらかのデータ構造が必要です。

その1、クラスタークラスを作る。
その2、ジャグ配列作る。
その3、辞書使う。

ここでは辞書を使います。
ベクトルList<PVector>に、各々ランダムなインデックスを割り付けると考えて、HashMap<PVector, Integer>が割り付けられた状態です。
この構造ではPVectorをnewしただけでぶっ壊れる可能性があります。

import java.util.Random;
Random random;

HashMap<PVector, Integer> setup_cluster(List<PVector> vectors, int k)
{
  var map = new HashMap<PVector, Integer>();
  for(int i = 0; i<vectors.size(); i++)
  {
    int random_index = random.nextInt(k);
    map.put(vectors.get(i), random_index);    
  }  
  return map;
}

HashMap<PVector, Integer>はList<PVector>の延長です。
座標単位で処理を行うにはこっちのほうが楽です。
クラスター単位で処理を行う場合はHashMap<Integer, List<PVector>>の方が楽でしょう。

HashMap<PVector, Integer>を変数mapとし
HashMap<Integer, List<PVector>>を変数clustersとします。
mapのkeyは座標群の個数だけあり、
clustersのkeyはクラスターの個数k個だけあります。

HashMap<Integer, List<PVector>> get_clusters(HashMap<PVector, Integer> map, int k)
{
  var clusters = new HashMap<Integer, List<PVector>>();
  
  for(int i = 0; i<k; i++)
  {
    clusters.put(i, new ArrayList<PVector>() );
  }
  
  //keyでループ
  //for(var pv : map.keySet())
  //{
  //  int cluster_index = map.get(pv);
  //  clusters.get(cluster_index).add(pv);
  //}
  
  //keyとvalueでループ
  for(var entry : map.entrySet())
  {
    clusters.get(entry.getValue()).add(entry.getKey());
  }
  
  return clusters;
}

②クラスタ毎に重心を作る

重心はクラスタ毎に処理をするのでHashMap<Integer, List<PVector>>を使います。

エラー処理の類をなんにもしとらんことにご注意ください。

List<PVector>setup_centroid(HashMap<Integer, List<PVector>> clusters, int k)
{
  var centroids = new ArrayList<PVector>();  
  for(var entry : clusters.entrySet())
  {
    if(entry.getValue().size()<1){continue;}
    
    var centroid = CalucuCentroid(entry.getValue());
    centroids.add(centroid);
  }
  return centroids;
}

PVector CalucuCentroid(List<PVector> vectors)
{
  PVector centroid = new PVector(0,0);
  for(var v : vectors)
  {
    centroid.add(v);
  }
  return centroid.div(vectors.size());
}

③重心との距離に応じて各データのクラスタを振り直す

座標単位でクラスタインデックスを振りなおすのでHashMap<PVector, Integer>を使います。

void update_cluster(HashMap<PVector, Integer> map, List<PVector> vectors, List<PVector> centroids)
{
  for(int i = 0; i<vectors.size(); i++)
  {
    var t = vectors.get(i);   
    //最も近い重心のインデックス
    int centroid_index = nearest_index(centroids, t);
    map.put(t,centroid_index);
  }      
}


//最近接点(今回未使用)
PVector nearest_vector(List<PVector> vectors, PVector v)
{
  float nearest_distance = Float.MAX_VALUE;
  int nearest_index = 0;
  
  for(int i = 0; i<vectors.size(); i++)
  {
    var t = vectors.get(i);
    var distance = PVector.sub(t,v).mag();
    if(distance<nearest_distance)
    {
      nearest_distance=distance;
      nearest_index = i;
    }    
  }
  return vectors.get(nearest_index);
}

//最近接点のインデックス
int nearest_index(List<PVector> vectors, PVector v)
{
  float nearest_distance = Float.MAX_VALUE;
  int nearest_index = 0;
  
  for(int i = 0; i<vectors.size(); i++)
  {
    var t = vectors.get(i);
    var distance = PVector.sub(t,v).mag();
    if(distance<nearest_distance)
    {
      nearest_distance=distance;
      nearest_index = i;
    }    
  }
  return nearest_index;  
}




この記事が気に入ったらサポートをしてみませんか?