原文:tensorlang
TensorFlow是一个在大规模进行机器学习研究的令人印象深刻的运行时。它有一个解释器、一个JIT(称为XLA)和一个提前编译器。它为ARM和amd64等处理器架构生成本机代码。它有在GPU和其他专用硬件上运行的快速内核。它运行在主要操作系统上,包括Linux、macOS、Windows、iOS和Android。
但是TensorFlow的端到端可用性(在某些情况下,性能)落后于主流的编程环境,比如Python。虽然有很多原因,但我们认为一个很大的原因是TensorFlow缺乏自己的语法。
Tensorlang正努力填补这一空白。Tensorlang定义了一组语法和语义,这些语法和语义提供了对TensorFlow运行时和工具链的完整性能和表示性的访问。
一旦完成,Tensorlang将是一个专门为机器学习而调整的编程环境。这个环境将立即与现有的机器学习生态系统兼容,并且比其他任何环境都更快、更强大、使用起来更愉快。
注:在早期的发展过程中,Tensorlang的代号为Nao(脑的汉语双关语)。仍有许多地方使用这个名称,但尚未迁移过来。
为什么要用一种新的编程语言?
根据对现有工具的经验,Tensorlang的设计满足了许多需求:
- 以线性比例饱和单机本地CPU和GPU的能力
- 无缝扩展到机器集群
- 能够将程序编译为在主要操作系统和移动设备上快速运行的本机代码
- 对符号区分的本地支持
- 图形错误的简单调试和实际堆栈跟踪
- 与其他编程环境匹配的执行模型(例如,无延迟执行)
- 高效的复制环境
- 与现有库和模型的兼容性
为了做到这一点,我们需要在许多方面提高技术水平:
- 调试
- 维护
- 组成(用较小的系统构建较大的系统)
- 清晰
- 在幕后,Tensorlang直接将程序编译为TensorFlow元图efs
为什么不直接使用现有的TensorFlow Python API呢?
TensorFlow专门构建计算图。这些图可以很大,它们的执行可以分布在大量的机器上。实现这一点的一部分技巧是允许表达式彼此异步求值。虽然现有的TensorFlow软件包提供了定义这些表达式的API,但它们不提供语法、高级工具链或生产性开发环境。
Tensorlang有一种语法,适用于当今机器学习模型中的各种数据流计算。它支持模板化、类型推理和符号微分。
为什么不直接将现有的Python语言编译成TensorFlow呢?
将Python这样的语言直接编译到TensorFlow需要两个不幸的折衷方案之一。或者:
默认情况下,使Python并行,但这意味着大多数现有的Python程序都无法工作。这首先降低了使用Python的好处。
放弃TensorFlow并行模型的优点。这将大大降低语言的灵活性和可伸缩性。
因此,我们需要的语言语义与主流语言中的有些不同。为什么要定义新语法?
语法是在编程语言中调用和操作特定概念的一种方法。一个好的语法可以在对新来者的亲和力和对底层语义的适当性之间取得平衡。我们的大多数语法都非常接近现有的语言(特别是Go、JavaScript和Python)。我们介绍了一些新的形式,它们特别适合于许多机器学习模型中的概念。
例如,机器学习的许多论文都包括描述应用于数据的转换的图表。这些图看起来有点像f->g->h。用主流语言的语法编写这些图会颠倒h(g(f))的顺序,这会模糊人们更喜欢谈论它的自然方式。接受一种新的语法意味着我们可以写出读起来像他们所表达的思想的表达式。在Tensorlang,我们可以写:
f -> g -> h
把它编译成h(g(f))。对于更高级的转换,我们可能需要包括其他参数:
f -> g(1.0, .) -> h
以上表达式编译为’h(g(1.0,f))``
这种语法有一种多行形式,它使用^字符。
f
g(1.0, ^) -- intermediate
h(^)
符号微分
由于这些表达式直接编译成TensorFlow图,并且TensorFlow支持符号微分,因此我们免费获得符号微分。它的语法仍然有点混乱,但这是一种定义函数及其符号渐变的方法。
squareAndMore = func(x) { emit x * x + x }
squareAndMoreDx = grad[squareAndMore]
// squareAndMore(1.0) == 2.0
// squareAndMoreDx(1.0) == 3.0
训练与功能优化
由于神经网络只是一个由许多其他函数组成的函数,每个函数都有一些内部状态,我们可以用这些概念来训练网络!我们可以通过实验发现可接受的值,而不是期望人类来确定网络的内部权重。这个发现的过程称为训练。为了训练一个函数,我们需要一些示例输入值和一种方法来确定函数的输出如何接近一个可接受的阈值。函数训练器使用符号微分以及关于如何更新函数隐藏状态的规则。
看看一个简单的MNIST分类器的例子。
原生循环
使用TensorFlow的Python API很难编写循环。但不一定是这样。
比较Python API的方式:
i = tf.constant(0)
c = lambda i: tf.less(i, 10)
b = lambda i: tf.add(i, 1)
r = tf.while_loop(c, b, [i])
以我们的方式:
注意,//行是表示循环执行后r的状态的注释。
r = for i = 5; foo = 1; i < 10 {
emit foo = foo * i
emit i = i + 1
}
// r:i == 10
// r:foo == 15120
原生条件句
比较TensorFlow Python API中的if/else语句:
x = tf.constant(2)
y = tf.constant(5)
def f1(): return x * 17
def f2(): return y + 23
r = tf.cond(tf.less(x, y), f1, f2)
和
x = 2
y = 5
if x < y {
x * 17
} else {
y + 23
}
函数
一个函数可以接受任意数量的张量作为输入,生成任意数量的张量作为输出。
函数体中的表达式是惰性和异步计算的。好消息是,不仅计算是自动并行的,而且没有计算是浪费计算值,你不需要。为了充分利用这些好处,你需要调整一下你对什么时候执行的想法。
func add3(x, y, z) {
emit sum = x + y + z
emit part = x + y
}
// r = add3(1, 2, 3)
// r:sum == 6
// r:part == 3
在上面的示例中,您将注意到一个外观熟悉的函数定义语法。不是返回,而是发出,因为函数可以发出具有不同名称的张量,但在发出这些值时,函数不会停止执行。
属性
有时您希望根据编译时已知的信息将灵活性引入到函数的实现中。在这些情况下,使用属性。
func increment[amount](x) {
return amount + x
}
// increment[amount: 1](1) == 2
// incrementByTwo = increment[amount: 2]
// incrementByTwo(1) == 3
如上所示,只提供现有函数的属性就可以定义新函数。虽然函数输入和输出只能是张量,但属性可以是任何东西。属性很容易被发现,因为它们在函数定义和函数应用程序中都被[]包围。函数属性必须始终以关键字形式给出。
宏
有时你想用高阶函数。这可以使用宏。
func incrementerFactory[amount] {
emit fn = func(x) {
emit sum = amount + x
}
}
如上所述,函数定义和宏定义之间的唯一区别是使用()指定零个或多个参数。如果()存在于定义中,则它是函数定义。如果没有,这是一个宏定义。
实现细节
Tensorlang编译器的当前实现是用Python 3编写的。它的未来版本可能根本不依赖于Python。它可以生成在没有Python的情况下可用的图。这对于部署在TensorFlow服务之类的地方很重要。
Python集成
如果您想在Python项目中使用Tensorlang生成的图,像TFI这样的项目可以提供从普通Python程序对模型的单行访问。TFI处理像image/png这样的mimetype到适当形状和dtype的张量的映射。