メトロポリス法
このページでは、複雑な確率分布をサンプリングする手法のひとつ、メトリポリス法について紹介する。
状態遷移と棄却法を組み合わせた乱数生成
正規分布に従う乱数列を得るアルゴリズムは色々と知られているし、Pythonに標準装備されているrandomモジュールを使えば
random.gauss(平均, 標準偏差)
で簡単に得ることができるが、ここでは、それらを全く知らないものと仮定して、
正規乱数を生成する方法を考えてみよう。まず、目的とする確率密度を
いま状態が
その際、とりあえず、乱数(例えば [-1,1]の範囲の一様乱数。左右対称な分布を持つ乱数ならば何でもよい)
ここで、
他方で、これと全く反対の状況、すなわち
状態が
確率の「流れ」の量が
確率過程(マルコフ連鎖)の理論が教えるところによれば、
上記の議論は、最初に
以上をまとめると、確率密度
の初期値を設定する- 「左右」対称な乱数
を使って、候補 を発生させる ならば、 の確率で を採択する。(採択しない場合は状態は のまま)。 ならば、 を採択する。- 更新した
を と置き直す。 - 「ときどき」
を出力する。 - 2から6のステップを繰り返す。
ここで、「ときどき」は、状態についての記憶(相関)が消えるくらいのステップ数、を意味する。
メトロポリス法では、棄却の判定条件(
メトロポリス法で正規分布に従う乱数列を発生させ、その分布を表示するPythonコードの例を以下に示す:
# coding: utf-8 import random import math import matplotlib.pyplot as plt def p(x): return 1/math.sqrt(2*math.pi)*math.exp(-x**2/2) x = 0 sdata=[ ] cnt=0 for _ in range(1000000): y = x + random.uniform(-1,1) alpha = min(1, p(y)/p(x)) r = random.uniform(0,1) if r > alpha: y = x x = y cnt += 1 if cnt%10==0: sdata.append(x) xdata=[] ydata=[] x=-5.0 while x<5.0: xdata.append(x) ydata.append(p(x)) x += 0.01 plt.title("NORMAL DISTRIBUTION") plt.plot(xdata,ydata,color=(0.0,0.0,0.7)) plt.hist(sdata, bins=100, density=True, color=(1.0,0,0.0)) plt.xlabel('x') plt.ylabel('P(x)') plt.legend() plt.grid(True) plt.show()
実行結果。赤棒がシミュレーションの結果得られたヒストグラム、青線が目的とした分布。
練習:二次元のガウス分布
メトロポリス法を使って、確率密度が
2次元のイジング模型
二次元の正方格子上に、小さな磁石(スピン)が並んでいて、隣(上下左右)と影響を及ぼし合っている。
磁石は「上向き」、または「下向き」に対応して+1と-1の2種類の状態のどちらかを取り、
隣同士が同じ向きのときは
スピン配列を
スピンの揃いやすさ
イジング模型の計算は、こちらのページで説明したように、ギブスサンプリングを使ったアルゴリズムでも可能である。 イジング模型の場合は各スピンの取り得る状態数が2つのみなのでギブスサンプリングが容易に実装できるが、状態が連続値を取るような問題を扱う場合は、メトロポリス法が適している。
エネルギーの式(1)を見ると、+1と-1について全く対称な形をしている。にもかかわらず、
ひとつのスピン
メトロポリス法によるイジング模型のシミュレーション
以上を踏まえると、二次元イジング模型の物理的な確率分布(カノニカル分布)をシミュレーションで再現するためには、以下のようにすればよい:
を決め、 に初期値を設定する(-1または+1) を一様かつランダムに選択する を計算する の一様乱数 を生成する なら、 を反転させる(そうでなければ、そのままにしておく)- (必要に応じて、磁化などの量を計算する)
- 2から6までを反復する
このアルゴリズムをPythonコードに翻訳した例を以下に示す。 状態を可視化するため pygletライブラリ(バージョン1.5.27以前)を使用しているので、必要に応じて追加インストールする必要がある。
右のコードでは変数J
がN
は系全体の大きさ(差し渡し)で、全体でSCALE
は、スピン1つあたりのマス目の大きさ。
# coding: utf-8 import pyglet from pyglet.gl import * import random import math import numpy as np # グローバル変数 J=0.4 N=100 SCALE=4 s=np.zeros([N,N]) counter=0 def alpha(i,j): return math.exp(-2*J*s[i,j]*(s[(i-1)%N,j]+s[(i+1)%N,j]+s[i,(j-1)%N]+s[i,(j+1)%N])) # 初期化 def init_state(): global s for i in range(N): for j in range(N): s[i,j] = random.choice([-1,+1]) # 状態更新 def update_state(n): global s,counter for _ in range(n): i = random.randrange(0,N,1) j = random.randrange(0,N,1) a = min(1,alpha(i,j)) r = random.uniform(0,1) if r < a: s[i,j] = -s[i,j] counter += 1 def next_frame(dt): return def redraw(): update_state(1000) for i in range(N): for j in range(N): if s[i,j]>0: glColor3d(0.8,0.3,0.1) else: glColor3d(0.2,0.6,0.7) glBegin(GL_POLYGON) glVertex2d((i+0)*SCALE,(j+0)*SCALE) glVertex2d((i+1)*SCALE,(j+0)*SCALE) glVertex2d((i+1)*SCALE,(j+1)*SCALE) glVertex2d((i+0)*SCALE,(j+1)*SCALE) glEnd() m = np.sum(s) / (N*N) t = counter/(N*N) label = pyglet.text.Label('t='+str(t)+' m='+str(m), font_name='Helvetica', color=(255,255,255,255), font_size=12, x=10, y=10) label.draw() init_state() config = pyglet.gl.Config(double_buffer=True) win = pyglet.window.Window(N*SCALE,N*SCALE,config=config,resizable=False) win.set_caption('2-d Ising Model') @win.event def on_draw(): glClear(GL_COLOR_BUFFER_BIT) glClearColor(0,0,0,1) redraw() pyglet.clock.schedule_interval(next_frame, 1/10.0) pyglet.app.run()
Pythonコードを実行している様子(YouTube)。 赤錆色が+1、浅葱色が-1。
Pyglet version 2用のコード
Pyglet 2以上をお使いの方はこちらのコードをお試しください。
import pyglet from pyglet.gl import * import random import math import numpy as np # グローバル変数 J=0.4 N=100 SCALE=4 s=np.zeros([N,N]) counter=0 config = pyglet.gl.Config(double_buffer=True) win = pyglet.window.Window(N*SCALE,N*SCALE,config=config,resizable=False) win.set_caption('2-d Ising Model') batch = pyglet.graphics.Batch() def alpha(i,j): return math.exp(-2*J*s[i,j]*(s[(i-1)%N,j]+s[(i+1)%N,j]+s[i,(j-1)%N]+s[i,(j+1)%N])) # 初期化 rects = np.empty(shape=(N,N),dtype='object') def init_state(): global s for i in range(N): for j in range(N): s[i,j] = random.choice([-1,+1]) rect = pyglet.shapes.Rectangle(x=i*SCALE, y=j*SCALE, width=SCALE, height=SCALE, batch=batch) if s[i,j]>0: rect.color = (230,50,20) else: rect.color = (30,170,200) rects[i,j] = rect @win.event def on_draw(): glClear(GL_COLOR_BUFFER_BIT) glClearColor(0,0,0,1) for i in range(N): for j in range(N): rect = rects[i,j] if s[i,j]>0: rect.color = (230,50,20) else: rect.color = (30,170,200) batch.draw() m = np.sum(s) / (N*N) t = counter/(N*N) label = pyglet.text.Label('t='+str(t)+' m='+str(m), font_name='Helvetica', color=(255,255,255,255), font_size=12, x=10, y=10) label.draw() # 状態更新 def update_state(n): global s,counter for _ in range(n): i = random.randrange(0,N,1) j = random.randrange(0,N,1) a = min(1,alpha(i,j)) r = random.uniform(0,1) if r < a: s[i,j] = -s[i,j] counter += 1 def update(dt): update_state(1000) init_state() pyglet.clock.schedule_interval(update,1/10) pyglet.app.run()
練習:二次元イジング模型
上のコード例を元にして、全体的にスピンの向きが揃う・揃わないの境界となるような