web-dev-qa-db-ja.com

Tensorflow C ++でグラフをエクスポートおよび実行するさまざまな方法

トレーニング済みのネットワークをC++にインポートするには、ネットワークをエクスポートしてそれを可能にする必要があります。多くを検索し、それに関する情報がほとんど見つからなかった後、それを行うには freeze_graph() を使用する必要があることが明らかになりました。

Tensorflowの新しい0.7バージョンのおかげで、彼らは documentation を追加しました。

ドキュメントを調べたところ、似たようなメソッドはほとんどないことがわかりました。freeze_graph()tf.train.export_meta_graphの違いは何ですか?似たようなパラメーターを持っていますが、モデルをC++にインポートします(違いは、この方法で出力されたファイルを使用するために、import_graph_def()しか使用できないということですか、それとも別のものですか?)

また、write_graph()の使用方法に関する質問:ドキュメントでは、graph_defsess.graph_defで指定されていますが、freeze_graph()の例ではsess.graph.as_graph_def()です。これら2つの違いは何ですか?

この質問は この問題 に関連しています

ありがとうございました!

26
Hamed MP

これは、TF 0.12で導入されたV2チェックポイントを利用した私のソリューションです。

すべての変数を定数に変換したり、 グラフをフリーズしたりする必要はありません

明確にするために、V2チェックポイントはディレクトリmodelsで次のようになります。

_checkpoint  # some information on the name of the files in the checkpoint
my-model.data-00000-of-00001  # the saved weights
my-model.index  # probably definition of data layout in the previous file
my-model.meta  # protobuf of the graph (nodes and topology info)
_

Pythonパーツ(保存)

_with tf.Session() as sess:
    tf.train.Saver(tf.trainable_variables()).save(sess, 'models/my-model')
_

tf.trainable_variables()を使用してSaverを作成すると、頭痛と記憶領域を節約できます。しかし、いくつかのより複雑なモデルでは、すべてのデータを保存する必要があります。その後、Saverへのこの引数を削除し、Saverafterグラフが作成されます。すべての変数/レイヤーに一意の名前を付けることも非常に賢明です。そうでない場合は、 異なる問題 で実行できます。

Pythonパーツ(推論)

_with tf.Session() as sess:
    saver = tf.train.import_meta_graph('models/my-model.meta')
    saver.restore(sess, tf.train.latest_checkpoint('models/'))
    outputTensors = sess.run(outputOps, feed_dict=feedDict)
_

C++パーツ(推論)

checkpointPathは既存のファイルへのパスではなく、単に共通のプレフィックスであることに注意してください。 _.index_ファイルへのパスを誤ってそこに置いた場合、TFはそれが間違っていることを知らせませんが、初期化されていない変数のために推論中に死にます。

_#include <tensorflow/core/public/session.h>
#include <tensorflow/core/protobuf/meta_graph.pb.h>

using namespace std;
using namespace tensorflow;

...
// set up your input paths
const string pathToGraph = "models/my-model.meta"
const string checkpointPath = "models/my-model";
...

auto session = NewSession(SessionOptions());
if (session == nullptr) {
    throw runtime_error("Could not create Tensorflow session.");
}

Status status;

// Read in the protobuf graph we exported
MetaGraphDef graph_def;
status = ReadBinaryProto(Env::Default(), pathToGraph, &graph_def);
if (!status.ok()) {
    throw runtime_error("Error reading graph definition from " + pathToGraph + ": " + status.ToString());
}

// Add the graph to the session
status = session->Create(graph_def.graph_def());
if (!status.ok()) {
    throw runtime_error("Error creating graph: " + status.ToString());
}

// Read weights from the saved checkpoint
Tensor checkpointPathTensor(DT_STRING, TensorShape());
checkpointPathTensor.scalar<std::string>()() = checkpointPath;
status = session->Run(
        {{ graph_def.saver_def().filename_tensor_name(), checkpointPathTensor },},
        {},
        {graph_def.saver_def().restore_op_name()},
        nullptr);
if (!status.ok()) {
    throw runtime_error("Error loading checkpoint from " + checkpointPath + ": " + status.ToString());
}

// and run the inference to your liking
auto feedDict = ...
auto outputOps = ...
std::vector<tensorflow::Tensor> outputTensors;
status = session->Run(feedDict, outputOps, {}, &outputTensors);
_
33
Martin Pecka

(および他のすべての操作)を予測するには、次のようなことができます。

最初にpythonする必要がありますname将来の使用のために変数または操作を行う必要があります

self.init = tf.initialize_variables(tf.all_variables(), name="nInit")

トレーニング後、そのように計算すると、変数を割り当てたら、それらすべてを通過し、グラフに定数として保存します。 (そのフリーズツールでもほぼ同じことができますが、私は通常自分でそれを行います、以下のpyとcppで「name = nWeights」を確認してください)

def save(self, filename):
    for variable in tf.trainable_variables():
        tensor = tf.constant(variable.eval())
        tf.assign(variable, tensor, name="nWeights")

    tf.train.write_graph(self.sess.graph_def, 'graph/', 'my_graph.pb', as_text=False)

C++に進み、グラフをロードし、保存された定数から変数をロードします。

void load(std::string my_model) {
        auto load_graph_status =
                ReadBinaryProto(tensorflow::Env::Default(), my_model, &graph_def);

        auto session_status = session->Create(graph_def);

        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("nWeights") != std::string::npos) {
                vNames.Push_back(n.name());
            }
        }

        session->Run({}, vNames, {}, &out);

これで、すべてのニューラルネットウェイトまたはその他の変数がロードされました。

同様に、他の操作を実行できます(名前を覚えていますか?)。適切なサイズの入力および出力テンソルを作成し、入力テンソルをデータで満たし、次のようにセッションを実行します。

auto operationStatus = session->Run(input, {"put_your_operation_here"}, {}, &out);
18
Alex Joz