CUBE SUGAR CONTAINER

技術系のこと書きます。

Python: Keras/TensorFlow の学習を GPU で高速化する (Mac OS X)

Keras というのは Python を使ってニューラルネットワークを組むためのフレームワーク。 Python でニューラルネットワークのフレームワークというと、他にも TensorFlow とか Chainer なんかが有名どころ。 Keras はそれらに比べると、より高い抽象度の API を提供しているところが特徴みたい。 実のところ Keras はデフォルトで TensorFlow をバックエンドとして動作する。 バックエンドとしては、他にも Theano が選べるらしい。

今回は Keras で組んだニューラルネットワークを GPU で学習させてみることにした。 そのとき CPU と比べて、どれくらい速くなるかを試してみたい。

使った環境は次の通り。

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.12.3
BuildVersion:   16D32
$ python --version
Python 3.5.3

当然だけどマシンには dGPU のグラフィックカードが載っている必要がある。 今回は GeForce GTX 675MX を使った。

まずは CPU で学習させるための下準備

まずは Python のパッケージ管理システムである pip を使って Keras をインストールする。

$ pip install keras

もし pip がそもそも入っていないときは、インストールしておこう。

$ curl -O https://bootstrap.pypa.io/get-pip.py | sudo python

ついでに言うと、システムにサードパーティ製のパッケージをどんどん突っ込んでいくと混乱の元になる。 なので virtualenv などを使って仮想環境を作れるようにした方が良い。 ここらへんに何を使うかは好みによる。

続いてバックエンドとして動作する TensorFlow (CPU 版) もインストールしておこう。

$ pip install tensorflow

インストールが終わったら python コマンドを実行して REPL を立ち上げよう。

$ python

REPL が起動したら Keras の API を使って MNIST データセットをダウンロードしよう。 これにはちょっと時間がかかる。

>>> from keras.datasets import mnist
Using TensorFlow backend.
>>> mnist.load_data()
Downloading data from https://s3.amazonaws.com/img-datasets/mnist.pkl.gz

ダウンロードが終わったら exit() 関数で REPL から抜けよう。

>>> exit()

続いて Keras のサンプルプログラムをダウンロードする。 これは Convolutional Neural Network (CNN) を使って MNIST データセットの画像を分類するものになっている。

$ curl -O https://raw.githubusercontent.com/fchollet/keras/master/examples/mnist_cnn.py

ちなみに、このプログラムにはリソースの開放関係でちょっとした問題があるっぽいのでパッチを当てておく。 これはそのうちいらなくなるかもしれないけど、余分にあったとしても特に問題はないコードなので。

$ echo 'K.clear_session()' >> mnist_cnn.py

CPU を使って学習させる

さて、上記でまずは Keras/TensorFlow を CPU を使って学習させる下準備が整った。

早速、先ほどダウンロードしてきたサンプルプログラムを実行してみよう。 time コマンドを先頭に入れることで学習にかかった時間を測ることにする。

$ time python mnist_cnn.py
Using TensorFlow backend.
X_train shape: (60000, 28, 28, 1)
60000 train samples
10000 test samples
Train on 60000 samples, validate on 10000 samples
Epoch 1/12
W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use SSE4.1 instructions, but these are available on your machine and could speed up CPU computations.
W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use SSE4.2 instructions, but these are available on your machine and could speed up CPU computations.
W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use AVX instructions, but these are available on your machine and could speed up CPU computations.
60000/60000 [==============================] - 78s - loss: 0.3744 - acc: 0.8854 - val_loss: 0.0887 - val_acc: 0.9716
Epoch 2/12
60000/60000 [==============================] - 78s - loss: 0.1357 - acc: 0.9595 - val_loss: 0.0634 - val_acc: 0.9800
Epoch 3/12
60000/60000 [==============================] - 79s - loss: 0.1033 - acc: 0.9693 - val_loss: 0.0572 - val_acc: 0.9818
Epoch 4/12
60000/60000 [==============================] - 78s - loss: 0.0890 - acc: 0.9740 - val_loss: 0.0448 - val_acc: 0.9853
Epoch 5/12
60000/60000 [==============================] - 76s - loss: 0.0784 - acc: 0.9767 - val_loss: 0.0423 - val_acc: 0.9865
Epoch 6/12
60000/60000 [==============================] - 80s - loss: 0.0710 - acc: 0.9788 - val_loss: 0.0384 - val_acc: 0.9871
Epoch 7/12
60000/60000 [==============================] - 84s - loss: 0.0656 - acc: 0.9808 - val_loss: 0.0364 - val_acc: 0.9891
Epoch 8/12
60000/60000 [==============================] - 84s - loss: 0.0606 - acc: 0.9818 - val_loss: 0.0354 - val_acc: 0.9888
Epoch 9/12
60000/60000 [==============================] - 79s - loss: 0.0555 - acc: 0.9839 - val_loss: 0.0336 - val_acc: 0.9883
Epoch 10/12
60000/60000 [==============================] - 76s - loss: 0.0545 - acc: 0.9837 - val_loss: 0.0325 - val_acc: 0.9895
Epoch 11/12
60000/60000 [==============================] - 76s - loss: 0.0500 - acc: 0.9856 - val_loss: 0.0325 - val_acc: 0.9900
Epoch 12/12
60000/60000 [==============================] - 78s - loss: 0.0476 - acc: 0.9860 - val_loss: 0.0328 - val_acc: 0.9893
Test score: 0.0328271338152
Test accuracy: 0.9893
python mnist_cnn.py  2470.93s user 664.19s system 326% cpu 16:00.66 total

