sonoshouのまじめなブログ

情報系大学生からのウェブ見習い人生の記録

自己組織化写像をprocessingで実装する。 part.4

お詫び

長らく更新が滞ってしまいました。
プログラム自体は随分前に書き終えていたのですが、
ブログの記事にするまでに時間がかかってしまいました。

……それだけなら良いのですが……。
当初自己組織化写像は簡単に実装できると考え、
コードを書きながらブログを更新していたのですが、
甘かったです。複雑になってしまいました><;
(力量不足。っていうのと丁寧なブログ記事にするのは割りと時間取られてしまう。)

つまり、説明が面倒になってしまいました。

結果

結果から先に示します!
10×10のマップで作った色の自己組織化写像です!
グラデーションになっていることが確認出来ればOKですよー!

f:id:sonoshou:20130713070450p:plain

コード

完成したコードを掲載します。
簡単のために、 Wikipedia:自己組織化写像の算法のステップにおける
4.重みベクトルを入力ベクトルに近づける処理を行う際、
最近傍のユニットのみ重みベクトルを更新しています。

final int MAP_WIDTH = 10;
final int MAP_HEIGHT = 10;
final int DIM = 3;
final int RAD = 20;
final float LEARNING_RATE = 0.001;
final float END_CONDITION = 0.05;

int[][][] unitArray;
int t;

void setup()
{
  size(int((RAD * sqrt(3) * (MAP_WIDTH-1)) + (10 * sqrt(3))), int((RAD * 3 * ((MAP_HEIGHT/2)-1)) + (RAD * 1.5)));

  unitArray = new int[MAP_HEIGHT][MAP_WIDTH][DIM];

  //1. set random vectors
  for (int i = 0; i < MAP_HEIGHT; i++)
  {
    for (int j = 0; j < MAP_WIDTH; j++)
    {
      // change
      for(int k = 0; k < DIM; k++)
      {
        unitArray[i][j][k] = int(random(256));
      }

      drawHexagon(i, j, color(unitArray[i][j][0], unitArray[i][j][1],unitArray[i][j][2]), color(0, 0, 0));

    }      
  }
}

void draw()
{
  //2. set a random input vector
  int[] inputArray = {int(random(256)),int(random(256)),int(random(256))};   

  //3. find Best Maching Unit
  int BMUi = 0, BMUj = 0;  
  float tempDistance;
  float minDistance = Float.MAX_VALUE;

  for (int i = 0; i < MAP_HEIGHT; i++)
  {
    for (int j = 0; j < MAP_WIDTH; j++)
    {
      tempDistance = calculateDistance(unitArray[i][j], inputArray);

      if(minDistance > tempDistance)
      {
        minDistance = tempDistance;
        BMUi = i;
        BMUj = j;
      }
    }
  }

  //4. update units around BMU
  updateUnits(BMUi, BMUj, inputArray);

  //5. noloop() if t reach at end condition
  if(calculateLearningRate(t) < END_CONDITION)
  {
    println("finish : t = " + t);
    noLoop();
  }

  t++;
}

//draw hexagon
void drawHexagon(int i, int j, color fillColor, color strokeColor)
{
    if (i % 2 == 0)
    {
      hexagon(j * RAD * sqrt(3), i * RAD * 1.5, RAD, color(unitArray[i][j][0],unitArray[i][j][1],unitArray[i][j][2]), color(0, 0, 0));
      } else {
      hexagon(j * RAD * sqrt(3) + (RAD/2) * sqrt(3) , i * RAD * 1.5, RAD,  color(unitArray[i][j][0],unitArray[i][j][1],unitArray[i][j][2]), color(0, 0, 0));
    }
}

// hexagon
void hexagon(float centerX, float centerY, float rad, color fillColor, color strokeColor)
{
  final float COS[] = {0, -0.8660254, -0.8660254, 0, 0.86602524, 0.86602524};
  final float SIN[] = {1, 0.5,  -0.5, -1, -0.5, 0.5};


  // color
  fill(fillColor);
  stroke(strokeColor);

  beginShape();
  for(int i = 0; i < 6; i++)
  {
    float tx = COS[i] * rad + centerX;
    float ty = SIN[i] * rad + centerY;
    vertex(tx, ty);
  }
  endShape(CLOSE);
}

// culcate distance
float calculateDistance(int[] unitArray1, int[] unitArray2)
{
  float distance;

  distance = sqrt(
             sq(unitArray1[0] - unitArray2[0]) + 
             sq(unitArray1[1] - unitArray2[1]) +
             sq(unitArray1[2] - unitArray2[2]));

  return distance;
}

// update unit
void updateUnits(int i, int j, int[] inputArray)
{
  int[][] dx = {{-1, -1,  0, 1, 0, -1}, {-1,  0,  1, 1, 1, 0}};
  int[][] dy = {{ 0, -1, -1, 0, 1,  1}, { 0, -1, -1, 0, 1, 1}};
  int ti, tj;

  // update weight vector
    for(int k = 0; k < 6; k++)
  {
    ti = i + dy[i%2][k];
    tj = j + dx[i%2][k];
    if(unitExists(ti, tj))
    {
      for(int l = 0; l < DIM; l++)
      {
        unitArray[ti][tj][l] = int(unitArray[ti][tj][l] + calculateLearningRate(t) * (inputArray[l] - unitArray[ti][tj][l]));
      }  
      drawHexagon(ti, tj, color(unitArray[ti][tj][0], unitArray[ti][tj][1],unitArray[ti][tj][2]), color(0, 0, 0));    
    }
  }
}

// Unit Exists
Boolean unitExists (int ti, int tj)
{
  return ti >= 0 && tj >= 0 && ti < MAP_HEIGHT && tj < MAP_WIDTH;
}

float calculateLearningRate(int t)
{
  return exp(-1 * LEARNING_RATE * t);
}

長くなってしまいました。
いろいろと変更しました。 コピペすれば動くはずなので、いろいろと試してみて下さい。

定数の説明

定数だけ説明します。
定数を説明すれば、いろいろと試せると思って。

コードの一番上の方に書かれているのが定数群です。

final int MAP_WIDTH = 10;
final int MAP_HEIGHT = 10;
final int DIM = 3;
final int RAD = 20;
final float LEARNING_RATE = 0.001;
final float END_CONDITION = 0.05;

MAP_WIDTHとMAP_HEIGHTはマップの個数を表しています。 DIMはDimension(次元数)です。
色が3次元なので、今回は3次元固定で大丈夫です。 RADはRadiusで、一つのマップ(六角形)の半径を表します。
LEARNING_RATEは学習係数を表します。
END_CONDITIONは学習率(学習係数と繰り返し回数から計算される)が一定以下の値になったかどうかを確かめるために用います。

以上が説明になります。

問題点

できた!と思いたいのですが、以下の図を見て下さい。
今回は20×20です。

f:id:sonoshou:20130713070659p:plain

汚いですよね。
同じような色が離れてしまっています。
これは、4.重みベクトルを入力ベクトルに近づける処理を行う際、
最近傍のユニットのみ重みベクトルを更新しているからです。
本来ならば、BMUの距離に応じて重みベクトルを更新しなければなりません。

この実装が大変だったので、
僕はコードの説明を諦めてしまうのでした。
続く。