機械学習機械学習
自己組織化写像;Self-organizing Maps

概要
自己組織化写像は入力層と出力層の2層からなる教師なしニューラルネットワークの一つである。
高次の入力ベクトルを低次(2,3次が多い)の出力ベクトルに写像し、入力ベクトルの類似度を出力ベクトルの距離に置き換え可視化することに用いられる。
ただし、類似度と距離は強く相関しないので多次元尺度構成法のほうが得られる画像は分かりやすい。

アルゴリズム
・パラメータ
 入力ベクトル : \(\boldsymbol{x_k} \), 入力ベクトル次元数 : \(V\)
 出力層 : \(\boldsymbol{y_{i j}} \), 出力層次元数 : \(M\)
 パラメータ変化影響範囲 : \(\sigma\), 学習定数 : \( \lambda(0 < \lambda < 1) \)
・初期値決定
 出力層をランダムに初期化
・学習シークエンス(マップの変化量がしきい値以下になるまで繰り返す)
 マップの変化量\( \boldsymbol{\Delta y_{i j}} \)を0に初期化
 各サンプルについて以下を繰り返す
 -サンプル\( \boldsymbol{x_k} \)ともっとも距離が近いベクトルのマップ上の位置\(\boldsymbol{z_{nearest}}\)を求める
 -マップ上の位置の各点\(\boldsymbol{z}=(i, j)^T\)に対して\( \boldsymbol{\Delta y_{i j}} = \boldsymbol{\Delta y_{i j}} + (\boldsymbol{x_k} - \boldsymbol{y_{i j}}) exp (-\frac{1}{2 \sigma^2} |\boldsymbol{z} - \boldsymbol{z_{nearest}} |^2 ) \)
 -\( \boldsymbol{y_{i j}} = \boldsymbol{y_{i j}} + \lambda \boldsymbol{\Delta y_{i j}} \)

自己組織化マップのコード

namespace MachineLearning {

    /// <summary>バッチ自己組織化マップ</summary>
    public class SelfOrganizingMap {

        /// <summary>コンストラクタ</summary>
        /// <param name="vector_dim">次元数</param>
        /// <param name="map_size">マップサイズ</param>
        /// <param name="vector_randomizer">ランダムベクトルを返す関数</param>
        public SelfOrganizingMap(int vector_dim, int map_size, Func<Vector> vector_randomizer) {
            if(vector_dim < 1) {
                throw new ArgumentException(nameof(vector_dim));
            }
            if(map_size < 1) {
                throw new ArgumentException(nameof(map_size));
            }
            if(vector_randomizer == null) {
                throw new ArgumentNullException(nameof(vector_randomizer));
            }
            if(vector_randomizer().Dim != vector_dim) {
                throw new ArgumentException(nameof(vector_randomizer));
            }

            VectorDim = vector_dim;
            MapSize = map_size;
            VectorRandomizer = vector_randomizer;

            InitializeMap();
        }

        /// <summary>ベクトル次元数</summary>
        public int VectorDim {
            get; private set;
        }

        /// <summary>マップサイズ</summary>
        public int MapSize {
            get; private set;
        }

        /// <summary>マップ</summary>
        public Vector[,] Map {
            get; private set;
        }

        /// <summary>ランダムベクトルを返す関数</summary>
        public Func<Vector> VectorRandomizer {
            get; private set;
        }

        /// <summary>学習</summary>
        /// <param name="sample_vectors">サンプルベクタ</param>
        /// <param name="lambda">学習定数</param>
        /// <param name="sigma">パラメータ変化影響範囲</param>
        public void Learn(List<Vector> sample_vectors, double lambda, double sigma) {
            //マップの変化量
            Vector[,] delta_map = new Vector[MapSize, MapSize];
            for(int x, y = 0; y < MapSize; y++) {
                for(x = 0; x < MapSize; x++) {
                    delta_map[x, y] = Vector.Zero(VectorDim);
                }
            }

            double threshold_dist = 10 * sigma * sigma, gamma = 1 / (2 * sigma * sigma);

            //各サンプルについてマップに与える変化量を求める
            foreach(var sample_vector in sample_vectors) {
                Vector reflash_vector = Vector.Zero(2), nearest_vector = NearestVector(sample_vector);

                for(int x, y = 0; y < MapSize; y++) {
                    reflash_vector.Y = y;

                    for(x = 0; x < MapSize; x++) {
                        reflash_vector.X = x;

                        double dist = Vector.SquareDistance(reflash_vector, nearest_vector);

                        if(dist > threshold_dist) {
                            continue;
                        }

                        delta_map[x, y] += (sample_vector - Map[x, y]) * Math.Exp(-gamma * dist);
                    }
                }
            }

            //マップの変化量を更新
            for(int x, y = 0; y < MapSize; y++) {
                for(x = 0; x < MapSize; x++) {
                    Map[x, y] += lambda * delta_map[x, y];
                }
            }
        }

