Tensorlang:基于TensorFlow的可微编程语言

近日,Adam Bouhenguel 在 GitHub 上发布了一种基于 TensorFlow 的新型编程语言 Tensorlang,适用于更快、更强大和更易用的大规模计算网络(如深度神经网络)。本文介绍了 Tensorlang 的优势。

GitHub 地址:https://github.com/tensorlang/tensorlang

我们的目标是为更快、更强大和更易用的大规模计算网络(如深度神经网络)定义一种编程语言。

注意:在早期开发阶段,Tensorlang 的代号是「Nao」(脑)。现在仍然有一些地方还在使用「Nao」,需要注意。

为什么要创建新的编程语言?

根据现有工具的使用经验,Tensorlang 的设计目标是解决以下需求:

  • 用线性缩放使单个机器的本地 CPU 和 GPU 饱和的能力;
  • 无缝扩展至机器集群;
  • 将程序编译成可在主要操作系统和移动设备上快速运行的本地代码的能力;
  • 本地支持符号微分;
  • 易于对图误差进行 debug 和实际的堆栈跟踪;
  • 匹配其他编程环境(如无延迟执行)的执行模型;
  • 高产的 REPL 环境;
  • 与现有库和模型的兼容性。

为达到以上目的,我们需要在多个方面进行改进:

  • debug
  • 维护
  • 构建(基于小系统构建大系统)
  • 清晰

这样,Tensorlang 可直接将程序编译为 TensorFlow MetaGraphDefs。

为什么不使用现有的 TensorFlow Python API?

TensorFlow 专门用于构建计算图。这些图比较大,且其执行需要在大量机器上展开。其运转的部分技巧在于允许异步评估表达式。尽管现有的 TensorFlow 软件包提供定义这些表达式的 API,但它们不提供高级别的语法工具链,或者高产的开发环境。

Tensorlang 具备适合当前机器学习中数据流计算的语法,支持模板、类型推断和符号微分。

为什么不直接将现有语言(如 Python)编译成 TensorFlow?

直接将语言编译成 TensorFlow 需要作出妥协(以下两种之一):

1. 默认 Python 可并行执行,但这意味着大部分现有 Python 程序无法运行,使用 Python 的益处大打折扣。

2. 放弃 TensorFlow 并行模型的优势。这将大幅降低语言的灵活性和可扩展特性。

所以我们需要和主流编程语言稍微不同的语言语义,那么为什么需要定义一种新的语法呢?

编程语法是用编程语言调用和操作一些特定概念的方法,大多数语法非常接近 GO、JavaScript 和 Python 等主流语言。我们在该项目中介绍了一种新型语言,它非常适合于构建许多当前流行的机器学习模型。

例如机器学习中的许多论文包含了将数据的转换描述为图形变换,这些图可能看起来像 f - > g - > h。若用主流语言描述这种变换,可能我们需要使用复合函数并颠倒书写顺序为 h(g(f)),这种方式阻碍了人们用更自然的方式表达这种变换。而构建一种专门化的语法意味着我们能按照原来的转换关系图表达运算过程。

在 Tensorlang 中,我们可以将转换关系写为:

  1. f -> g -> h

这一个语句会编译成 h(g(f)),对于更高阶的转换来说,我们可能希望添加一些额外的参数:

  1. f -> g(1.0, .) -> h

上面的表达式被编译为 h(g(1.0,f)),这个语句同样能使用多线形式表达,其中只要使用「^」就能表达中间变量或自变量的关系。

  1. f

  2. g(1.0, ^)  -- intermediate

  3. h(^)

符号微分

因为这些表达式可直接编译到 TensorFlow 计算图,且 TensorFlow 支持符号微分,那么我们就能免费得到符号微分的方法。这一部分的语法仍然有小问题,但是这也是一种定义函数及其符号梯度的方法。

  1. squareAndMore = func(x) { emit x * x + x }

  2. squareAndMoreDx = grad[squareAndMore]

  3. // squareAndMore(1.0) == 2.0

  4. // squareAndMoreDx(1.0) == 3.0

训练和函数优化

由于神经网络只是由许多其他函数(每个函数具备某种内部状态)构成的函数,我们可以使用这些概念训练神经网络!我们不期待人类来确定网络的内部权重,而是用实验方法发现可接受的权重值。这一过程就是训练。为了训练函数,我们需要 一些输入值示例,以及一种确定函数输出与可接受阈值的近似程度的方法。函数训练器使用符号微分和更新函数隐藏状态的规则。

查看简单 MNIST 分类器的示例:https://github.com/tensorlang/tensorlang/blob/master/root/src/demo/digits_nb.ipynb。

本地循环(Native loop)

循环难以使用 TensorFlow 的 Python API 编写。但是它不必这样。

对比 Python API 方法:

  1. i = tf.constant(0)

  2. c = lambda i: tf.less(i, 10)

  3. b = lambda i: tf.add(i, 1)

  4. r = tf.while_loop(c, b, [i])

与我们的方法:

  1. r = for i = 5; foo = 1; i < 10 {

  2.  emit foo = foo * i

  3.  emit i = i + 1

  4. }

  5. // r:i == 10

  6. // r:foo == 15120

本地条件(Native conditional)

对比 TensorFlow Python API 中的 if/else 语句:

  1. x = tf.constant(2)

  2. y = tf.constant(5)

  3. def f1(): return x * 17

  4. def f2(): return y + 23

  5. r = tf.cond(tf.less(x, y), f1, f2)

  1. x = 2

  2. y = 5

  3. if x < y {

  4.  x * 17

  5. } else {

  6.  y + 23

  7. }

函数

函数可以采取任意数量的张量作为输入,并生成任意数量的张量作为输出。函数体中的表达式被懒惰而异步地评估。好消息不仅仅是计算自动并行化,而且在计算你不需要的值时,没有计算浪费。为了最大化这些优势,你需要调整一下对执行的看法。

  1. func add3(x, y, z) {

  2.  emit sum = x + y + z

  3.  emit part = x + y

  4. }

  5. // r = add3(1, 2, 3)

  6. // r:sum == 6

  7. // r:part == 3

在上述示例中你将会发现一个看起来熟悉的函数定义语法。我们有 emit 而不是 return,函数可以 emit 具有不同名称的张量,但是当这些值发出时,函数无法停止执行。

属性(Attribute)

有时你想为基于编译时已知信息的函数实现引入灵活性。可以在这些用例中使用属性。

  1. func increment[amount](x) {

  2.  return amount + x

  3. }

  4. // increment[amount: 1](1) == 2

  5. // incrementByTwo = increment[amount: 2]

  6. // incrementByTwo(1) == 3

如上所见,有可能通过提供一个现有函数的属性即可定义一个新函数。函数的输入和输出只能是向量,而属性可以是一切。属性容易被识别,因为它们在函数定义和应用中都被 [] 围绕。函数属性必须始终以关键字形式给出。

宏指令(Macro)

有时你想使用更高阶函数工作。这可能要使用宏指令。

  1. func incrementerFactory[amount] {

  2.  emit fn = func(x) {

  3.    emit sum = amount + x

  4.  }

  5. }

如上所见,函数定义与宏指令定义之间唯一的区别是使用 () 指定零或更多参数。如果定义中有 (),则是函数定义;如果没有,则是宏指令定义。

工程
暂无评论
暂无评论~
返回顶部