MNIST を分類したときの正解率が 98.93% となっている。 かかった時間は 2471 秒で、ようするに 40 分もかかったことになる。

もし仮に一からニューラルネットワークを組むとしたら、もちろんこんな風に一発では上手くいかない。 たくさんのトライアンドエラーを繰り返して、その毎回にこんな時間がかかるとしたら気が遠くなってくる。

TensorFlow (GPU 版) をインストールする

CPU での学習にかかる時間に絶望したところで、次は代わりに GPU を使って学習させてみよう。 GPU は並列化しやすい単純な計算が得意で、ニューラルネットワークで内部的に用いられる行列演算もそれに当たる。

ニューラルネットワークがどうして行列演算を使うのかを知りたいときは、次の本を読むのがおすすめ。 この本はディープラーニングについて理論と実装の両面から理解を深められる貴重な本だと思う。

ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装

ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装

話が脱線したけど Keras/TensorFlow で組むニューラルネットワークを GPU で学習させるには CUDA が必要になる。 また、バックエンドとして動作する TensorFlow についても GPU 対応版のものをインストールする必要がある。 代わりに、サンプルコードに関しては、学習に CPU を使うときと GPU を使うときで同じものが使える。

まずは Homebrew を使って coreutils をインストールしておく。

$ brew install coreutils

もし Homebrew が入っていないという場合には公式サイトの記述をもとにインストールしておこう。

続いて CUDA をインストールする。 これも Homebrew Cask を使って入れられる。

$ brew tap caskroom/cask
$ brew cask install cuda

続いて cuDNN をインストールする。 これだけはコマンドラインでちょちょいとインストールすることができない。 なぜかというと、サイトでユーザ登録をしないとダウンロードできないようになっているから。

なので、まずは以下の公式サイトでユーザ登録をしよう。

NVIDIA cuDNN | NVIDIA Developer

その上で、今なら “cuDNN v5.1 Library for OSX” というバージョンをダウンロードしてくる。

ダウンロードできたら、それを CUDA のディレクトリに解凍して放り込む。

$ sudo tar -xvf cudnn-8.0-osx-x64-v5.1.tgz -C /usr/local

ここで念のためマシンを再起動しておこう。

$ sudo shutdown -r now

再起動が終わったら、まずは CPU 版の TensorFlow をアンインストールする。 そして、改めて GPU 版の TensorFlow をインストールしよう。 これには名前のサフィックスとして -gpu がついている。

$ pip uninstall -y tensorflow
$ pip install tensorflow-gpu

GPU を使って学習させる

さて、これで GPU を使ってニューラルネットワークを学習させるための準備が整った。 先ほどと同じサンプルコードを実行して GPU を使った学習を試してみよう。

$ time python mnist_cnn.py
Using TensorFlow backend.
I tensorflow/stream_executor/dso_loader.cc:135] successfully opened CUDA library libcublas.8.0.dylib locally
I tensorflow/stream_executor/dso_loader.cc:135] successfully opened CUDA library libcudnn.5.dylib locally
I tensorflow/stream_executor/dso_loader.cc:135] successfully opened CUDA library libcufft.8.0.dylib locally
I tensorflow/stream_executor/dso_loader.cc:126] Couldn't open CUDA library libcuda.1.dylib. LD_LIBRARY_PATH: :/usr/local/cuda/lib
I tensorflow/stream_executor/dso_loader.cc:135] successfully opened CUDA library libcuda.dylib locally
I tensorflow/stream_executor/dso_loader.cc:135] successfully opened CUDA library libcurand.8.0.dylib locally
X_train shape: (60000, 28, 28, 1)
60000 train samples
10000 test samples
Train on 60000 samples, validate on 10000 samples
Epoch 1/12
W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use SSE4.1 instructions, but these are available on your machine and could speed up CPU computations.
W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use SSE4.2 instructions, but these are available on your machine and could speed up CPU computations.
W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use AVX instructions, but these are available on your machine and could speed up CPU computations.
I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:874] OS X does not support NUMA - returning NUMA node zero
I tensorflow/core/common_runtime/gpu/gpu_device.cc:885] Found device 0 with properties:
name: GeForce GTX 675MX
major: 3 minor: 0 memoryClockRate (GHz) 0.719
pciBusID 0000:01:00.0
Total memory: 1023.69MiB
Free memory: 639.50MiB
I tensorflow/core/common_runtime/gpu/gpu_device.cc:906] DMA: 0
I tensorflow/core/common_runtime/gpu/gpu_device.cc:916] 0:   Y
I tensorflow/core/common_runtime/gpu/gpu_device.cc:975] Creating TensorFlow device (/gpu:0) -> (device: 0, name: GeForce GTX 675MX, pci bus id: 0000:01:00.0)
60000/60000 [==============================] - 16s - loss: 0.3717 - acc: 0.8865 - val_loss: 0.0904 - val_acc: 0.9714
Epoch 2/12
60000/60000 [==============================] - 13s - loss: 0.1365 - acc: 0.9596 - val_loss: 0.0616 - val_acc: 0.9809
Epoch 3/12
60000/60000 [==============================] - 13s - loss: 0.1048 - acc: 0.9694 - val_loss: 0.0515 - val_acc: 0.9841
Epoch 4/12
60000/60000 [==============================] - 13s - loss: 0.0894 - acc: 0.9736 - val_loss: 0.0439 - val_acc: 0.9857
Epoch 5/12
60000/60000 [==============================] - 13s - loss: 0.0769 - acc: 0.9771 - val_loss: 0.0401 - val_acc: 0.9869
Epoch 6/12
60000/60000 [==============================] - 13s - loss: 0.0700 - acc: 0.9790 - val_loss: 0.0360 - val_acc: 0.9885
Epoch 7/12
60000/60000 [==============================] - 13s - loss: 0.0646 - acc: 0.9806 - val_loss: 0.0364 - val_acc: 0.9881
Epoch 8/12
60000/60000 [==============================] - 13s - loss: 0.0611 - acc: 0.9819 - val_loss: 0.0345 - val_acc: 0.9882
Epoch 9/12
60000/60000 [==============================] - 13s - loss: 0.0553 - acc: 0.9838 - val_loss: 0.0323 - val_acc: 0.9889
Epoch 10/12
60000/60000 [==============================] - 13s - loss: 0.0534 - acc: 0.9841 - val_loss: 0.0307 - val_acc: 0.9898
Epoch 11/12
60000/60000 [==============================] - 13s - loss: 0.0504 - acc: 0.9851 - val_loss: 0.0297 - val_acc: 0.9895
Epoch 12/12
60000/60000 [==============================] - 13s - loss: 0.0474 - acc: 0.9860 - val_loss: 0.0298 - val_acc: 0.9899
Test score: 0.0298051692682
Test accuracy: 0.9899
python mnist_cnn.py  138.85s user 22.06s system 91% cpu 2:55.87 total

今度は 139 秒で学習が終わった。

CPU を使ったときが 2471 秒なので、およそ 17.8 倍も学習が高速化できた。 学習時間が約 5.6% に短縮されているともいえる。 これだけ違うとトライアンドエラーを繰り返すときにかかる時間は全く変わってくる。

まとめ

  • Keras/TensorFlow で学習に GPU を使いたいときは、以下のものを入れる
    • CUDA
    • cuDNN
    • GPU 対応版 TensorFlow
  • ソースコードは CPU/GPU どちらを学習に使うときであっても共用できる
  • 学習にかかる時間を CPU と GPU で比較してみた
    • 今回試したパターンでは GPU を使うと CPU 比で 17.8 倍も高速化できた
  • 結論: ディープラーニングをやるなら GPU は絶対に使おう

めでたしめでたし。