C++ で TensorFlow の推論処理を実行する

# C++ で TensorFlow の推論処理を実行する

## はじめに

[前回](https://qiita.com/rxoxixyxd/items/d821a3bc3e7fb1eed47c) は Python で作成したモデルに対して C++ で学習を行うところまで説明しました。
今回は，学習したモデルの freeze と推論処理の実行を行いたいと思います。

今回作成したコードは前回同様 [github](https://github.com/ryojiysd/tf-train-cpp) に置いてあるので、詳細はこちらをご確認ください。

## 実行環境

（前回と同じです）

- Windows
- Python 3.6
- keras 2.2.4
- tensorflow 1.10.0
- Visual Studio 2015

なお、Python と C++ で使用する TensorFlow のバージョンは揃えていないとエラーが発生する場合があるようです。

C++ 用の tensorflow.dll はこちらのサイトからダウンロードしました。
- [GitHub - fo40225/tensorflow-windows-wheel: Tensorflow prebuilt binary for Windows](https://github.com/fo40225/tensorflow-windows-wheel)

## 基本的な流れ

1. モデルの freeze を行う (Python)
2. freeze されたモデルを使用して推論を行う (C++)


## モデルの freeze を行う

前回作成した `model.meta` と checkpoint を読み込み、 `tf.graph_util.convert_variables_to_constants()` を使用してモデルの freeze を行います。

```py
output_node_names = ['output/Softmax']

with tf.Session() as sess:
    # Restore the graph
    saver = tf.train.import_meta_graph("model.meta")

    # Load weights
    saver.restore(sess, './checkpoints/model.ckpt')

    # Freeze the graph
    frozen_graph_def = tf.graph_util.convert_variables_to_constants(
        sess,
        sess.graph_def,
        output_node_names
        )

    # Save the frozen graph
    with open('frozen_graph.pb', 'wb') as f:
        f.write(frozen_graph_def.SerializeToString())
```

## C++ で推論を行う

Freeze されたモデルを読み込みます。

```cpp
const string graph_def_filename = "frozen_graph.pb";

// Setup global state for TensorFlow.
tensorflow::port::InitMain(argv[0], &argc, &argv);

// Load a frozen model
tensorflow::GraphDef graph_def;
TF_CHECK_OK(tensorflow::ReadBinaryProto(tensorflow::Env::Default(),
                                        graph_def_filename, &graph_def));
```

読み込んだモデルを指定して Session を作成します。

```cpp
// Create a session
std::unique_ptr<tensorflow::Session> session(tensorflow::NewSession(tensorflow::SessionOptions()));
TF_CHECK_OK(session->Create(graph_def));
```

評価用の画像データとラベルデータを読み込み、推論を実行します。

```cpp
auto test_x = read_training_file("MNIST_data/t10k-images.idx3-ubyte");
auto test_y = read_label_file("MNIST_data/t10k-labels.idx1-ubyte");
predict(session, test_x, test_y);
```

推論の処理は以下の通りです。

"input" に (画像ファイル数, 784) の Tensor を指定し、 "output/Softmax" の出力を取得しています。
モデルで "Dropout" を使用している場合は、 `keras_learning_phase` に `false` を指定する必要があるようです。

```cpp
void predict(const std::unique_ptr<tensorflow::Session>& session, const vector<vector<float>>& batch, const vector<float>& labels) {
  // Create an input data
  tensorflow::Tensor lp(tensorflow::DT_BOOL, tensorflow::TensorShape({}));
  lp.flat<bool>().setZero();
  vector<std::pair<string, tensorflow::Tensor>> inputs = {
    {"input", MakeTensor(batch)},
    {"batch_normalization_1/keras_learning_phase", lp}
  };

  std::vector<tensorflow::Tensor> out_tensors;

  // Predict
  TF_CHECK_OK(session->Run(inputs, {"output/Softmax"}, {}, &out_tensors));
}
```

"output/Softmax" からは (入力画像ファイル数, 10) の Tensor が取得できます。
得られた Tensor から精度を計算する処理は以下の通りです。

```cpp
int hits = 0;
for (auto tensor : out_tensors) {
  auto items = tensor.shaped<float, 2>({static_cast<int>(batch.size()), 10});
  for (int i = 0; i < batch.size(); i++) {
    int arg_max = 0;
    float val_max = items(i, 0);
    for (int j = 0; j < 10; j++) {
      if (items(i, j) > val_max) {
        arg_max = j;
        val_max = items(i, j);
      }
    }
    if (arg_max == labels[i]) {
      hits++;
    }
  }
}
std::cout << "Accuracy: " << hits / (float)batch.size() << std::endl;
```