TensorFlow.js 的 Keras 用户层 API

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(标签或目标)数据作为输入;xy 是位置参数,与随后的充当关键字参数的配置对象分开。例如

// 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_errorcategoricalCrossentropy 而不是 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.savetf.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() 方法。