今回の目標
前回でTensorFlowをインストールしたので、今回はチュートリアルを進めていきます。
その中で、今回はImage RecognitionのUsage with the C++ APIのパートを進めました。
(自分はC/C++界隈の人間なので…)
具体的な処理内容には触れず、TensorFlowで何かをやる際の流れだけまとめます。
図などはないので、その辺りは想像してください!
次回は何か簡単なものを実装してみたいですね…
TensorFlowでの機械学習の基本的な流れ
単純にまとめると、
- Computation Graphというものを作成する
- Sessionに作成したグラフを渡して計算する
Computation Graphはモデルを表すオブジェクト
Sessionはそのグラフを受け取り実行したりするオブジェクト
…だと思います…。
なぜこういう形式なのか?
PythonにもNumPyの様に、重い計算はPythonの外部で、別の高速な言語で処理するライブラリもあります。しかしそれでもやはり、単一の計算毎にそういう切り替えのコストを掛けるのはかなりのオーバーヘッドになるようです。
ですので、TensorFlowでは、モデルが行う計算を表す構造としてComputation Graphというものを作成し、そのグラフに従ってバックエンド(C++)が一連の計算を引き受けるという構造になっています。(これはTheanoやTorchと類似したアプローチみたいです。)
という訳で、まずやるべきことはComputation Graphを作成するということになります。
Computation Graphを作成する
グラフというからにはノードとエッジで構成されています。
ノードが何かしらの計算(op)に対応していて、あるノードがデータを受け取り、計算を行い、エッジで接続された他のノードに出力データを渡すといったことを繰り返して、最終的な出力を生成します。
このグラフを流れるデータがTensorと呼ばれるN次元配列のような構造になっています。
Nodeを作る
Nodeは以下のようにして作ります。
tensorflow::Node* node_name = tensorflow::ops::Test(input, b.opts());
この場合、Testというopに対応するノードがbに追加されます。
引数は、ノードへの入力と、ノードを追加する対象のGraphDefBuilder (b.opts())です。
GraphDefBuilderはグラフを作成するためのオブジェクトです(グラフそのものではなく)。
Graphを作る
簡単に言うと、GraphDefBuilderに必要なノードを突っ込むだけです。
以下は、入力画像の前処理をするグラフを定義する処理になります。
tensorflow::GraphDefBuilder b;
string input_name = "file_reader";
string output_name = "normalized";
// ReadFile op
tensorflow::Node* file_reader =
tensorflow::ops::ReadFile(tensorflow::ops::Const(file_name, b.opts()), b.opts().WithName(input_name));
// DecodePng op
tensorflow::Node* image_reader =
tensorflow::ops::DecodePng(file_reader,
bopts().WithAttr("channels", 3).WithName("png_reader"));
// Cast op
tensorflow::Node* float_caster = tensorflow::ops::Cast(
image_reader, tensorflow::DT_FLOAT, b.opts().WithName("float_caster"));
// ExpandDims op
tensorflow::Node* dims_expander = tensorflow::ops::ExpandDims(
float_caster, tensorflow::ops::Const(0, b.opts()), b.opts());
// ResizedBilinear op
tensorflow::Node* resized = tensorflow::ops::ResizeBilinear(
dims_expander, tensorflow::ops::Const({input_height, input_width},
b.opts().WithName("size")),
b.opts());
// Sub & Div op
tensorflow::ops::Div(
tensorflow::ops::Sub(
resized, tensorflow::ops::Const({input_mean}, b.opts()), b.opts()),
tensorflow::ops::Const({input_std}, b.opts()),
b.opts().WithName(output_name));
// b によって実際にグラフを作成する
tensorflow::GraphDef graph;
b.ToGraphDef(&graph);
見てわかる通り、bにReadFile、DecodePng、Cast、ExpandDims、ResizeBilinear、Sub、Divのノードを突っ込んでToGraphDefで実際にグラフを作成しています。
protobufから定義済みのGraphDefを読み込む
上の例では、GraphDefBuilderを使ってグラフを定義しました。
一方で定義済みのGraphDefをprotobufから直接ロードすることもできます。
tensorflow::GraphDef graph_def;
Status load_graph_status =
ReadBinaryProto(tensorflow::Env::Default(), graph_file_name, &graph_def);
Sessionを作る
上で作ったグラフを使ってSessionを作って実行する。
std::unique_ptr<tensorflow::Session> session(
tensorflow::NewSession(tensorflow::SessionOptions()));
session->Create(graph);
session->Run({}, {output_name}, {}, out_tensors);
これによって前処理された画像データがout_tensorsに入るわけですね。
main関数
これは、
入力画像→前処理用のグラフ→Inception-v3→識別結果(出力)
という処理を実行しています。
Inception-v3とは画像認識のモデルでprotobufに既に定義済みのものをロードします。
// TensorFlowのセットアップ処理
tensorflow::port::InitMain(argv[0], &argc, &argv);
// protobufまでのPATH
string graph_path = tensorflow::io::JoinPath(root_dir, graph);
// 定義済みのInception-v3をprotobufからロードしてsessionを作る
LoadGraph(graph_path, &session);
/* 入力データの前処理 */
std::vector<Tensor> resized_tensors;
string image_path = tensorflow::io::JoinPath(root_dir, image);
// 上でまとめた前処理を行うグラフの作成とセッションの作成、動作までを行う関数
ReadTensorFromImageFile(image_path, input_height, input_width, input_mean,
input_std, &resized_tensors);
const Tensor& resized_tensor = resized_tensors[0];
std::vector<Tensor> outputs;
// Incepton-v3とのSessionを走らせる
session->Run({{FLAGS_input_layer, resized_tensor}},
{FLAGS_output_layer}, {}, &outputs);