TensorFlow に iOS サポートが追加された というニュースを見かけたので、ビルドして、iOSで動作させてみました。
(たまたま目の前にあった扇風機もバッチリ認識してくれました)
本記事では最終的にうまくいった手順を書いています。この手順をなぞってみるにあたってTensorFlowや機械学習・ディープラーニングについての専門知識は不要ですのでぜひお試しください!
ビルド手順
iOSサポートバージョンであるv0.9.0をチェックアウトして、以下の手順でビルドしていきます。手順はこの通りになぞるだけですし、ほぼスクリプトを実行するだけで非常に簡単です(が、実行時間がかなりかかります)。
- スクリプトを実行して Eigen や Protobuf 等の依存ライブラリをダウンロード
cd tensorflow
tensorflow/contrib/makefile/download_dependencies.sh
- protobuf をビルド&インストール
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”パッケージに含まれているらしいので、automakeをインストール。
brew instal automake
で、改めて
./autogen.sh ./configure make sudo make install
- iOS native版のprotobufをビルド
cd ../../../../..
tensorflow/contrib/makefile/compile_ios_protobuf.sh
- iOSアーキテクチャ向けにTensorFlowをビルド
tensorflow/contrib/makefile/compile_ios_tensorflow.sh
サンプルをビルドする
`tensorflow/tensorflow/contrib/ios_example` に2種類のサンプルがあります。
simple と名付けられたサンプルアプリは、今回ビルドした static library をアプリ内に組み込む際の最小実装としての参考にはなりそうですが、デモとして見て楽しい機能があるわけではないので、本記事ではもう一方の camera の方を紹介します。
まず、このファイルをダウンロードして、解凍すると入っている、
- imagenet_comp_graph_label_strings.txt
- tensorflow_inception_graph.pb
の2つのファイルを、`tensorflow/contrib/ios_examples/camera/data` に置きます。*1
この Inception というのはGoogleが提供する画像認識用の学習済みモデルのようです。
- Research Blog: Train your own image classifier with Inception in TensorFlow
- https://github.com/google/inception
cameraサンプル実行例
cameraサンプルを実行すると、すぐにカメラが起動します。身近のものを色々と映してみました。(リアルタイムカメラ入力に対する認識結果を表示してくれます)
MacBook
"Notebook" "Laptop" どっちでも正解!
扇風機
"Electric Fan" 正解!
椅子
"Folding Chair"(折りたたみ椅子) 惜しい。。ですが、そもそも `imagenet_comp_graph_label_strings.txt` には chair が3種類しかなくて、その中では一番近いと言ってもいいかもしれません。
iPhone
"iPod"・・・惜しい!iPhoneなのでほぼ正解と言ってもいいかもしれません。
パフォーマンス
今回cameraサンプルを試したデバイスはiPhone 6だったのですが、認識はまだまだリアルタイムとは言い難く、数秒単位の遅延がある感じでした。まだiOS向けにビルドできるようになったというだけでiOSにおける GPU Accelerated はされてないですし、パフォーマンスの最適化はまだまだこれからのようです。
ちなみに iOS 10 で Accelerate.framework に追加された BNNS (Basic neural network subroutines) を使用した最適化もissueに挙がっています。
- [Use Basic neural network subroutines (BNNS) on iOS · Issue #3001 · tensorflow/tensorflow](https://github.com/tensorflow/tensorflow/issues/3001)
ビルド手順の情報ソース
iOSサポートバージョンである0.9.0のトップ階層にあるREADMEを見ても、iOSについては書かれていません。
上述したビルド手順は`contrib/makefile` 配下のREADMEに書かれています。
関連情報を以下にまとめておきます。
- iOSサポートのIssue: iOS Support and Example · Issue #16
- iOSサポートのPull Request: Added iOS example by petewarden · Pull Request #2561
- 一番関連するコミット: Enabled optimization flags for makefile, and added iOS example
サンプルのソースコードを読んでみる
サンプルは全編Objective-C++で書かれています。読んでもわからないだろうと思いつつ、せっかくなので読んでみます。
tensorflow_utils
モデルとラベルデータを読み込むメソッドと、認識結果を返すメソッドを持つユーティリティクラスのようです。
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
画像ファイルを読み込むクラス。
std::vector<tensorflow::uint8> LoadImageFromFile(const char* file_name, int* out_width, int* out_height, int* out_channels);
プロジェクトには追加されているけど使われてないっぽい。
CameraExampleViewController
サンプル本体。肝っぽいところだけを拾っていくと、
`std::unique_ptr<tensorflow::Session>` なメンバ変数を定義して、
std::unique_ptr<tensorflow::Session> tf_session;
viewDidLoadのタイミングでモデルを〜.pbファイルからロード。
tensorflow::Status load_status = LoadModel(@"tensorflow_inception_graph", @"pb", &tf_session);
で、カメラからのリアルタイム入力が得られるたびに呼ばれるデリゲートメソッドを見てみると、
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); [self runCNNOnFrame:pixelBuffer]; }
CMSampleBufferRef から CVPixelBufferRef を取得して `runCNNOnFrame:` を呼んでいるだけのようです。
`runCNNOnFrame:` はコードが若干長いですが、このへんと、
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();
このへんが肝っぽいです。
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の処理をちゃんと理解する必要がありそうです。。(勉強します)
自分のアプリに組み込む
試せていませんが、こちら に手順が示されていました。
- ユニバーサルな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についての専門知識を必要としませんし、ぜひお手元で動かしてみてください!
次のステップとしては、下記記事で紹介されているテンソルの計算部分に特化してSwiftで書かれたライブラリと比較したりしつつ、ちゃんとTensorFlowでやっていることを理解したいと思います。
また、本記事同様に「とりあえず動かしてみる」シリーズとして、こちらもよろしければどうぞ:
*1:ちなみにこのとき、 https://github.com/miyosuda/TensorFlowAndroidDemo/find/master にある同名ファイルを使っても、ちゃんと動きません。参考