文/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
Python 会内置一些经常使用的方法在 builtin 中,具体的 API 列表在:docs.python.org/3.7/library… ,那么 Boa 也提供了对应的方法:
python
const { len, list, range } = boa.builtins();
复制代码
除了内置的方法外,最重要的功能即是加载 Python 包,那么 import 就是作这个事儿的。
git
const np = boa.import('numpy');
复制代码
接下来是 Python 中的关键字参数(Keyword Arguments),在 Python 中,提供了一种使用 Map 的方式来表示参数,如:
github
foobar(100, x=10, y=20)
复制代码
它能更好地帮助调用者了解每一个参数的含义,为此,在 Boa 中增长了 kwargs 方法来支持这种用法:
数组
foobar(100, boa.kwargs({ x: 10, y: 20 }));
复制代码
With 可能对于一些熟悉 JavaScript 历史的人会比较眼熟,但 Python 中的 with,用法和目的并不与 JavaScript 相同,Python 中的 with 语句有点相似于 JavaScript 中的 Block Scoping:
机器学习
with(localcontext()) {
# balabala
}
复制代码
上面的 Python 代码是将 localcontext() 的状态保存下来,而后开始执行 with 语句中的块代码,最后,将 localcontext() 的状态释放。
内部的实现机制就是每一个传到 with 语句中的变量须要实现两个方法:enter 和 exit,而后分别在块代码执行先后调用,所以对于 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() 设置了一个上下文,而后开始执行计算的代码,在块代码执行结束后,会自动将状态恢复。
最后一个要说的,就是动态的执行一些 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 来“学习和使用”机器学习和深度学习了!