読者です 読者をやめる 読者になる 読者になる

StatModeling Memorandum

StanとRとPythonでベイズ統計モデリングします. たまに書評.

PyMC3の計算でGPUを使っている気がしただけの話

たまには浮気させてください。PyMC3は内部でTheanoを使っており、自動微分(auto-diff)が計算可能でStanのサンプラーであるNUTSも実装済みです。またTheanoがGPUに対応しているため、これはMCMCの超高速化が簡単にできるのではッ!と試した記事になります。

まずは環境設定から。Windows 7 64bitにVisual Studio Express 2012, CUDA 6, Anaconda 2.0.0 (python 2.7 + numpyとか), theano 0.6.0, PyMC3をインストールします。手順が少し多い場合は分かる範囲でDAG構造を描くのがオススメです。学術書の章案内もDAG構造があるといいですね。さて、今回の手順の依存性をDAG構造で表すと以下になります。

1. Visual Studio Express 2012のインストール

ここからisoをダウンロードして、マウントしてYes!Yes!Yes!でインストールしました。 CUDA 6から使うために追加で以下の作業が必要(こちらの記事を参考にしました)。

  • C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\BIN\x86_amd64C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\BIN\amd64 (最後の x86_amd64amd64 になっている)にコピーする。
  • C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\BIN\amd64\vcvarsx86_amd64.batvcvars64.bat にリネームする。

2. CUDA 6のインストール

ここから環境にあうexeを落としてYes!Yes!Yes!でインストール。 PATHが通っているか環境変数を見て確認。自分の場合はPATHに C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v6.0\bin が追加されていることを確認しました。

3. Anaconda 2.0.0のインストール

ここから環境にあうexeを落としてYes!Yes!Yes!でインストール。PATHも自動的に追加されました。コマンドプロンプトを立ち上げて以下を実行して確認します。

python -V

Python3系を使うためには追加でここに書いてある作業が必要。僕はデフォルト厨ですのでデフォルトのバージョンを使います。

4. theano 0.6.0のインストール

コマンドプロンプトを立ち上げて以下を実行します。

pip install theano

さらにTheanoでGPUを使うため環境変数に以下を新しい変数として設定します。 変数「THEANO_FLAGS」 値「mode=FAST_RUN,device=gpu,floatX=float32

5. theano + GPU のテスト

チュートリアルにある以下のpythonコードを実行して最後に「Used the gpu」と出力されるか確認します。

from theano import function, config, shared
import theano.tensor as T
import numpy
import time

vlen = 10 * 30 * 768  # 10 x
iters = 1000

rng = numpy.random.RandomState(22)
x = shared(numpy.asarray(rng.rand(vlen), config.floatX))
f = function([], T.exp(x))
print f.maker.fgraph.toposort()
t0 = time.time()
for i in xrange(iters):
    r = f()
t1 = time.time()
print 'Looping %d times took' % iters, t1 - t0, 'seconds'
print 'Result is', r
if numpy.any([isinstance(x.op, T.Elemwise) for x in f.maker.fgraph.toposort()]):
    print 'Used the cpu'
else:
    print 'Used the gpu'

気になる実行結果は…

CPUの場合

Looping 1000 times took 14.370000124 seconds
Result is [ 1.23178029  1.61879337  1.52278066 ...,  2.20771813  2.29967761 1.62323284]
Used the cpu

GPUの場合

Looping 1000 times took 1.16500020027 seconds
Result is [ 1.23178029  1.61879349  1.52278066 ...,  2.20771813  2.29967761 1.62323296]
Used the gpu

おおおおktkr!!10倍以上高速に実行できました!CPUはCorei7の速いやつでグラフィックカードは「GeForce GTX 660」です。いやーさすがに驚きです。元が遅すぎるのでは疑惑が出るほどです。

6. PyMC3のインストール

githubのページからzip(pymc-master)を落として解凍後、コマンドプロンプトを立ち上げてフォルダ内に入って以下を実行します。

python setup.py install

7. PyMC3 + GPU のテスト

PyMC3のチュートリアルにありました以下のpythonコードを実行して途中で「Using gpu device 0: GeForce GTX 660」とか出力されるか確認します。

import pymc as pm
import numpy as np

ndims = 2
nobs = 20

xtrue = np.random.normal(scale=2., size=1)
ytrue = np.random.normal(loc=np.exp(xtrue), scale=1, size=(ndims, 1))
zdata = np.random.normal(loc=xtrue + ytrue, scale=.75, size=(ndims, nobs))

with pm.Model() as model:
    x = pm.Normal('x', mu=0., sd=1)
    y = pm.Normal('y', mu=pm.exp(x), sd=2., shape=(ndims, 1))  # here, shape is telling us it's a vector rather than a scalar.
    z = pm.Normal('z', mu=x + y, sd=.75, observed=zdata)  # shape is inferred from zdata

with model:
    start = pm.find_MAP()

print("MAP found:")
print("x:", start['x'])
print("y:", start['y'])

print("Compare with true values:")
print("ytrue", ytrue)
print("xtrue", xtrue)

with model:
    step = pm.NUTS()

with model:
    trace = pm.sample(3000, step, start)

pm.traceplot(trace).savefig("traceplot.jpg")
print(trace[y].shape)

気になる実行結果は…

CPUの場合

MAP found:
('x:', array(-0.1509315453499642))
('y:', array([[-0.82497227], [ 2.22018515]]))
Compare with true values:
('ytrue', array([[-0.35525814], [ 2.46812207]]))
('xtrue', array([-0.4425249]))
C:\Program Files\Anaconda\lib\site-packages\theano\scan_module\scan_perform_ext.py:85: RuntimeWarning: numpy.ndarray size changed, may indicate binary incompatibility from scan_perform.scan_perform import *
  3000 of 3000 complete in 8.5 sec
 (3000L, 2L, 1L)
(iterationを100000にした場合は)
  100000 of 100000 complete in 293.9 sec
(100000L, 2L, 1L)

GPUの場合

Using gpu device 0: GeForce GTX 660
MAP found:
('x:', array(-0.4272603317772879))
('y:', array([[ 0.30743761], [-0.03717903]]))
Compare with true values:
('ytrue', array([[ 0.24040611], [-0.03926875]]))
('xtrue', array([-0.2838333]))
C:\Program Files\Anaconda\lib\site-packages\theano\scan_module\scan_perform_ext.py:85: RuntimeWarning: numpy.ndarray size changed, may indicate binary incompatibility from scan_perform.scan_perform import *
  3000 of 3000 complete in 10.3 sec
(3000L, 2L, 1L)
(iterationを100000にした場合は)
  100000 of 100000 complete in 306.1 sec
(100000L, 2L, 1L)

いやー高速化できてないわー。iterationを増やしても変わらないわー。知ってたけどね、世の中そんなに甘くないって。どうも先ほどのtheano_gpu_test.pyと比べるとCUDA(nvcc)のコンパイルが走っていない様子です。とするとPyMC3のNUTS()関数の内部計算をtheanoのGPUを使うように修正しないと高速化しないんでしょうね。このままだとStanよりもだいぶ遅い印象です。僕は現状pythonにあまり詳しくないので、誰か詳しい人のトライを待ちます。