TensorflowとC ++を友達にする方法







Google TensorFlowは、ニューラルネットワークに焦点を当てたますます人気のある機械学習ライブラリです。 すばらしい機能が1つあります。Pythonプログラムだけでなく、C ++プログラムでも機能します。 ただし、判明したように、C ++の場合は、この料理を適切に調理するために少し手を加える必要があります。 もちろん、TensorFlowを使用する開発者と研究者の大半はPythonで働いています。 ただし、このスキームを放棄する必要がある場合があります。 たとえば、モデルをトレーニングし、モバイルアプリケーションまたはロボットで使用したい場合。 または、TensorFlowを既存のC ++プロジェクトに統合することもできます。 これを行う方法に興味がある場合は、猫にようこそ。



libtensorflow.soのコンパイル



テンソルフローをコンパイルするには、GoogleのBazelビルドシステムが使用されます。 したがって、最初にそれを配置する必要があります。 システムを詰まらせないために、私は別のフォルダにバゼルを入れました:



このようなもの
git clone https://github.com/bazelbuild/bazel.git ${BAZELDIR} cd ${BAZELDIR} ./compile.sh cp output/bazel ${INSTALLDIR}/bin
      
      







それでは、TensorFlowの構築を始めましょう。 念のため:公式のインストールドキュメントはこちらです。 以前は、ライブラリを取得するには、 このようなことをしなければなりませんでした。



しかし、今では少し簡単です
 git clone -b r0.10 https://github.com/tensorflow/tensorflow Tensorflow cd Tensorflow ./configure bazel build :libtensorflow_cc.so
      
      







お茶を飲みに行きましょう。 ここで結果が待っています。



 bazel-bin/tensorflow/libtensorflow_.so
      
      





ヘッダーファイルの取得



ライブラリを入手しましたが、それを使用するにはより多くのヘッダーファイルが必要です。 ただし、すべてのヘッダーに簡単にアクセスできるわけではありません。 Tensorflowprotobufライブラリを使用して計算グラフをシリアル化します。 シリアル化されるオブジェクトはプロトコルバッファ言語で記述され、コンソールユーティリティを使用して、オブジェクト自体のC ++コードが生成されます。 私たちにとって、これは.protoファイルからヘッダーを自分で生成する必要があることを意味します(ソースでこれらのヘッダーが見つからなかっただけで、誰かがどこにいるのかを知っている場合はコメントで書いてください)。 これらのヘッダーを生成します



このスクリプト
 #!/bin/bash mkdir protobuf-generated/ DIRS="" FILES="" for i in `find tensorflow | grep .proto$` do FILES+=" ${i}" done echo $FILES ./bazel-out/host/bin/google/protobuf/protoc --proto_path=./bazel-Tensorflow/external/protobuf/src --proto_path=. --cpp_out=protobuf-generated/ $FILES
      
      







コンパイラがヘッダーファイルを含むものとして指定する必要があるフォルダーの完全なリスト
 Tensorflow Tensorflow/bazel-Tensorflow/external/protobuf/src Tensorflow/protobuf-generated Tensorflow/bazel-Tensorflow Tensorflow/bazel-Tensorflow/external/eigen_archive
      
      







テンソルフローのソースコードの構造が変わるため、バージョンごとにフォルダーのリストが変わります。



カウント読み込み



ヘッダーとライブラリが用意できたので、TensorFlowをC ++プログラムに接続できます。 ただし、少し残念ですが、現時点ではC ++からグラフ構築機能を利用できないため、Pythonなしではできません。 したがって、計画は次のとおりです。



Pythonでグラフを作成し、.pbファイルに保存します
 import numpy as np import tempfile import tensorflow as tf session = tf.Session() #     tf.train.write_graph(session.graph_def, 'models/', 'graph.pb', as_text=False)
      
      







C ++で保存したグラフを読み込む
 #include "tensorflow/core/public/session.h" using namespace tensorflow; void init () { tensorflow::GraphDef graph_def; tensorflow::Session* session; Status status = NewSession(SessionOptions(), &session); if (!status.ok()) { std::cerr << "tf error: " << status.ToString() << "\n"; } //   status = ReadBinaryProto(Env::Default(), "models/graph.pb", &graph_def); if (!status.ok()) { std::cerr << "tf error: " << status.ToString() << "\n"; } //     TensorFlow status = session->Create(graph_def); if (!status.ok()) { std::cerr << "tf error: " << status.ToString() << "\n"; } }
      
      







