Macでも高速に機械学習できるかもしれないPlaidMLを試してみた

Kerasの作者のfcholletさんがTwitterで紹介していた、PlaidMLという新しい機械学習用のライブラリを試してみた。`Early Alpha Quality`とのことであるが、試してみたらとても速いしKerasで書いてたコードが(ほぼ)そのまま使えて感激した。これは機械学習が捗る…かも?

## 短くまとめると
* PlaidMLという新しいフレームワークを使うと、nVidia積んでないMacでもGPU使って高速に機械学習できるかも
* Kerasのバックエンドとして使えるので、今まで書いてきたKerasのコードを(ほぼ)そのまま使える
* 動かしてみたら、確かに速い。
* 10月に試した段階では不具合があったが、アップデートで修正された!

## PlaidML

https://github.com/plaidml/plaidml

<blockquote class="twitter-tweet" data-lang="ja"><p lang="en" dir="ltr">PlaidML is an open-source Keras backend that runs on OpenCL, with good performance relative to TensorFlow: <a href="https://t.co/tCmzXHfFUO">https://t.co/tCmzXHfFUO</a> <a href="https://t.co/3YAQI0EMX5">pic.twitter.com/3YAQI0EMX5</a></p>&mdash; François Chollet (@fchollet) <a href="https://twitter.com/fchollet/status/921815941112930304?ref_src=twsrc%5Etfw">2017年10月21日</a></blockquote> <script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

TensorFlowと違って、OpenCLサポートがあるためAMDのGPUでもハードウェアアクセラレーションが効くらしい。そして、このPlaidMLはKerasのバックエンドとして使える。つまり、今まで書いてきたKerasのコードを、そのままバックエンドをPlaidMLに切り替えるだけで高速に実行できる可能性があるということ。

これはMacで機械学習を楽しむ上ではとても意味のあることなので、早速試してみた。

## OpenCLが使えるか調べる

`clinfo`を使って、使用中のMacのGPUでOpenCLが使えるか調べておこう。

```
brew install clinfo
clinfo
```

`Number of platforms`が1以上になっていたらOK。

## インストール

2017年10月末現在、PlaidMLは`pip`経由でのインストールが可能になっている。基本的にはpip経由でインストールする方が手軽でオススメ。

### pipでインストールする

2017/10/27から、Mac版もpip経由でのインストールが可能になったので、特別理由がなければpip経由でインストールするのが良い(2017/10/29追記)

#### virtualenvで環境を作る
必須ではないが、試行錯誤するにあたって環境を汚したくないのでvirtualenvで新しい環境を作る。

```
virtualenv --system-site-packages ~/plaidml
source ~/plaidml/bin/activate
pip install setuptools --upgrade
```

#### pipでインストールする

```
pip install plaidml-keras
```

### ソースコードからビルドする(リリース前の実装を試したい場合)
レポジトリ上の最新のコードからビルドしたい場合は、下記の手順でビルドできる。

#### 必要なライブラリを入れる
brewで必要なライブラリを入れておく。古い場合は適宜アップデートすること。(特にbazelは0.6.0以降が必要)

```
brew install bazel
brew install bison
brew install flex
```

#### virtualenvで環境を作る
必須ではないが、試行錯誤するにあたって環境を汚したくないのでvirtualenvで新しい環境を作る。

```
virtualenv --system-site-packages ~/plaidml
source ~/plaidml/bin/activate
pip install setuptools --upgrade
```

`setuptools`が古いとビルド/インストールに失敗するため、念のためアップデートしておく。`36.6.0`ではうまくいった。


#### ソースコードをクローンする
```
git clone https://github.com/plaidml/plaidml.git

```

#### ビルド+インストール
`bazel`を使ってビルド、作成されたwheelをインストールする。

```
bazel build -c opt plaidml:wheel plaidml/keras:wheel
pip install -U bazel-bin/plaidml/*whl bazel-bin/plaidml/keras/*whl
```

## PlaidMLの設定

インストールが完了したら、`plaidml-setup`を実行して環境の設定を行う。ここで計算に使用するGPUを指定する。

```
plaidml-setup 
```

基本的には質問に答えていくだけ。

```
PlaidML Setup (0.0.0.dev0)

...

Default Config Devices:
   No devices.

Experimental Config Devices:
   amd_radeon_pro_570_compute_engine.0 : AMD AMD Radeon Pro 570 Compute Engine
   intel(r)_core(tm)_i5-7500_cpu_@_3.40ghz.0 : Intel Intel(R) Core(TM) i5-7500 CPU @ 3.40GHz

Using experimental devices can cause poor performance, crashes, and other nastiness.

Enable experimental device support? (y,n)[n]:y
```

検証されていないハードウェアだと`Experimental`にリストされるようだが、とりあえず試してみる。

```
Multiple devices detected (You can override by setting PLAIDML_DEVICE_IDS).
Please choose a default device:

   1 : amd_radeon_pro_570_compute_engine.0
   2 : intel(r)_core(tm)_i5-7500_cpu_@_3.40ghz.0

Default device? (1,2)[1]:1

Selected device:
    amd_radeon_pro_570_compute_engine.0

```

