种草TensorFlowJS

种草 TensorFlow.js

导读

本文的目的主要是引导刚接触人工智能或者机器学习的同学,能够从第一步开始学习TensorFlowJS。阅读本文先确认具备以下基础技能:

  1. 会使用 JS 编程(TensorFlow 也支持 Python、Java、C++、Go)
  2. 一些数组相关的知识(线性代数没忘干净就行)
  3. 最好再懂点机器学习相关的知识(临时百度、Google也ok)

什么是TensorFlow?

TensorFlow是一个采用数据流图(Data Flow graphs),各类机器学习算法的人工智能的基础库。谷歌大脑自2011年成立起开展了面向科学研究和谷歌产品开发的大规模深度学习应用研究,早期TensorFlow的前身是DistBelief。DistBelief的功能是构建各尺度下的神经网络分布式学习和交互系统,也被称为“第一代机器学习系统” 。
2015年11月,在DistBelief的基础上,谷歌大脑完成了对“第二代机器学习系统”TensorFlow的开发并对代码开源。相比于前作,TensorFlow在性能上有显著改进、构架灵活性和可移植性也得到增强。

TensorFlow支持多种客户端语言下的安装和运行。截至版本1.12.0,绑定完成并支持版本兼容运行的语言为C和Python,其它试验性绑定完成的语言为JavaScript、C++、Java、Go和Swift,依然处于开发阶段的包括C#、Haskell、Julia、Ruby、Rust和Scala。它灵活的架构让你可以在多种平台上展开计算,例如台式计算机中的一个或多个CPU(或GPU),服务器,移动设备等等。TensorFlow 最初由Google机器智能研究机构的研究员和工程师们开发出来,用于机器学习和深度神经网络方面的研究,但这个系统的通用性使其也可广泛用于其他计算领域。TensorFlow.js官网

Tensorflow特点:高度的灵活性,真正的可移植性,多语言支持,性能最优化

什么是数据流图(Data Flow Graph)?

数据流图用“结点”(nodes)和“线”(edges)的有向图来描述数学计算。“节点” 一般用来表示施加的数学操作,但也可以表示数据输入的起点/输出的终点,或者是读取/写入持久变量的终点。“线”表示“节点”之间的输入/输出关系。这些数据“线”可以输运“size可动态调整”的多维数据数组,即“张量”(tensor)。

Data Flow graph

张量从图中流过的直观图像是这个工具取名为“Tensorflow”的原因。一旦输入端的所有张量准备好,节点将被分配到各种计算设备完成异步并行地执行运算。

TensorFlow 中的数据流图有以下几个优点:

  • 可并行:计算节点之间有明确的线进行连接,系统可以很容易的判断出哪些计算操作可以并行执行
  • 可分发:图中的各个节点可以分布在不同的计算单元(CPU、 GPU)或者不同的机器中,每个节点产生的数据可以通过明确的线发送的下一个节点中
  • 可优化:TensorFlow 中的 XLA 编译器可以根据数据流图进行代码优化,加快运行速度
  • 可移植:数据流图的信息可以不依赖代码进行保存,如使用Python创建的图,经过保存后可以在C++或Java中使用

是不是上来直接看TensorFlow有些晦涩难懂呢?那我们就从人工智能的底层模型讲起,人工智能的底层模型是“神经网络”。许多复杂的应用和高级模型(比如深度学习)都基于它,学习人工智能,应该是是先从了解它开始。

什么是神经网络?

顾名思义,每个人都有神经,人体的神经遍布全身,形成一张张的神经网络;科学家一直希望模拟人的大脑,造出可以思考的机器;那么人为什么能够思考?科学家发现,原因在于人体的神经网络;外部刺激神经末梢,转化为电信号,转导到神经细胞(又叫神经元);无数神经元构成神经中枢,神经中枢综合各种信号,做出判断;人体根据神经中枢的指令,对外部刺激做出反应。如果能够”人造神经元”,就能组成人工神经网络,学着人类一样模拟思考。所以我们所说的人工智能中的神经网络,通常指的就是“人工神经网络”

举一个通俗的决策例子:周末科技馆举办科技展览,小明拿不定主意,周末要不要去参观,他决定考虑三个因素。

1.天气:周末是否晴天?

2.同伴:能否找到人一起去?

3.价格:门票是否可承受?

那么这三个就是决定他去不去展览的因素,我们先定义这三个因素权重为(w1,w2,w3),假如对应分别为(4,2,2),权重总和为8,那我们设置阈值,当小明觉着阈值大于等于4的时候,那么他就有比较强烈的意向去餐馆科技展览,如果小于4的话,小明就不太想去参加科技展了,设置的阈值的高低代表了意愿的强烈,阈值越高就表示越想去,越低就越不想去。那我在设置一个感知器(x1,x2,x3),如果满足条件为1,不满足则为0,总阈值为Output,那么我们简单画一下小明参加科技展的这个最简单的决策模型:

