深度学习
神经网络是机器学习的子领域,其中实现数据表示转换的系统,其架构部分参考了人和动物大脑中神经元的连接方式。那么,大脑中的神经元是如何连接的呢?虽然连接方式会因物种和大脑区域而有所不同,但有一点是共通的,那就是层结构。哺乳动物大脑的很多部分展现了层的特征,比如视网膜、大脑皮层和小脑皮层
至少表面上看来,这一特征和人工神经网络 的结构大体相似,它们的数据都是通过多个可分离的步骤进行处理的。因此,将这些步骤称为层(layer)恰如其分。这些层通常彼此叠加,只有相邻的层之间会建立连接
神经网络中的层和数学中的函数概念相似,它们都是从输入值到输出值的映射。然而它们又有所不同,这是因为神经网络中的层是有状态的(stateful)。换言之,它们在内部保留有记忆,这些记忆封装在相应的权重中。权重(weight)就是一组属于层的数值,这些数值决定了每一个输入的表示如何转换成输出的表示。比如,常用的密集层(denselayer)在转换输入数据时,会将它乘以一个矩阵,然后让该结果加上一个向量,这里的矩阵和向量就是密集层的权重。当神经网络用数据进行训练时,各层的权重会进行系统性修改,最终让损失函数(lossfunction)的值趋于最小
尽管神经网络确实从人类大脑结构中汲取了部分灵感,但是我们不应将这两者过度类比。神经网络不是为了学习或模仿人类大脑的工作机制,它是完全不同的学术领域——神经科学的研究范畴,旨在让机器能够通过学习数据来执行有意义的实际任务。无论怎样,这种相似性不应被过度解读。尤其是目前并没有证据表明大脑通过任何形式的梯度下降优化进行学习,而这恰恰是神经网络的主要训练方式。对于很多引领深度学习革命的重要的神经网络技术,它们的发明和应用并不是因为有神经科学的理论支撑,而是因为能帮助神经网络更好、更快地解决实际学习任务
了解神经网络之后,接下来看一下深度学习(deep learning)的概念。深度学习就是关于深度神经网络(deep neural network)的学习和应用。而深度神经网络,简单来说,就是有很多层(通常多达数十甚至上百层)的神经网络。在这里,深(deep)是指为数众多的连续的表示层,数据模型拥有的层数叫作模型的深度(depth)。这一领域还有其他名称,比如分层表示学习(layered representation learning)和层级表示学习(hierarchical representation learning)。现代深度学习通常包含数十至上百个连续的表示层,它们都是从训练数据中自动学习的。与此相反,其他机器学习方法倾向于专注学习一到两个表示层,因此,它们又叫作浅层学习(shallow learning)
使用 JavaScript
很多人还觉得“用 JavaScript 进行深度学习”只是一个新奇的想法,一个用来吸引眼球的小工具,对于某些应用场景还算有趣,但并不值得进行严肃的研究。当时 Python 已有好几个完善且功能强大的深度学习框架,而 JavaScript 相应的机器学习还处于分裂且不完善的状态。JavaScript 中的深度学习库屈指可数,而且绝大部分仅支持部署由其他语言(一般是 Python)预训练的模型。另外,对仅有的几个支持从零开始构建和训练模型的库而言,其所支持的模型类型范围有限。考虑到 JavaScript 的流行程度和横跨客户端、服务器端的普及度,这是一个非常奇怪的处境
TensorFlow.js 是第一个成熟的工业级 JavaScript 神经网络软件库。它提供的功能包含多个维度:第一,支持种类繁多的神经网络层,适用于从数字到文本、从音频到图像等不同的数据类型;第二,提供用于加载预训练模型的 API,从而进行推断,微调预训练模型,以及从零构建并训练模型;第三,为那些选择使用成熟层类型的从业者提供类 Keras 的高阶 API,为那些希望实现较新算法的从业者提供类 TensorFlow 的底层API;第四,适用于多种环境及硬件类型,包括 Web 浏览器端、服务器端(Node.js)、移动端(比如 React Native 和微信小程序)以及桌面端(Electron)。除了多维度的功能,对于集成到更大的 TensorFlow/Keras生态,TensorFlow.js 是这个过程的关键组成部分。具体来说,它的 API 和 TensorFlow/Keras 生态是一致的,并且和该生态下产生的模型格式是双向兼容的
从传统意义上讲,JavaScript 是一种用于创建 Web 浏览器 UI 和后端业务逻辑(通过Node.js)的编程语言,而深度学习革命似乎是 Python、R 和 C++ 这些语言的专属领域。因此,作为用 JavaScript 来表达想法和发挥创造力的人,可能觉得自己有点脱离深度学习革命了。在通过叫作 TensorFlow.js 的 JavaScript 深度学习库,将深度学习与 JavaScript 结合起来。如此一来,无须学习新的编程语言,JavaScript 开发者就可以学习如何编写深度神经网络
用 Node.js 进行深度学习
对训练涉及大量数据的大型机器学习模型来说,这意味着浏览器并不是一个理想的环境。但是,当 Node.js 出现后,JavaScript 的地位就不可同日而语了。Node.js 让 JavaScript 脱离了 Web 浏览器的桎梏,使它能够最大限度地利用系统的原生资源,比如内存和文件系统。TensorFlow.js 包含了一个 Node.js 版本,叫作 tfjsnode,可以与 C++ 和 CUDA 代码编译而成的 TensorFlow 库直接对接,这样 TensorFlow.js 用户也能够受益于 Python 版 TensorFlow 所使用的 CPU 并行计算和 GPU 核函数计算
TensorFlow.js
TensorFlow.js 是 JavaScript 语言的深度学习库。可以看到,TensorFlow.js 在设计上是与 TensorFlow(Python深度学习框架)保持一致且兼容的。要想真正理解 TensorFlow.js,需要先简单了解一下 TensorFlow 的发展历程
TensorFlow 是由谷歌从事深度学习的团队于 2015 年 11 月创造的开源资源。从开源至今,TensorFlow 获得了巨大的成功,在谷歌以及更大的技术社区的各种工业应用程序和研究项目中得到了广泛应用。另外,数据的表示又叫作张量(tensor),它会“流经”(flow)模型的每一层和其他数据处理节点,从而实现机器学习模型的推断和训练,因此“TensorFlow”这一名字暗指在该框架下编写的典型程序中发生的事情
什么是张量?这只不过是计算机科学家对“多维矩阵”更严谨的说法罢了。在神经网络和深度学习中,每个数据和计算结果都会用张量表示。例如,一个灰度图像可以表示为一个二维数组,这就是一个二维张量;同理,彩色图像多出了一个维度,即颜色通道,因此可以表示为三维张量。音频、视频、文本以及其他任何类型的数据都可以用张量来表示。每个张量都由两种基本属性构成:数据的类型(比如float32或int32)和形状。数据的形状描述了张量各个维度的尺寸,例如二维张量的形状可能是[128, 256],而三维张量的形状则可能是[10, 20,128]。一旦数据转换成了某个特定的数据类型和形状,无论它最初的意义是什么,都可以输入到任何与其类型和形状匹配的层。因此,张量其实就是深度学习模型赖以沟通的语言
但为什么要用张量呢?运行深度神经网络所需的大部分计算通常以并行化的方式在 GPU 上进行,这也意味着要对很多数据进行相同的运算。张量能够将数据结构化,从而实现高效并行计算。如果将形状为[128, 128]的张量 A 与形状为[128, 128]的张量 B 相加,显而易见,这将产生 128×128个 独立的加法运算
那 TensorFlow 中的“flow”又指什么呢?把张量想象成能够承载数据的流体就明白了。只不过在 TensorFlow 中,张量流过的是图(graph),即由各种数学运算(节点)互相连接而成的数据结构。这些节点可以看作神经网络中连续的层,每一个节点都将张量作为输入,然后产生新的张量作为输出。随着张量“流经” TensorFlow 图的各个节点,它也转换成不同的形状和值。这实际上就是表示的转换,也正是神经网络的关键。利用 TensorFlow,机器学习工程师可以编写各种不同的神经网络,包括从浅层神经网络到有相当深度的神经网络,以及从用于计算机视觉的 convnet 到用于处理序列数据的循环神经网络。TensorFlow 的图数据结构可以被序列化,并且被部署到包括大型机和手机在内的各种设备上
线性回归
这里要用 TensorFlow.js(有时简称为tfjs)来构建一个极简的神经网络,它能根据文件大小来预测下载该文件所需的时间
首先要获取训练用的数据(即训练集),在机器学习中,数据可以从多种渠道获得,比如从硬盘中读取、从网络上下载、直接通过程序生成,或简单地硬编码
const data = [1, 2, 3, 4, 5];
下一步就是创建模型,这与函数的概念类似,相当于设计一个可训练函数,能够将输入数据映射到预测目标。在本例中,输入数据和预测目标都是数字。一旦准备好模型和数据后,就可以开始训练模型,并查看它在训练过程中生成的度量指标报告了。在这一切完成后,就可以用训练好的模型来预测未曾出现的数据,同时评估模型的准确率
/* 创建序贯模型 */
const model = tf.sequential();
/* 添加一个密集层 */
model.add(tf.layers.dense({
inputShape: [1], // 输入张量的形状
units: 1 // 输出单元数
}))
/* 编译模型 */
model.compile({
optimizer: 'sgd', // 优化器使用随机下降
loss: 'meanAbsoluteError' // 损失函数使用平均绝对误差
});
序贯模型
序贯结构包含单输入与单输出,其神经网络由网络层堆叠而成。每一个网络层仅以上一层网络的输出为输入,并将经过处理后的输出送给下一层
将数据转换为张量:
// 训练集
const trainTensors = tf.tensor1d(data);
// 测试集
const testTensors = tf.tensor1d(data);
总体来说,张量是当下所有机器学习系统的基本数据结构,对于机器学习领域具有根本意义上的重要性,TensorFlow 和 TensorFlow.js 的名字都源自张量,由此可见一斑。张量本质上是数据的容器,而数据几乎总是数值类型。因此,可以将它看作数值的容器。可能已经很熟悉向量和矩阵,其实它们本质上分别是一维张量和二维张量。张量是将矩阵概念泛化到任意维度的结果,维度数和每个维度的尺寸叫作张量的形状(shape)
在张量的语境下,维度通常又叫作轴(axis)。在 TensorFlow.js 中,无论底层使用 CPU、GPU 还是其他硬件,都使用张量来实现互相通信和协作,它是不同组件之间共通的表示
神经网络的核心组成部分是层(layer),它是一个数据处理模块,可以看作张量之间的一个可调函数。这里的模型由单个密集层组成,正如参数 inputShape: [1] 所定义的那样,这一层对输入张量的形状进行了约束。也就是说,这一层需要一个一维张量作为输入,其中仅包含一个数值。对于所有样例,密集层都会输出一个一维张量,但其尺寸可以通过 units 属性进行配置。对这个示例而言,只需要输出一个数字,这是因为想要预测的 timeSec 就是一个数字
开始训练
(async function () {
/* 拟合(训练)模型,并传入 trainTensors 和 testTensors 作为训练数据和测试数据 */
await model.fit(trainTensors, testTensors, {
epochs: 10 // 迭代训练数据 10 次
});
})();