- PVSM.RU - https://www.pvsm.ru -
У Google TensorFlow есть одна замечательная особенность, оно умеет работать не только в программах на Python, а также и в программах на C++. Однако, как оказалось, в случае С++ нужно немного повозиться, чтобы правильно приготовить это блюдо. Конечно, основная часть разработчиков и исследователей, которые используют TensorFlow работают в Python. Однако, иногда бывает необходимо отказаться от этой схемы. Например вы натренировали вашу модель и хотите ее использовать в мобильном приложении или роботе. А может вы хотите интегрировать TensorFlow в существующий проект на С++. Если вам интересно как это сделать, добро пожаловать под кат.
Для компиляции tensorflow используется гугловая система сборки Bazel [1]. Поэтому для начала придется поставить ее. Чтобы не засорять систему, я ставлю bazel в отдельную папку:
git clone https://github.com/bazelbuild/bazel.git ${BAZELDIR}
cd ${BAZELDIR}
./compile.sh
cp output/bazel ${INSTALLDIR}/bin
Теперь приступим к сборке TensorFlow. На всякий случай: официальная документация по установке здесь [2]. Раньше чтобы получить библиотеку приходилось делать что-то вроде этого [3].
git clone -b r0.10 https://github.com/tensorflow/tensorflow Tensorflow
cd Tensorflow
./configure
bazel build :libtensorflow_cc.so
Идем пить чай. Результат нас будет ждать здесь
bazel-bin/tensorflow/libtensorflow_сс.so
Мы получили библиотеку, но чтобы ей воспользоваться нужны еще заголовочные файлы. Но не все хедеры легко доступны. Tensorflow использует библиотеку protobuf [4] для сериализации графа вычислений. Объекты, подлежащие сериализации, описываются на языке Protocol Buffers [5], и затем, с помощью консольной утилиты генерируется код 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.
Теперь когда у нас есть хедеры и библиотека мы можем подключить TensorFlow к нашей С++ программе. Однако, нас ждет небольшое разочарование, без Python нам все таки не обойтись, так как на данный момент функционал по построению графа недоступен из С++. Поэтому наш план таков:
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)
#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";
}
}
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++);
}
}
Иногда хочется прервать тренировку модели и продолжить ее на другом устройстве или просто позже. Или, например, просто сохранить состояние предобученного графа для последующего использования. В С++ нет какого-то стандартного пути. Но, оказывается, довольно несложно организовать этот функционал самостоятельно.
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)
// Сохранение состояния
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;
};
Примерно так, как описано в статье я тренирую модель пока что двумерного квадрокоптера. Выглядит это вот так:
Задача дронов прилететь в центр крестика и находиться там, для этого они могут включать или выключать двигатели (используется алгоритм DQN). На видео они находятся в среде с довольно большим трением, поэтому двигаются медленно. На данный момент работаю над полетом в среде без трения и облетом препятствий. При получении хорошего результата планирую еще одну статью.
Автор: Parilo
Источник [6]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/algoritmy/177031
Ссылки в тексте:
[1] Bazel: http://bazel.io/
[2] здесь: https://www.tensorflow.org/versions/r0.10/get_started/os_setup.html#installing-from-sources
[3] этого: https://medium.com/jim-fleming/loading-tensorflow-graphs-via-host-languages-be10fd81876f#.vwkf80c9d
[4] protobuf: https://developers.google.com/protocol-buffers/
[5] Protocol Buffers: https://ru.wikipedia.org/wiki/Protocol_Buffers
[6] Источник: https://habrahabr.ru/post/308002/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.