什么是Tensorflow.js?

TensorFlow.js是一个开源的基于硬件加速的JavaScript库,用于训练和部署机器学习模型。谷歌推出的第一个基于TensorFlow的前端深度学习框架TensorFlow.js 是一个开源的用于开发机器学习项目的 WebGL-accelerated JavaScript 库。TensorFlow.js可以提供高性能的、易于使用的机器学习构建模块,允许在浏览器上训练模型,或以推断模式运行预训练的模型。TensorFlow.js 不仅可以提供低级的机器学习构建模块,还可以提供高级的类似 Keras 的 API 来构建神经网络。

Tensorflow.js的优点

  • 1、不用安装驱动器和软件,通过链接即可分享程序。
  • 2、网页应用交互性更强。
  • 3、有访问GPS,Camera,Microphone,Accelerator,Gyroscope等传感器的标准api(主要是指手机端)。
  • 4、安全性,因为数据都是保存在客户端的。

TensorFlow.js的应用方式

  • 1、在浏览器中开发ML。使用简单直观的API从头构建模型,然后使用低级别的JavaScript线性代数库或高层API进行训练。

  • 2、运行现有模型。使用TensorFlow.js模型转换器在浏览器中运行预训练好的TensorFlow模型。

  • 3、重新训练现有模型。使用连接到浏览器的传感器数据或其他客户端数据重新训练ML模型。

一、基本概念

张量(Tensor)和变量(Variable)是TensorFlow.js中数据的主要表现形式,两者不同之处在于张量是不可变的,而变量是可变的。

(一)张量(Tensors)

张量=容器,张量是现代机器学习的基础。它的核心是一个数据容器,多数情况下,它包含数字,有时候它也包含字符串,但这种情况比较少。因此把它想象成一个数字的水桶。

张量是由一组数值形成一个或多个维度的数组。 张量实例具有定义数组形状的形状属性。

Tensorflow.js中数据的主要表现形式就是tensor(张量):由 一组数值形成一维或多维数组。一个Tensor实例有一个shape属性来定义这一组数值如何组成张量,而最主要的Tensor实例的构造函数就是 tf.tensor 函数,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
// 2x3 Tensor
const shape = [2, 3]; // 2 行, 3 列
const a = tf.tensor([1.0, 2.0, 3.0, 10.0, 20.0, 30.0], shape);
a.print(); // 打印张量值
// 输出: [[1 , 2 , 3 ],
// [10, 20, 30]]

// shape也可以用下面的方式实现:
const b = tf.tensor([[1.0, 2.0, 3.0], [10.0, 20.0, 30.0]]);
b.print();
// 输出: [[1 , 2 , 3 ],
// [10, 20, 30]]

但是,为了构造低秩张量,我们推荐使用下面的函数来增强代码的可读性:tf.scalar(零维), tf.tensor1d(一维), tf.tensor2d(二维), tf.tensor3d(三维)、tf.tensor4d(四维)以及 tf.ones(值全是1)或者tf.zeros(值全是0) ,如下所示:

1
2
3
4
5
6
7
8
9
10
11
const a = tf.scalar(3.14);
a.print(); // 输出零维张量

const b = tf.tensor2d([[2, 3, 4], [5, 6, 7]]);
b.print(); // 输出二维张量

const c = tf.zeros([2, 3]);
c.print(); // 输出2行3列的值全是0的张量

const d = tf.ones([3, 5]);
d.print(); // 输出3行5列的值全是1的张量

在TensorFlow.js中,张量是不变的; 一旦创建你就不能改变它们的值。 但是,您可以对它们执行操作来生成新的张量。

(二)变量(Variable)

变量(Variables)是通过张量进行初始化得到的。不像Tensor的值不可变,变量的值是可变的。你可以使用变量的assign方法分配一个新的tensor到这个变量上,这是变量就会改变:

1
2
3
4
5
6
7
const initialValues = tf.zeros([5]);
const biases = tf.variable(initialValues); // 初始化biases
biases.print(); // 输出: [0, 0, 0, 0, 0]

