TensorFlowにiOSサポートが追加されたそうなので試してみた

[TensorFlow に iOS サポートが追加された](http://japan.cnet.com/news/service/35083908/) というニュースを見かけたので、<b>ビルドして、iOSで動作</b>させてみました。

<img src="https://qiita-image-store.s3.amazonaws.com/0/3180/4b7ddd7b-357d-a2fc-32a7-64878e020de5.jpeg" width="240">

（<b>たまたま目の前にあった扇風機もバッチリ認識</b>してくれました）


本記事では<b>最終的にうまくいった手順</b>を書いています。この手順をなぞってみるにあたって<b><span class="deco" style="color:#FF0000;">TensorFlowや機械学習・ディープラーニングについての専門知識は不要</span></b>ですのでぜひお試しください！


##ビルド手順

###（2017.4.15追記）v1.1.0 RC2 のビルド

現時点での最新Release（候補）である v1.1.0 RC2 も、`tensorflow/contrib/makefile/build_all_ios.sh` を実行するだけでビルドできました。

###（2016.8.22追記）v0.10.0 RC0 のビルド

現時点での最新Releaseである [v0.10.0 RC0](https://github.com/tensorflow/tensorflow/releases/tag/v0.10.0rc0) は、下記手順でビルドしようとすると compile_ios_tensorflow.sh を実行するところでコケてしまったのですが、今は各ビルドの手順がまるっと入ったスクリプトが用意されているようです。

```
tensorflow/contrib/makefile/build_all_ios.sh
```

###（※2016年7月の本記事公開時の古い手順）

iOSサポートバージョンである<b>v0.9.0をチェックアウト</b>して、以下の手順でビルドしていきます。手順はこの通りになぞるだけですし、<b>ほぼスクリプトを実行するだけ</b>で非常に簡単です（が、<span class="deco" style="color:#FF0000;">実行時間がかなりかかります</span>）。


- スクリプトを実行して Eigen や Protobuf 等の依存ライブラリをダウンロード

```bash
cd tensorflow
tensorflow/contrib/makefile/download_dependencies.sh
```


- protobuf をビルド＆インストール

```bash
cd tensorflow/contrib/makefile/downloads/protobuf/
./autogen.sh
./configure
make
sudo make install
```


ここで、僕の環境では `./autogen.sh` 実行時に

```
Can't exec "aclocal": No such file or directory at /usr/local/Cellar/autoconf/2.69/share/autoconf/Autom4te/FileUtils.pm line 326.
autoreconf: failed to run aclocal: No such file or directory
shuichi-MacBook-Pro-Retina-15inch:protobuf shuichi$ ./configure
-bash: ./configure: No such file or directory
```

というエラーが出ました。["aclocal"というのは、"automake"パッケージに含まれているらしい](http://qiita.com/DQNEO/items/11d6056547cab0632901)ので、automakeをインストール。

```bash
brew instal automake
```


で、改めて

```bash
./autogen.sh
./configure
make
sudo make install
```


- iOS native版のprotobufをビルド

```bash
cd ../../../../..
tensorflow/contrib/makefile/compile_ios_protobuf.sh
```


- iOSアーキテクチャ向けにTensorFlowをビルド

```bash
tensorflow/contrib/makefile/compile_ios_tensorflow.sh
```

**依存ライブラリのダウンロードからiOSアーキテクチャ向けビルドまで、これ一発でok**でした。


##サンプルをビルドする

`tensorflow/tensorflow/contrib/ios_example` に2種類のサンプルがあります。


simple と名付けられたサンプルアプリは、今回<b>ビルドした static library をアプリ内に組み込む際の最小実装</b>としての参考にはなりそうですが、デモとして見て楽しい機能があるわけではないので、本記事ではもう一方の camera の方を紹介します。


まず、[このファイル](https://storage.googleapis.com/download.tensorflow.org/models/inception5h.zip)をダウンロードして、解凍すると入っている、

- imagenet_comp_graph_label_strings.txt
- tensorflow_inception_graph.pb

の2つのファイルを、`tensorflow/contrib/ios_examples/camera/data` に置きます。[^1]

[^1]: ちなみにこのとき、 https://github.com/miyosuda/TensorFlowAndroidDemo/find/master にある同名ファイルを使っても、ちゃんと動きません。[参考](https://github.com/tensorflow/tensorflow/issues/2923)


この Inception というのはGoogleが提供する<b>画像認識用の学習済みモデル</b>のようです。

- https://research.googleblog.com/2016/03/train-your-own-image-classifier-with.html
- https://github.com/google/inception


##cameraサンプル実行例

cameraサンプルを実行すると、すぐにカメラが起動します。身近のものを色々と映してみました。（リアルタイムカメラ入力に対する認識結果を表示してくれます）


###MacBook

<img src="https://qiita-image-store.s3.amazonaws.com/0/3180/eb67230c-ae2b-22ae-4e95-e5b0fb17c0cf.jpeg" width="240">

"Notebook" "Laptop" どっちでも正解！


###扇風機

<img src="https://qiita-image-store.s3.amazonaws.com/0/3180/4b7ddd7b-357d-a2fc-32a7-64878e020de5.jpeg" width="240">

"Electric Fan" 正解！


###椅子

<img src="https://qiita-image-store.s3.amazonaws.com/0/3180/120b6678-aead-b9f7-b460-5b1164095395.jpeg" width="240">

"Folding Chair"（折りたたみ椅子） 惜しい。。ですが、そもそも `imagenet_comp_graph_label_strings.txt` には chair が3種類しかなくて、その中では一番近いと言ってもいいかもしれません。


###iPhone

<img src="https://qiita-image-store.s3.amazonaws.com/0/3180/5a587869-cfec-565e-2765-c115d66acd92.jpeg" width="240">

"iPod"・・・惜しい！<b>iPhone 6 Plusなのでほぼ正解</b>と言ってもいいかもしれません。


##パフォーマンス

<del>今回cameraサンプルを試したデバイスはiPhone 6だったのですが、**認識はまだまだリアルタイムとは言い難く、数秒単位の遅延**がある感じでした。まだiOS向けにビルドできるようになったというだけで<b>iOSにおける GPU Accelerated はされてない</b>ですし、パフォーマンスの最適化はまだまだこれからのようです。</del> （2017.4.15追記）現時点での最新タグv1.1.0 rc2 / iPhone 7 で試したところ、<b>それなりにリアルタイム</b>で動きます。`contrib/ios_examples` 配下のコミットログを見たところ、**Metal等による最適化はまだ**されてないようですが、いくつか**パフォーマンスに関連しそうな改善**はあったようです。

- [Improved iOS camera example and binary footprint optimizations by petewarden · Pull Request #4457 · tensorflow/tensorflow](https://github.com/tensorflow/tensorflow/pull/4457)
- [Reduce memory usage and increase performance for convolution on iOS by petewarden · Pull Request #3778 · tensorflow/tensorflow](https://github.com/tensorflow/tensorflow/pull/3778)

（追記ここまで）

ちなみにiOS 10 で Accelerate.framework に追加された <b>BNNS</b> (Basic neural network subroutines) や MetalPerformanceShaders.framework に追加されたCNNのAPIを使用する最適化もissueに挙がっています。

- [Use Basic neural network subroutines (BNNS) on iOS &#183; Issue #3001 &#183; tensorflow/tensorflow](https://github.com/tensorflow/tensorflow/issues/3001)
- [Support MPSCNN (MetalPerformanceShaders) on iOS · Issue #7958 · tensorflow/tensorflow](https://github.com/tensorflow/tensorflow/issues/7958)

MPSCNNについてはいくつか記事を書いたのでよろしければ：

- [iOSのMPSCNNに渡すモデルパラメータのフォーマット / TensorFlowからの書き出し - Qiita](http://qiita.com/shu223/items/c735ab888a83f214f89a)
- [iOSのMetalで畳み込みニューラルネットワーク - MPSCNNを用いた手書き数字認識の実装 - Qiita](http://qiita.com/shu223/items/bdd537f38872a5af4e4c)


##ビルド手順の情報ソース

iOSサポートバージョンである[0.9.0](https://github.com/tensorflow/tensorflow/tree/v0.9.0)の**トップ階層にあるREADMEを見ても、iOSについては書かれていません**。

上述したビルド手順は[`contrib/makefile` 配下のREADME](https://github.com/petewarden4prs/tensorflow/blob/c7c966eb2f1ac5d89ce7999d7eab53783e74abb7/tensorflow/contrib/makefile/README.md#ios)に書かれています。

関連情報を以下にまとめておきます。

- iOSサポートのIssue: [iOS Support and Example &#183; Issue #16](https://github.com/tensorflow/tensorflow/issues/16)
- iOSサポートのPull Request: [Added iOS example by petewarden &#183; Pull Request #2561](https://github.com/tensorflow/tensorflow/pull/2561)
- 一番関連するコミット: [Enabled optimization flags for makefile, and added iOS example](https://github.com/tensorflow/tensorflow/pull/2561/commits/c7c966eb2f1ac5d89ce7999d7eab53783e74abb7)


##サンプルのソースコードを読んでみる

サンプルは全編Objective-C++で書かれています。読んでもわからないだろうと思いつつ、せっかくなので読んでみます。


###tensorflow_utils

モデルとラベルデータを読み込むメソッドと、認識結果を返すメソッドを持つユーティリティクラスのようです。

```cpp:tensorflow_utils.h
tensorflow::Status LoadModel(NSString* file_name, NSString* file_type,
                             std::unique_ptr<tensorflow::Session>* session);
tensorflow::Status LoadLabels(NSString* file_name, NSString* file_type,
                              std::vector<std::string>* label_strings);
void GetTopN(const Eigen::TensorMap<Eigen::Tensor<float, 1, Eigen::RowMajor>,
             Eigen::Aligned>& prediction, const int num_results,
             const float threshold,
             std::vector<std::pair<float, int> >* top_results);
```

汎用的に使えそう。


###ios_image_load

画像ファイルを読み込むクラス。

```cpp:ios_image_load.h
std::vector<tensorflow::uint8> LoadImageFromFile(const char* file_name,
             int* out_width,
             int* out_height,
             int* out_channels);
```

プロジェクトには追加されているけど使われてないっぽい。


###CameraExampleViewController

サンプル本体。肝っぽいところだけを拾っていくと、


`std::unique_ptr<tensorflow::Session>` なメンバ変数を定義して、

```cpp:CameraExampleViewController.mm
std::unique_ptr<tensorflow::Session> tf_session;
```


viewDidLoadのタイミングで<b>モデルを&#12316;.pbファイルからロード</b>。

```cpp:CameraExampleViewController.mm
tensorflow::Status load_status =
  LoadModel(@"tensorflow_inception_graph", @"pb", &tf_session);
```


で、カメラからのリアルタイム入力が得られるたびに呼ばれるデリゲートメソッドを見てみると、

```objc:CameraExampleViewController.mm
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
       fromConnection:(AVCaptureConnection *)connection {
  CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
  [self runCNNOnFrame:pixelBuffer];
}
```

CMSampleBufferRef から CVPixelBufferRef を取得して `runCNNOnFrame:` を呼んでいるだけのようです。


`runCNNOnFrame:` はコードが若干長いですが、このへんと、

```cpp:CameraExampleViewController.mm
tensorflow::Tensor image_tensor(
    tensorflow::DT_FLOAT,
    tensorflow::TensorShape(
        {1, wanted_height, wanted_width, wanted_channels}));
auto image_tensor_mapped = image_tensor.tensor<float, 4>();
tensorflow::uint8 *in = sourceStartAddr;
float *out = image_tensor_mapped.data();
```


このへんが肝っぽいです。

```cpp:CameraExampleViewController.mm
if (tf_session.get()) {
  std::string input_layer = "input";
  std::string output_layer = "output";
  std::vector<tensorflow::Tensor> outputs;
  tensorflow::Status run_status = tf_session->Run(
      {{input_layer, image_tensor}}, {output_layer}, {}, &outputs);
  if (!run_status.ok()) {
    LOG(ERROR) << "Running model failed:" << run_status;
  } else {
    tensorflow::Tensor *output = &outputs[0];
    auto predictions = output->flat<float>();

    // ラベル取得
  }
}
```


が、すみません、それぞれ何をやっているのかを解説するにはTensorFlowの処理をちゃんと理解する必要がありそうです。。（勉強します）


##自分のアプリに組み込む

試せていませんが、[こちら](http://stackoverflow.com/questions/37769904/how-to-compile-ios-example-in-tensorflow) に手順が示されていました。

- ユニバーサルなstatic library（libtensorflow-core.a）をビルドしてプロジェクトに追加する
  - [Library Search Paths] にも追加。

> The `compile_ios_tensorflow.sh' script builds a universal static library in tensorflow/contrib/makefile/gen/lib/libtensorflow-core.a. You'll need to add this to your linking build stage, and in Search Paths add tensorflow/contrib/makefile/gen/lib to the Library Search Paths setting.

- libprotobuf.a と libprotobuf-lite.a をプロジェクトに追加する。
  - [Library Search Paths]にも追加

> You'll also need to add libprotobuf.a and libprotobuf-lite.a from tensorflow/contrib/makefile/gen/protobuf_ios/lib to your Build Stages and Library Search Paths.


- [Header Search paths] を追加

> The Header Search paths needs to contain the root folder of tensorflow, tensorflow/contrib/makefile/downloads/protobuf/src, tensorflow/contrib/makefile/downloads, tensorflow/contrib/makefile/downloads/eigen-eigen-, and tensorflow/contrib/makefile/gen/proto.

- [Other Linker Flags] に `-force_load` を追加

> In the Linking section, you need to add -force_load followed by the path to the TensorFlow static library in the Other Linker Flags section. This ensures that the global C++ objects that are used to register important classes inside the library are not stripped out. To the linker, they can appear unused because no other code references the variables, but in fact their constructors have the important side effect of registering the class.

- bitcodeを無効にする

> The library doesn't currently support bitcode, so you'll need to disable that in your project settings.


##まとめ

TensorFlowをiOS向けにビルドし、付属のサンプルを動かしてみました。パフォーマンスの最適化等、まだまだこれから感はありますが、扇風機や椅子、ノートパソコンやiPhone等、大きさも形も全然違うものを認識してくれて未来を感じます。


試してみるだけなら機械学習やTensorFlowについての専門知識を必要としませんし、ぜひお手元で動かしてみてください！

また、本記事同様に「とりあえず動かしてみる」シリーズとして、こちらもよろしければどうぞ：

- [ディープラーニングの有名ライブラリ5種を最短距離で試す半日コース（TensorFlow, Chainer, Caffe, DeepDream, 画風変換） - Qiita](http://qiita.com/shu223/items/a4fc17eb3356a6068553)