TensorFlow in other languages(TensorFlow其他语言)
其他语言的TensorFlow
背景
本文档旨在为那些对创建或开发其他编程语言中的TensorFlow功能感兴趣的人员提供指导。它描述了TensorFlow的功能以及推荐使用其他编程语言提供的步骤。
Python是TensorFlow支持的第一种客户端语言,目前支持最多的功能。越来越多的功能被移入TensorFlow的核心(用C ++实现)并通过C API公开。客户端语言应该使用该语言的外部函数接口(FFI)来调用此C API以提供TensorFlow功能。
概述
在编程语言中提供TensorFlow功能可以细分为几大类:
运行预定义图
:给定GraphDef
(或MetaGraphDef
)协议消息,能够创建会话,运行查询并获得张量结果。这对于想要在预先训练的模型上运行推理的移动应用或服务器就足够了。
至少,语言绑定应该支持运行预定义的图形,但大多数应该也支持图形构建。TensorFlow Python API提供了所有这些功能。
当前状态
新的语言支持应该建立在C API之上。但是,正如您在下表中看到的,并非所有功能都可用于C语言。在C API中提供更多功能是一个正在进行的项目。
特征 | Python | C |
---|---|---|
运行预定义的图 | tf.import_graph_def,tf.Session | TF_GraphImportGraphDef,TF_NewSession |
具有生成的操作函数的图构造 | Yes | 是(C API支持执行此操作的客户端语言) |
梯度 | tf.gradients | |
功能 | tf.python.framework.function.Defun | |
控制流 | tf.cond,tf.while_loop | |
神经网络库 | tf.train,tf.nn,tf.contrib.layers,tf.contrib.slim | |
推荐方法
运行预定义的图形
预计语言绑定将定义以下类:
Graph
:代表TensorFlow计算的图形。由操作组成(用客户端语言表示Operation
)并对应TF_Graph
于C API中的一个。主要用作创建新Operation
对象和启动时的参数Session
。还支持遍历graph(TF_GraphNextOperation
)中的操作,通过name(TF_GraphOperationByName
)查找操作,以及从GraphDef
协议消息(TF_GraphToGraphDef
并TF_GraphImportGraphDef
在C API中)进行转换。
图构建
TensorFlow有许多操作,并且列表不是静态的,所以我们建议生成将操作添加到图形的函数,而不是单独手动编写它们(尽管手动编写几个函数是找出生成器应该是什么的好方法生成)。生成函数所需的信息包含在OpDef
协议消息中。
有几种方法可以获得OpDef
已注册操作的列表:
TF_GetAllOpList
在C API中检索所有注册的OpDef
协议消息。这可用于以客户端语言编写生成器。这要求客户端语言具有协议缓冲区支持才能解释OpDef
消息。
OpDef
指定如下:
- CamelCase中的操作名称。对于生成的函数,遵循该语言的约定。例如,如果语言使用snake_case,那么使用该字符而不是使用CamelCase作为操作的函数名称。
一个OpDef
可被转换为附加操作,运算图表的功能的文本TF_
使用OperationDescription
C API(包裹在该语言的FFI):
- 开始
TF_NewOperation()
创建TF_OperationDescription*
。
现有示例运行代码生成器作为构建过程的一部分(使用Bazel genrule)。或者,代码生成器可以通过自动cron进程运行,可能会检查结果。这会在生成的代码和OpDef
检入到存储库中的代码之间产生分歧,但对于预期会像go get
Go和cargo ops
Rust 那样预先生成代码的语言而言非常有用。另一方面,对于某些语言,代码可以从中tensorflow/core/ops/ops.pbtxt
动态生成。
处理常量
如果用户可以为输入参数提供常量,调用代码将更加简洁。生成的代码应该将这些常量转换为添加到图的操作,并用作实例化操作的输入。
可选参数
如果语言允许一个函数的可选参数(例如Python中带有默认值的关键字参数),请将它们用于可选属性,操作名称,设备,控制输入等。在某些语言中,可以使用动态范围来设置这些可选参数(如“与”在Python中的块)。如果没有这些功能,库可能采取“构建器模式”,就像在TensorFlow API的C ++版本中所做的那样。
名称范围
支持使用某种范围层次结构命名图操作是个好主意,特别是考虑到TensorBoard依靠它以合理的方式显示大图的事实。现有的Python和C ++ API采用不同的方法:在Python中,名称的“目录”部分(到最后一个“/”的所有内容都来自with
块)。实际上,有一个线程本地堆栈,范围定义了名称层次结构。名称的最后一个组件由用户明确提供(使用可选的name
关键字参数)或默认为要添加的操作的类型的名称。在C ++中,名称的“目录”部分存储在明确的Scope
对象中。NewSubScope()
方法附加到名称的那部分并返回一个新的Scope
。该名称的最后一个组件是使用该WithOpName()
方法设置的,并且像Python一样默认为要添加的op类型的名称。Scope
对象显式传递以指定上下文的名称。
封装
将生成的函数保留为某些私有操作可能是有意义的,以便可以使用执行一点额外工作的封装函数。这也提供了一个外部孵化器来支持生成代码范围之外的功能。
封装的一个用途是支持SparseTensor
输入和输出。SparseTensor
是3个稠密张量的元组:索引,值和形状。值是矢量大小n,形状是矢量大小等级,而索引是矩阵大小n,等级。有一些稀疏操作使用这个三元组来表示单个稀疏张量。
使用包装的另一个原因是拥有状态的操作。有几个这样的操作(例如一个变量)附带随从操作保持该状态。Python API为构造函数创建操作的这些操作符提供了类,并且该类上的方法向操作该状态的图上添加操作。
其他考虑事项
- 最好有一个关键字列表,用于重命名与语言关键字(或其他会导致问题的符号,如生成的代码中引用的库函数或变量的名称)相冲突的操作函数和参数。
梯度,功能和控制流程
此时,除Python以外的语言不支持梯度,函数和控制流操作(“if”和“while”)。这将在C API提供必要的支持时更新。