TensorFlow.js 的层 API 模仿 Keras,我们努力使 层 API 尽可能类似于 Keras,考虑到 JavaScript 和 Python 之间的差异。这使得有经验的 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 '@tensorflow/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 代码行:它们都创建了一个 Dense 层。
# Python:
keras.layers.Dense(units=1, inputShape=[1])
// JavaScript:
tf.layers.dense({units: 1, inputShape: [1]});
JavaScript 函数没有 Python 函数中关键字参数的等效项。我们希望避免在 JavaScript 中实现构造函数选项作为位置参数,这对于具有大量关键字参数的构造函数(例如,LSTM)来说尤其麻烦。这就是我们使用 JavaScript 配置对象的原因。此类对象提供与 Python 关键字参数相同的级别的位置不变性和灵活性。
模型类的某些方法,例如,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
特性,以便此函数可以在不阻塞浏览器中运行时的主 UI 线程的情况下使用。这类似于 JavaScript 中其他可能长时间运行的函数,例如 async
fetch。请注意,async
是 Python 中不存在的构造。虽然 Keras 中的 fit()
方法返回一个 History 对象,但 JavaScript 中的 fit()
方法的对应方法返回一个 Promise of History,它可以被 awaited(如上面的示例所示)或与 then() 方法一起使用。
TensorFlow.js 没有 NumPy
Python Keras 用户经常使用 NumPy 来执行基本的数值和数组操作,例如在上面的示例中生成 2D 张量。
# Python:
xs = np.array([[1], [2], [3], [4]])
在 TensorFlow.js 中,这种基本的数值操作由包本身完成。例如
// JavaScript:
const xs = tf.tensor2d([[1], [2], [3], [4]], [4, 1]);
tf.*
命名空间还提供许多其他用于数组和线性代数操作的函数,例如矩阵乘法。有关更多信息,请参阅 TensorFlow.js 核心文档。
使用工厂方法,而不是构造函数
这行 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});
选项字符串值是 lowerCamelCase,而不是 snake_case
在 JavaScript 中,与 Python 不同,使用骆驼大小写表示符号名称更为常见(例如,请参阅 Google JavaScript 样式指南),在 Python 中,蛇形大小写很常见(例如,在 Keras 中)。因此,我们决定对选项的字符串值使用 lowerCamelCase,包括以下内容
- DataFormat,例如,
channelsFirst
而不是channels_first
- Initializer,例如,
glorotNormal
而不是glorot_normal
- Loss 和 metrics,例如,
meanSquaredError
而不是mean_squared_error
,categoricalCrossentropy
而不是categorical_crossentropy
。
例如,如上面的示例所示
// JavaScript:
model.compile({optimizer: 'sgd', loss: 'meanSquaredError'});
关于模型序列化和反序列化,请放心。TensorFlow.js 的内部机制确保 JSON 对象中的蛇形大小写被正确处理,例如,从 Python Keras 加载预训练模型时。
使用 apply() 运行层对象,而不是将它们作为函数调用
在 Keras 中,层对象定义了 __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() 支持对具体张量的命令式(急切)评估
目前,在 Keras 中,**call** 方法只能操作 (Python) TensorFlow 的 tf.Tensor
对象(假设使用 TensorFlow 后端),这些对象是符号化的,不包含实际的数值。这正是上一节示例中展示的内容。然而,在 TensorFlow.js 中,层的 apply() 方法可以在符号模式和命令式模式下运行。如果 apply()
使用 SymbolicTensor(与 tf.Tensor 非常类似)调用,则返回值将是 SymbolicTensor。这通常发生在模型构建期间。但如果 apply()
使用实际的具体 Tensor 值调用,它将返回一个具体 Tensor。例如
// JavaScript:
const flatten = tf.layers.flatten();
flatten.apply(tf.ones([2, 3, 4])).print();
此功能让人联想到 (Python) TensorFlow 的 Eager Execution。除了为构建动态神经网络打开大门之外,它还为模型开发提供了更大的交互性和可调试性。
优化器正在训练中。,而不是优化器。
在 Keras 中,优化器对象的构造函数位于 keras.optimizers.*
命名空间下。在 TensorFlow.js Layers 中,优化器的工厂方法位于 tf.train.*
命名空间下。例如
# Python:
my_sgd = keras.optimizers.sgd(lr=0.2)
// JavaScript:
const mySGD = tf.train.sgd({lr: 0.2});
loadLayersModel() 从 URL 加载,而不是从 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 包将 model.json 从 Keras HDF5 文件转换。
// JavaScript:
const model = await tf.loadLayersModel('https://foo.bar/model.json');
还要注意,tf.loadLayersModel()
返回一个 Promise
,其中包含 tf.Model
。
通常,在 TensorFlow.js 中保存和加载 tf.Model
使用 tf.Model.save
和 tf.loadLayersModel
方法分别完成。我们设计这些 API 使其类似于 Keras 的 save 和 load_model API。但浏览器环境与 Keras 等主要深度学习框架运行的后端环境截然不同,尤其是在持久化和传输数据的各种途径方面。因此,TensorFlow.js 和 Keras 中的 save/load 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() 方法仅用于基于张量的模型训练。
层和模型对象的内存管理
TensorFlow.js 在浏览器的 WebGL 上运行,其中层和模型对象的权重由 WebGL 纹理支持。但是,WebGL 没有内置的垃圾回收支持。层和模型对象在推理和训练调用期间为用户内部管理张量内存。但它们也允许用户处置它们,以释放它们占用的 WebGL 内存。这在单个页面加载中创建和释放许多模型实例的情况下非常有用。要处置层或模型对象,请使用 dispose()
方法。