TensorFlow 的 C++ API 提供了用于构建和执行数据流图的机制。该 API 旨在实现简洁性:它使用 “函数式” 构造样式清晰表达图操作(包括轻松指定名称、设备放置等),可以高效地运行生成的图,并用几行代码提取所需结果。本指南介绍了使用 C++ 开始构建和执行 TensorFlow 图需要了解的基本概念和数据结构。
C++ API 仅适用于 TensorFlow bazel build。如果您需要单独使用,请使用 C API。要详细了解如何将 TensorFlow 作为子项目纳入其中(而不是在 TensorFlow 中构建项目,如本例所示),请查看这些 说明(https://docs.bazel.build/versions/master/external.html?hl=zh-CN)。
基础知识
我们从一个简单的示例开始,该示例说明了如何使用 C++ API 构建和执行图。
// tensorflow/cc/example/example.cc
#include "tensorflow/cc/client/client_session.h"
#include "tensorflow/cc/ops/standard_ops.h"
#include "tensorflow/core/framework/tensor.h"
int main() {
using namespace tensorflow;
using namespace tensorflow::ops;
Scope root = Scope::NewRootScope();
// Matrix A = [3 2; -1 0]
auto A = Const(root, { {3.f, 2.f}, {-1.f, 0.f} });
// Vector b = [3 5]
auto b = Const(root, { {3.f, 5.f} });
// v = Ab^T
auto v = MatMul(root.WithOpName("v"), A, b, MatMul::TransposeB(true));
std::vector outputs;
ClientSession session(root);
// Run and fetch v
TF_CHECK_OK(session.Run({v}, &outputs));
// Expect outputs[0] == [19; -3]
LOG(INFO) << outputs[0].matrix();
return 0;
}
将此示例代码放在克隆的 TensorFlow GitHub 代码库内的 tensorflow/cc/example/example.cc 文件中。另外,将 BUILD 文件放在同一目录中,其中包含以下内容:
load("//tensorflow:tensorflow.bzl", "tf_cc_binary")
tf_cc_binary(
name = "example",
srcs = ["example.cc"],
deps = [
"//tensorflow/cc:cc_ops",
"//tensorflow/cc:client_session",
"//tensorflow/core:tensorflow",
],
)
使用 tf_cc_binary 而不是 Bazel 的原生 cc_binary 从 libtensorflow_framework.so 中关联所需的符号。您应该能够使用以下命令编译并运行此示例(确保首先在编译沙盒中运行 ./configure):
bazel run -c opt //tensorflow/cc/example:example
此示例演示了 C++ API 的一些重要功能,例如:
从 C++ 嵌套初始化器列表构造张量常量
构造和命名 TensorFlow 操作
为操作构造函数指定可选属性
从 TensorFlow 会话执行和提取张量值
我们将在下面深入介绍各项内容。
图构造
Scope
tensorflow::Scope 是存储图构造的当前状态的主要数据结构。Scope 充当正在构造的图的句柄,并用于存储 TensorFlow 操作属性。Scope 对象是操作构造函数的第一个参数,使用给定 Scope 作为其第一个参数的操作会继承该 Scope 的属性(例如通用名称前缀)。多个 Scope 可以引用同一个图,下文对此进行了详细说明。
调用 Scope::NewRootScope 可以创建一个新的 Scope 对象。这会创建一些资源,例如将操作添加到的图。这还会创建一个 tensorflow::Status 对象,用于指示在构造操作时遇到的错误。Scope 类具有值语义,因此,Scope 对象可以随意复制和传递。
Scope::NewRootScope 返回的 Scope 对象称为根作用域。可以通过调用 Scope 类的各个成员函数从根作用域构造 “子” 作用域,从而形成作用域层次结构。子作用域会继承父作用域的所有属性,通常会添加或更改一个属性。例如,NewSubScope(name) 会将 name 附加到使用返回的 Scope 对象创建的操作的名称前缀。
以下是由 Scope 对象控制的一些属性:
操作名称
操作的一组控件依赖项
操作的设备放置位置
操作的内核属性
要查看允许您使用新属性创建子作用域的成员函数的完整列表,请参阅 tensorflow::Scope。
操作构造函数
您可以使用操作构造函数创建图操作,每个 TensorFlow 操作一个 C++ 类。与使用蛇形命名法命名操作构造函数的 Python API 不同,C++ API 使用驼峰式命名法以符合 C++ 编码风格。例如,MatMul 操作具有同名的 C++ 类。
使用这种每个操作一个类方法,可以按如下方式构造操作(但不建议这样做):
// Not recommended
MatMul m(scope, a, b);
建议您在构造操作时使用以下 “函数式” 样式:
// Recommended
auto m = MatMul(scope, a, b);
所有操作构造函数的第一个参数始终是 Scope 对象。其余参数是张量输入和必需属性。
对于可选参数,构造函数具有允许可选属性的可选参数。对于带有可选参数的操作,构造函数的最后一个可选参数是一个名为 [operation]:Attrs 的 struct 类型,其中包含每个可选属性的数据成员。您可以通过多种方式构造此类 Attrs:
要指定单个可选属性,您可以使用 C++ 类中为该操作提供的 static 函数构造 Attrs 对象。例如:
auto m = MatMul(scope, a, b, MatMul::TransposeA(true));
您可以通过将 Attrs 结构体中可用的函数连在一起来指定多个可选属性。例如:
auto m = MatMul(scope, a, b, MatMul::TransposeA(true).TransposeB(true));
// Or, alternatively
auto m = MatMul(scope, a, b, MatMul::Attrs().TransposeA(true).TransposeB(true));
系统会以不同方式处理操作的参数和返回值,具体取决于操作的类型:
对于返回单个张量的操作,操作对象返回的对象可以直接传递给其他操作构造函数。例如:
auto m = MatMul(scope, x, W);
auto sum = Add(scope, m, bias);
对于生成多个输出的操作,操作构造函数返回的对象具有与每个输出对应的一个成员。这些成员的名称与操作的 OpDef 中存在的名称相同。例如:
auto u = Unique(scope, a);
// u.y has the unique values and u.idx has the unique indices
auto m = Add(scope, u.y, b);
生成列表类型输出的操作会返回可以使用 [] 操作符编制索引的对象。该对象也可以直接传递给需要列表类型输入的其他构造函数。例如:
auto s = Split(scope, 0, a, 2);
// Access elements of the returned list.
auto b = Add(scope, s[0], s[1]);
// Pass the list as a whole to other constructors.
auto c = Concat(scope, s, 0);
常量
您可以将许多不同类型的 C++ 值直接传递给张量常量。您可以通过用各种 C++ 值调用 tensorflow::ops::Const 函数来显式创建张量常量。例如:
标量
auto f = Const(scope, 42.0f);
auto s = Const(scope, "hello world!");
嵌套初始化器列表
// 2x2 matrix
auto c1 = Const(scope, { {1, 2}, {2, 4} });
// 1x3x1 tensor
auto c2 = Const(scope, { { {1}, {2}, {3} } });
// 1x2x0 tensor
auto c3 = ops::Const(scope, { { {}, {} } });
明确指定的形状
// 2x2 matrix with all elements = 10
auto c1 = Const(scope, 10, /* shape */ {2, 2});
// 1x3x2x1 tensor
auto c2 = Const(scope, {1, 2, 3, 4, 5, 6}, /* shape */ {1, 3, 2, 1});
您可以通过以下任一方法直接将常量传递给其他操作构造函数:使用 Const 函数显式构造一个常量,或根据以上任一类型的 C++ 值隐式构造常量。例如:
// [1 1] * [41; 1]
auto x = MatMul(scope, { {1, 1} }, { {41}, {1} });
// [1 2 3 4] + 10
auto y = Add(scope, {1, 2, 3, 4}, 10);
图执行
执行图时,您需要一个会话。C++ API 提供了一个 tensorflow::ClientSession 类,它将执行由操作构造函数创建的操作。TensorFlow 将自动确定需要执行图的哪些部分,以及需要提供哪些值。例如:
Scope root = Scope::NewRootScope();
auto c = Const(root, { {1, 1} });
auto m = MatMul(root, c, { {41}, {1} });
ClientSession session(root);
std::vector outputs;
session.Run({m}, &outputs);
// outputs[0] == {42}
同理,可以将操作构造函数返回的对象用作参数,以指定在执行图时提供的值。此外,可以使用用于指定张量常量的不同类型的 C++ 值来指定要提供的值。例如:
Scope root = Scope::NewRootScope();
auto a = Placeholder(root, DT_INT32);
// [3 3; 3 3]
auto b = Const(root, 3, {2, 2});
auto c = Add(root, a, b);
ClientSession session(root);
std::vector outputs;
// Feed a session.Run({ {a, { {1, 2}, {3, 4} } } }, {c}, &outputs);
// outputs[0] == [4 5; 6 7]
要详细了解如何使用执行输出,请参阅 tensorflow::Tensor 文档。
更多 AI 相关阅读:
Swisscom 的 TensorFlow 自定义模型 — 通过文本分类改善业务运营
GPU 版 TensorFlow Lite
使用 TensorFlow.js 分类文本:检测浏览器中攻击性语言的示例