はじめに
Pythonでホップフィールドネットワークを書いた記事はいくつか見つかる [1-3]。参考記事 [1] で紹介されている実装では、2次元のビットパターン画像を記憶・想起する仕様になっている。しかし、ホップフィールドネットワークの本質的な動作原理を理解するという観点からは、必ずしも画像である必要はない。
本記事では、よりシンプルで扱いやすいランダムな1次元のビットパターン系列に特化した実装を紹介する。1次元系列であれば、画像処理に関する特別な知識は不要で、ネットワークの挙動を直感的に把握できる。また、画像データの前処理にかかる手間を省けるため、手軽に実験を行える。
参考記事 [1] は事前に準備した記憶対象のパターンをロードする形で実装されているが、データを変えて実験するためには毎回データを事前に作成しておく必要があった。また参考記事 [2, 3] は共に、記憶対象のサンプルパターンがプログラムのうえで固定されているという課題があった。本記事では、パターンの系列長やパターン数を自由に設定できるランダムなパターン生成機能を提供することで、様々な条件でネットワークの動作を試せるように工夫した。
実装
以下に置いた。Enjoy!
main関数を示す。以下の順に沿って各処理が実行される。
- コマンドライン引数を解析
- ホップフィールドネットワークのクラスをインスタンス化
- 学習対象となるビットパターン系列のセットを取得
- Hebb則によりネットワークを学習
- 学習済ネットワークにより想起テストを実行
def main() -> None: """Run the Hopfield network simulation.""" # 1. Set up argument parser args = parse_arguments() # 2. Create Hopfield network instance hopfield = HopfieldNetwork( args.num_neurons, args.update_rule, args.neuron_threshold ) # 3. Define bit patterns to be learned patterns = define_patterns(args.num_neurons, args.num_patterns) # 4. Learn patterns by Hebbian learning rule hopfield.learn(patterns) # 5. Run recall test (initial state is a noisy version of each pattern) recall_test(hopfield, patterns, args)
コマンドライン引数で指定可能なパラメータを以下の表に示す。
| パラメータ名 | 説明 | デフォルト値 |
|---|---|---|
num_neurons |
ネットワークのニューロン数 | 20 |
num_patterns |
記憶対象のパターン数 | 3 |
max_iterations |
想起の最大繰り返し回数 | 100 |
recall_threshold |
想起の収束しきい値 | 1e-6 |
noise_strength |
各ニューロンがビット反転される確率 | 0.2 |
update_rule |
想起にかかる状態更新の方法 | "sync"(同期型) |
async_iterations |
非同期型の状態更新の繰り返し回数 | max_iterations |
neuron_threshold |
ニューロン発火のしきい値 | 0.0 |
想起にかかる状態更新の方法は "sync"(同期型)もしくは "async"(非同期型)のどちらかを選択可能である。
学習対象となるビットパターン系列を生成するのは define_patterns 関数である。系列長が num_neurons に等しいランダムビットパターンを、num_patterns 個だけ生成する。ビットパターン系列には +1 または -1 が並ぶ。これら系列のセットは1つの2次元アレイに格納される。
def define_patterns(num_neurons: int, num_patterns: int) -> npt.NDArray[np.int64]: """Define bit patterns to be learned. Args: num_neurons (int): The number of neurons in the network. num_patterns (int): The number of patterns to generate. Returns: npt.NDArray[np.int64]: A 2D NumPy array where each row represents a pattern. """ patterns: npt.NDArray[np.int64] = ( np.random.randint(0, 2, (num_patterns, num_neurons), dtype=np.int64) * 2 - 1 ) return patterns
連想記憶をシミュレーションする想起テストでは、学習パターンに対して「ビット反転」のノイズを加えたものをテストデータにしている。
実際にビット反転ノイズを加えるのは noisy_initial_state 関数である。noise_strength に等しい確率で各ニューロンをビット反転させる。
def noisy_initial_state( pattern: npt.NDArray[np.int64], num_neurons: int, noise_strength: float ) -> npt.NDArray[np.int64]: """Create a noisy initial state for a given pattern. Args: pattern (npt.NDArray[np.int64]): The original pattern. num_neurons (int): The number of neurons in the network. noise_strength (float): Probability that each neuron will be flipped. Returns: initial_state (npt.NDArray[np.int64]): The noisy initial state. """ # Filp each neuron with the probability of noise_strength. mask: npt.NDArray[np.int64] = np.random.choice( [-1, 1], (num_neurons,), p=[noise_strength, 1.0 - noise_strength] ) initial_state: npt.NDArray[np.int64] = pattern * mask return initial_state
Hebb則に基づいてネットワークの学習を行うのは HopfieldNetwork クラスの learn メソッドである。ネットワークの重みの更新に、ビットパターンを表すベクトルどうしのテンソル積(numpy.outer による計算)が現れるところに特徴がある。
def learn(self, patterns: npt.NDArray[np.int64]) -> None: """Learn the given patterns by Hebbian learning rule. Args: patterns (npt.NDArray[np.int64]): A 2D NumPy array where each row represents a pattern. """ # Adjust the weights by Hebbian learning rule num_neurons = patterns.shape[1] for pattern in patterns: self.weights += np.outer(pattern, pattern) self.weights /= num_neurons np.fill_diagonal(self.weights, 0.0)
パターン想起の処理を行うのは同じクラスの recall メソッドである。与えられたビットパターンからニューロンの初期状態が決まり、ネットワークの状態更新を「同期型」もしくは「非同期型」のルールに従って行う。
同期型:全てのニューロンの状態を同時に更新する。そのため、並列処理により高速化できるというメリットがある。しかし、ニューロン間の結合に特定のパターン(例:サイクル)がある場合、ネットワークが安定状態に収束するとは限らず、ネットワークの状態が特定のパターンを繰り返して変化し続けることがある。実際の脳のニューロンは、必ずしも全てのニューロンが同時に状態を更新するわけではないため、同期型更新は生物学的な妥当性に欠ける(という指摘もある)。
非同期型:一度に1つのニューロンの状態しか更新しない。そのため、エネルギー関数が単調に減少(単調非増加)することが保証され、ネットワークは安定状態に収束しやすいメリットがある。ただし、安定状態に収束するためには、全てのニューロンの状態が十分に更新される必要がある。非同期型更新は、脳のニューロンが非同期的に活動する様子をより良く反映していると考えられている。
いずれの更新ルールを用いても、最終的にネットワークが安定状態に収束したとき、初期値として与えたビットパターンに対応するパターンが想起されたことになる。しかし、ネットワークの設計、記憶させるパターン数、初期値との距離によっては、想起に失敗することもある。例えば、記憶させるパターン数が多すぎるとクロストークが発生し、誤ったパターンが想起されたり、エネルギー関数の局所解に陥り、本来想起されるべきパターンとは異なるパターンに収束してしまうことがある [3]。
実行例
デフォルト値であるニューロン数 20(=ビットパターン系列長 20) 、記憶対象パターン数 3 、ビット反転確率 0.2 、かつ同期型の状態更新("sync")を設定したときの実行例を示す。
Pattern 0: Recall successful! Correct pattern: ++++++--+--+--+-++++ Initial pattern: +++++++-+--+-+--++++ Recalled state: ++++++--+--+--+-++++ Closest pattern: 0 Minimum distance: 0 Pattern 1: Recall successful! Correct pattern: -+-+--+--+---+-+---- Initial pattern: -+++--+--+--++-+--+- Recalled state: -+-+--+--+---+-+---- Closest pattern: 1 Minimum distance: 0 Pattern 2: Recall successful! Correct pattern: ---+++++---++-+++-++ Initial pattern: --+++++--+-++-+++--+ Recalled state: ---+++++---++-+++-++ Closest pattern: 2 Minimum distance: 0
+1,-1 のニューロン値をそれぞれ +, - で表現している。Corrent pattern が記憶対象のビットパターン(正解データ)、Initial patternがビット反転誤りを含む入力ビットパターンである(テストデータ)。Recalled state が連想記憶によりホップフィールドネットが想起したパターンである。Closest pattern は Recalled state に最も近い正解パターンのインデックスを示し、Minimum distance はそのとき計算されたハミング距離を示している。
上記の結果から、ビット誤りを含むパターンが入力されても、適切に誤り訂正を行ってパターンを想起できている様子が確認できる。Closest pattern の値と入力パターンのインデックス値が一致しており、かつ Minimum distance も 0 となっているので、すべて想起成功である。
もちろん、常に想起成功するわけではない。想起失敗を含む実行例を以下に示す。パラメータは全てデフォルト値に設定した。
Pattern 0: Recall failed. Correct pattern: -+++----++++++----++ Initial pattern: +--+----++++++----+- Recalled state: -+-+-+----+-++----++ Closest pattern: 1 Minimum distance: 2 Pattern 1: Recall failed. Correct pattern: ---+-+----+-++----+- Initial pattern: ++-+-+-+--+-++-+--++ Recalled state: -+-+-+----+-++----++ Closest pattern: 1 Minimum distance: 2 Pattern 2: Recall successful! Correct pattern: -+-++--++---+------+ Initial pattern: ---++--++-+-+--+---+ Recalled state: -+-++--++---+------+ Closest pattern: 2 Minimum distance: 0
例えば入力パターン 0 の場合、想起結果に最も近いものはパターン 1 になってしまった(ハミング距離は 2 )が、元のパターン 0 ではないため、想起失敗である。
また、入力パターン 1 については、想起結果もパターン 1 が最も近く、準最適という意味では想起成功かもしれない。しかしながら、結果には依然として複数のビット誤りが含まれているため、想起失敗とみなす。
誤りの程度をどこまで許容して想起成功とするかの判断は文脈に依存する。最近傍探索によって得られたパターンが正解パターンと一致していれば、想起成功として許容するロジックもまた一つの選択である。
おわりに
本記事では、Pythonを用いてランダムビットパターン系列を連想記憶するホップフィールドネットワーク(Hopfieldモデル)を実装し、その基礎的な動作を解説した。
Hopfieldモデルはエネルギー最小化によって連想記憶を実現している。Simulated Annealing (SA) は、このようなエネルギーベースモデルにおいて、局所解からの脱出に有効な手法として知られている [4]。そこで、今後は本記事のHopfieldモデルにSAを導入し、その効果を検証するとともに、より高度な連想記憶モデルの構築を目指したい。実装結果については、改めて記事として公開する予定である。
以下はHopfieldモデルの勉強に役立つ記事やテキストの情報である。
Webテキスト『Juliaで学ぶ計算論的神経科学』には、HopfieldモデルやHebb則の概要が分かりやすくまとめられており、大いに参考になった。たとえば、なぜHebb則にベクトルどうしのテンソル積が現れるのか、その導出が簡潔に説明されている。このWebテキストは2025年春頃に書籍として出版予定であり、今から楽しみにしている。
参考記事 [3, 5] はHopfieldモデルの基礎を一通り初学者向けに解説した記事であり、モデルの理解を深めるのに役立つ。特に参考記事 [3] は具体的なPythonのコードを示しながら、クロストークや局所解への収束を解説しており、貴重な情報源である。
『数理科学 2024年10月号』[6] には、唐木田氏によるエネルギーベースモデルの解説記事が掲載されており、Hopfieldモデルについても触れられている。ただし、この雑誌は既に在庫切れを起こしており、入手困難なのが残念である。
Hopfieldモデルを扱った書籍として文献 [7] と [8] を挙げておく。文献 [9] はボルツマンマシンを中心に扱っているが、Hopfieldモデルとの関連が深いため、併せて参照するとよい。さらに、最近出版された文献 [10] は1978年に出版された同名の本が文庫化されたものであるが、連想記憶の神経回路網モデルが扱われている。同じ著者による文献 [11] には、Hopfieldモデルのダイナミクスが一章を割いて説明されている。
参考記事・参考文献
[1] パターンを記憶するニューラルネット,ホップフィールドネットワークを実装してみる #Python - Qiita
[2] Theano で Deep Learning <6の準備>: ホップフィールドネットワーク - StatsFragments
[3] ホップフィールドネットワーク 【Deep Learning アドベントカレンダー2020】 | AGIRobots Blog
[4] S. Kirkpatrick, C. D. Gelatt Jr, and M. P. Vecchi, "Optimization by simulated annealing," Science, 220(4598), 671-680, 1983.
[5] (Classical) Hopfield Networkについて詳しく解説 | AGIRobots Blog
[6] 数理科学 2024年 10月号, サイエンス社, 2024. Link
[7] 田中 章詞,富谷 昭夫,橋本 幸士,“ディープラーニングと物理学,” 講談社, 2019. Link
[8] 麻生 英樹 [ほか]; 神嶌 敏弘編,“深層学習,” 近代科学社, 2015. Link
[9] 恐神 貴行,“ボルツマンマシン,” コロナ社, 2019. Link