おいも貴婦人ブログ

生物系博士課程満期退学をしたAIエンジニアのブログ。

続:ホップフィールド(Hopfield Network)

大昔にHopfield Networks(HN:この略し方に一般性はありません。)についてのPythonプログラムを書いたが、あまりにも酷かったので書きなおしてみました。HNは連想記憶モデル、言い換えると、教師あり型のパターン認識方法の一種だと考えられます。教師あり型のパターン認識は一般的に、学習(learning,training)のフェーズと認識(recognizing)のフェーズがあります。

以下に紹介するプログラムは、教師データとして長さ35のリストで表された0,1を用意し、入力データを認識します。

#!/usr/bin/env python

import numpy as np

Number =[[1,1,1,1,1,
          1,0,0,0,1,
          1,0,0,0,1,
          1,0,0,0,1,
          1,0,0,0,1,
          1,0,0,0,1,
          1,1,1,1,1],
         [0,0,0,0,1,
          0,0,0,0,1,
          0,0,0,0,1,
          0,0,0,0,1,
          0,0,0,0,1,
          0,0,0,0,1,
          0,0,0,0,1]]

Numberは教師データとしての0と1を定義しています。1を黒、0を白と考え、心の目で見ていただくと、それぞれの外形が定義されていることが分かります。

前処理
size = len(Number[0])
Number = np.matrix(Number)
Number = np.where(Number==1,Number,-1)
N = len(Number)

X = [0,0,0,0,1,
     0,0,0,1,1,
     0,0,1,0,1,
     0,1,0,0,1,
     0,0,0,0,1,
     0,0,0,0,1,
     0,0,0,0,0]
X = np.array(X)
X = np.where(X!=0,X,-1)

sizeに7*5が入り、Numberをnumpyのマトリックスとして定義しなおし、0に-1を代入する(最初に0で定義したのは視覚的な理由)。Nに教師データの数を定義しています。
識別対象として、Xを用意しました。これも同様にnumpyのマトリックスで定義しなおし、0に-1を代入しています。

学習
def learning(Number,size):
    w = np.zeros((size,size))
    for inum in Num:
        t = inum.T.dot(inum)
        w += t
    for i in range(len(w)):
        w[i][i] = 0
    return w/N
W = learning(Number,size)

以下の数式を元に学習し、重み関数Wを定義しています。numpyのモジュールを使っていますが数式と見比べると分かりやすいと思います。

\(\bf{W} = \frac{1}{N}\sum_i^N \bf{x_i^T}\bf{x_i} (w_{ii} = 0)\)

識別(更新)
def recognizing(X):
    loop = 10
    for i in range(loop):
        Epre = energy(W,X)
        X1 = np.sign(W.dot(X))
        Eafter = energy(W,X1)
        if (np.abs(Epre - Eafter) < 0.0001):
            return X
        X = X1
    return X

ここでは、元の配列(X)と重み関数(W)からエネルギー(Epre)を計算し、HNで1step進んだ後の状態のそれを計算しています。
最終的にその差は、0に収束することから0.0001より小さくなったら、計算終了として、その状態を返します。

エネルギー関数
def energy(w,x):
    return -0.5*(np.dot(np.dot(x,w),x))

以下をエネルギー関数として定義しています。

\(\bf{E} = -\frac{1}{2}\bf{x}\bf{W}\bf{x^T} \)

最後に

非常に簡単なプログラミングなのに、時間がかかりました。(5hくらい...。単純なプログラムミスでしたが...。)この方法を使って、0~9までのパターンを学習させ、認識しようとすると、失敗します。これはどのパターン認識の方法にも言えることですが、教師データの次元が少なすぎることによります。教師データ同士がいかに直交しているかが重要となります。また、識別方法として、同期的更新と非同期的更新があります(今回は同期的更新)。

  • 同期的更新:全ユニットをまとめて状態更新する
  • 非同期的更新:ランダムに選択した1ユニットの状態を更新する
以下、妄想=>ボルツマンマシンでした...。

状態の出現確率をカノニカル分布に従うとして、詳細釣り合いの原理を満たすとすると、逆温度が定義でいる。そうなるとモンテカルロ・シミュレーションが実行可能で、アンサンブルを十分に取れば、自由エネルギーマップを書くことが出来る。そのマップを見れば、初期状態がどの状態にどれくらい近いか理解することができると思う。(多分、参考文献に書いていたような...。)

ついでにボルツマンマシン

結局、状態iから状態jの遷移は、\(exp(-\frac{\Delta Eij}{kT})\)に従うと言うもの。ここでは同期的更新から、非同期的更新にしています。recognizing以下のように書き換えるとボルツマンマシンが実行できます。

import copy
def recognizing(X):
    T = 0.3
    loop = 100
    for i in range(loop):
        Epre = energy(W,X)
        X1 = copy.copy(X)
        mp = np.random.randint(size)
        X1[mp] = -X1[mp]
        Eafter = energy(W,X1)
        if Eafter < Epre:
            X = X1
        else:
            p = np.random.random()
            if p<np.exp(-(Eafter-Epre)/T):
                X = X1
        print Epre
    return X

mpでランダムにユニットを選び、符号を逆転させます。ボルツマンマシンだと思いますが、何分、思いつきでコードを書いていますのであしからず。T=0.2付近がいい温度なのかも知れません。

参考URL:sinhrks.hatenablog.com
非常にわかりやすかったです。
参考文献:
ニューラルネットワークの統計力学 (パリティ物理学コース クローズアップ)