const updatedValues = tf.tensor1d([0, 1, 0, 1, 0]);
biases.assign(updatedValues); // 更新 biases的值
biases.print(); // 输出: [0, 1, 0, 1, 0]

如上所示,首先使用tf.zeros得到一个张量,然后利用这个张量初始化得到一个变量,接着我们就可以打印这个变量,并且通Object.prototype.toString.call(biases)方法可以判断变量也是一个对象,接着,我们再生成一个张量,然后变量调用assign方法传入这个张量,就可以得到一个新的变量了。

由此我们可以得出一个结论:变量由张量生成,且张量不可变而变量可变。

二、Tensorflow.js 模型

在Tensorflow.js中,从概念上来说,一个模型就是一个给定一些输入将会产生特定的输出的函数。简单来说,一个模型就是一个函数,只是它完成了特定的任务。

在TensorFlow.js中有两种方式来创建模型,一种是通过操作(ops)来直接完成模型本身所做的工作,另外一种就是通过高级API tf.model来创建一个模型,显然第二种是更容易的。

我们先看第一种创建模型的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function predict(input) {
// y = a * x ^ 2 + b * x + c
// More on tf.tidy in the next section
return tf.tidy(() => {
const x = tf.scalar(input);

const ax2 = a.mul(x.square());
const bx = b.mul(x);
const y = ax2.add(bx).add(c);

return y;
});
}

const a = tf.scalar(2);
const b = tf.scalar(4);
const c = tf.scalar(8);

const result = predict(2);
result.print();

如上所示,我们定义的predict函数就是一个模型,对于给定的输入,我们就可以得到预测的输出。注意:所有的数字都需要经过tf.scalar()张量处理。

而第二种创建模型的方法就是用 TensorFlow.js 中的 tf.model 方法(这里的model并不是真正可以调用的方法,而是一个总称,比如实际上可以调用的是tf.sequential模型),这在深度学习中是非常流行的概念。 下面的代码就创建了 tf.sequential 模型:

1
2
3
4
5
6
7
8
9
10
11
12
const model = tf.sequential();
model.add(
tf.layers.simpleRNN({
units: 20,
recurrentInitializer: 'GlorotNormal',
inputShape: [80, 4]
})
);

const optimizer = tf.train.sgd(LEARNING_RATE);
model.compile({optimizer, loss: 'categoricalCrossentropy'});
model.fit({x: data, y: labels)});

三、Tensorflow.js 内存管理

因为TensorFlow.js使用了GPU来加速数学运算,因此当tensorflow处理张量和变量时就有必要来管理GPU内存。在TensorFlow.js中,我们可以通过dispose 和 tf.tidy这两种方法来管理内存。

(一)dispose

您可以在张量或变量上调用dispose来清除它并释放其GPU内存:

1
2
3
4
5
const x = tf.tensor2d([[0.0, 2.0], [4.0, 6.0]]);
const x_squared = x.square();

x.dispose();
x_squared.dispose();

(二)tf.tidy

进行大量的张量操作时使用dispose可能会很麻烦。 TensorFlow.js提供了另一个函数tf.tidy,它对JavaScript中的常规范围起到类似的作用,不同的是它针对GPU支持的张量。

tf.tidy执行一个函数并清除所有创建的中间张量,释放它们的GPU内存。 它不清除内部函数的返回值。

1
2
3
4
5
6
7
8
const average = tf.tidy(() => {
const y = tf.tensor1d([1.0, 2.0, 3.0, 4.0]);
const z = tf.ones([4]);

return y.sub(z).square().mean();
});

average.print();

使用tf.tidy将有助于防止应用程序中的内存泄漏。它也可以用来更谨慎地控制内存何时回收。

两个重要的注意事项:

  • 1、传递给tf.tidy的函数应该是同步的,并且不会返回Promise。我们建议在tf.tidy内不要有更新UI或在发出远程请求的代码。

  • 2、tf.tidy不会清理变量。变量通常持续到机器学习模型的整个生命周期,因此TensorFlow.js不会清理它们,即使它们是在tidy中创建的。不过,您可以手动调用dispose处理它们。

四、实例

(一)使用tensorflow进行线性回归