小明参加科技展的决策模型

Output = x1 w1 + x2 w2 + x3 * w3

那么判断逻辑如下:

Output >= 4 ? Yes : No

比如:天气晴天,小明即便没有找到同伴,哪怕贵一点也就去了;又或者天气不好,但是有同伴门票也不贵于是也就去了。
这是一个比较简单的决策模型,那么现实中很多事情,依赖的因素很多,就不是这么简简单单的模型了,就像下图中的决策模型,它就有多层,不是像上面小明这个例子通过一层决策就可以的,多层结构的模型就会让事情的结果变得更复杂,让结果变得更加变幻莫测,例如下面的决策模型:

现实世界的决策模型

其中,最困难的部分就是确定权重和阈值。目前为止,这两个值都是主观给出的,但现实中很难估计它们的值,必需有一种方法,可以找出答案。
这种方法就是试错法,其他参数都不变,w的微小变动,记作Δw,然后观察输出有什么变化。不断重复这个过程,直至得到对应最精确输出的那组权重和阈值,就是我们需要的值。这个过程称为模型的训练,整个过程需要海量计算,所以要使用专门为机器学习定制的 GPU 来计算。

上面讲了这些神经网络的基础知识,那么下面我们来讲下TensorFlow的基础知识:

TensorFlow基础知识

安装

  • 使用Script Tag
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>
<head>
<!-- Load TensorFlow.js -->
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@0.9.0"> </script>
<script>
const b = tf.tensor2d([[2, 3, 4], [5, 6, 7]]);
b.print();
...
});
</script>

</head>
<body>
</body>
</html>
  • 通过NPM(或yarn)
1
2
yarn add @tensorflow/tfjs  
npm install @tensorflow/tfjs

张量

TensorFlow 内部的计算都是基于张量(Tensor)的,因此我们有必要先对张量有个认识。张量是在我们熟悉的标量、向量之上定义的,详细的定义比较复杂,我们可以先简单的将它理解为一个多维数组:

1
2
3
4
3                                       # 这个 0 阶张量就是标量,shape=[]
[1., 2., 3.] # 这个 1 阶张量就是向量,shape=[3]
[[1., 2., 3.], [4., 5., 6.]] # 这个 2 阶张量就是二维数组,shape=[2, 3]
[[[1., 2., 3.]], [[7., 8., 9.]]] # 这个 3 阶张量就是三维数组,shape=[2, 1, 3]

TensorFlow 内部使用tf.Tensor类的实例来表示张量,每个 tf.Tensor有两个属性:

  • dtype Tensor 存储的数据的类型,可以为tf.float32、tf.int32、tf.string.
  • shape Tensor 存储的多维数组中每个维度的数组中元素的个数,如上面例子中的shape.

例子:

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
// 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]]

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

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

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

TensorFlow应用实例

上面介绍了 TensorFlow 中的一些基本概念,下面我们通过一个小例子来了解一下怎么使用 TensorFlow 进行机器学习。这里我们将使用TensorFlow来根据数据做拟合曲线。即使用多项式产生数据然后再改变其中某些数据(点),然后我们会训练模型来找到用于产生这些数据的多项式的系数。简单的说,就是给一些在二维坐标中的散点图,然后我们建立一个系数未知的多项式,通过TensorFlow.js来训练模型,最终找到这些未知的系数,让这个多项式和散点图拟合。一般做一个应用大致分为以下几个步骤:

第一步:建立模型

第二步:实现模型

第三步:训练模型

第四步:评估模型

步骤一:建立模型

例如下面一个简单例子,下表是我们进行某项实验获得的一些实验数据:

Input Output
1 4.8
2 8.5
3 10.4
6 21
8 25.3

我们将这些数据放到一个二维的图表上可以看的更直观一些,如下,这些数据在图中表现为一些离散的点:

我们需要根据现有的这些数据归纳出一个通用模型,通过这个模型我们可以预测其他的输入值产生的输出值。如果用 x 表示输入, y 表示输出,线性模型可以用下面的方程表示(如果我们学过线性代数的话应该了解下面的方程式):

1
y = w * x + b

即使我们选择了直线模型,可以选择的模型也会有很多,如下图的三条直线都像是一种比较合理的模型,只是w和b参数不同。

这时我们需要设计一个损失模型(loss model),来评估一下哪个模型更合理一些,并找到一个最准确的模型。

我们用y′表示实验得到的实际输出,用下面的方程表示我们的损失模型:

显然,损失模型里得到的loss越小,说明我们的线性模型越准确。

步骤二:实现模型

上面我们根据实验数据建立了一个线性模型,并为这个线性模型设计了一个损失模型,下面介绍的是怎么在 TensorFlow 中实现我们设计的模型。
在我们的线性模型 y = w * x + b 中,输入x可以用占位 Tensor 表示,输出y可以用线性模型的输出表示,我们需要不断的改变W和b的值,来找到一个使loss最小的值。这里W和b可以用变量 Tensor 表示,如下就是我们模型的实现代码逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import tensorflow as tf

