Up

自己組織化マップ(SOM: Self-Organizing Map)試作記

 

自己組織化マップ(SOM)の簡明な解説(神野ら、2009)があったので、Pythonで実装してみた。以下は忘備録である。

なお、プログラムファイルなどは圧縮ファイルfiles_som.zipにまとめた。ダウンロード解凍すれば自由に利用できる。

SOMは、高次元空間のデータを低次元(2次元など)上のニューロンと呼ばれる点に対応させる方法である。ニューロンは、2次元の場合は平面上に配置される。各ニューロンは参照ベクトルと呼ばれるベクトルをもち、参照ベクトルは入力データと同次元である。入力データがいずれかの参照ベクトルとよく対応するようにの値が調整される。具体的なアルゴリズムは以下のようである(神野ら、2009)。

SOMのアルゴリズム

入力データの次元をn、その個数をJMとおく。入力データの成分の値は、区間[0,1]の範囲になるようにスケーリングしておく。2次元の格子上にニューロンを配置し、各ニューロンにn次元参照ベクトルを付ける。

ステップ0:

参照ベクトルを一様乱数で初期化する。

ステップ数tの値を0とおく。

更新の繰り返し最大数をt_maxとおく。

ステップ1:

各入力データに対して以下の参照ベクトルの更新手続きを行う。

に最も近い参照ベクトルをとおく。

すべての参照ベクトルを次式で更新する。

ここで、

である。

すべての入力データによる参照ベクトルの更新が終わったら、

とおく。

ステップ2:

ならば、ステップ1に戻る。

ステップ3:

各入力データを、それぞれの最も近い参照ベクトルの位置にプロットする。

 

上のアルゴリズムをリスト1のようにPythonで実装した。

リスト1のプログラムを実行すると、入力データファイル名の入力が求められる(図)。図1では、ExcelデータファイルAnimals.xlsxが入力されている。

図1

 

データファイルは、図2の形式で用意する。図2は古川(2017の表1.1Excelで用意したものである。

                                                                                                                                 図2 ファイル名Animals.xlsx

 

第1行目に変数名、第1列にケース名が設定されている。データ値は2行目以降に入力されている。データ値の1は「該当する」、0は「該当しない」を表す。

図1において、データファイル名を設定するとファイルからデータが読み込まれて計算が始まるが、モジュールxlrdがないなどのエラーが出たときは、無いというエラーの出たモジュールをpipコマンドなどでインストールしてから実行し直せばよい。

計算が終了すると、図3のようなマップが表示される。

図3

 

各入力データのケース名が、一番近い参照ベクトルのニューロンの格子上の位置に表示されている。文字列の左下の角がその位置である。色は、参照ベクトルの近傍の傾きを表し、最も緩やかなところが緑、最も急峻なところが赤であるが、アルファ値で透明度の調節を行っている。

図3では、上に鳥類、下に哺乳類がまとまっているが、SOMにおける計算において初期値を乱数でランダムに与えているので、鳥類と哺乳類のグループ分けが左右に分かれることもある。

図2のデータの場合、図3に示されるように鳥類と哺乳類の2つのグループに分かれたマップが得られた。次に、ニューロンを2個とした場合のSOMを求めてみる。ニューロンが2個の場合のSOMを求めるプログラムをリスト3に示す。ニューロンの個数が2個であることを除いてリスト1とアルゴリズムは同じである。

リスト3のプログラムを実行すると、入力データファイル名の設定が求められる(図4)。

図4

 

図2のファイル名を設定してEnterキーを押すと計算が始まる。

計算が終了すると図5のグラフが表示される。

図5

 

横軸が各ケースとニューロン0の参照ベクトルとの距離の2乗であり、縦軸がニューロン1の参照ベクトルとの距離の2乗を表す。哺乳類がニューロン0の参照ベクトルに近く、鳥類がニューロン1の参照ベクトルに近いことが分かる。

 

 

参考文献

古川徹生(2017).自己組織化マップ入門.Ver. 1.00.00.

神野健二、横田いずみ、井芹慶彦、井料隆太(2009).自己組織化マップ(Self-Organizing Maps)法と応用例紹介.地下水学会誌、51151162

 

 

リスト1 SOMのプログラム例(MainSom.py):本プログラムはリスト2のモジュールdrawmap.pyを用いている。

import pandas as pd

import numpy as np

import scipy.stats as ss

from drawmap import *

 

#

#       Excelファイルの読み込み

#

flnm = input('Data File Name (*.xlsx) = ')

xlsx = pd.ExcelFile(flnm)      

data = pd.read_excel(xlsx) 

 

X = data.values

var_names = data.columns.values

print(var_names)

n = len(X[0]) - 1

JM = len(X)

print('n = ', n)

print('JM = ', JM)

 

case_labels = X[:,0]

print('case_labels = ', case_labels)

 

xi = np.empty((JM, n))                  #   入力データ

for i in range(JM):

    for j in range(n):

        xi[i][j] = X[i][j + 1]

print('xi =\n', xi)

 

g_size = 10

m = np.empty((g_size, g_size, n))       #   参照ベクトル

for i in range(g_size):

    for j in range(g_size):

        for k in range(n):

            m[i][j][k] = ss.uniform.rvs()

 

t_max = 100

diffs = np.empty((g_size, g_size))

a_0 = 0.5

sgm_t = g_size / 2

for t in range(t_max):

    print('t = ', t)

    a_t = a_0 * (t_max - t) / t_max

    for IN in range(JM):

        for i in range(g_size):

            for j in range(g_size):

                diffs[i][j] = ((m[i][j] - xi[IN]) ** 2).sum()

        tc = np.argmin(diffs)       #   勝者ベクトル

        tc_i = tc // g_size         #   勝者ベクトルの行位置

        tc_j = tc % g_size          #   勝者ベクトルの列位置

        #

        #       参照ベクトルの更新

        #

        for i in range(g_size):

            for j in range(g_size):

                h_cr_IN = a_t * np.exp(-((i - tc_i)**2 + (j - tc_j)**2) /

                                       (2 * (sgm_t**2)))

                m[i][j] = m[i][j] + h_cr_IN * (xi[IN] - m[i][j])

 

    sgm_t = 1 + (sgm_t - 1) * (t_max - t) / t_max

       

#

#       マップの描画

#

draw_som(case_labels, xi, m)

 

 

リスト2 リスト1用描画モジュールdrawmap.py

 

import matplotlib.pyplot as plt

import numpy as np

 

def draw_som(case_labels, xi, m):

    JM = len(xi)

    n = len(xi[0])

    g_size = len(m)

 

    #

    #       参照ベクトルの傾き

    #

    slopes = np.empty((g_size, g_size))

    for i in range(0, g_size):

        for j in range(0, g_size):

            count = 0

            v = 0.0

            for ix in range(-1, 2):

                for jy in range(-1, 2):

                    try:

                        if ix != 0 or jy != 0:

                            if (i+ix) >= 0 and (j+jy) >= 0:

                                v += np.absolute(m[i+ix][j+jy] - m[i][j]).sum()

                                count += 1

                    except:

                        pass

          

            slopes[i][j] = v / count

    slope_max = np.ma.max(slopes[0:g_size, 0:g_size])

    slope_min = np.ma.min(slopes[0:g_size, 0:g_size])

 

    #       日本語表記の設定

    plt.rcParams['font.family'] = ['Yu Gothic', 'Noto Sans CJK JP',

                                   'Hiragino Maru Gothic Pro']

 

    #

    #       傾きに応じた色付け

    #

    plt.figure(figsize = (7, 7))

    plt.axis('off')

    plt.title('自己組織化マップ\nSOM', fontsize = 20)

    for i in range(0, g_size):

        for j in range(0, g_size):

            vc = (slopes[i][j] - slope_min) / (slope_max - slope_min)

            plt.fill_between([i-0.5, i+0.5], [j-0.5, j-0.5], [j+0.5, j+0.5],

                             color = (vc, 1.0-vc, 0.0), alpha = 0.5)

 

    m_c = []

    diffs = np.empty((g_size, g_size))

    plt.xticks([-1, g_size+1], [' ', ' '])

    plt.yticks([-1, g_size+1], [' ', ' '])

    for IN in range(JM):

        #

        #       勝者ベクトル

        #

        for i in range(g_size):

            for j in range(g_size):

                diffs[i][j] = ((m[i][j] - xi[IN]) ** 2).sum()

        tc = np.argmin(diffs)

        tc_i = tc // g_size

        tc_j = tc % g_size

        #

        #       勝者ベクトルの位置にプロット

        #

        plt.text(tc_i, tc_j, case_labels[IN])

 

    plt.show()

 

 

リスト3 2個のニューロンに対するSOMを求めるプログラム(MainTwoClusters.py)。このプログラムはリスト3に示す描画用モジュールmap2clusters.pyを用いている。

import pandas as pd

import numpy as np

import scipy.stats as ss

from map2clusters import *

 

#

#       Excelファイルの読み込み

#

flnm = input('Data File Name (*.xlsx) = ')

xlsx = pd.ExcelFile(flnm)              

data = pd.read_excel(xlsx)         

 

X = data.values

var_names = data.columns.values

print(var_names)

n = len(X[0]) - 1

JM = len(X)

print('n = ', n)

print('JM = ', JM)

 

case_labels = X[:,0]

print('case_labels = ', case_labels)

 

xi = np.empty((JM, n))          #   入力データ

for i in range(JM):

    for j in range(n):

        xi[i][j] = X[i][j + 1]

print('xi =\n', xi)

 

g_size = 2

m = np.empty((g_size, n))       #   参照ベクトル

for i in range(g_size):

    for k in range(n):

            m[i][k] = ss.uniform.rvs()

#print('m[0] = n', m[0])

 

t_max = 100

diffs = np.empty(g_size)

a_0 = 0.5

sgm_t = g_size / 2

for t in range(t_max):

    print('t = ', t)

    a_t = a_0 * (t_max - t) / t_max

    for IN in range(JM):

        for i in range(g_size):

            diffs[i] = ((m[i] - xi[IN]) ** 2).sum()

        tc = np.argmin(diffs)

        for i in range(g_size):

            h_cr_IN = a_t * np.exp(-((i - tc)**2) /

                                       (2 * (sgm_t**2)))

            m[i] = m[i] + h_cr_IN * (xi[IN] - m[i])

 

    sgm_t = 1 + (sgm_t - 1) * (t_max - t) / t_max

       

       

draw_som_2clusters(case_labels, xi, m)

 

 

リスト4 描画モジュールmap2clusters.py

import matplotlib.pyplot as plt

import numpy as np

 

def draw_som_2clusters(case_labels, xi, m):

    JM = len(xi)

    n = len(xi[0])

    g_size = len(m)

 

    diffs0 = []

    diffs1 = []

    for i in range(JM):

        v = ((m[0] - xi[i]) ** 2).sum()

        diffs0.append(v)

        v = ((m[1] - xi[i]) ** 2).sum()

        diffs1.append(v)

   

 

    plt.plot(diffs0, diffs1, 'bo')

    for j in range(JM):

        plt.text(diffs0[j], diffs1[j], case_labels[j])

    plt.xlabel('$Dist.^2$ from Unit-0', fontsize = 12)

    plt.ylabel('$Dist.^2$ from Unit-1', fontsize = 12)

    plt.show()

 

 

 

Up