Boa: 在 Node.js 中使用 Python

文/Yorkiejavascript


Hello,你們好,有一段時間不見了。

此次主要給你們帶來一個好東西,它的主要用途就是能讓你們在 Node.js 中使用 Python 的接口和函數。可能你看到這裏會好奇,會疑惑,會不解,我 Node.js 大法那麼好,幹嗎要用 Python 呢?若是你以前嘗試瞭解過一些機器學習的 JavaScript 的應用,就會比較清楚這背後的緣由。

現狀是機器學習生態幾乎是捆綁在 Python 這門語言在高速迭代着的,而 JavaScript 只能望其項背,若是咱們指望從零作到 Python 現在的規模,須要付出的工做量是巨大的,這個我在幾年前寫了 tensorflow-nodejs 的時候,就已經這麼以爲了。

因此,咱們就必須換一個思路,既然沒法超越你,那麼就利用你。對於腳本語言的開發者來講,其實並不在乎底層是如何實現的,只要上層的語言和接口是我熟悉的就好,所以 Boa 就是爲此而誕生的一個 Node.js 庫,它經過橋接 CPython 來讓 JavaScript 具有訪問 Python 生態的能力,另外又藉助於 ES6 新特性,來爲使用者提供無縫的開發體驗,那麼究竟是如何的體驗呢?

下面來看一個簡單的例子:
html

const boa = require('@pipcook/boa');
const os = boa.import('os');
console.log(os.getpid()); // prints the pid from python.

// using keyword arguments namely `kwargs`
os.makedirs('..', boa.kwargs({
  mode: 0x777,
  exist_ok: false,
}));

// using bult-in functions
const { range, len } = boa.builtins();
const list = range(0, 10); // create a range array
console.log(len(list)); // 10
console.log(list[2]); // 2
複製代碼


是否是很簡單呢,只須要經過 boa.import 將 Python 的對象加載進來後,剩下的對象訪問、函數調用以及數組訪問都與咱們使用 JavaScript 毫無區別。
java

const boa = require('@pipcook/boa');
const { len, tuple, enumerate } = boa.builtins();
const torch = boa.import('torch');
const torchtext = boa.import('torchtext');
const { nn, optim } = torch;

class TextSentiment extends nn.Module {
  constructor(sizeOfVocab, dimOfEmbed, numOfClass) {
    super();
    this.embedding = nn.EmbeddingBag(sizeOfVocab, dimOfEmbed, boa.kwargs({
      sparse: true,
    }));
    this.fc = nn.Linear(dimOfEmbed, numOfClass);
    this.init_weights();
  }
  init_weights() {
    const initrange = 0.5
    this.embedding.weight.data.uniform_(-initrange, initrange);
    this.fc.weight.data.uniform_(-initrange, initrange);
    this.fc.bias.data.zero_();
  }
  forward(text, offsets) {
    const embedded = this.embedding(text, offsets);
    return this.fc(embedded);
  }
}
複製代碼


上面的例子除了示例瞭如何從 JavaScript 中繼承自一個 Python 的類以外,還展現了咱們如何使用 PyTorch 來建立一個模型,這是否是很 JavaScript 呢?

值得一提的是,在 Boa 的代碼中,沒有對 PyTorch 作過任何的封裝,只要你在本地經過 Python 安裝了對應的包就能夠像上面的代碼同樣使用了,因此理論上你能夠對任何 Python 包作上面所作的事情。

接下來,咱們分別介紹一些主要的方法。
node

builtins()


Python 會內置一些經常使用的方法在 builtin 中,具體的 API 列表在:docs.python.org/3.7/library… ,那麼 Boa 也提供了對應的方法:
python

const { len, list, range } = boa.builtins();
複製代碼

import(name)


除了內置的方法外,最重要的功能即是加載 Python 包,那麼 import 就是作這個事兒的。
git

const np = boa.import('numpy');
複製代碼

kwargs(map)


接下來是 Python 中的關鍵字參數(Keyword Arguments),在 Python 中,提供了一種使用 Map 的方式來表示參數,如:
github

foobar(100, x=10, y=20)
複製代碼


它能更好地幫助調用者瞭解每一個參數的含義,爲此,在 Boa 中增長了 kwargs 方法來支持這種用法:
數組