# 创建变量 W 和 b 节点,并设置初始值
W = tf.Variable([.1], dtype=tf.float32)
b = tf.Variable([-.1], dtype=tf.float32)
# 创建 x 节点,用来输入实验中的输入数据
x = tf.placeholder(tf.float32)
# 创建线性模型
linear_model = w * x + b

# 创建 y 节点,用来输入实验中得到的输出数据,用于损失模型计算
y = tf.placeholder(tf.float32)
# 创建损失模型
loss = tf.reduce_sum(tf.square(linear_model - y))

# 创建 Session 用来计算模型
sess = tf.Session()

# 初始化变量,通过tf.Variable生成的初始值不能立即使用
init = tf.global_variables_initializer()
sess.run(init)

我们可以先用上面对 wb 设置一个随机的初始值为 0.1-0.1 运行一下我们的线性模型看看结果:

1
2
print(sess.run(loss, {x: [1, 2, 3, 6, 8], y: [4.8, 8.5, 10.4, 21.0, 25.3]}))
// 1223.05 loss的值有点大

我们需要不断调整变量w和b的值,找到使损失值最小的w和b;这肯定是一个非常无聊的过程,因此 TensorFlow 提供了训练模型的方法,自动帮我们进行这些繁琐的训练工作。

步骤三:训练模型

TensorFlow 提供了很多优化算法来帮助我们训练模型。最简单的优化算法是梯度下降(Gradient Descent)算法,它通过不断的改变模型中变量的值,来找到最小损失值。
如下的代码就是使用梯度下降优化算法帮助我们训练模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 创建一个梯度下降优化器,学习率为0.001
optimizer = tf.train.GradientDescentOptimizer(0.001)
train = optimizer.minimize(loss)

# 用两个数组保存训练数据
x_train = [1, 2, 3, 6, 8]
y_train = [4.8, 8.5, 10.4, 21.0, 25.3]

# 训练10000次
for i in range(10000):
sess.run(train, {x: x_train, y: y_train})

# 打印一下训练后的结果
print('W: %s b: %s loss: %s' % (sess.run(W), sess.run(b), sess.run(loss, {x: x_train , y: y_train})))

// W: [ 2.98236108] b: [ 2.07054377] loss: 2.12941

通过使用一组实验数据训练我们的线性模型,我们得到了一个自认为损失最小的最优模型,根据训练结果我们的最优模型可以表示为下面的方程:

1
y = 2.98x + 2.07 /* w:2.98 b:2.07 当然舍弃的精度越多输出结果不会太准 */

步骤四:评估模型

但是这个我们自认为的最优模型是否会一直是最优的?

那么我们就需要评估这个模型,我们需要通过一些新的实验数据来评估模型的泛化性能,如果新的实验数据应用到到这个模型中损失值越小,那么这个模型的泛化性能就越好,反之就越差。

核心关键点:训练模型的数据和评估模型的数据,必须是两波不一样的数据;用训练数据单独训练完成之后,再用评估的数据来验证,不能有重复数据

前面我们构建了一个线性模型,通过训练得到一个线性回归方程,TensorFlow中也提供了中也提供了线性回归的训练模型 tf.estimator.LinearRegressor,下面的简单描述一下使用LinearRegressor训练并评估模型的方法:

一.导入训练用的数据和评估用的数据分别用不同的数组存储;

二.用训练数据创建一个输入模型,用来进行后面的模型训练;

三.再用训练数据创建一个输入模型,用来进行后面的模型评估;

四.用评估数据创建一个输入模型,用来进行后面的模型评估;

五.使用训练数据训练1000次;

六.使用原来训练数据评估一下模型,目的是查看训练的结果;

七.使用评估数据评估一下模型,目的是验证模型的泛化性能;

写在最后

TensorFlow在国内而言,对于国内的一些初创型AI企业来说,TensorFlow显得尤为重要,当然也有些老牌的人工智能公司还在用Keras;对于国外而言,TensorFlow是非常受欢迎的,从Github公开的数据中我们可以看到,光是该工程的Fork就已经达到了69123,Star数量高达113302TensorFlow的github主页(数据在不断增加),位于同类型工具的第一位。

对于中文社区而言,TensorFlow的python版本更全面,教程以及API文档更加全面和详细,对于JS版本的还不是特别多,但是也不影响你对TensorFlow的学习,总得来说你可以先学习更多python的教程,实践的时候按照TensorFlowJS的API文档对照来编程也是ok的。TensorFlowJS的官网还提供了很多有趣的Demo,例如:手写数字图片识别等。

无论怎样,我们都不得不承认TensorFlow是一个很棒的系统,人们用人工智能做了很多令人惊叹的事情,包括一些医疗、电影推荐引擎、音乐、个性化广告以及社交媒体情感挖掘等,TensorFlow确实是现阶段比较火的人工智能框架.