AMDを選んでみた。

```

PlaidML sends anonymous usage statistics to help guide improvements.
We'd love your help making it better.

Enable telemetry reporting? (y,n)[y]:y

Almost done. Multiplying some matrices...
Tile code:
  function (B[X,Z], C[Z,Y]) -> (A) { A[x,y : X,Y] = +(B[x,z] * C[z,y]); }
Whew. That worked.

Save settings to /Users/foo/.plaidml? (y,n)[y]:
Success!
```

マトリクスの計算が行われ、どうやらうまく動いたようだ。この設定を`.plaidml`に保存すると、次回以降`PlaidML`を実行する際に、このハードウェア設定が使われるようだ。

## Kerasのバックエンドとして使ってみる

Kerasのバックエンドとして使う場合は、`import keras`より前に下記のコードを呼んであげれば良い。

```
import plaidml.keras
plaidml.keras.install_backend()
```

### VGG-LIKEなサンプルで比較してみる

試しに、KerasのホームページにあるVGG-LIKEのサンプルをそれぞれのバックエンドで実行してパフォーマンスを計測してみた。fitの処理にかかった時間のみを計測。参考までに、FloydHub(GPU使用)の結果も。

なお、処理が早く終わりすぎてしまうのでEPOCHは50に増やした。

| ライブラリ | マシン | 時間(四捨五入) |
|----------|-------|--------------|
| TensorFlow | Intel Core i5 2.9GHz | 145秒 |
| TensorFlow | Intel Core i5 3.4GHz | 117秒 |
| PlaidML | Intel Iris Graphics 550 | 110秒 |
| PlaidML | AMD Radeon Pro 570 | 55秒 |
| TensorFlow | FloydHub(GPUインスタンス) | 14秒 |

* iMac 2017 CPU 3.4 GHz Intel Core i5 + AMD Radeon Pro 570
* MacBook Pro 2016 CPU 2.9GHz Intel Core i5 + Intel Iris Graphics 550

なるほど。

本題から外れるけど、FloydHub圧倒的にはええ。

## 自分のコードで試してみた結果
### バックエンドの置き換えは簡単
自分のコードでも試してみた。

[TensorFlow + Kerasでフレンズ識別する - その1: 学習データの準備編](https://qiita.com/croquette0212/items/0fc7562f1bd2c78f96bf)

こちらの記事で使ったコードのバックエンドをPlaidMLに置き換えてみた。バックエンドの置き換え自体は本当に簡単で

```
import plaidml.keras
plaidml.keras.install_backend()
```

を加える以外、ほとんどやることはなかったのだが、TensorBoardなど一部TensorFlowの機能に依存した実装部分は当然外す必要があった。

### 学習に失敗した(不具合、解決済み)

ただ実際に学習を走らせてみたところ、途中でTrain Loss / Train Accuracy / Val Loss / Val Accuracy全てがゼロに収束してしまうという現象が起きてしまい、うまくいかなかった。

2017.10.23追記: どうやら、これはMac版のバグのようだ。

[model.fit() loss: nan #26](https://github.com/plaidml/plaidml/issues/26)

> Thanks Andy, we've seen issues on Mac as well and are working on tracking them down. Once we get that all ironed out we'll mark Mac "supported" and ship a new release with binaries.

2017.11.04追記: アップデートで対策された。

[Inconsistent computations #47](https://github.com/plaidml/plaidml/issues/47)

> Yes, the CPU issue is an known limitation of Apple's CPU OpenCL which allows only 1 thread per work-group. We have not addressed this, as the resulting CPU performance is likely to be underwhelming. As for the main issue, I've tracked the problem down to a set of kernels that behave incorrectly, but only on AMD and only on Apple's drivers. I'm still checking, but the kernels strongly appear to be correct, and I believe this may be a bug in the OpenCL drivers themselves. There is some discussion of similar correctness issues with Apple's OpenCL drivers in other OpenCL projects. That said, it appears to happen only for a subset of kernels. There is a configuration change that seems to work around the problem by avoiding such kernels. There may be a performance penalty for the change, and since I don't know what the specific driver issue is, I'm not sure if this workaround will fully resolve all correctness issues. However, it did fix the issue for me.

どうやら、この問題はmacOS用のOpenCLドライバーの問題のようだ。パフォーマンスは若干犠牲になるが、Ver.0.1.2でこの問題にはワークアラウンドが適用され、正しい計算結果が出るようになった。今後、ドライバーが修正されることを期待したい。


## まだ使うには早すぎるが、今後にとても期待

Macは公式には未対応、`Early Alpha Quality`の段階ということもあり、まだ実戦で使うには早すぎるのは当然かもしれないが、OpenCL対応で、かつKerasのコードをそのまま使えるというのは本当にありがたい。数週間以内にMacにも公式に対応予定とのことで、今後に期待したい。

## リンク
* [PlaidML](https://github.com/plaidml/plaidml)