foobar(100, boa.kwargs({ x: 10, y: 20 }));
複製代碼

with(ctx, fn)


With 可能對於一些熟悉 JavaScript 歷史的人會比較眼熟,但 Python 中的 with,用法和目的並不與 JavaScript 相同,Python 中的 with 語句有點相似於 JavaScript 中的 Block Scoping:
機器學習

with(localcontext()) {
  # balabala
}
複製代碼


上面的 Python 代碼是將 localcontext() 的狀態保存下來,而後開始執行 with 語句中的塊代碼,最後,將 localcontext() 的狀態釋放。

內部的實現機制就是每一個傳到 with 語句中的變量須要實現兩個方法:enterexit,而後分別在塊代碼執行先後調用,所以對於 Boa 中的用法,以下:
函數

boa.with(torch.no_grad(), () => {
  const output = model(text, offsets);
  const loss = criterion(output, cls);
  validLoss += loss.item();
  validAcc += boa.eval`(${output.argmax(1)} == ${cls}).sum().item()`;
});
複製代碼


上面的例子是 PyTorch 中一個普通的計算模型效果的邏輯,首先經過 torch.no_grad() 設置了一個上下文,而後開始執行計算的代碼,在塊代碼執行結束後,會自動將狀態恢復。

eval(str)


最後一個要說的,就是動態的執行一些 Python 表達式(單行),爲何要提供這麼一個方法呢?這仍是要說回 Python 的優點,在一些很複雜的數據處理的場景,每每 Python 表達式仍是能很是簡單易懂地表達,這樣就大大地減小了代碼的複雜度,咱們先來看一個例子:

const line = (boa.eval`'\t'.join([str(x) for x in ${vec}])`);
複製代碼


上面的代碼若是要換成 JavaScript 的話:

vec.map(x => x.toString()).join('\t');
複製代碼


看着彷佛差很少了多少是吧?那麼再來看看下面的例子:

boa.eval`{u:i for i, u in enumerate(${vocab})}`;
boa.eval`[${char2idx}[c] for c in ${text}]`
boa.eval`${chunk}[:-1]`
boa.eval`${chunk}[0:-1:2]`
複製代碼


怎麼樣,是不是感受上面的例子已經無法使用 JavaScript 簡單的一行就能搞定了呢?

不過值得一提的是,JavaScript 在這方面也在漸漸地彌補,這裏 是整理的一些 TC39 正在作的一些相關的標準,其中就包括上面的 Slice Notation。


說回到 eval 的定位,它像是對 JavaScript 的補充,它在一些標準還未落地和穩定以前,可讓咱們使用 Python 表達式來更簡單地表達,而所須要的僅僅是一些低成本的學習便可。

接下來就說說 eval 到底如何使用,它接受一個「字符串」,但咱們通常在使用時都會經過 Template String,下來先看兩個例子:

boa.eval('print("foobar")');
boa.eval(`print("${txt}")`);
複製代碼


看完上面兩行代碼,它們是比較少見的用法。真正經常使用,也是最能發揮出 eval 效果的是使用 Tagged Template String,這種用法就像咱們一開始看到的同樣,在 eval 後面直接跟模版字符串的內容,這樣作的好處是 eval 函數會接收到全部的模版參數,這樣咱們即可以將 JavaScript 的對象和 Python 表達式打通,實現更平滑的使用體驗,以下:

const chunk = range(0, 10);
boa.eval`${chunk}[0:-1:2]`
複製代碼


上面就是把 chunk 傳到了表達式中,再經過 Python 的 Slice Notation 語法去取到對應的值,最後返回到 JavaScript 的世界中。

尾聲


好了,簡單的 API 介紹就先到這裏,若是想了解更多 API 和 Boa 的能力,能夠到 Boa 的文檔瞭解:github.com/alibaba/pip…

另外,Boa 做爲 Pipcook 的一個子項目,也很是歡迎你們來加入進來,對於想加入的同窗能夠經過這些 Issue 做爲不錯的開始:github.com/alibaba/pip…。 最後再說一下 Boa 的初衷,就是但願能讓 Node.js 開發者更無縫地使用 Python 中豐富的機器學習生態。能夠說,從今天開始,你就能夠開始看着 Python 的文檔,使用 JavaScript 來「學習和使用」機器學習和深度學習了!

相關文章
相關標籤/搜索