TensorFlowをビルドしてcondaでインストールできるようにする


# TensorFlowをビルドしてcondaでインストールできるようにする

TensorFlowをソースコードからビルドし、さらに、`conda`でインストールできるようにしました。その結果、1.2倍ほど（雀の涙）速度が速くなりました。


## やったこと

大きく分けて、以下二つのことをしました。

1. TensorFlowのソースコードをビルドして、wheelファイルを作る
1. wheelファイルを、`conda`でインストール可能な形式にビルドする

1の時点で`pip`でインストール可能ですが、私の環境はAnacondaなので、一貫性のため、`conda`でインストールできるようにしました。


## 環境／構成

**環境**

- iMac (21.5-inch, 2017)
- CPU：2.3 GHz Intel Core i5
- メモリ：8GB

**構成**

- macOS High Sierra
- Homebrew
- pyenv
- Anaconda（3系）
- TensorFlow / Keras

[構築手順](https://qiita.com/tanuk1647/items/3cf2f4a348220d90c612)にしたがい構築していることを前提にしますが、他の構成でも読み替えればうまくいくと思います。ただし、Xcode、Command Line Toolsをインストールしておかないと、Bazelがコンパイラを見つけられずに失敗しそうな気がします。

## 動機

以下ログが出力されているのを見つけたこと。

```
2018-08-07 18:02:45.085140: I tensorflow/core/platform/cpu_feature_guard.cc:141] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
```

このログは、「君のPCのCPUが『拡張命令』をサポートしているにも関わらず、TensorFlowが拡張命令を使うようにコンパイルされていない」と言っています。 つまり、スペックを引き出しきれていないよ、ということです。

私の使っているTensorFlowは、[ANACONDA CLOUDのconda-forgeリポジトリ](https://anaconda.org/conda-forge/tensorflow)から取得した「tensorflow 1.9.0 py36_0」です。バージョン管理の一貫性のために、次の二つの選択肢のうち、前者を選んだということです。

- [ANACONDA CLOUD](https://anaconda.org/search?q=tensorflow)から`conda`でインストールする
- [TensorFlow公式サイト](https://www.tensorflow.org/install/install_mac#installing_with_anaconda)から`pip`でインストールする

ネットの総論では「ソースコードからビルドすれば、このログは出なくなり、多少なりとも速くなる。しかし、手間に見合うかどうか微妙なので、ログを無視するか、GPUを手に入れたほうがいい」とのことのようです。確かに、GPUだと10〜100倍速くなるらしいですからね。でも、ま、何事も経験ということで、ソースコードからのビルドを試してみました。


## wheel作成

JDK8をインストールします。（Bazelを動かすのに必要。）

```bash
brew cask install homebrew/cask-versions/java8
```

ビルドツールBazelをインストールします。

```bash
brew install bazel
```

作業ディレクトリを作成のうえ、TensorFlowのソースコードをクローンしてきます。時間をかけたくなかったので、バージョンをしぼりました。本稿執筆時点（2018/08/08）での最新安定バージョンです。

```bash
cd <作業ディレクトリ>
git clone --depth=1 -b v1.9.0 https://github.com/tensorflow/tensorflow
```

Python3に切り替えの上、構成ファイル作成プログラムを実行します。

```bash
pyenv versions
pyenv global <Python3の入っているAnaconda>
cd tensorflow
./configure
```

質問には基本デフォルトで回答します。つまり、\[return\]を連打すればよいです。その結果、`.bazelrc`と`.tf_configure.bazelrc`が生成されます。

さて、Bazelでビルドを開始します。まず、コマンド`bazel clean --expunge`を実行します。ビルド時、「ツールチェインの解決」に失敗することがあり、そのための予防措置です。おまじないのようなものだと思ってください。

次に、ビルド。以下、`--copt=m`に続いて、ログに出ていた拡張命令を小文字で記述します。mを忘れるとエラーになるので、気をつけてください。

```bash
bazel build -c opt --copt=-mavx2 --copt=-mfma -k //tensorflow/tools/pip_package:build_pip_package
```

***ビルドは時間がかかります。***私の場合、82分かかりました。さらに（TensorFlowのプログラマに対する）警告が山ほど出ますが、気にする必要はありません。

ビルドが完了したら、カレントディレクトリを移動せずに、以下コマンドを実行します。

```bash
bazel-bin/tensorflow/tools/pip_package/build_pip_package tensorflow.pkg
```

結果、カレントディレクトリ内にpkgファイルが生成されます。Finderでpkgファイルを右クリック→「パッケージの内容を表示」から、wheelファイルをコピーして取り出すことができます。

## conda-build

wheelファイル保管ディレクトリを作成し、取り出したwheelファイルを置きます。

新たに作業ディレクトリ2を作成し、直下に二種類のファイルを作成します。

まずは、`build.sh`。

```bash
#!/bin/bash

WHEELHOUSE=<wheelファイル保管ディレクトリへの絶対パス>

pip install --no-deps $WHEELHOUSE/tensorflow-1.9.0-cp36-cp36m-macosx_10_7_x86_64.whl

```

次に、`meta.yaml`。

```yaml
package:
  name: tensorflow
  version: "1.9.0"

build:
  number: 0
  entry_points:
    - freeze_graph = tensorflow.python.tools.freeze_graph:run_main
    - toco_from_protos = tensorflow.contrib.lite.toco.python.toco_from_protos:main
    - tflite_convert = tensorflow.contrib.lite.python.tflite_convert:main
    - toco = tensorflow.contrib.lite.python.tflite_convert:main
    - saved_model_cli = tensorflow.python.tools.saved_model_cli:main

requirements:
  build:
    - python
    - pip
  run:
    - python
    - absl-py >=0.1.6
    - astor >=0.6.0
    - gast >=0.2.0
    - numpy >=1.13.3
    - six >=1.10.0
    - protobuf >=3.4.0
    - tensorboard 1.9.*
    - termcolor >=1.1.0
    - grpcio >=1.8.6

test:
  imports:
    - tensorflow
  commands:
    - tensorboard --help | grep USAGE
    - freeze_graph --help
    - toco_from_protos --help
    - toco --help
    - saved_model_cli --help 

about:
  home: http://tensorflow.org/
  license: Apache 2.0
  license_family: Apache
  summary: TensorFlow is a machine learning library, base package contains only tensorflow.
  description: |
    TensorFlow provides multiple APIs.The lowest level API, TensorFlow Core
    provides you with complete programming control.
    Base package contains only tensorflow, not tensorflow-tensorboard.
  dev_url: https://github.com/tensorflow/tensorflow
  doc_url: https://www.tensorflow.org/get_started/get_started
  doc_source_url: https://github.com/tensorflow/tensorflow/tree/master/tensorflow/docs_src

```

内容は、[conda-forge](https://github.com/conda-forge/tensorflow-feedstock/tree/master/recipe)の真似事です。より応用の効くやり方としては、[維持管理手順](https://qiita.com/tanuk1647/items/c73aeb0d308dce800750#%E9%A0%85%E7%9B%AE3-%E4%BB%AE%E6%83%B3%E7%92%B0%E5%A2%83%E3%81%AE%E8%BF%BD%E5%8A%A0)を参考に、Pythonのみ、Anaconda無しの仮想環境を新規に追加。さらに、pipdeptreeとTensorFlowを`pip`で追加したのち、コマンド`pipdeptree`で依存パッケージを調べてみると良いでしょう。

ファイルが作成できたら、`conda`のパッケージをビルドします。テストも行われるので、15分はかかるでしょう。途中、新規にパッケージを追加する旨が表示されますが、ビルド／テスト用の環境に追加されるものであり、既存環境に追加されるものではないので安心してください。

```
conda build -c conda-forge <作業ディレクトリ2>
```

パッケージは、以下の二箇所に出力されます。

- ~/.pyenv/versions/anaconda<バージョン>/conda-bld/osx-64
- ~/.pyenv/versions/anaconda<バージョン>/pkgs

出力されたパッケージを検証するため、[維持管理手順](https://qiita.com/tanuk1647/items/c73aeb0d308dce800750#%E9%A0%85%E7%9B%AE3-%E4%BB%AE%E6%83%B3%E7%92%B0%E5%A2%83%E3%81%AE%E8%BF%BD%E5%8A%A0)を参考に、TensorFlowの入っていない仮想環境を一つ追加します。

仮想環境が追加できたら、ワークスペースに移動し、仮想環境に入ります。

```bash
cd <ワークスペース>
conda activate <作成した仮想環境>
```

仮想環境にTensorFlowとKerasをインストールします。内容確認、そしてインストールの順です。最後のコマンドは、TensorFlowが確かにローカルからインストールされたことを確かめるためのものです。

```bash
conda install --dry-run -c conda-forge --use-local tensorflow
conda install -c conda-forge --use-local tensorflow
conda install --dry-run keras
conda install keras
conda list | grep tensorflow
```

これで一連の手順は終わりです。最後に仮想環境から抜け、システムのPythonをデフォルト（system）に戻しておきましょう。

```bash
conda deactivate
pyenv global system
cd ~
pyenv versions
```


## 性能測定

Jupyter Notbookを用いて、性能を測定しましょう。性能測定用のコードはこんな感じです。

```python
from keras.datasets import mnist
mnist.load_data()

import time
start = time.time()

## mnist_cnn.py ##

K.clear_session()

elapsed_time = time.time() - start
print ("elapsed_time:{0}".format(elapsed_time) + "[sec]")
```

`## mnist_cnn.py ##`の箇所は、[mnist_cnn.py](https://github.com/keras-team/keras/blob/master/examples/mnist_cnn.py)から取得したコードで置き換えてください。


## 測定結果

**比較対象**

以下の三環境で性能を測定し、比較しました。

1. 拡張命令なし（＝[ANACONDA CLOUDのconda-forgeリポジトリ](https://anaconda.org/conda-forge/tensorflow)から`conda`でインストールした環境）
1. 拡張命令あり（＝今回作った環境）
1. 公式（＝[TensorFlow公式サイト](https://www.tensorflow.org/install/install_mac#installing_with_anaconda)から`pip`でインストールした環境）

**比較結果**

1. 拡張命令なし：elapsed_time:1364.960855960846\[sec\]
1. 拡張命令あり：elapsed_time:1112.0023930072784\[sec\]
1. 公式：elapsed_time:1381.5033478736877\[sec\]

| epoch | 1.拡張命令なし | 2.拡張命令あり | 3.公式 |
|:-----:|:--------------:|:--------------:|:------:|
|  1    | 116s           | 95s            | 117s   |
|  2    | 115s           | 93s            | 117s   |
|  3    | 113s           | 91s            | 113s   |
|  4    | 113s           | 92s            | 114s   |
|  5    | 113s           | 92s            | 114s   |
|  6    | 112s           | 93s            | 114s   |
|  7    | 113s           | 91s            | 114s   |
|  8    | 113s           | 91s            | 115s   |
|  9    | 113s           | 92s            | 113s   |
|  10   | 112s           | 91s            | 114s   |
|  11   | 112s           | 93s            | 114s   |
|  12   | 112s           | 92s            | 115s   |
| 平均  | 113.1s         | 92.2s          | 114.5s |

**考察**

t検定を行ってみました。


```python
from scipy import stats
without_expansion = [116,115,113,113,113,112,113,113,113,112,112,112]
with_expansion = [95,93,91,92,92,93,91,91,92,91,93,92]
official = [117,117,113,114,114,114,114,115,113,114,114,115]
res1 = stats.ttest_ind(without_expansion, official, equal_var = False)
res2 = stats.ttest_ind(without_expansion, with_expansion, equal_var = False)
print(res1)
print(res2)
```

    Ttest_indResult(statistic=-2.715855310874572, pvalue=0.012643877897195724)
    Ttest_indResult(statistic=42.099960209201704, pvalue=1.6828611571718665e-22)


t検定の結果に対する考察は以下のとおりです。

- 「拡張命令なし」と「公式」は、5%水準で有意差があると言える。だが、実際の差を見ると微差である。結局、バイナリを広く配布するわけで、特定のCPUに特化したビルドは行っていないのである。
- 「拡張命令あり」と「拡張命令なし」は、1%水準で有意差がある。速度にして1.23倍、時間にして0.81倍。CPUの拡張命令を使った結果、処理が速くなったと考えられる。

## まとめ

本稿で分かったことを、以下にまとめます。

- `pip`用のwheelファイルを元に、`conda`用のパッケージをビルドできる。
- TensorFlowをソースコードからビルドし、CPUの拡張命令を使うようにすることで、1.2倍ほど速度が速くなる。ただし、桁数が変わるほどは速くならない。

[この記事](https://qiita.com/tanuk1647/items/cdf2f18335008a428bf8)で書いたように、機械学習は仮説検証の繰り返しです。検証の精度を上げるには、桁数が変わる程度の変化が欲しいところです。

…誰か、NVIDIAのGPUを積んだ高性能マシンを買ってくれないかな。


## 参考サイト

- [TensorFlowのインストール](https://hinaser.github.io/Machine-Learning/deeplearning-by-tensorflow.html)
- [Python: Keras/TensorFlow の学習を CPU の拡張命令で高速化する (Mac OS X)](https://blog.amedama.jp/entry/2017/03/08/223308)
- [拡張命令セットでTensorFlowをコンパイルする方法](https://qiita.com/yijyun-wang/items/12a76667482551eadabc)
- [TensorFlow CPU最適化ビルド](http://yamori-jp.blogspot.com/2017/10/tensorflow-cpu.html)
- [TensorFlowをソースからインストールしてみる(Mac)](http://blue-black.ink/?p=3888)
- [TensorFlowをソースからビルドする方法とその効果](https://www.kabuku.co.jp/developers/tensorflow_source_build)
- [Building conda packages from scratch](https://conda.io/docs/user-guide/tutorials/build-pkgs.html)
- [Using wheel files with conda](https://conda.io/docs/user-guide/tasks/build-packages/wheel-files.html)
- [conda-forge/tensorflow-feedstock/recipe](https://github.com/conda-forge/tensorflow-feedstock/tree/master/recipe)
- [AnacondaRecipes/tensorflow_recipes/tensorflow-base](https://github.com/AnacondaRecipes/tensorflow_recipes/tree/master/tensorflow-base)
- [Python: pipdeptree でパッケージの依存関係を調べる](https://blog.amedama.jp/entry/2016/05/29/182402)
- [Docs » Command reference » conda build](https://conda.io/docs/commands/build/conda-build.html)
- [Docs » Command reference » conda install](https://conda.io/docs/commands/conda-install.html)
- [【Python】処理にかかる時間を計測して表示](https://qiita.com/fantm21/items/3dc7fbf4e935311488bc)
- [keras-team/keras/examples/mnist_cnn.py](https://github.com/keras-team/keras/blob/master/examples/mnist_cnn.py)