C ++でのグラフ操作の値の計算は次のようになります。
 void calc () { Tensor inputTensor1 (DT_FLOAT, TensorShape({size1, size2})); Tensor inputTensor2 (DT_FLOAT, TensorShape({size3, size3})); // -  for (int i...) { for (int j...) { inputTensor1.matrix<float>()(i, j) = value1; } } std::vector<std::pair<string, tensorflow::Tensor>> inputs = { { "tensor_scope/tensor_name1", inputTensor1 }, { "tensor_scope/tensor_name2", inputTensor2 } }; //    -   std::vector<tensorflow::Tensor> outputTensors; //          auto status = session->Run(inputs, { "op_scope/op_with_outputs_name" // ,   }, { "op_scope/op_without_outputs_name", //     }, &outputTensors); if (!status.ok()) { std::cerr << "tf error: " << status.ToString() << "\n"; return 0; } //  - for (int i...) { outputs [0].matrix<float>()(0, i++); } }
      
      







グラフの状態の保存と読み込み



モデルトレーニングを中断して、別のデバイスまたはそれ以降で継続したい場合があります。 または、たとえば、後で使用するために事前に訓練されたグラフの状態を保存するだけです。 C ++には標準的な方法はありません。 しかし、この機能を自分で整理するのは非常に簡単です。



まず、変数値の読み込みと読み込みの操作をグラフに追加する必要があります
 import numpy as np import tempfile import tensorflow as tf session = tf.Session() #     session.run(tf.initialize_all_variables()) #         for variable in tf.trainable_variables(): tf.identity (variable, name="readVariable") tf.assign (variable, tf.placeholder(tf.float32, variable.get_shape(), name="variableValue"), name="resoreVariable") tf.train.write_graph(session.graph_def, 'models/', 'graph.pb', as_text=False)
      
      







C ++では、グラフの状態を保存およびロードする操作は次のようになります
 //   void saveGraphState (const std::string fileSuffix) { std::vector<tensorflow::Tensor> out; std::vector<string> vNames; //     int node_count = graph_def.node_size(); for (int i = 0; i < node_count; i++) { auto n = graph_def.node(i); if ( n.name().find("readVariable") != std::string::npos ) { vNames.push_back(n.name()); } } //     Status status = session->Run({}, vNames, {}, &out); if (!status.ok()) { std::cout << "tf error1: " << status.ToString() << "\n"; } //      int variableCount = out.size (); std::string dir ("graph-states-dir"); std::fstream output(dir + "/graph-state-" + fileSuffix, std::ios::out | std::ios::binary); output.write (reinterpret_cast<const char *>(&variableCount), sizeof(int)); for (auto& tensor : out) { int tensorSize = tensor.TotalBytes(); //   protobuf TensorProto p; tensor.AsProtoField (&p); std::string pStr; p.SerializeToString(&pStr); int serializedTensorSize = pStr.size(); output.write (reinterpret_cast<const char *>(&serializedTensorSize), sizeof(int)); output.write (pStr.c_str(), serializedTensorSize); } output.close (); } //  bool loadGraphState () { std::string dir ("graph-states-dir"); std::fstream input(dir + "/graph-state", std::ios::in | std::ios::binary); if (!input.good ()) return false; std::vector<std::pair<string, tensorflow::Tensor>> variablesValues; std::vector<string> restoreOps; int variableCount; input.read(reinterpret_cast<char *>(&variableCount), sizeof(int)); for (int i=0; i<variableCount; i++) { int serializedTensorSize; input.read(reinterpret_cast<char *>(&serializedTensorSize), sizeof(int)); std::string pStr; pStr.resize(serializedTensorSize); char* begin = &*pStr.begin(); input.read(begin, serializedTensorSize); TensorProto p; p.ParseFromString (pStr); std::string variableSuffix = (i==0?"":"_"+std::to_string(i)); variablesValues.push_back ({"variableValue" + variableSuffix, Tensor ()}); Tensor& t (variablesValues.back ().second); t.FromProto (p); restoreOps.emplace_back ("resoreVariable" + variableSuffix); } input.close (); std::vector<tensorflow::Tensor> out; Status status = session->Run(variablesValues, {}, restoreOps, &out); if (!status.ok()) { std::cout << "tf error2: " << status.ToString() << "\n"; } return true; };
      
      







ちょっとしたビデオ



おおよそ記事で説明したように、私はこれまで2次元クアドロコプターのモデルを訓練しています。 次のようになります。



ドローンのタスクは、十字架の中心に飛んでそこにいることです。このため、エンジンをオンまたはオフにできます(DQNアルゴリズムが使用されます)。 ビデオでは、彼らは非常に多くの摩擦がある環境にいるので、ゆっくり動きます。 現時点では、摩擦のない環境での飛行と障害物の周りの飛行に取り組んでいます。 良い結果が得られたら、別の記事を計画します。



All Articles