Tensorflow.js 入门学习指南
Tensorflow介绍是一个机器学习框架,使用 TensorFlowJS 可以创建生产级机器学习模型
安装包
浏览器设置
您可以通过两种主要方式在浏览器项目中获取 TensorFlow.js:
如果您是 Web 开发新手,或者从未听说过诸如 Webpack 或 Parcel 的工具,建议您使用脚本代码。如果您比较有经验或想编写更大的程序,则最好使用构建工具进行探索。
使用脚本代码
将以下脚本代码添加到您的主 HTML 文件中。
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@2.0.0/dist/tf.min.js"></script>
请参见代码示例了解脚本代码设置
model.compile({loss: 'meanSquaredError', optimizer: 'sgd'});
// Generate some synthetic data for training. const xs = tf.tensor2d([1, 2, 3, 4], [4, 1]); const ys = tf.tensor2d([1, 3, 5, 7], [4, 1]);
// Train the model using the data. model.fit(xs, ys, {epochs: 10}).then(() => { // Use the model to do inference on a data point the model hasn't seen before: model.predict(tf.tensor2d([5], [1, 1])).print(); // Open the browser devtools to see the output });
从 NPM 安装
您可以使用 npm cli 工具或 yarn 安装 TensorFlow.js。
yarn add @tensorflow/tfjs
或
npm install @tensorflow/tfjs
请参见示例代码以通过 NPM 安装
// Define a model for linear regression. const model = tf.sequential(); model.add(tf.layers.dense({units: 1, inputShape: [1]}));
model.compile({loss: 'meanSquaredError', optimizer: 'sgd'});
// Generate some synthetic data for training. const xs = tf.tensor2d([1, 2, 3, 4], [4, 1]); const ys = tf.tensor2d([1, 3, 5, 7], [4, 1]);
// Train the model using the data. model.fit(xs, ys, {epochs: 10}).then(() => { // Use the model to do inference on a data point the model hasn't seen before: model.predict(tf.tensor2d([5], [1, 1])).print(); // Open the browser devtools to see the output });
Node.js 设置
您可以使用 npm cli 工具或 yarn 安装 TensorFlow.js。
选项 1:使用原生 C++ 绑定安装 TensorFlow.js。
yarn add @tensorflow/tfjs-node
或
npm install @tensorflow/tfjs-node
选项 2:(仅限 Linux)如果您的系统搭载具备 CUDA 支持的 NVIDIA® GPU,请使用 GPU 软件包以获得更高性能。
yarn add @tensorflow/tfjs-node-gpu
或
npm install @tensorflow/tfjs-node-gpu
选项 3:安装纯净版 JavaScript。这是性能最慢的选项。
yarn add @tensorflow/tfjs
或
npm install @tensorflow/tfjs
请参见示例代码以了解 Node.js 用法
// Optional Load the binding: // Use '@tensorflow/tfjs-node-gpu' if running with GPU. require('@tensorflow/tfjs-node');
// Train a simple model: const model = tf.sequential(); model.add(tf.layers.dense({units: 100, activation: 'relu', inputShape: [10]})); model.add(tf.layers.dense({units: 1, activation: 'linear'})); model.compile({optimizer: 'sgd', loss: 'meanSquaredError'});
const xs = tf.randomNormal([100, 10]); const ys = tf.randomNormal([100, 1]);
model.fit(xs, ys, { epochs: 100, callbacks: { onEpochEnd: (epoch, log) => console.log(`Epoch ${epoch}: loss = ${log.loss}`) } });
TypeScript
使用 TypeScript 时,如果您的项目使用严格的 null 检查,则可能需要在 tsconfig.json
文件中设置 skipLibCheck: true
,否则在编译过程中会遇到错误。
张量和运算
Tensorflow.js是一种框架,用于以Javascript定义和运行使用张量的计算。张量是向量和矩阵向更高纬度的泛画。
张量
TensorFlow.js 中数据的中央单元为 tf.Tensor
:一组形状为一维或多维数组的值。tf.Tensor
与多维数组非常相似。
tf.Tensor
还包含以下属性:
rank
:定义张量包含的维数shape
:定义数据每个维度的大小dtype
:定义张量的数据类型
注:我们会将“维度”一词与秩互换使用。有时在机器学习中,张量的“维数”也可以指特定维度的大小(例如,形状为 [10, 5] 的矩阵为 2 秩张量或二维张量。第一维的维数为 10。这可能会造成混淆,但由于您可能会遇到该术语的这两种说法,因此我们在此提供了此注释)。
可以使用 tf.tensor()
方法从数组创建 tf.Tensor
:
// Create a rank-2 tensor (matrix) matrix tensor from a multidimensional array.
const a = tf.tensor([[1, 2], [3, 4]]);
console.log('shape:', a.shape);
a.print();
// Or you can create a tensor from a flat array and specify a shape.
const shape = [2, 2];
const b = tf.tensor([1, 2, 3, 4], shape);
console.log('shape:', b.shape);
b.print();
默认情况下,tf.Tensor
将具有 float32
dtype
。也可以使用 bool、int32、complex64 和字符串数据类型创建 tf.Tensor
:
const a = tf.tensor([[1, 2], [3, 4]], [2, 2], 'int32');
console.log('shape:', a.shape);
console.log('dtype', a.dtype);
a.print();
TensorFlow.js 还提供了一组便捷的方法,用于创建随机张量、填充特定值的张量、HTMLImageElement
中的张量,以及此处所列的更多张量。
更改张量的形状
tf.Tensor
中元素的数量是其形状大小的乘积。由于通常可以有多个具有相同大小的形状,因此将 tf.Tensor
重塑为具有相同大小的其他形状通常非常实用。这可以通过 reshape()
方法实现:
const a = tf.tensor([[1, 2], [3, 4]]);
console.log('a shape:', a.shape);
a.print();
const b = a.reshape([4, 1]);
console.log('b shape:', b.shape);
b.print();
从张量获取值
您还可以使用 Tensor.array()
或 Tensor.data()
方法从 tf.Tensor
中获取值:
const a = tf.tensor([[1, 2], [3, 4]]);
// Returns the multi dimensional array of values.
a.array().then(array => console.log(array));
// Returns the flattened data that backs the tensor.
a.data().then(data => console.log(data));
我们还提供了这些方法的同步版本,这些版本更易于使用,但会在您的应用中引起性能问题。在生产应用中,您应始终优先使用异步方法。
const a = tf.tensor([[1, 2], [3, 4]]);
// Returns the multi dimensional array of values.
console.log(a.arraySync());
// Returns the flattened data that backs the tensor.
console.log(a.dataSync());
运算
张量可用于存储数据,而运算则可用于操作该数据。TensorFlow.js 还提供了可对张量执行的适用于线性代数和机器学习的多种运算。
示例:对 tf.Tensor
中的所有元素执行 x2 计算:
const x = tf.tensor([1, 2, 3, 4]);
const y = x.square(); // equivalent to tf.square(x)
y.print();
示例:对两个 tf.Tensor
的元素执行逐元素相加:
const a = tf.tensor([1, 2, 3, 4]);
const b = tf.tensor([10, 20, 30, 40]);
const y = a.add(b); // equivalent to tf.add(a, b)
y.print();
由于张量是不可变的,因此这些运算不会改变其值。相反,return 运算总会返回新的 tf.Tensor
。
注:大多数运算都会返回
tf.Tensor
,但结果实际上可能并未准备就绪。这意味着您获得的tf.Tensor
实际上是计算的句柄。当您调用Tensor.data()
或Tensor.array()
时,这些方法将返回仅在计算完成时才解析值的 promise。在界面上下文(例如浏览器应用)中运行时,应始终首选这些方法的异步版本而非同步版本,以免在计算完成之前阻塞界面线程。
您可以在此处找到 TensorFlow.js 所支持运算的列表。
内存
使用 WebGL 后端时,必须显式管理 tf.Tensor
内存(即使 tf.Tensor
超出范围也不足以释放其内存)。
要销毁 tf.Tensor 的内存,您可以使用 dispose()
方法或 tf.dispose()
:
const a = tf.tensor([[1, 2], [3, 4]]);
a.dispose(); // Equivalent to tf.dispose(a)
在应用中将多个运算链接在一起十分常见。保持对用于处置这些运算的所有中间变量的引用会降低代码的可读性。为了解决这个问题,TensorFlow.js 提供了 tf.tidy()
方法,可清理执行函数后未被该函数返回的所有 tf.Tensor
,类似于执行函数时清理局部变量的方式:
const a = tf.tensor([[1, 2], [3, 4]]);
const y = tf.tidy(() => {
const result = a.square().log().neg();
return result;
});
在此示例中,square()
和 log()
的结果将被自动处置。neg()
的结果不会被处置,因为它是 tf.tidy() 的返回值。
您还可以获取 TensorFlow.js 跟踪的张量数量:
console.log(tf.memory());
tf.memory()
打印的对象将包含有关当前分配了多少内存的信息。您可以在此处查找更多信息。
平台和环境
TensorFlow.js 可以在浏览器和 Node.js 中运行,并且在两个平台中都具有许多不同的可用配置。每个平台都有一组影响应用开发方式的独特注意事项。
在浏览器中,TensorFlow.js 支持移动设备以及桌面设备。每种设备都有一组特定的约束(例如可用 WebGL API),系统会自动为您确定和配置这些约束。
在 Node.js 中,TensorFlow.js 支持直接绑定到 TensorFlow API 或搭配较慢的普通 CPU 实现运行。
环境
执行 TensorFlow.js 程序时,特定配置称为环境。环境由单个全局后端以及一组控制 TensorFlow.js 细粒度功能的标志构成。
后端
TensorFlow.js 支持可实现张量存储和数学运算的多种不同后端。在任何给定时间内,均只有一个后端处于活动状态。在大多数情况下,TensorFlow.js 会根据当前环境自动为您选择最佳后端。但是,有时必须要知道正在使用哪个后端以及如何进行切换。
要确定您使用的后端,请运行以下代码:
console.log(tf.getBackend());
如果要手动更改后端,请运行以下代码:
tf.setBackend('cpu');
console.log(tf.getBackend());
WebGL 后端
WebGL 后端 ‘webgl’ 是当前适用于浏览器的功能最强大的后端。此后端的速度比普通 CPU 后端快 100 倍。张量将存储为 WebGL 纹理,而数学运算将在 WebGL 着色器中实现。以下为使用此后端时需要了解的一些实用信息:
避免阻塞界面线程
当调用诸如 tf.matMul(a, b) 等运算时,生成的 tf.Tensor 会被同步返回,但是矩阵乘法计算实际上可能还未准备就绪。这意味着返回的 tf.Tensor 只是计算的句柄。当您调用 x.data()
或 x.array()
时,这些值将在计算实际完成时解析。这样,就必须对同步对应项 x.dataSync()
和 x.arraySync()
使用异步 x.data()
和 x.array()
方法,以避免在计算完成时阻塞界面线程。
内存管理
请注意,使用 WebGL 后端时需要显式内存管理。浏览器不会自动回收 WebGLTexture(最终存储张量数据的位置)的垃圾。
要销毁 tf.Tensor
的内存,您可以使用 dispose()
方法:
const a = tf.tensor([[1, 2], [3, 4]]);
a.dispose();
在应用中将多个运算链接在一起十分常见。保持对用于处置这些运算的所有中间变量的引用会降低代码的可读性。为了解决这个问题,TensorFlow.js 提供了 tf.tidy()
方法,可清理执行函数后未被该函数返回的所有 tf.Tensor
,类似于执行函数时清理局部变量的方式:
const a = tf.tensor([[1, 2], [3, 4]]);
const y = tf.tidy(() => {
const result = a.square().log().neg();
return result;
});
注:在具有自动垃圾回收功能的非 WebGL 环境(例如 Node.js 或 CPU 后端)中使用
dispose()
或tidy()
没有弊端。实际上,与自然发生垃圾回收相比,释放张量内存的性能可能会更胜一筹。
精度
在移动设备上,WebGL 可能仅支持 16 位浮点纹理。但是,大多数机器学习模型都使用 32 位浮点权重和激活进行训练。这可能会导致为移动设备移植模型时出现精度问题,因为 16 位浮点数只能表示 [0.000000059605, 65504]
范围内的数字。这意味着您应注意模型中的权重和激活不超出此范围。要检查设备是否支持 32 位纹理,请检查 tf.ENV.getBool('WEBGL_RENDER_FLOAT32_CAPABLE')
的值,如果为 false,则设备仅支持 16 位浮点纹理。您可以使用 tf.ENV.getBool('WEBGL_RENDER_FLOAT32_ENABLED')
来检查 TensorFlow.js 当前是否使用 32 位纹理。
着色器编译和纹理上传
TensorFlow.js 通过运行 WebGL 着色器程序在 GPU 上执行运算。当用户要求执行运算时,这些着色器会迟缓地进行汇编和编译。着色器的编译在 CPU 主线程上进行,可能十分缓慢。TensorFlow.js 将自动缓存已编译的着色器,从而大幅加快第二次调用具有相同形状输入和输出张量的同一运算的速度。通常,TensorFlow.js 应用在应用生命周期内会多次使用同一运算,因此第二次通过机器学习模型的速度会大幅提高。
TensorFlow.js 还会将 tf.Tensor 数据存储为 WebGLTextures。创建 tf.Tensor
时,我们不会立即将数据上传到 GPU,而是将数据保留在 CPU 上,直到在运算中使用 tf.Tensor
为止。第二次使用 tf.Tensor
时,数据已位于 GPU 上,因此不存在上传成本。在典型的机器学习模型中,这意味着在模型第一次预测期间会上传权重,而第二次通过模型则会快得多。
如果您在意通过模型或 TensorFlow.js 代码执行首次预测的性能,我们建议您在使用实际数据之前先通过传递相同形状的输入张量来预热模型。
例如:
const model = await tf.loadLayersModel(modelUrl);
// Warmup the model before using real data.
const warmupResult = model.predict(tf.zeros(inputShape));
warmupResult.dataSync();
warmupResult.dispose();
// The second predict() will be much faster
const result = model.predict(userData);
Node.js TensorFlow 后端
在 TensorFlow Node.js 后端 ‘node’ 中,使用 TensorFlow C API 加速运算。这将在可用情况下使用计算机的可用硬件加速(例如 CUDA)。
在这个后端中,就像 WebGL 后端一样,运算会同步返回 tf.Tensor
。但与 WebGL 后端不同的是,运算在返回张量之前就已完成。这意味着调用 tf.matMul(a, b)
将阻塞界面线程。
因此,如果打算在生产应用中使用,则应在工作线程中运行 TensorFlow.js 以免阻塞主线程。
有关 Node.js 的更多信息,请参阅本指南。
WASM 后端
TensorFlow.js 提供了 WebAssembly 后端 (wasm
),可实现 CPU 加速,并且可以替代普通的 JavaScript CPU (cpu
) 和 WebGL 加速 (webgl
) 后端。用法如下:
// Set the backend to WASM and wait for the module to be ready.
tf.setBackend('wasm');
tf.ready().then(() => {...});
如果您的服务器在不同的路径上或以不同的名称提供 .wasm
文件,则在初始化后端前请使用 setWasmPath
。有关更多信息,请参阅自述文件中的“使用 Bundler”部分:
import {setWasmPath} from '@tensorflow/tfjs-backend-wasm';
setWasmPath(yourCustomPath);
tf.setBackend('wasm');
tf.ready().then(() => {...});
注:TensorFlow.js 会为每个后端定义优先级并为给定环境自动选择支持程度最高的后端。要显式使用 WASM 后端,我们需要调用
tf.setBackend('wasm')
。
为何使用 WASM?
WASM 于 2015 年作为一种基于 Web 的新型二进制格式面世,提供以 JavaScript、C、C++ 等语言编写的程序。WASM 自 2017 年起受到 Chrome、Safari、Firefox 和 Edge 支持,并获得全球 90% 设备的支持。
性能
WASM 后端利用 XNNPACK 库来优化神经网络算子的实现。
对比 JavaScript:浏览器加载、解析和执行 WASM 二进制文件通常比 JavaScript 软件包要快得多。JavaScript 的动态键入和垃圾回收功能可能会导致运行时速度缓慢。
对比 WebGL:WebGL 对于大多数模型而言速度均快于 WASM,但 WASM 针对小型模型的性能则会比 WebGL 更胜一筹,原因是执行 WebGL 着色器存在固定的开销成本。下文中的“应在何时使用 WASM”部分讨论了做此决定的启发法。
可移植性和稳定性
WASM 具有可移植的 32 位浮点运算,可在所有设备之间提供精度奇偶校验。另一方面,WebGL 特定于硬件,不同的设备可能具有不同的精度(例如,在 iOS 设备上回退到 16 位浮点)。
与 WebGL 一样,WASM 也受到所有主流浏览器的官方支持。与 WebGL 的不同之处为,WASM 可以在 Node.js 中运行,并且无需编译原生库即可在服务器端使用。
应在何时使用 WASM?
模型大小和计算需求
通常,当模型较小或您在意不具备 WebGL 支持(OES_texture_float
扩展)或 GPU 性能较弱的的低端设备时,WASM 是一种不错的选择。下表显示了在 2018 款 MacBook Pro 上使用 Chrome 基于 WebGL、WASM 和 CPU 后端针对官方支持的 5 种模型的推断时间(自 TensorFlow.js 1.5.2 起):
较小的模型
模型 | WebGL | WASM | CPU | 内存 |
---|---|---|---|---|
BlazeFace | 22.5 ms | 15.6 ms | 315.2 ms | 0.4 MB |
FaceMesh | 19.3 ms | 19.2 ms | 335 ms | 2.8 MB |
较大的模型
模型 | WebGL | WASM | CPU | 内存 |
---|---|---|---|---|
PoseNet | 42.5 ms | 173.9 ms | 1514.7 ms | 4.5 MB |
BodyPix | 77 ms | 188.4 ms | 2683 ms | 4.6 MB |
MobileNet v2 | 37 ms | 94 ms | 923.6 ms | 13 MB |
上表显示,针对这些模型,WASM 比普通的 JS CPU 后端快 10-30 倍;并且针对 BlazeFace(轻量化 (400KB) 但运算数量尚可 (~140))之类的较小模型,则可与 WebGL 抗衡。考虑到 WebGL 程序每执行一次运算的固定开销成本,这就解释了像 BlazeFace 这样的模型在 WASM 上速度更快的原因。
这些结果将因您的具体设备而异。确定 WASM 是否适合您的应用的最佳方式是在我们不同的后端上对其进行测试。
推断与训练
为解决部署预训练模型的主要用例,WASM 后端的开发工作在推断方面的支持将优先于训练。请参见 WASM 所支持运算的最新列表,如果您的模型具有不受支持的运算,请告诉我们。对于训练模型,建议使用 Node (TensorFlow C++) 后端或 WebGL 后端。
CPU 后端
CPU 后端 ‘cpu’ 是性能最低且最简单的后端。所有运算均在普通的 JavaScript 中实现,这使它们的可并行性较差。这些运算还会阻塞界面线程。
此后端对于测试或在 WebGL 不可用的设备上非常有用。
标志
TensorFlow.js 具有一组可自动评估的环境标志,这些标志可以确定当前平台中的最佳配置。大部分标志为内部标志,但有一些可以使用公共 API 控制的全局标志。
tf.enableProdMode()
:启用生产模式,在此模式下将移除模型验证、NaN 检查和其他有利于性能的正确性检查。tf.enableDebugMode()
:启用调试模式,在此模式下会将执行的每项运算以及运行时性能信息(如内存占用量和总内核执行时间)记录到控制台。请注意,这将大幅降低您应用的速度,请勿在生产中使用。
注:这两个方法应在使用任何 TensorFlow.js 代码之前使用,因为它们会影响将缓存的其他标志的值。出于相同的原因,没有“disable”模拟函数。
注:您可以通过将
tf.ENV.features
记录到控制台来查看所有已评估的标志。尽管它们不是公共 API 的一部分(因此不能保证版本之间的稳定性),但它们对于跨平台和设备进行调试或微调行为而言非常实用。您可以使用tf.ENV.set
重写标志的值。
自定义操作,内核和梯度
不是专业人士,学不明白这一章,但是不影响后续学习
Overview
This guide outlines the mechanisms for defining custom operations (ops), kernels and gradients in TensorFlow.js. It aims to provide an overview of the main concepts and pointers to code that demonstrate the concepts in action.
Who is this guide for?
This is a fairly advanced guide that touches on some internals of TensorFlow.js, it may be particularly useful for the following groups of people:
- Advanced users of TensorFlow.js interested in customizing behaviour of various mathematical operations (e.g. researchers overriding existing gradient implementations or users who need to patch missing functionality in the library)
- Users building libraries that extend TensorFlow.js (e.g. a general linear algebra library built on top of TensorFlow.js primitives or a new TensorFlow.js backend).
- Users interested in contributing new ops to tensorflow.js who want to get a general overview of how these mechanisms work.
This is not a guide to general use of TensorFlow.js as it goes into internal implementation mechanisms. You do not need to understand these mechanisms to use TensorFlow.js
You do need to be comfortable with (or willing to try) reading TensorFlow.js source code to make the most use of this guide.
Terminology
For this guide a few key terms are useful to describe upfront.
Operations (Ops) — A mathematical operation on one or more tensors that produces one or more tensors as output. Ops are ‘high level’ code and can use other ops to define their logic.
Kernel — A specific implementation of an op tied to specific hardware/platform capabilities. Kernels are ‘low level’ and backend specific. Some ops have a one-to-one mapping from op to kernel while other ops use multiple kernels.
Gradient / GradFunc — The ‘backward mode’ definition of an op/kernel that computes the derivative of that function with regards to some input. Gradients are ‘high level’ code (not backend specific) and can call other ops or kernels.
Kernel Registry – A map from a (kernel name, backend name) tuple to a kernel implementation.
Gradient Registry — A map from a kernel name to a gradient implementation.
Code organization
Operations and Gradients are defined in tfjs-core.
Kernels are backend specific and are defined in their respective backend folders (e.g. tfjs-backend-cpu).
Custom ops, kernels and gradients do not need to be defined inside these packages. But will often use similar symbols in their implementation.
Implementing Custom Ops
One way to think of a custom op is just as a JavaScript function that returns some tensor output, often with tensors as input.
- Some ops can be completely defined in terms of existing ops, and should just import and call these functions directly. Here is an example.
- The implementation of an op can also dispatch to backend specific kernels. This is done via
Engine.runKernel
and will be described further in the “implementing custom kernels” section. Here is an example.
Implementing Custom Kernels
Backend specific kernel implementations allow for optimized implementation of the logic for a given operation. Kernels are invoked by ops calling tf.engine().runKernel()
. A kernel implementations is defined by four things
- A kernel name.
- The backend the kernel is implemented in.
- Inputs: Tensor arguments to the kernel function.
- Attributes: Non-tensor arguments to the kernel function.
Here is an example of a kernel implementation. The conventions used to implement are backend specific and are best understood from looking at each particular backend’s implementation and documentation.
Generally kernels operate at a level lower than tensors and instead directly read and write to memory that will be eventually wrapped into tensors by tfjs-core.
Once a kernel is implemented it can be registered with TensorFlow.js by using registerKernel
function from tfjs-core. You can register a kernel for every backend you want that kernel to work in. Once registered the kernel can be invoked with tf.engine().runKernel(...)
and TensorFlow.js will make sure to dispatch to the implementation in the current active backend.
Implementing Custom Gradients
Gradients are generally defined for a given kernel (identified by the same kernel name used in a call to tf.engine().runKernel(...)
). This allows tfjs-core to use a registry to look up gradient definitions for any kernel at runtime.
Implementing custom gradients are useful for:
- Adding a gradient definition that may not be present in the library
- Overriding an existing gradient definition to customize the gradient computation for a given kernel.
You can see examples of gradient implementations here.
Once you have implemented a gradient for a given call it can be registered with TensorFlow.js by using registerGradient
function from tfjs-core.
The other approach to implementing custom gradients that by-passes the gradient registry (and thus allows for computing gradients for arbitrary functions in arbitrary ways is using tf.customGrad.
Here is an example of an op within the library of using customGrad
模型和层
本页内容使用 Layers API 创建模型序贯模型函数式模型验证模型摘要序列化自定义层使用 Core API 创建模型
在机器学习中,模型是一个带有可学习参数的函数,可将输入映射至输出。通过在数据上训练模型获得最佳参数。训练好的模型可以提供从输入到所需输出的准确映射。
在 TensorFlow.js 中,您可以通过两种方式创建机器学习模型:
- 使用 Layers API(使用层构建模型)
- 使用 Core API(借助低级运算,例如
tf.matMul()
、tf.add()
等)
首先,我们会了解 Layers API,Layers API 是用于构建模型的高级 API。然后,我们将演示如何使用 Core API 构建相同的模型。
使用 Layers API 创建模型
你可以通过两种方式使用 Layers API 创建模型:序贯模型和函数式模型。下面两部分将详细介绍两种类型。
序贯模型
最常见的模型是 [Sequential](https://js.tensorflow.org/api/0.15.1/#class:Sequential)
模型,序贯模型是层的线性堆叠。您可以通过将层列表传递到 [sequential()](https://js.tensorflow.org/api/0.15.1/#sequential)
函数来创建 Sequential
模型:
const model = tf.sequential({
layers: [
tf.layers.dense({inputShape: [784], units: 32, activation: 'relu'}),
tf.layers.dense({units: 10, activation: 'softmax'}),
]
});
inputShape: [784]
: 指定输入数据的形状为一个长度为784的一维数组,表示输入数据的维度为784。units: 32
: 指定该层的神经元数量为32。activation: 'relu'
: 指定该层的激活函数为ReLU(Rectified Linear Unit),用于增加模型的非线性表达能力。units: 10
: 指定该层的神经元数量为10。activation: 'softmax'
: 指定该层的激活函数为Softmax,用于将输出转换为概率分布,通常用于多分类问题的最后一层。
或通过 add()
方法:
const model = tf.sequential();
model.add(tf.layers.dense({inputShape: [784], units: 32, activation: 'relu'}));
model.add(tf.layers.dense({units: 10, activation: 'softmax'}));
重要提示:模型的第一层需要
inputShape
。提供inputShape
时请确保排除批次大小。例如,创建模型时,如果您计划馈送形状为[B, 784]
(其中B
可为任何批次大小)的模型张量,请将inputShape
指定为[784]
。
您可以通过 model.layers
访问模型的层,更具体而言为 model.inputLayers
和 model.outputLayers
。
函数式模型
创建 LayersModel
的另一种方式是通过 tf.model()
函数。tf.model()
和 tf.sequential()
的主要区别为,tf.model()
可用于创建层的任意计算图,前提是层没有循环。
以下代码段可以使用 tf.model()
API 定义与上文相同的模型:
// Create an arbitrary graph of layers, by connecting them
// via the apply() method.
const input = tf.input({shape: [784]});
const dense1 = tf.layers.dense({units: 32, activation: 'relu'}).apply(input);
const dense2 = tf.layers.dense({units: 10, activation: 'softmax'}).apply(dense1);
const model = tf.model({inputs: input, outputs: dense2});
我们在每一层调用 apply()
以将其连接到另一个层的输出。在这种情况下,apply()
的结果是一个 SymbolicTensor
,后者类似于 Tensor
,但不包含任何具体值。
请注意,与序贯模型不同,我们通过 tf.input()
创建 SymbolicTensor
,而非向第一层提供 inputShape
。
如果您向 apply()
传递一个具体 Tensor
,它也会为您提供一个具体 Tensor
:
const t = tf.tensor([-2, 1, 0, 5]);
const o = tf.layers.activation({activation: 'relu'}).apply(t);
o.print(); // [0, 1, 0, 5]
这对于单独测试层并查看它们的输出非常有用。
与在序贯模型中一样,您可以通过 model.layers
访问模型的层,更具体而言为 model.inputLayers
和 model.outputLayers
。
验证
序贯模型和函数式模型都是 LayersModel
类的实例。使用 LayersModels
的一个主要优势是验证:它会强制您指定输入形状,并稍后将其用于验证您的输入。LayersModel
还会在数据流经层时自动推断形状。提前了解形状后,模型就可以自动创建它的参数,并告知您两个相邻的层是否相互兼容。
模型摘要
调用 model.summary()
以打印模型的实用摘要,其中包括:
- 模型中所有层的名称和类型
- 每个层的输出形状
- 每个层的权重参数数量
- 每个层接收的输入(如果模型具有一般拓扑,下文将讨论)
- 模型的可训练和不可训练参数总数
对于上面定义的模型,我们在控制台上获取以下输出:
层(类型) | 输出形状 | 参数数量 |
---|---|---|
dense_Dense1(密集) | [null,32] | 25120 |
dense_Dense2(密集) | [null,10] | 330 |
参数总数:25450 可训练参数:25450 不可训练参数:0 |
注意层的输出形状中的 null
值:这表示模型希望输入的批次大小为最外层维度,在这种情况下,由于 null
值,批次大小比较灵活。
序列化
在较低级别的 API 上使用 LayersModel
的一个主要优势是能够保存和加载模型。LayersModel
了解:
- 模型的架构,让您可以创新创建模型
- 模型的权重
- 训练配置(损失、优化器和指标)
- 优化器的状态,让您可以恢复训练
保存或加载模型只需要 1 行代码:
const saveResult = await model.save('localstorage://my-model-1');
const model = await tf.loadLayersModel('localstorage://my-model-1');
上面的示例可将模型保存到浏览器的本地存储空间中。请参阅 [model.save() 文档](https://js.tensorflow.org/api/latest/#tf.Model.save)
和保存并加载指南,了解如何保存到不同的媒介(例如,文件存储空间、IndexedDB
、触发浏览器下载等)。
自定义层
层是模型的基本要素。如果您的模型需要进行自定义计算,您可以定义一个自定义层,它可以与层的其他部分很好地交互。我们在下面定义的自定义层可以计算平方总数:
class SquaredSumLayer extends tf.layers.Layer {
constructor() {
super({});
}
// In this case, the output is a scalar.
computeOutputShape(inputShape) { return []; }
// call() is where we do the computation.
call(input, kwargs) { return input.square().sum();}
// Every layer needs a unique name.
getClassName() { return 'SquaredSum'; }
}
要对其进行测试,我们可以调用包含具体张量的 apply()
方法:
const t = tf.tensor([-2, 1, 0, 5]);
const o = new SquaredSumLayer().apply(t);
o.print(); // prints 30
重要提示:如果添加自定义层,将无法序列化模型。
使用 Core API 创建模型
在本指南开头处,我们提到可以通过两种方式在 TensorFlow.js 中创建机器学习模型。
一般来说,您始终应当先尝试使用 Layers API,因为它基于被广泛使用的 Keras API,后者遵循最佳做法并降低了认知负担。Layers API 还提供了各种现成的解决方案,如权重初始化、模型序列化、训练监视、概率和安全检查。
在以下情况下,您可能需要使用 Core API:
- 您需要最大程度的灵活性和控制
- 您不需要序列化或可以实现自己的序列化逻辑
使用 Core API 创建的模型是以一个或多个 Tensor
作为输入并输出 Tensor
的函数。使用 Core API 编写的上面同一个模型如下所示:
// The 权重 and 偏差 for the two dense layers.
const w1 = tf.variable(tf.randomNormal([784, 32]));
const b1 = tf.variable(tf.randomNormal([32]));
const w2 = tf.variable(tf.randomNormal([32, 10]));
const b2 = tf.variable(tf.randomNormal([10]));
function model(x) {
return x.matMul(w1).add(b1).relu().matMul(w2).add(b2).softmax();
}
w1
: 这是一个权重矩阵,用于连接输入层和第一个隐藏层。它的形状是[784, 32],表示输入层有784个神经元,第一个隐藏层有32个神经元。这个权重矩阵是通过随机正态分布初始化的。b1
: 这是一个偏置向量,用于添加到第一个隐藏层的输出上。它的形状是[32],表示第一个隐藏层有32个神经元。这个偏置向量也是通过随机正态分布初始化的。w2
: 这是一个权重矩阵,用于连接第一个隐藏层和输出层。它的形状是[32, 10],表示第一个隐藏层有32个神经元,输出层有10个神经元。这个权重矩阵也是通过随机正态分布初始化的。b2
: 这是一个偏置向量,用于添加到输出层的输出上。它的形状是[10],表示输出层有10个神经元。这个偏置向量也是通过随机正态分布初始化的。model(x)
: 这是定义的模型函数,接受一个输入张量x
作为参数。在函数内部,首先将输入张量与权重矩阵w1
相乘,然后加上偏置向量b1
,接着应用ReLU激活函数,再将结果与权重矩阵w2
相乘,最后加上偏置向量b2
,并应用softmax激活函数。最终返回经过这些操作后的输出张量。
请注意,在 Core API 中,我们需要创建和初始化模型的权重。每个权重都由一个 Variable
支持,变量可以告知 TensorFlow.js 这些张量是可学习张量。您可以使用 tf.variable() 并传入现有 Tensor
来创建 Variable
。
本文介绍了如何使用 Layers API 和 Core API 创建模型。接下来,请参阅训练模型指南了解如何训练模型。
训练模型
本页内容简介模型参数优化器、损失和指标训练model.fit()model.fitDataset()预测新数据Core API
本指南假定您已阅读模型和层指南。
在 TensorFlow.js 中,您可以通过以下两种方式训练机器学习模型:
- 使用 Layers API 与
[LayersModel.fit()](https://js.tensorflow.org/api/latest/#tf.Model.fit)
或[LayersModel.fitDataset()](https://js.tensorflow.org/api/latest/#tf.Model.fitDataset)
。 - 使用 Core API 与
[Optimizer.minimize()](https://js.tensorflow.org/api/latest/#tf.train.Optimizer.minimize)
。
首先,我们将了解 Layers API,它是一种用于构建和训练模型的高级 API。然后,我们将展示如何使用 Core API 训练相同的模型。
简介
机器学习模型是一种具有可学习参数的函数,可将输入映射到所需输出。基于数据训练模型可以获得最佳参数。
训练涉及多个步骤:
- 获取一批次数据来训练模型。
- 让模型做出预测。
- 将该预测与“真实”值进行对比。
- 确定每个参数的更改幅度,使模型在未来能够针对该批次数据做出更好的预测。
训练得当的模型将提供从输入到所需输出的准确映射。
模型参数
让我们使用 Layers API 来定义一个简单的 2 层模型:
const model = tf.sequential({
layers: [
tf.layers.dense({inputShape: [784], units: 32, activation: 'relu'}),
tf.layers.dense({units: 10, activation: 'softmax'}),
]
});
模型以可学习参数(常称为权重)为基础,基于数据进行训练。让我们打印与此模型及其形状关联的权重的名称:
model.weights.forEach(w => {
console.log(w.name, w.shape);
});
我们得到以下输出:
> dense_Dense1/kernel [784, 32]
> dense_Dense1/bias [32]
> dense_Dense2/kernel [32, 10]
> dense_Dense2/bias [10]
共有 4 个权重,每个密集层 2 个。这是可以预期的,因为密集层表示一个函数,通过等式 y = Ax + b
将输入张量 x
映射到输出张量 y
,其中 A
(内核)和 b
(偏差)为密集层参数。
注:默认情况下,密集层将包含偏差,但您可以通过在创建密集层时的选项中指定
{useBias: false}
将其排除。
如果您想简要了解模型并查看参数总数,model.summary()
是一种实用的方法:
层(类型) | 输出形状 | 参数数量 |
---|---|---|
dense_Dense1(密集) | [null,32] | 25120 |
dense_Dense2(密集) | [null,10] | 330 |
参数总数:25450 可训练参数:25450 不可训练参数:0 |
模型中的每个权重均由 [Variable](https://js.tensorflow.org/api/0.14.2/#class:Variable)
对象提供支持。在 TensorFlow.js 中,Variable
为浮点型 Tensor
,具有一个用于更新值的附加方法 assign()
。Layers API 会使用最佳做法自动初始化权重。出于演示目的,我们可以通过在基础变量上调用 assign()
来覆盖权重:
model.weights.forEach(w => {
const newVals = tf.randomNormal(w.shape);
// w.val is an instance of tf.Variable
w.val.assign(newVals);
});
优化器、损失和指标
进行任何训练之前,您需要确定以下三项内容:
- 优化器。优化器的作用是在给定当前模型预测的情况下,决定对模型中每个参数实施更改的幅度。使用 Layers API 时,您可以提供现有优化器的字符串标识符(例如
'sgd'
或'adam'
),也可以提供[Optimizer](https://js.tensorflow.org/api/latest/#Training-Optimizers)
类的实例。 - 损失函数。模型将以最小化损失作为目标。该函数旨在将模型预测的“误差程度”量化为具体数字。损失以每一批次数据为基础计算,因此模型可以更新其权重。使用 Layers API 时,您可以提供现有损失函数的字符串标识符(例如
'categoricalCrossentropy'
),也可以提供任何采用预测值和真实值并返回损失的函数。请参阅我们的 API 文档中的可用损失列表。 - 指标列表。 与损失类似,指标也会计算一个数字,用于总结模型的运作情况。通常要在每个周期结束时基于整体数据来计算指标。至少,我们要监控损失是否随着时间推移而下降。但是,我们经常需要准确率等更人性化的指标。使用 Layers API 时,您可以提供现有指标的字符串标识符(例如
'accuracy'
),也可以提供任何采用预测值和真实值并返回分数的函数。请参阅我们的 API 文档中的可用指标列表。
确定后,使用提供的选项调用 model.compile()
来编译 LayersModel
:
model.compile({
optimizer: 'sgd',
loss: 'categoricalCrossentropy',
metrics: ['accuracy']
});
在编译过程中,模型将进行一些验证以确保您所选择的选项彼此兼容。
训练
您可以通过以下两种方式训练 LayersModel
:
- 使用
model.fit()
并以一个大型张量形式提供数据。 - 使用
model.fitDataset()
并通过Dataset
对象提供数据。
model.fit()
如果您的数据集适合装入主内存,并且可以作为单个张量使用,则您可以通过调用 fit()
方法来训练模型:
// Generate dummy data.
const data = tf.randomNormal([100, 784]);
const labels = tf.randomUniform([100, 10]);
function onBatchEnd(batch, logs) {
console.log('Accuracy', logs.acc);
}
// Train for 5 epochs with batch size of 32.
model.fit(data, labels, {
epochs: 5,
batchSize: 32,
callbacks: {onBatchEnd}
}).then(info => {
console.log('Final accuracy', info.history.acc);
});
model.fit()
在后台可以完成很多操作:
- 将数据拆分为训练集和验证集,并使用验证集衡量训练期间的进度。
- 打乱数据顺序(仅在拆分后)。为了安全起见,您应该在将数据传递至
fit()
之前预先打乱数据顺序。 - 将大型数据张量拆分成大小为
batchSize
的小型张量。 - 在计算相对于一批次数据的模型损失的同时,调用
optimizer.minimize()
。 - 可以在每个周期或批次的开始和结尾为您提供通知。我们的示例使用
callbacks.onBatchEnd
选项在每个批次的结尾提供通知。其他选项包括:onTrainBegin
、onTrainEnd
、onEpochBegin
、onEpochEnd
和onBatchBegin
。 - 受制于主线程,确保 JS 事件循环中排队的任务可以得到及时处理。
有关更多信息,请参阅 fit()
的文档。请注意,如果您选择使用 Core API,则必须自行实现此逻辑。
model.fitDataset()
如果您的数据不能完全装入内存或进行流式传输,则您可以通过调用 fitDataset()
来训练模型,它会获取一个 Dataset
对象。以下为相同的训练代码,但具有包装生成器函数的数据集:
function* data() {
for (let i = 0; i < 100; i++) {
// Generate one sample at a time.
yield tf.randomNormal([784]);
}
}
function* labels() {
for (let i = 0; i < 100; i++) {
// Generate one sample at a time.
yield tf.randomUniform([10]);
}
}
const xs = tf.data.generator(data);
const ys = tf.data.generator(labels);
// We zip the data and labels together, shuffle and batch 32 samples at a time.
const ds = tf.data.zip({xs, ys}).shuffle(100 /* bufferSize */).batch(32);
// Train the model for 5 epochs.
model.fitDataset(ds, {epochs: 5}).then(info => {
console.log('Accuracy', info.history.acc);
});
有关数据集的更多信息,请参阅 model.fitDataset()
文档。
预测新数据
在模型完成训练后,您可以调用 model.predict()
,基于未见过的数据进行预测:
// Predict 3 random samples.
const prediction = model.predict(tf.randomNormal([3, 784]));
prediction.print();
注:正如我们在模型和层指南中所讲,LayersModel
期望输入的最外层维度为批次大小。在上例中,批次大小为 3。
Core API
之前,我们提到您可以通过两种方式在 TensorFlow.js 中训练机器学习模型。
根据常规经验法则,可以首先尝试使用 Layers API,因为它是由广为采用的 Keras API 建模而成。Layers API 还提供了各种现成的解决方案,例如权重初始化、模型序列化、监控训练、可移植性和安全性检查。
在以下情况下,您可以使用 Core API:
- 您需要最大的灵活性或控制力。
- 并且您不需要序列化,或者可以实现自己的序列化逻辑。
有关此 API 的更多信息,请参阅模型和层指南中的“Core API”部分。
使用 Core API 编写上述相同模型,方法如下:
// The weights and biases for the two dense layers.
const w1 = tf.variable(tf.randomNormal([784, 32]));
const b1 = tf.variable(tf.randomNormal([32]));
const w2 = tf.variable(tf.randomNormal([32, 10]));
const b2 = tf.variable(tf.randomNormal([10]));
function model(x) {
return x.matMul(w1).add(b1).relu().matMul(w2).add(b2);
}
除了 Layers API 以外,Data API 也可与 Core API 无缝协作。让我们重用先前在 model.fitDataset() 部分中定义的数据集,该数据集已完成打乱顺序和批处理操作:
const xs = tf.data.generator(data);
const ys = tf.data.generator(labels);
// Zip the data and labels together, shuffle and batch 32 samples at a time.
const ds = tf.data.zip({xs, ys}).shuffle(100 /* bufferSize */).batch(32);
让我们训练模型:
const optimizer = tf.train.sgd(0.1 /* learningRate */);
// Train for 5 epochs.
for (let epoch = 0; epoch < 5; epoch++) {
await ds.forEachAsync(({xs, ys}) => {
optimizer.minimize(() => {
const predYs = model(xs);
const loss = tf.losses.softmaxCrossEntropy(ys, predYs);
loss.data().then(l => console.log('Loss', l));
return loss;
});
});
console.log('Epoch', epoch);
}
以上代码是使用 Core API 训练模型时的标准方法:
- 循环周期数。
- 在每个周期内,循环各批次数据。使用
Dataset
时,[dataset.forEachAsync()](https://js.tensorflow.org/api/0.15.1/#tf.data.Dataset.forEachAsync)
可方便地循环各批次数据。 - 针对每个批次,调用
[optimizer.minimize(f)](https://js.tensorflow.org/api/latest/#tf.train.Optimizer.minimize)
,它可以执行f
并通过计算相对于我们先前定义的四个变量的梯度来最小化其输出。 f
可计算损失。它使用模型的预测和真实值调用预定义的损失函数之一。
保存和加载模型
本页内容保存 tf.Model本地存储空间(仅限浏览器)IndexedDB(仅限浏览器)文件下载(仅限浏览器)HTTP(S) 请求原生文件系统(仅限 Node.js)加载 tf.Model本地存储空间(仅限浏览器)
TensorFlow.js 提供了保存和加载模型的功能,这些模型可以使用 Layers
API 创建或从现有 TensorFlow 模型转换而来。可能是您自己训练的模型,也可能是其他人训练的模型。使用 Layers API 的一个主要好处是,使用它创建的模型是可序列化模型,这就是我们将在本教程中探讨的内容。
本教程将重点介绍如何保存和加载 TensorFlow.js 模型(可通过 JSON 文件识别)。我们也可以导入 TensorFlow Python 模型。以下两个教程介绍了如何加载这些模型:
保存 tf.Model
tf.Model
和 tf.Sequential
都提供了 model.save
函数,您可以借助该函数保存模型的拓扑和权重 。
- 拓扑:这是一个描述模型架构的文件(例如模型使用了哪些运算)。它包含对外部存储的模型权重的引用。
- 权重:这些是以有效格式存储给定模型权重的二进制文件。它们通常存储在与拓扑相同的文件夹中。
我们来看看用于保存模型的代码:
const saveResult = await model.save('localstorage://my-model-1');
一些需要注意的地方:
save
方法采用以协议名称开头的类网址字符串参数。它描述了我们想保存模型的地址的类型。在上例中,协议名称为localstorage://
。- 协议名称之后是路径。在上例中,路径是
my-model-1
。 save
方法是异步的。model.save
的返回值是一个 JSON 对象,包含模型的拓扑和权重的字节大小等信息。- 用于保存模型的环境不会影响可以加载模型的环境。在 node.js 中保存模型不会阻碍在浏览器中加载模型。
我们将在下面查看不同协议名称。
本地存储空间(仅限浏览器)
协议名称: localstorage://
await model.save('localstorage://my-model');
这会在浏览器的本地存储空间中以名称 my-model
保存模型。这样能够在浏览器刷新后保持不变,而当存储空间成为问题时,用户或浏览器本身可以清除本地存储。每个浏览器还可为给定域设置本地存储空间中可以存储的数据量。
IndexedDB(仅限浏览器)
协议名称: indexeddb://
await model.save('indexeddb://my-model');
这会将模型保存到浏览器的 IndexedDB 存储空间中。与本地存储一样,它在刷新后仍然存在,同时所存储对象大小的上限更高。
文件下载(仅限浏览器)
协议名称: downloads://
await model.save('downloads://my-model');
这会让浏览器将模型文件下载至用户的机器上。将生成两个文件:
- 一个名为
[my-model].json
的 JSON 文本文件,其中包含模型拓扑和对下文所述权重文件的引用。 - 一个二进制文件,其中包含名为
[my-model].weights.bin
的权重值。
您可以更改 [my-model]
名称以获得一个名称不同的文件。
由于 .json
文件使用相对路径指向 .bin
,因此两个文件应位于同一个文件夹中。
注:某些浏览器要求用户先授予权限,然后才能同时下载多个文件。
HTTP(S) 请求
协议名称:http://
或 https://
await model.save('http://model-server.domain/upload')
这将创建一个 Web 请求,以将模型保存到远程服务器。您应该控制该远程服务器,确保它能够处理该请求。
模型将通过 POST 请求发送至指定的 HTTP 服务器。POST 主体采用 multipart/form-data
格式并包含两个文件:
- 一个名为
model.json
的 JSON 文本文件,其中包含模型拓扑和对下文所述权重文件的引用。 - 一个二进制文件,其中包含名为
model.weights.bin
的权重值。
请注意,这两个文件的名称需要始终与上面所指定的完全相同(因为名称内置于函数中)。此 API 文档包含一个 Python 代码段,演示了如何使用 Flask Web 框架处理源自 save
的请求。
通常,您必须向 HTTP 服务器传递更多参数或请求头(例如,用于身份验证,或者如果要指定应保存模型的文件夹)。您可以通过替换 tf.io.browserHTTPRequest
中的网址字符串参数来获得对来自 save
的请求在这些方面的细粒度控制。此 API 在控制 HTTP 请求方面提供了更大的灵活性。
例如:
await model.save(tf.io.browserHTTPRequest(
'http://model-server.domain/upload',
{method: 'PUT', headers: {'header_key_1': 'header_value_1'} }));
原生文件系统(仅限 Node.js)
协议名称: file://
await model.save('file:///path/to/my-model');
在 Node.js 上运行时,我们还可以直接访问文件系统并保存模型。上面的命令会将两个文件保存到在 scheme
后指定的 path
中。
- 一个名为
[model].json
的 JSON 文本文件,其中包含模型拓扑和对下文所述权重文件的引用。 - 一个二进制文件,其中包含名为
[model].weights.bin
的权重值。
请注意,这两个文件的名称需要始终与上面所指定的完全相同(因为名称内置于函数中)。
加载 tf.Model
给定一个使用上述方法之一保存的模型,我们可以使用 tf.loadLayersModel
API 加载它。
我们来看看加载模型的代码:
const model = await tf.loadLayersModel('localstorage://my-model-1');
一些需要注意的地方:
- 与
model.save()
类似,loadLayersModel
函数也采用以协议名称开头的类网址字符串参数。它描述了我们想要从中加载模型的目标类型。 - 协议名称之后是路径。在上例中,路径是
my-model-1
。 - 类网址字符串可以替换为与 IOHandler 接口匹配的对象。
tf.loadLayersModel()
函数是异步的。tf.loadLayersModel
的返回值为tf.Model
。
我们将在下面查看不同协议名称。
本地存储空间(仅限浏览器)
协议名称: localstorage://
const model = await tf.loadLayersModel('localstorage://my-model');
这将从浏览器的本地存储空间加载一个名为 my-model
的模型。
IndexedDB(仅限浏览器)
协议名称: indexeddb://
const model = await tf.loadLayersModel('indexeddb://my-model');
这将从浏览器的 IndexedDB 存储空间加载一个模型。
HTTP(S)
协议名称:http://
或 https://
const model = await tf.loadLayersModel('http://model-server.domain/download/model.json');
这将从 HTTP 端点加载模型。加载 json
文件后,函数将请求 json
文件引用的对应 .bin
文件。
注:此实现依赖于
fetch
方法,如果您的环境没有提供原生 fetch 方法,您可以提供满足接口要求的全局方法名称fetch
,或者使用类似于 (node-fetch
)[www.npmjs.com/package/nod…] 的库。
原生文件系统(仅限 Node.js)
协议名称: file://
const model = await tf.loadLayersModel('file://path/to/my-model/model.json');
在 Node.js 上运行时,我们还可以直接访问文件系统并加载模型。请注意,在上面的函数调用中,我们引用 model.json 文件本身(在保存时,我们指定一个文件夹)。对应的 .bin
文件应与 json
文件位于同一个文件夹中。
使用 IOHandler 加载模型
如果上述协议名称没有满足您的需求,您可以使用 IOHandler
实现自定义加载行为。Tensorflow.js 提供的一个 IOHandler
是 tf.io.browserFiles
,它允许浏览器用户在浏览器中上传模型文件。请参阅文档了解更多信息。
使用自定义 IOHandler 保存或加载模型
如果上述协议名称没有满足您的保存或加载需求,您可以通过实现 IOHandler
来实现自定义序列化行为。
IOHandler
是一个包含 save
和 load
方法的对象。
save
函数采用一个与 ModelArtifacts 接口匹配的参数,应返回一个解析为 SaveResult 对象的 promise。
load
函数不采用参数,应返回一个解析为 ModelArtifacts 对象的 promise。这是传递给 save
的同一对象。
请参阅 BrowserHTTPRequest 获取如何实现 IOHandler 的示例。
模型转换
TensorFlow.js 附带各种预训练模型,这些模型可以在浏览器中使用,您可以在我们的模型仓库中找到它们。但是,您可能已经在其他地方找到或创建了一个 TensorFlow 模型,并希望在网络应用中使用该模型。TensorFlow.js 为此目的提供了一个模型转换器。TensorFlow.js 转换器有两个组件:
- 一个命令行实用工具,用于转换 Keras 和 TensorFlow 模型以在 TensorFlow.js 中使用。
- 一个 API ,用于在浏览器中使用 TensorFlow.js 加载和执行模型。
转换您的模型
TensorFlow.js 转换器可以转换以下几种格式的模型:
SavedModel:保存 TensorFlow 模型的默认格式。有关 SavedModel 格式的详细信息,请参阅此处。
Keras 模型:Keras 模型通常保存为 HDF5 文件。有关保存 Keras 模型的更多信息,请访问此处。
TensorFlow Hub 模块:这些是打包后用于在 TensorFlow Hub 上分发的模型,TensorFlow Hub 是一个共享和发现模型的平台。模型库位于此处。
根据您尝试转换的模型的类型,您需要将不同的参数传递给转换器。例如,假设您将一个名为 model.h5
的 Keras 模型保存到 tmp/
目录中。要使用 TensorFlow.js 转换器转换模型,您可以运行以下命令:
$ tensorflowjs_converter --input_format=keras /tmp/model.h5 /tmp/tfjs_model
这会转换 /tmp/model.h5
下的模型并将 model.json
文件及二进制权重文件输出到 tmp/tfjs_model/
目录中。
有关不同模型格式对应的命令行参数的更多详细信息,请参阅 TensorFlow.js 转换器自述文件。
在转换过程中,我们会遍历模型计算图并检查 TensorFlow.js 是否支持每个运算。如果支持,我们会将计算图转换成浏览器可以使用的格式。我们尝试通过将权重分成 4MB 的文件(这样它们可以被浏览器缓存)来优化模型以便在网络上应用。我们也尝试使用开放源代码 Grappler 项目简化模型计算图。计算图简化包括折叠相邻运算,从而消除常见子计算图等。这些变更对模型的输出没有影响。要进行进一步优化,用户可以传入参数以指示转换器将模型量化到特定的字节大小。量化是一种缩减模型大小的技术,它使用更少的位来表示权重。用户必须谨慎操作,以确保量化后模型的准确率保持在可接受范围内。
如果在转换过程中遇到不支持的运算,该过程将失败,我们将为用户打印该运算的名称。请在我们的 GitHub 下提交议题告诉我们相关信息,我们会尝试根据用户需求实现新运算。
最佳做法
虽然我们会在转换过程中尽力优化您的模型,但通常确保您的模型高效运行的最佳方式是在构建时考虑资源受限的环境。这意味着避免过于复杂的架构和尽可能减少参数(权重)的数量。
运行您的模型
成功转换模型之后,您将得到一组权重文件和一个模型拓扑文件。TensorFlow.js 提供了模型加载 API,您可以使用这些 API 提取模型资源并在浏览器中运行推断。
以下是适用于转换后的 TensorFlow SavedModel 或 TensorFlow Hub 模块的 API:
const model = await tf.loadGraphModel(‘path/to/model.json’);
以下是适用于转换后的 Keras 模型的 API:
const model = await tf.loadLayersModel(‘path/to/model.json’);
tf.loadGraphModel
API 返回 tf.FrozenModel
,这意味着参数已被固定并且您无法使用新数据微调模型。tf.loadLayersModel
API 返回可训练的 tf.Model。有关如何训练 tf.Model 的信息,请参阅训练模型指南。
转换后,建议您运行几次推断并对模型的速度进行基准测试。为此,我们提供了一个独立的基准测试页面:tensorflow.github.io/tfjs/e2e/be…。您可能注意到我们丢弃了初始预热运行中的测量值,这是因为(通常情况下),由于创建纹理和编译着色器的开销,您的模型的首次推断将比后续推断慢几倍。
适用于 Keras 用户的 TensorFlow.js 层 API
本页内容构造函数将 JavaScript 对象作为配置Model.fit() 是异步方法TensorFlow.js 中没有 NumPy使用工厂方法,而不是构造函数选项字符串值为小驼峰式命名法,而不是蛇形命名法
TensorFlow.js 的 Layers API 以 Keras 为模型。考虑到 JavaScript 与 Python 之间的差异,我们努力使 Layers API 与 Keras 类似。这样,具有使用 Python 开发 Keras 模型经验的用户可以更轻松地迁移到使用 JavaScript 编写的 TensorFlow.js 层。例如,以下 Keras 代码可以转换为 JavaScript:
# Python:
import keras
import numpy as np
# Build and compile model.
model = keras.Sequential()
model.add(keras.layers.Dense(units=1, input_shape=[1]))
model.compile(optimizer='sgd', loss='mean_squared_error')
# Generate some synthetic data for training.
xs = np.array([[1], [2], [3], [4]])
ys = np.array([[1], [3], [5], [7]])
# Train model with fit().
model.fit(xs, ys, epochs=1000)
# Run inference with predict().
print(model.predict(np.array([[5]])))
// JavaScript:
import * as tf from '@tensorlowjs/tfjs';
// Build and compile model.
const model = tf.sequential();
model.add(tf.layers.dense({units: 1, inputShape: [1]}));
model.compile({optimizer: 'sgd', loss: 'meanSquaredError'});
// Generate some synthetic data for training.
const xs = tf.tensor2d([[1], [2], [3], [4]], [4, 1]);
const ys = tf.tensor2d([[1], [3], [5], [7]], [4, 1]);
// Train model with fit().
await model.fit(xs, ys, {epochs: 1000});
// Run inference with predict().
model.predict(tf.tensor2d([[5]], [1, 1])).print();
但是,我们希望在本文档中说明并解释一些差异。一旦理解了这些差异及其背后的基本原理,将您的程序从Python 迁移到JavaScript(或反向迁移)应该会是一种相对平稳的体验。
构造函数将 JavaScript 对象作为配置
比较上例中的以下 Python 和 JavaScript 代码:它们都可以创建一个密集层。
# Python:
keras.layers.Dense(units=1, inputShape=[1])
// JavaScript:
tf.layers.dense({units: 1, inputShape: [1]});
JavaScript 函数在 Python 函数中没有等效的关键字参数。我们希望避免在 JavaScript 中将构造函数选项作为位置参数实现,这对于记忆和使用具有大量关键字参数的构造函数(例如 LSTM)来说尤其麻烦。这就是我们使用 JavaScript 配置对象的原因。这些对象提供与 Python 关键字参数相同的位置不变性和灵活性。
Model 类的一些方法(例如 Model.compile()
)也将 JavaScript 配置对象作为输入。但是请记住,Model.fit()
、Model.evaluate()
和 Model.predict()
略有不同。因为这些方法将强制 x
(特征)和 y
(标签或目标)数据作为输入;x
和 y
是与后续配置对象分开的位置参数,属于关键字参数。例如:
// JavaScript:
await model.fit(xs, ys, {epochs: 1000});
Model.fit() 是异步方法
Model.fit()
是用户在 TensorFlow.js 中执行模型训练的主要方法。此方法通常可以长时间运行(持续数秒或数分钟)。因此,我们利用 JavaScript 语言的 async
特性,因此在浏览器中运行时,能够以不阻塞主界面线程的方式使用此函数。这与 JavaScript 中其他可能长时间运行的函数类似,例如 async
获取。请注意,async
是一个在 Python 中不存在的构造。Keras 中的 fit()
方法返回一个 History 对象,而 fit()
方法在 JavaScript 中的对应项则返回 History 的 Promise,这个响应可以等待(如上例中所示),也可与 then() 方法一起使用。
TensorFlow.js 中没有 NumPy
Python Keras 用户经常使用 NumPy 来执行基本的数值和数组运算,例如在上例中生成二维张量。
# Python:
xs = np.array([[1], [2], [3], [4]])
在 TensorFlow.js 中,这种基本的数值运算是使用软件包本身完成的。例如:
// JavaScript:
const xs = tf.tensor2d([[1], [2], [3], [4]], [4, 1]);
tf.*
命名空间还为数组和线性代数运算(例如矩阵乘法)提供了大量其他函数。有关更多信息,请参阅 TensorFlow.js Core 文档。
使用工厂方法,而不是构造函数
Python 中的这一行(来自上例)是一个构造函数调用:
# Python:
model = keras.Sequential()
如果严格转换为 JavaScript,则等效构造函数调用将如下所示:
// JavaScript:
const model = new tf.Sequential(); // !!! DON'T DO THIS !!!
不过,我们决定不使用“new”构造函数,因为 1)“new”关键字会使代码更加膨胀;2)“new”构造函数被视为 JavaScript 的“不良部分”:一个潜在的陷阱,如 JavaScript: the Good Parts 中所讨论。要在 TensorFlow.js 中创建模型和层,可以调用具有 lowerCamelCase(小驼峰式命名法)名称的工厂方法,例如:
// JavaScript:
const model = tf.sequential();
const layer = tf.layers.batchNormalization({axis: 1});
选项字符串值为小驼峰式命名法,而不是蛇形命名法
在 JavaScript 中,更常见的是为符号名称使用驼峰命名法(例如,请参阅 Google JavaScript 样式指南),而在 Python 中,蛇形命名法很常见(例如,在 Keras 中)。因此,我们决定使用小驼峰式命名法作为选项的字符串值,包括:
- DataFormat,例如,channelsFirst 而不是 channels_first
- 初始值设定项,例如,
glorotNormal
而不是glorot_normal
- 损失和指标,例如,
meanSquaredError
而不是mean_squared_error
,categoricalCrossentropy
而不是categorical_crossentropy
。
例如,如上例所示:
// JavaScript:
model.compile({optimizer: 'sgd', loss: 'meanSquaredError'});
对于模型序列化和反序列化,请放心。TensorFlow.js 的内部机制可以确保正确处理 JSON 对象中的蛇形命名法,例如,在从 Python Keras 加载预训练模型时。
使用 apply() 运行 Layer 对象,而不是将其作为函数调用
在 Keras 中,Layer 对象定义了 __call__
方法。因此,用户可以通过将对象作为函数调用来调用层的逻辑,例如:
# Python:
my_input = keras.Input(shape=[2, 4])
flatten = keras.layers.Flatten()
print(flatten(my_input).shape)
这个 Python 语法糖在 TensorFlow.js 中作为 apply() 方法实现:
// JavaScript:
const myInput = tf.input({shape: [2, 4]});
const flatten = tf.layers.flatten();
console.log(flatten.apply(myInput).shape);
Layer.apply() 支持对具体张量进行命令式 (Eager) 执行
目前,在 Keras 中,调用方法只能在 (Python) TensorFlow 的 tf.Tensor
对象上运行(假设 TensorFlow 是后端),这些对象是符号对象并且不包含实际数值。这就是上一部分中的示例所显示的内容。但是,在 TensorFlow.js 中,层的 apply()
方法可以在符号和命令模式下运行。如果使用 SymbolicTensor(类似于 tf.Tensor)调用 apply()
,返回值将为 SymbolicTensor。这通常发生在模型构建期间。但是,如果使用实际的具体张量值调用 apply()
,将返回一个具体的张量。例如:
// JavaScript:
const flatten = tf.layers.flatten();
flatten.apply(tf.ones([2, 3, 4])).print();
这个特性让人联想到 (Python) TensorFlow 的 Eager Execution。它在模型开发期间提供了更出色的交互性和可调试性,并且为构建动态神经网络打开了大门。
优化器在 train. 下,而不是在 optimizers. 下
在 Keras 中,Optimizer 对象的构造函数位于 keras.optimizers.
命名空间下。在 TensorFlow.js Layers 中,Optimizer 的工厂方法位于 tf.train.
命名空间下。例如:
# Python:
my_sgd = keras.optimizers.sgd(lr=0.2)
// JavaScript:
const mySGD = tf.train.sgd({lr: 0.2});
loadLayersModel() 从网址而不是 HDF5 文件加载
在 Keras 中,模型通常保存为 HDF5 (.h5) 文件,然后可以使用 keras.models.load_model()
方法加载。该方法采用 .h5 文件的路径。load_model()
在 TensorFlow.js 中的对应项是 tf.loadLayersModel()
。由于 HDF5 文件格式对浏览器并不友好,因此 tf.loadLayersModel()
采用 TensorFlow.js 特定的格式。tf.loadLayersModel()
将 model.json 文件作为其输入参数。可以使用 tensorflowjs 的 pip 软件包从 Keras HDF5 文件转换 model.json。
// JavaScript:
const model = await tf.loadLayersModel('https://foo.bar/model.json');
还要注意,tf.loadLayersModel()
返回 tf.Model
的 Promise
。
一般来说,在 TensorFlow.js 中分别使用 tf.Model.save
和 tf.loadLayersModel
方法保存和加载 tf.Model
。我们将这些 API 设计为类似于 Keras 的 save_model 和 load_model API。但是,浏览器环境与 Keras 等主要深度学习框架运行的后端环境完全不同,特别是在用于持久化和传输数据的路由数组中。因此,TensorFlow.js 和 Keras 中的保存/加载 API 之间存在一些有趣的差异。有关更多详细信息,请参阅我们有关保存和加载 tf.Model 的教程。
利用 fitDataset()
训练使用 tf.data.Dataset
对象的模型
在 Python TensorFlow 的 tf.keras 中,模型可以使用 Dataset 对象进行训练。模型的 fit()
方法直接接受此类对象。TensorFlow.js 模型可以使用 Dataset 对象的 JavaScript 对应项进行训练(请参阅 TensorFlow.js 中的 tf.data API 文档。不过,与 Python 不同,基于 Dataset 的训练是通过一个名为 fitDataset 的专用方法完成的。fit() 方法仅适用于基于张量的模型训练。
Layer 对象和 Model 对象的内存管理
TensorFlow.js 在浏览器中的 WebGL 上运行,其中 Layer 对象和 Model 对象的权重由 WebGL 纹理支持。不过,WebGL 不支持内置垃圾收集。在推断和训练调用过程中,Layer 对象和 Model 对象为用户在内部管理张量内存。但是,它们也允许用户清理以释放占用的 WebGL 内存。对于在单页加载中创建和释放许多模型实例的情况,这样做很有用。要想清理 Layer 对象或 Model 对象,请使用 dispose()
方法。
Node 中的 TensorFlow.js
本页内容TensorFlow CPUTensorFlow GPU普通 CPU生产考量因素APItf.browsertf.node
TensorFlow CPU
TensorFlow CPU 软件包可以按如下方式导入:
import * as tf from '@tensorflow/tfjs-node'
从此软件包导入 TensorFlow.js 时,您导入的模块将由 TensorFlow C 二进制文件加速并在 CPU 上运行。CPU 上的 TensorFlow 使用硬件加速来加速后台的线性代数运算。
此软件包可以在支持 TensorFlow 的 Linux、Windows 和 Mac 平台上运行。
注:您不必导入 ‘@tensorflow/tfjs’ 或者将其添加到您的 package.json 中。它由 Node 库间接导入。
TensorFlow GPU
TensorFlow GPU 软件包可以按如下方式导入:
import * as tf from '@tensorflow/tfjs-node-gpu'
与 CPU 软件包一样,您导入的模块将由 TensorFlow C 二进制文件加速,但是它将在支持 CUDA 的 GPU 上运行张量运算,因此只能在 Linux 平台上运行。此绑定比其他绑定选项至少快一个数量级。
注:此软件包目前仅适用于 CUDA。在选择本方案之前,您需要在带有 NVIDIA 显卡的的计算机上安装 CUDA。
注:您不必导入 ‘@tensorflow/tfjs’ 或者将其添加到您的 package.json 中。它由 Node 库间接导入。
普通 CPU
使用普通 CPU 运算运行的 TensorFlow.js 版本可以按如下方式导入:
import * as tf from '@tensorflow/tfjs'
此软件包与您在浏览器中使用的软件包相同。在此软件包中,运算在 CPU 上以原生 JavaScript 运行。此软件包比其他软件包小得多,因为它不需要 TensorFlow 二进制文件,但是速度要慢得多。
由于此软件包不依赖于 TensorFlow,因此它可用于支持 Node.js 的更多设备,而不仅仅是 Linux、Windows 和 Mac 平台。
生产考量因素
Node.js 绑定为 TensorFlow.js 提供了一个同步执行运算的后端。这意味着当您调用一个运算(例如 tf.matMul(a, b)
)时,它将阻塞主线程,直到运算完成。
因此,绑定当前非常适合脚本和离线任务。如果您要在正式应用(例如网络服务器)中使用 Node.js 绑定,应设置一个作业队列或设置一些工作进程线程,以便您的 TensorFlow.js 代码不会阻塞主线程。
API
一旦您在上面的任何选项中将软件包作为 tf 导入,所有普通的 TensorFlow.js 符号都将出现在导入的模块上。
tf.browser
在普通的 TensorFlow.js 软件包中,tf.browser.*
命名空间中的符号将在 Node.js 中不可用,因为它们使用浏览器特定的 API。
目前,存在以下 API:
- tf.browser.fromPixels
- tf.browser.toPixels
tf.node
两个 Node.js 软件包还提供了一个名为 tf.node
的命名空间,其中包含 Node 特定的 API。
TensorBoard 是一个值得注意的 Node.js 特定的 API 示例。
在 Node.js 中将摘要导出到 TensorBoard 的示例:
const model = tf.sequential();
model.add(tf.layers.dense({ units: 1, inputShape: [200] }));
model.compile({
loss: 'meanSquaredError',
optimizer: 'sgd',
metrics: ['MAE']
});
// Generate some random fake data for demo purpose.
const xs = tf.randomUniform([10000, 200]);
const ys = tf.randomUniform([10000, 1]);
const valXs = tf.randomUniform([1000, 200]);
const valYs = tf.randomUniform([1000, 1]);
// Start model training process.
async function train() {
await model.fit(xs, ys, {
epochs: 100,
validationData: [valXs, valYs],
// Add the tensorBoard callback here.
callbacks: tf.node.tensorBoard('/tmp/fit_logs_1')
});
}
train();