00、背景
作为后端程序员,经常喜欢用Java实现计算接口,然后Deploy至ECS上,但这样也带来了服务器购买、运维、流量等成本。
其实,我们可以将很多计算逻辑进行拆解,部分计算转移到用户前端浏览器,从而降低服务器负担。
01、vectorious是什么
Vectorious 是一个 JavaScript 库用来操作 N 维数组和向量。支持 Node.js 和浏览器。
用 TypeScript 编写,并通过与 BLAS 和 LAPACK 的 C++ 绑定进行加速。
LAPACK是一个高性能的线性代数计算库,以BLAS(Basic Linear Algebra Subprograms)为基础,用Fortran语言编写,可用于计算诸如求解线性代数方程、线性系统方程组的最小平方解、计算特征值和特征向量等问题。
BLAS 提供了一些基本的矩阵和向量运算,LAPACK 提供了更丰富的线性方程求解、二次规划、特征值分解等等的运算。
API文档地址
https://docs.vectorious.org/vectorious/6.1.0/
02、安装
请遵循 nlapack 和 nblas 中的安装说明以获得最佳性能。
# with C++ bindings
$ npm install vectorious
# or, if you don't want C++ bindings
$ npm install vectorious --no-optional
该包中公开了三个输出包。
CommonJS
Node.js 包可以在 dist/index.js 中找到,并使用 require() 语法导入:
const v = require('vectorious');
浏览器
浏览器捆绑包可以在 dist/index.browser.js 中找到,并使用 <script> 标签导入:
<script src="dist/index.browser.js" />
它在 window 对象中公开了一个名为 v 的全局变量,并且可以像这样访问:
<script>
const x = v.array([1, 2, 3]);
</script>
ES模块
在版本 6.1.0 中添加,vectorious 在 dist/index.mjs 公开了一个 ES 模块包,可以使用 import 语法导入:
import { array } from 'vectorious';
const x = array([1, 2, 3]);
03、入门用法
import { array, random, range } from 'vectorious';
// 创建一个随机2x2矩阵
const x = random(2, 2);
/*
array([
[
0.26472008228302,
0.4102575480937958
],
[
0.4068726599216461,
0.4589384198188782
]
], dtype=float64)
*/
// 创建一个包含从0到8的值的一维向量,并将其reshape为3x3矩阵。
const y = range(0, 9).reshape(3, 3);
/*
array([
[ 0, 1, 2 ],
[ 3, 4, 5 ],
[ 6, 7, 8 ]
], dtype=float64)
*/
// 将x的第二行添加到第一行
y.slice(0, 1).add(y.slice(1, 2));
/*
array([
[ 3, 5, 7 ],
[ 3, 4, 5 ],
[ 6, 7, 8 ]
], dtype=float64)
*/
// 交换矩阵
y.swap(0, 1);
/*
array([
[ 3, 4, 5 ],
[ 3, 5, 7 ],
[ 6, 7, 8 ]
], dtype=float64)
*/
// 创建一个2x2x1的张量
const z = array([
[[1], [2]],
[[3], [4]],
]);
/*
array([
[ [ 1 ], [ 2 ] ],
[ [ 3 ], [ 4 ] ]
], dtype=float64)
*/
04、几个示例
1、求解线性方程组
import { random } from '../src/core/random';
const a = random(3, 3).scale(10);
const b = random(3, 1).scale(10);
console.log(`a: ${a}`);
console.log(`b: ${b}`);
console.log(`a.solve(b): ${a.solve(b)}`);
cmd执行:
ts-node solve.ts
2、神经网络
import { array } from '../src/core/array';
import { random } from '../src/core/random';
import { ones } from '../src/core/ones';
import { map } from '../src/core/map';
const sigmoid: (ddx: boolean) => (value: number) => number =
(ddx: boolean): ((value: number) => number) =>
(value: number): number =>
ddx ? value * (1 - value) : 1 / (Math.exp(-value) + 1);
((): void => {
// 输入
const x = array([
[0, 0, 1],
[0, 1, 1],
[1, 0, 1],
[1, 1, 1],
]);
// 输出
const y = array([[0, 1, 1, 0]]).T;
// 初始化权重 [-1, 1)
const syn0 = random(3, 4).scale(2).subtract(ones(3, 4));
const syn1 = random(4, 1).scale(2).subtract(ones(4, 1));
// Layers and deltas
let l0 = random(4, 4);
let l1 = random(4, 1);
let l0_delta = random(4, 4);
let l1_delta = random(4, 1);
let i: number;
for (i = 0; i < 10000; i += 1) {
console.log(`${((100 * i) / 10000).toFixed(2)} %`);
l0 = map(x.multiply(syn0), sigmoid(false));
l1 = map(l0.multiply(syn1), sigmoid(false));
l1_delta = y
.copy()
.subtract(l1)
.product(map(l1, sigmoid(true)));
l0_delta = l1_delta.multiply(syn1.T).product(map(l0, sigmoid(true)));
syn1.add(l0.T.multiply(l1_delta));
syn0.add(x.T.multiply(l0_delta))
}
// 最终的训练神经网络输出!
// 应接近 [[0, 1, 1, 0]] 转置"
console.log(l1);
})();
3、逻辑回归
import { NDArray } from '../src/core';
import { array } from '../src/core/array';
import { map } from '../src/core/map';
import { ones } from '../src/core/ones';
import { zeros } from '../src/core/zeros';
// Softmax是一种数学函数,通常用于将一组任意实数转换为表示概率分布的实数。其本质上是一种归一化函数,可以将一组任意的实数值转化为在[0, 1]之间的概率值
const softmax = (x: NDArray) => {
const { data: d1 } = x;
const [, c] = x.shape;
const max: number = x.max();
let sum: number;
return x
.add(ones(...x.shape).scale(-max))
.exp()
.map((value: number, index: number) => {
const i: number = Math.floor(index / c);
const j: number = index % c;
if (j === 0) {
sum = 0;
let k: number;
for (k = 0; k < c; k += 1) {
sum += d1[i * c + k];
}
}
return value / sum;
});
};
// 获取矩阵的列均值作为向量
const mean = (x: NDArray) => {
const { data: d1 } = x;
const [, c] = x.shape;
const vec = zeros(c);
return vec.map((_: number, index: number): number => {
let sum: number = 0;
let j: number;
for (j = 0; j < c; j += 1) {
sum += d1[index * c + j];
}
return sum / c;
});
};
// 逐行将向量添加到矩阵中
const addMatVec = (x: NDArray, y: NDArray) => {
const { data: d1 } = y;
const [, c] = x.shape;
return map(x, (value: number, index: number): number => {
const j: number = index % c;
return value + d1[j];
});
};
((): void => {
const x = array([
[1, 1, 1, 0, 0],
[0, 0, 1, 1, 1],
]);
const y = array([
[1, 0],
[0, 1],
]);
const w = zeros(x.shape[1], y.shape[1]);
const b = zeros(y.shape[1]);
// 学习率
const alpha: number = 0.01;
let prob: NDArray;
let delta: NDArray;
// 训练
let i: number;
for (i = 0; i < 800; i += 1) {
prob = softmax(addMatVec(x.multiply(w), b));
delta = y.copy().subtract(prob);
w.add(x.T.multiply(delta).scale(alpha));
b.add(mean(delta).scale(alpha));
}
// 预测
const z = array([
[1, 1, 0, 0, 0],
[0, 0, 0, 1, 1],
[1, 1, 1, 1, 1],
]);
console.log(softmax(addMatVec(z.multiply(w), b)));
// 预测结果应接近[[1, 0], [0, 1], [0.5, 0.5]]
})();
05、单元测试
所有函数都附带了一个 .spec.ts 文件。
Jest 测试框架用于测试,整个测试套件可以使用单个命令运行:
npm test
输出测试报告:
06、基准测试
所有函数都附带一个 .bench.ts 文件。
使用以下命令运行所有基准测试:
npm run benchmark
或者对于单个函数:
npx ts-node src/core/abs.bench.ts
此处代码,在window下无法执行。
提示如下错误:
解决办法:
1、全局安装:npm install mkdirp -g
2、修改src/bench.ts文件第11行代码
将execSync(mkdir /p benchmarks/${group});
替换为execSync(mkdirp /p benchmarks/${group});
注意:这就是mkdirp包存在的原因——将其添加为开发依赖项并mkdirp在脚本中使用二进制文件而不是特定于平台的mkdir。
本文暂时没有评论,来添加一个吧(●'◡'●)