下面是一个使用tensorflow.js进行线性回归的案例,完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
<!DOCTYPE html>
<html style="height: 100%">
<head>
<meta charset="utf-8">
</head>
<body style="height: 100%; margin: 0">
<div id="container" style="height: 100%"></div><!-- 绘图区域 -->
<script type="text/javascript" src="https://echarts.baidu.com/gallery/vendors/echarts/echarts.min.js"></script> <!-- 引入echart进行绘图 -->
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@0.14.2/dist/tf.min.js"></script><!-- 引入tersorflow.js -->
<script type="text/javascript">

function generateData(numPoints, coeff, sigma = 0.04) {//产生伪随机数
return tf.tidy(() => {
const [k, b] = [tf.scalar(coeff.k),tf.scalar(coeff.b)];

const xs = tf.randomUniform([numPoints], -1, 1);//x坐标
const ys = k.mul(xs).add(b)//y坐标
.add(tf.randomNormal([numPoints], 0, sigma));//叠加噪声

return {xs, ys: ys};
})
}

// Step 1. 要回归的变量
const k = tf.variable(tf.scalar(Math.random()));
const b = tf.variable(tf.scalar(Math.random()));
// Step 2. 选取优化器、迭代次数等参数
const numIterations = 75;
const learningRate = 0.5;
const optimizer = tf.train.sgd(learningRate);
// Step 3. 预测函数,定义为线性函数y = k * x + b
function predict(x) {// y = k * x + b
return tf.tidy(() => {return k.mul(x).add(b);});
}
// Step 4. 计算方差,方差越小说明预测值越精确
function loss(prediction, labels) {
const error = prediction.sub(labels).square().mean();
return error;
}
// Step 5. 训练函数
async function train(xs, ys, numIterations) {
for (let iter = 0; iter < numIterations; iter++) {
//优化并使方差最小
optimizer.minimize(() => {
const pred = predict(xs);//根据输入数据预测输出值
return loss(pred, ys);//计算预测值与训练数据间的方差
});

await tf.nextFrame();//
}
}
//机器学习
async function learnCoefficients() {//
const trueCoefficients = {k: 0.6, b: 0.8};//真实值
const trainingData = generateData(100, trueCoefficients);//用于模型训练的数据

await train(trainingData.xs, trainingData.ys, numIterations);// 模型训练

var xvals = await trainingData.xs.data();//训练数据的x坐标值
var yvals = await trainingData.ys.data();//训练数据的y坐标值
var sDatas = Array.from(yvals).map((y,i) => {return [xvals[i],yvals[i]]});//整理训练数据以便绘图

console.log("k&b:",k.dataSync()[0],b.dataSync()[0]);//经过训练后的系数
showResult(sDatas,k.dataSync()[0],b.dataSync()[0]);
}

//使用echart绘制结果
function showResult(scatterData,k,b){
var dom = document.getElementById("container");
var myChart = echarts.init(dom);
function realFun(x){return 0.6*x+0.8;}//理想曲线
function factFun(x){return k*x+b;}//回归后的曲线
var realData = [[-1,realFun(-1)],[1,realFun(1)]];
var factData = [[-1,factFun(-1)],[1,factFun(1)]];

var option = {
title: {text: '线性回归',left: 'left'},
tooltip: {trigger: 'axis',axisPointer: {type: 'cross'}},
xAxis: {type: 'value',splitLine: {lineStyle: {type: 'dashed'}},},
yAxis: {type: 'value',splitLine: {lineStyle: {type: 'dashed'}}},
series: [{
name: '离散点',type: 'scatter',
label: {
emphasis: {
show: true,
position: 'left',
textStyle: {
color: 'blue',
fontSize: 16
}
}
},
data: scatterData
},
{name: '理想曲线',type: 'line',showSymbol: false,data: realData,},
{name: '回归曲线',type: 'line',showSymbol: false,data: factData,},],
legend: {data:['离散点','理想曲线','回归曲线']},//图例文字
};

myChart.setOption(option, true);
}
learnCoefficients();
</script>
</body>
</html>

运行效果如下: