ChainerでSOMを実装してみた
こんにちは.
今回はタイトルの通り,自己組織化マップ(The Self-Organizing Maps,SOM)をChainerを使って実装してみました.
SOMとは
サラッと説明します.詳しくはコホネン先生の論文を読むか,検索してみてください.
SOMはニューラルネットの一種で,高次元データの集合の関係を低次元マップに写像する役割を持ちます. ネットワークの構造は入力層と競合層の二層から成っています. ネットワークに入力されるのが高次元データ(高次元である必要はありません)で, 競合層が低次元マップ(多くの場合が二次元のグリッドです)になっています.
学習は教師なし競合学習という方法で行われます. まず,ネットワークにベクトルを入力し,入力層・競合層間の結合重みのうち最も入力に類似するものを探索します. (この結合重みに接続されている競合層のニューロンを勝者ニューロンと呼びます) 勝者ニューロンとその周辺のニューロンの結合重みを入力ベクトルに近づけます. これを繰り返すことで,高次元データの低次元マップを形成していきます.
今回は以下の更新式で実装しました.
wはとあるニューロン間の結合重み,xは入力ベクトル,aは学習率,hは近傍係数を示しています. 学習率は適当な小さい値,近傍係数は更新するニューロンが勝者に近いほど大きい値になります. 今回は勝者を中心とするガウス関数で実装しました.
実装したコード
今回は気まぐれでGistを使ってみました.
MNISTを学習させたところ,以下のようなマップを得ることが出来ました. 似た形をした数字が近くに配置されています. 28x28ピクセルの画像という高次元データの関係性を2次元マップに写像したことになります.
簡単な解説
ネットワークの表現
ネットワークはchainer.links.Linearクラスを活用しています. 今回実装したSOMのモデルはバイアスは不要なので,nobiasをTrueに定めています.
competitive = L.Linear(in_size=None, out_size=self.width * self.width, nobias=True)
勝者決定
ここでは入力ベクトルと結合重みの類似度を内積で表現しています. 内積が最大だったニューロンが勝者になります.
def predict(self, x): ip = self.competitive(x) pos = np.argmax(ip.data) return pos/self.width, pos%self.width
近傍係数
個人的にこの部分の実装がミソだと思います. numpy.meshgridをつかって勝者ニューロンを中心としたXY平面のグリッドを生成しています. これをそのまま指数関数につっこんで,近傍係数を計算しています. for文を使わないのがポイントですね.
def __neighbor(self, center, var): y = np.abs(np.arange(-center[0], self.width - center[0])) x = np.abs(np.arange(-center[1], self.width - center[1])) xx, yy = np.meshgrid(x, y) d2 = xx**2 + yy**2 return np.exp(- d2 / (2 * (var**2)))
言い訳
お気づきの方もいらっしゃるかもしれませんが,今回の実装はChainerである必要があまりないです. NumPyを使えば良いです.
なんとなくで実装を始めてしまったので,実用性については全く考えていませんでした. CuPyを使った高速演算とか,バックプロパゲーションを活用した新しい学習法とか, なんとか活用できないでしょうか. 今度考えてみます.