Tensorlang:基於TensorFlow的可微編程語言

本文經機器之心(微信公眾號:almosthuman2014)授權轉載,禁止二次轉載。

GitHub 地址:github.com/tensorlang/t

我們的目標是為更快、更強大和更易用的大規模計算網路(如深度神經網路)定義一種編程語言。

注意:在早期開發階段,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 中,我們可以將轉換關係寫為:

f -> g -> hn

這一個語句會編譯成 h(g(f)),對於更高階的轉換來說,我們可能希望添加一些額外的參數:

f -> g(1.0, .) -> hn

上面的表達式被編譯為 h(g(1.0,f)),這個語句同樣能使用多線形式表達,其中只要使用「^」就能表達中間變數或自變數的關係。

fng(1.0, ^) -- intermediatenh(^)n

符號微分

因為這些表達式可直接編譯到 TensorFlow 計算圖,且 TensorFlow 支持符號微分,那麼我們就能免費得到符號微分的方法。這一部分的語法仍然有小問題,但是這也是一種定義函數及其符號梯度的方法。

squareAndMore = func(x) { emit x * x + x }nsquareAndMoreDx = grad[squareAndMore]nn// squareAndMore(1.0) == 2.0n// squareAndMoreDx(1.0) == 3.0n

訓練和函數優化

由於神經網路只是由許多其他函數(每個函數具備某種內部狀態)構成的函數,我們可以使用這些概念訓練神經網路!我們不期待人類來確定網路的內部權重,而是用實驗方法發現可接受的權重值。這一過程就是訓練。為了訓練函數,我們需要 一些輸入值示例,以及一種確定函數輸出與可接受閾值的近似程度的方法。函數訓練器使用符號微分和更新函數隱藏狀態的規則。

查看簡單 MNIST 分類器的示例:github.com/tensorlang/t

本地循環(Native loop)

循環難以使用 TensorFlow 的 Python API 編寫。但是它不必這樣。

對比 Python API 方法:

i = tf.constant(0)nc = lambda i: tf.less(i, 10)nb = lambda i: tf.add(i, 1)nr = tf.while_loop(c, b, [i])n

與我們的方法:

r = for i = 5; foo = 1; i < 10 {n emit foo = foo * in emit i = i + 1n}nn// r:i == 10n// r:foo == 15120n

本地條件(Native conditional)

對比 TensorFlow Python API 中的 if/else 語句:

x = tf.constant(2)ny = tf.constant(5)ndef f1(): return x * 17ndef f2(): return y + 23nr = tf.cond(tf.less(x, y), f1, f2)n

x = 2ny = 5nif x < y {n x * 17n} else {n y + 23n}n

函數

函數可以採取任意數量的張量作為輸入,並生成任意數量的張量作為輸出。函數體中的表達式被懶惰而非同步地評估。好消息不僅僅是計算自動並行化,而且在計算你不需要的值時,沒有計算浪費。為了最大化這些優勢,你需要調整一下對執行的看法。

func add3(x, y, z) {n emit sum = x + y + zn emit part = x + yn}nn// r = add3(1, 2, 3)n// r:sum == 6n// r:part == 3n

在上述示例中你將會發現一個看起來熟悉的函數定義語法。我們有 emit 而不是 return,函數可以 emit 具有不同名稱的張量,但是當這些值發出時,函數無法停止執行。

屬性(Attribute)

有時你想為基於編譯時已知信息的函數實現引入靈活性。可以在這些用例中使用屬性。

func increment[amount](x) {n return amount + xn}nn// increment[amount: 1](1) == 2n// incrementByTwo = increment[amount: 2]n// incrementByTwo(1) == 3n

如上所見,有可能通過提供一個現有函數的屬性即可定義一個新函數。函數的輸入和輸出只能是向量,而屬性可以是一切。屬性容易被識別,因為它們在函數定義和應用中都被 [] 圍繞。函數屬性必須始終以關鍵字形式給出。

宏指令(Macro)

有時你想使用更高階函數工作。這可能要使用宏指令。

func incrementerFactory[amount] {n emit fn = func(x) {n emit sum = amount + xn }n} n

如上所見,函數定義與宏指令定義之間唯一的區別是使用 () 指定零或更多參數。如果定義中有 (),則是函數定義;如果沒有,則是宏指令定義。

原 文:機器之心

更多技術文章:SDK.CN - 中國領先的開發者服務平台

推薦閱讀:

如何理解Scala中的借貸模式(loan pattern)?
你見過哪些令你瞠目結舌的 Scala 代碼技巧?
Scala 是一門怎樣的語言,具有哪些優缺點?
2016年你應該學習的語言和框架

TAG:TensorFlow | 机器学习 | 编程语言 |