        /// <summary>最近傍ベクトル</summary>
        /// <param name="sample_vector">サンプルベクタ</param>
        public Vector NearestVector(Vector sample_vector) {
            Vector nearest_vector = Vector.Zero(2);
            double dist, min_dist = double.PositiveInfinity;

            for(int x, y = 0; y < MapSize; y++) {
                for(x = 0; x < MapSize; x++) {
                    dist = Vector.SquareDistance(Map[x, y], sample_vector);
                    if(dist < min_dist) {
                        min_dist = dist;
                        nearest_vector.X = x;
                        nearest_vector.Y = y;
                    }
                }
            }

            return nearest_vector;
        }

        /// <summary>マップを初期化</summary>
        public void InitializeMap() {
            Map = new Vector[MapSize, MapSize];

            for(int x, y = 0; y < MapSize; y++) {
                for(x = 0; x < MapSize; x++) {
                    Map[x, y] = VectorRandomizer();
                }
            }
        }
    }
}

実行サンプル

namespace MachineLearning {
    class Program {
        static void Main(string[] args) {
            const int vector_dim = 15;

            Random random = new Random(5);
            SelfOrganizingMap som = new SelfOrganizingMap(vector_dim, 20, () => new Vector(new double[vector_dim].Select((_) => random.NextDouble()).ToArray()));

            List<Vector> vectors = new List<Vector>();
            List<string> labels = new List<string>();
            
            vectors.Add(new Vector(1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0));
            vectors.Add(new Vector(1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0));
            vectors.Add(new Vector(1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0));
            vectors.Add(new Vector(1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0));
            vectors.Add(new Vector(1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1));
            vectors.Add(new Vector(1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0));
            vectors.Add(new Vector(0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0));
            vectors.Add(new Vector(0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1));
            vectors.Add(new Vector(0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0));
            vectors.Add(new Vector(0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0));
            vectors.Add(new Vector(1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1));
            vectors.Add(new Vector(0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1));
            vectors.Add(new Vector(0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1));
            vectors.Add(new Vector(0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0));
            vectors.Add(new Vector(0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0));
            vectors.Add(new Vector(0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0));

            labels.Add("ハト");
            labels.Add("ニワトリ");
            labels.Add("アヒル");
            labels.Add("カモ");
            labels.Add("フクロウ");
            labels.Add("タカ");
            labels.Add("ワシ");
            labels.Add("キツネ");
            labels.Add("イヌ");
            labels.Add("オオカミ");
            labels.Add("ネコ");
            labels.Add("トラ");
            labels.Add("ライオン");
            labels.Add("ウマ");
            labels.Add("シマウマ");
            labels.Add("ウシ");

            Plot(som, vectors, labels, $"plot/smo_reflash0.png");
            
            for(int i = 1; i <= 200; i++) {
                som.Learn(vectors, 0.1, 2.5);
                if(i % 1 == 0) {
                    Plot(som, vectors, labels, $"plot/smo_reflash{i}.png");
                }
            }
        }

        static void Plot(SelfOrganizingMap som, List<Vector> vectors, List<string> labels, string filepath) {
            const int scale = 10, point_size = 2, margin = 40;

            Bitmap image = new Bitmap(som.MapSize * scale + margin * 2, som.MapSize * scale + margin * 2);

            using(Graphics g = Graphics.FromImage(image)) {
                g.Clear(Color.White);

                g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
                g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;

                for(int i = 0; i < vectors.Count; i++) {
                    Vector som_vector = som.NearestVector(vectors[i]);

                    g.FillEllipse(new SolidBrush(Color.Black), 
                        new Rectangle((int)som_vector.X * scale + margin - point_size, (int)som_vector.Y * scale + margin - point_size, point_size * 2, point_size * 2));
                    
                    g.DrawString(labels[i], new Font("Arial", 10), new SolidBrush(Color.Gray), new Point((int)som_vector.X * scale + margin, (int)som_vector.Y * scale + margin));
                }

            }

            image.Save(filepath, ImageFormat.Png);
        }
    }
}

結果
自己組織化写像

参考文献
九州工業大学 古川研究室
http://www.brain.kyutech.ac.jp/~furukawa/data/som.html
http://www.brain.kyutech.ac.jp/~furukawa/data/SOMtext.pdf

関連項目
ベクトルクラス

ライブラリライブラリ
確率統計確率統計
線形代数線形代数
幾何学幾何学
最適化最適化
微分方程式微分方程式
画像処理画像処理
補間補間
機械学習機械学習
クラスタリングクラスタリング
パズルゲーム・パズル
未分類未分類