TensorFlow for iOS のモデルについて

以前、『[TensorFlowの学習済みモデルを拾ってきてiOSで利用する](http://qiita.com/shu223/items/a2808e9dfccbd23387ff)』という記事を書いたのですが、そのとき用いたモデルデータはいずれも **Protocol Buffers** 形式でした。

![](https://qiita-image-store.s3.amazonaws.com/0/3180/211bd94a-8959-aea2-3edb-423596dc8d59.jpeg)

（YOLOモデルでリアルタイム一般物体認識）

Protocol Buffersはプラットフォーム等を問わない汎用的なフォーマットですが、上の記事を書いたときにわからなかったのが、`.pb`でエクスポートされたTensorFlow用のモデルでも、**iOSで使えるものと使えないものの違いはあるのかないのか**、あるとしたら何なのか（単にサイズや使用メモリ量とかが制限になってくるのか）、という点です。

理解の手がかりになるかわかりませんが、**TensorFlowのiOSサンプルがどうやってモデルデータを読み込んでいるか**、コードをちょっとだけ追ってみます。

なお、今回利用するサンプルは[simple](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/ios_examples/simple)です。

##.pbファイル読み込み〜TensorFlowグラフ生成


###1. モデルのファイルパスを取得

まず、`.pb`ファイルのファイル名を渡しているところはここ。

```cpp:RunModelViewController.mm
NSString* network_path = FilePathForResourceName(@"tensorflow_inception_graph", @"pb");
```

この`FilePathForResourceName()`は次のように実装されています。

```cpp:RunModelViewController.mm
NSString* FilePathForResourceName(NSString* name, NSString* extension) {
  NSString* file_path = [[NSBundle mainBundle] pathForResource:name ofType:extension];
  if (file_path == NULL) {
    LOG(FATAL) << "Couldn't find '" << [name UTF8String] << "."
	       << [extension UTF8String] << "' in bundle.";
  }
  return file_path;
}
```

アプリバンドル内にあるファイルのパスを取得しているだけ、ということがわかります。

###2. Protocol Buffersをデコード

そして次に、そのファイルパスと、`tensorflow::GraphDef`へのポインタを`PortableReadFileToProto()`という関数の引数に渡しています。

```cpp:RunModelViewController.mm
PortableReadFileToProto([network_path UTF8String], &tensorflow_graph);
```

この関数の中身を見てみると、こんな感じです。

```cpp:RunModelViewController.mm
bool PortableReadFileToProto(const std::string& file_name,
                             ::google::protobuf::MessageLite* proto) {
  ::google::protobuf::io::CopyingInputStreamAdaptor stream(
      new IfstreamInputStream(file_name));
  stream.SetOwnsCopyingStream(true);
  // TODO(jiayq): the following coded stream is for debugging purposes to allow
  // one to parse arbitrarily large messages for MessageLite. One most likely
  // doesn't want to put protobufs larger than 64MB on Android, so we should
  // eventually remove this and quit loud when a large protobuf is passed in.
  ::google::protobuf::io::CodedInputStream coded_stream(&stream);
  // Total bytes hard limit / warning limit are set to 1GB and 512MB
  // respectively. 
  coded_stream.SetTotalBytesLimit(1024LL << 20, 512LL << 20);
  return proto->ParseFromCodedStream(&coded_stream);
}
```

コメントにも書いてありますが、`SetTotalBytesLimit()`という関数を用いて、**読み込むモデルファイルのサイズを1024MB（1GB）に制限**（512MB以上でwarning）しています。**[cameraサンプルでも同様の実装](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/ios_examples/camera/tensorflow_utils.mm)**になっていました。

ここではファイルを読み込み、`ParseFromCodedStream()`でProtocol Buffersをデコードしています。

ちなみに、第2引数が`::google::protobuf::MessageLite`となっていますが、`tensorflow::GraphDef`の定義を見ると、次のようになっています。

```cpp:graph.pb.h
class GraphDef : public ::google::protobuf::Message
```

**`GraphDef`は`protobuf::Message`を継承**しているクラスであることがわかります。


###3. グラフを作成

最後に、`Session::Create()`で、セッションで使用するグラフを作成します。

```cpp:RunModelViewController.mm
tensorflow::Status s = session->Create(tensorflow_graph);
```


##グラフの実行

ここまで、**モデルに依存する部分はファイル名だけ**だったわけですが、グラフをRunするところでは、次のように入力・出力ノード名をそれぞれ文字列で渡しています。

```cpp:RunModelViewController.mm
std::string input_layer = "input";
std::string output_layer = "output";
std::vector<tensorflow::Tensor> outputs;
tensorflow::Status run_status = session->Run({{input_layer, image_tensor}},
			               {output_layer}, {}, &outputs);
```

このへんは**モデル（グラフ）に依存**してるところで、先日の[公開されているモデルを利用](http://qiita.com/shu223/items/a2808e9dfccbd23387ff)する記事でも、モデルに応じて変えていました。


##グラフの中身を出力してみる

先程、`tensorflow::GraphDef`は`protobuf::Message`のサブクラスである、と書きました。

`Message`には、`DebugString()`という、message（ここでは**Protocol Buffersをデコードして得られたデータ**）を**人間に読める形で出力**してくれるメソッドが用意されています。

```cpp::message.h
// Generates a human readable form of this message, useful for debugging
// and other purposes.
string DebugString() const;
```

で、これをコンソールに出力してくれるメソッドもあります。

```cpp::message.h
void PrintDebugString() const;
```

これを使って出力してみます。

```cpp:RunModelViewController.mm
tensorflow_graph.PrintDebugString();
```

```json
node {
  name: "input"
  op: "Placeholder"
  device: "/cpu:0"
  attr {
    key: "dtype"
    value {
      type: DT_FLOAT
    }
  }
  attr {
    key: "shape"
    value {
      shape {
      }
    }
  }
}
node {
  name: "conv2d0_w"
  op: "Const"
  device: "/cpu:0"
  attr {
    key: "dtype"
    value {
      type: DT_FLOAT
    }
  }
  attr {
    key: "value"
    value {
      tensor {
        dtype: DT_FLOAT
        tensor_shape {
          dim {
            size: 7
          }
          dim {
            size: 7
          }
          dim {
            size: 3
          }
          dim {
            size: 64
          }
        }
        tensor_content: "{超長いバイナリ（？）データ}"
      }
    }
  }
}
node {
  name: "conv2d0_b"
  op: "Const"
  device: "/cpu:0"
  attr {
    key: "dtype"
    value {
      type: DT_FLOAT
    }
  }
  attr {
    key: "value"
    value {
      tensor {
        dtype: DT_FLOAT
        tensor_shape {
          dim {
            size: 64
          }
        }
        tensor_content: "{短めのバイナリ（？）データ}"
      }
    }
  }
}
node {
  name: "conv2d1_w"
  op: "Const"
  device: "/cpu:0"
  attr {
    key: "dtype"
    value {
      type: DT_FLOAT
    }
  }
  attr {
    key: "value"
    value {
      tensor {
        dtype: DT_FLOAT
        tensor_shape {
          dim {
            size: 1
          }
          dim {
            size: 1
          }
          dim {
            size: 64
          }
          dim {
            size: 64
          }
        }
        tensor_content: "{かなり長いバイナリ（？）データ}"
      }
    }
  }
}
（以下略）
```

ということで、グラフの中身が見れて、"input"というノード名も見つけられました。`.pb`形式のTensorFlow用学習済みモデルが公開されているものの、**`Run()`の引数に渡すノード名がわからない**ということもあったので、ここらへんを手がかりにできそうです。


##まとめ

TensorFlowのiOSサンプルのモデルまわりのコードを追ってみました。こんなことをするよりもそもそもちゃんとTensorFlowや機械学習について理解しなさいという話はありつつも、前回時点では（自分にとって）完全なブラックボックスだったモデルというものが、

- Protocol Buffersという**フォーマット自体はプラットフォームを問わない汎用的なもの**である
- 1024MBのモデルのサイズ制限は、サンプルアプリ側で行っている（**TensorFlow for iOSの制約ではない**）
- モデルを読み込みグラフを作成するところまでは、どのモデルも同様の手順である
- Runするところでモデル（グラフ）に依存した手続きが必要
  - `protobuf::Message::PrintDebugString()`でProtocol Buffersからデコードしてきたデータの中身を見れるので、他人が作ったモデル（グラフ）を使うときにここをヒントにできそう

と色々と手がかりが得られました。