开发者指南 / 将 Keras 2 代码迁移到多后端 Keras 3

将 Keras 2 代码迁移到多后端 Keras 3

作者: Divyashree Sreepathihalli
创建日期 2023/10/23
最后修改日期 2023/10/30
描述: 指导和故障排除,用于将您的 Keras 2 代码迁移到多后端 Keras 3。

在 Colab 中查看 GitHub 源代码

本指南将帮助您将仅限 TensorFlow 的 Keras 2 代码迁移到多后端 Keras 3 代码。迁移的开销很小。迁移完成后,您可以在 JAX、TensorFlow 或 PyTorch 上运行 Keras 工作流。

本指南分为两部分

  1. 将旧版 Keras 2 代码迁移到 Keras 3,并在 TensorFlow 后端上运行。这通常非常简单,但有一些需要注意的细微问题,我们将在下面详细介绍。
  2. 进一步将 Keras 3 + TensorFlow 代码迁移到多后端 Keras 3,以便它可以在 JAX 和 PyTorch 上运行。

让我们开始吧。


设置

首先,安装 keras-nightly

此示例使用 TensorFlow 后端 (os.environ["KERAS_BACKEND"] = "tensorflow")。迁移代码后,您可以将 "tensorflow" 字符串更改为 "jax""torch",然后在 Colab 中点击“重启运行时”,您的代码将在 JAX 或 PyTorch 后端上运行。

!pip install -q keras-nightly
import os

os.environ["KERAS_BACKEND"] = "tensorflow"

import keras
import tensorflow as tf
import numpy as np
 [notice] A new release of pip is available: 23.3.1 -> 24.0
 [notice] To update, run: pip install --upgrade pip

从 Keras 2 到 Keras 3(使用 TensorFlow 后端)

首先,替换您的导入

  1. from tensorflow import keras 替换为 import keras
  2. from tensorflow.keras import xyz(例如 from tensorflow.keras import layers)替换为 from keras import xyz(例如 from keras import layers
  3. tf.keras.* 替换为 keras.*

接下来,开始运行您的测试。大多数情况下,您的代码在 Keras 3 上都能正常执行。您可能遇到的所有问题都将在下面详细介绍,并附带修复方法。

GPU 上默认将 jit_compile 设置为 True

在 Keras 3 中,`Model` 构造函数的 `jit_compile` 参数的默认值已设置为 GPU 上的 `True`。这意味着在 GPU 上,模型将默认使用即时 (JIT) 编译进行编译。

JIT 编译可以提高某些模型的性能。但是,它可能不适用于所有 TensorFlow 操作。如果您使用的是自定义模型或层,并且看到与 XLA 相关的错误,您可能需要将 `jit_compile` 参数设置为 `False`。以下是使用 TensorFlow 的 XLA 时可能遇到的 已知问题列表。除了这些问题之外,还有一些操作不受 XLA 支持。

您可能会遇到的错误消息如下

Detected unsupported operations when trying to compile graph
__inference_one_step_on_data_125[] on XLA_GPU_JIT

例如,以下代码片段将重现上述错误

class MyModel(keras.Model):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def call(self, inputs):
        string_input = tf.strings.as_string(inputs)
        return tf.strings.to_number(string_input)


subclass_model = MyModel()
x_train = np.array([[1, 2, 3], [4, 5, 6]])
subclass_model.compile(optimizer="sgd", loss="mse")
subclass_model.predict(x_train)

如何修复:model.compile(..., jit_compile=False) 中将 jit_compile=False 设置为 False,或者像这样将 jit_compile 属性设置为 False

class MyModel(keras.Model):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def call(self, inputs):
        # tf.strings ops aren't support by XLA
        string_input = tf.strings.as_string(inputs)
        return tf.strings.to_number(string_input)


subclass_model = MyModel()
x_train = np.array([[1, 2, 3], [4, 5, 6]])
subclass_model.jit_compile = False
subclass_model.predict(x_train)
 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 51ms/step

array([[1., 2., 3.],
       [4., 5., 6.]], dtype=float32)

以 TF SavedModel 格式保存模型

Keras 3 不再支持通过 model.save() 保存到 TF SavedModel 格式。

您可能会遇到的错误消息如下

>>> model.save("mymodel")
ValueError: Invalid filepath extension for saving. Please add either a `.keras` extension
for the native Keras format (recommended) or a `.h5` extension. Use
`model.export(filepath)` if you want to export a SavedModel for use with
TFLite/TFServing/etc. Received: filepath=saved_model.

以下代码片段将重现上述错误

sequential_model = keras.Sequential([
    keras.layers.Dense(2)
])
sequential_model.save("saved_model")

如何修复:使用 model.export(filepath) 而不是 model.save(filepath)

sequential_model = keras.Sequential([keras.layers.Dense(2)])
sequential_model(np.random.rand(3, 5))
sequential_model.export("saved_model")
INFO:tensorflow:Assets written to: saved_model/assets

INFO:tensorflow:Assets written to: saved_model/assets

Saved artifact at 'saved_model'. The following endpoints are available:
* Endpoint 'serve'
  args_0 (POSITIONAL_ONLY): TensorSpec(shape=(3, 5), dtype=tf.float32, name='keras_tensor')
Output Type:
  TensorSpec(shape=(3, 2), dtype=tf.float32, name=None)
Captures:
  14428321600: TensorSpec(shape=(), dtype=tf.resource, name=None)
  14439128528: TensorSpec(shape=(), dtype=tf.resource, name=None)

加载 TF SavedModel

通过 keras.models.load_model() 加载 TF SavedModel 文件不再受支持。如果您尝试将 keras.models.load_model() 与 TF SavedModel 一起使用,您将收到以下错误

ValueError: File format not supported: filepath=saved_model. Keras 3 only supports V3
`.keras` files and legacy H5 format files (`.h5` extension). Note that the legacy
SavedModel format is not supported by `load_model()` in Keras 3. In order to reload a
TensorFlow SavedModel as an inference-only layer in Keras 3, use
`keras.layers.TFSMLayer(saved_model, call_endpoint='serving_default')` (note that your
`call_endpoint` might have a different name).

以下代码片段将重现上述错误

keras.models.load_model("saved_model")

如何修复:使用 keras.layers.TFSMLayer(filepath, call_endpoint="serving_default") 将 TF SavedModel 重新加载为 Keras 层。这不限于源自 Keras 的 SavedModels – 它适用于任何 SavedModel,例如 TF-Hub 模型。

keras.layers.TFSMLayer("saved_model", call_endpoint="serving_default")
<TFSMLayer name=tfsm_layer, built=True>

在函数式模型中使用深度嵌套的输入

Model() 不再接受深度嵌套的输入/输出(嵌套超过 1 层,例如张量的列表的列表)。

您会遇到以下错误

ValueError: When providing `inputs` as a dict, all values in the dict must be
KerasTensors. Received: inputs={'foo': <KerasTensor shape=(None, 1), dtype=float32,
sparse=None, name=foo>, 'bar': {'baz': <KerasTensor shape=(None, 1), dtype=float32,
sparse=None, name=bar>}} including invalid value {'baz': <KerasTensor shape=(None, 1),
dtype=float32, sparse=None, name=bar>} of type <class 'dict'>

以下代码片段将重现上述错误

inputs = {
    "foo": keras.Input(shape=(1,), name="foo"),
    "bar": {
        "baz": keras.Input(shape=(1,), name="bar"),
    },
}
outputs = inputs["foo"] + inputs["bar"]["baz"]
keras.Model(inputs, outputs)

如何修复:将嵌套输入替换为字典、列表和张量输入元组。

inputs = {
    "foo": keras.Input(shape=(1,), name="foo"),
    "bar": keras.Input(shape=(1,), name="bar"),
}
outputs = inputs["foo"] + inputs["bar"]
keras.Model(inputs, outputs)
<Functional name=functional_2, built=True>

TF Autograph

在 Keras 2 中,TF Autograph 默认在自定义层的 call() 方法上启用。在 Keras 3 中,它没有。这意味着您可能需要使用 cond ops(如果正在使用控制流),或者您可以用 @tf.function 装饰您的 call() 方法。

您会遇到如下错误

OperatorNotAllowedInGraphError: Exception encountered when calling MyCustomLayer.call().

Using a symbolic [`tf.Tensor`](https://tensorflowcn.cn/api_docs/python/tf/Tensor) as a Python `bool` is not allowed. You can attempt the
following resolutions to the problem: If you are running in Graph mode, use Eager
execution mode or decorate this function with @tf.function. If you are using AutoGraph,
you can try decorating this function with @tf.function. If that does not work, then you
may be using an unsupported feature or your source code may not be visible to AutoGraph.
Here is a [link for more information](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/autograph/g3doc/ref
erence/limitations.md#access-to-source-code).

以下代码片段将重现上述错误

class MyCustomLayer(keras.layers.Layer):

  def call(self, inputs):
    if tf.random.uniform(()) > 0.5:
      return inputs * 2
    else:
      return inputs / 2


layer = MyCustomLayer()
data = np.random.uniform(size=[3, 3])
model = keras.models.Sequential([layer])
model.compile(optimizer="adam", loss="mse")
model.predict(data)

如何修复:@tf.function 装饰您的 call() 方法

class MyCustomLayer(keras.layers.Layer):
    @tf.function()
    def call(self, inputs):
        if tf.random.uniform(()) > 0.5:
            return inputs * 2
        else:
            return inputs / 2


layer = MyCustomLayer()
data = np.random.uniform(size=[3, 3])
model = keras.models.Sequential([layer])
model.compile(optimizer="adam", loss="mse")
model.predict(data)
 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 43ms/step

array([[0.59727275, 1.9986179 , 1.5514829 ],
       [0.56239295, 1.6529864 , 0.33085832],
       [0.67086476, 1.5208522 , 1.99276   ]], dtype=float32)

使用 KerasTensor 调用 TF Ops

在函数式模型构建期间,将 TF Op 应用于 Keras Tensor 是不允许的:“KerasTensor 不能作为 TensorFlow 函数的输入”。

您会遇到的错误如下

ValueError: A KerasTensor cannot be used as input to a TensorFlow function. A KerasTensor
is a symbolic placeholder for a shape and dtype, used when constructing Keras Functional
models or Keras Functions. You can only use it as input to a Keras layer or a Keras
operation (from the namespaces `keras.layers` and `keras.operations`).

以下代码片段将重现此错误

input = keras.layers.Input([2, 2, 1])
tf.squeeze(input)

如何修复:使用 keras.ops 中等效的 Op。

input = keras.layers.Input([2, 2, 1])
keras.ops.squeeze(input)
<KerasTensor shape=(None, 2, 2), dtype=float32, sparse=None, name=keras_tensor_6>

多输出模型 evaluate()

多输出模型的 evaluate() 方法不再单独返回各个输出损失。您应该利用 compile() 方法中的 metrics 参数来跟踪这些损失。

当处理多个命名输出(例如 output_a 和 output_b)时,旧版 tf.keras 会包含_loss,_loss,以及指标中的类似条目。但是,在 keras 3.0 中,这些条目不会自动添加到指标中。它们必须在每个单独输出的指标列表中明确提供。

以下代码片段将重现上述行为

from keras import layers
# A functional model with multiple outputs
inputs = layers.Input(shape=(10,))
x1 = layers.Dense(5, activation='relu')(inputs)
x2 = layers.Dense(5, activation='relu')(x1)
output_1 = layers.Dense(5, activation='softmax', name="output_1")(x1)
output_2 = layers.Dense(5, activation='softmax', name="output_2")(x2)
model = keras.Model(inputs=inputs, outputs=[output_1, output_2])
model.compile(optimizer='adam', loss='categorical_crossentropy')
# dummy data
x_test = np.random.uniform(size=[10, 10])
y_test = np.random.uniform(size=[10, 5])

model.evaluate(x_test, y_test)
from keras import layers

# A functional model with multiple outputs
inputs = layers.Input(shape=(10,))
x1 = layers.Dense(5, activation="relu")(inputs)
x2 = layers.Dense(5, activation="relu")(x1)
output_1 = layers.Dense(5, activation="softmax", name="output_1")(x1)
output_2 = layers.Dense(5, activation="softmax", name="output_2")(x2)
# dummy data
x_test = np.random.uniform(size=[10, 10])
y_test = np.random.uniform(size=[10, 5])
multi_output_model = keras.Model(inputs=inputs, outputs=[output_1, output_2])
multi_output_model.compile(
    optimizer="adam",
    loss="categorical_crossentropy",
    metrics=["categorical_crossentropy", "categorical_crossentropy"],
)
multi_output_model.evaluate(x_test, y_test)
 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 112ms/step - loss: 4.0217 - output_1_categorical_crossentropy: 4.0217

[4.021683692932129, 4.021683692932129]

TensorFlow 变量跟踪

tf.Variable 设置为 Keras 3 层或模型的属性将不会自动跟踪该变量,这与 Keras 2 不同。以下代码片段将显示 tf.Variables 未被跟踪。

class MyCustomLayer(keras.layers.Layer):
    def __init__(self, units):
        super().__init__()
        self.units = units

    def build(self, input_shape):
        input_dim = input_shape[-1]
        self.w = tf.Variable(initial_value=tf.zeros([input_dim, self.units]))
        self.b = tf.Variable(initial_value=tf.zeros([self.units,]))

    def call(self, inputs):
        return keras.ops.matmul(inputs, self.w) + self.b


layer = MyCustomLayer(3)
data = np.random.uniform(size=[3, 3])
model = keras.models.Sequential([layer])
model.compile(optimizer="adam", loss="mse")
model.predict(data)
# The model does not have any trainable variables
for layer in model.layers:
    print(layer.trainable_variables)

您将看到以下警告

UserWarning: The model does not have any trainable weights.
  warnings.warn("The model does not have any trainable weights.")

如何修复:使用 self.add_weight() 方法或选择 keras.Variable。如果您当前使用的是 tf.variable,则可以切换到 keras.Variable

class MyCustomLayer(keras.layers.Layer):
    def __init__(self, units):
        super().__init__()
        self.units = units

    def build(self, input_shape):
        input_dim = input_shape[-1]
        self.w = self.add_weight(
            shape=[input_dim, self.units],
            initializer="zeros",
        )
        self.b = self.add_weight(
            shape=[
                self.units,
            ],
            initializer="zeros",
        )

    def call(self, inputs):
        return keras.ops.matmul(inputs, self.w) + self.b


layer = MyCustomLayer(3)
data = np.random.uniform(size=[3, 3])
model = keras.models.Sequential([layer])
model.compile(optimizer="adam", loss="mse")
model.predict(data)
# Verify that the variables are now being tracked
for layer in model.layers:
    print(layer.trainable_variables)
 1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 33ms/step
[<KerasVariable shape=(3, 3), dtype=float32, path=sequential_2/my_custom_layer_1/variable>, <KerasVariable shape=(3,), dtype=float32, path=sequential_2/my_custom_layer_1/variable_1>]

嵌套 call() 参数中的 None 条目

None 条目不允许作为 Layer.call() 中嵌套(例如列表/元组)张量参数的一部分,也不允许作为 call() 的嵌套返回值的一部分。

如果参数中的 None 是故意的并且有特定目的,请确保该参数是可选的,并将其构建为单独的参数。例如,请考虑使用可选参数定义 `call` 方法。

以下代码片段将重现此错误。

class CustomLayer(keras.layers.Layer):
    def __init__(self):
        super().__init__()

    def call(self, inputs):
        foo = inputs["foo"]
        baz = inputs["bar"]["baz"]
        if baz is not None:
            return foo + baz
        return foo

layer = CustomLayer()
inputs = {
    "foo": keras.Input(shape=(1,), name="foo"),
    "bar": {
        "baz": None,
    },
}
layer(inputs)

如何修复

解决方案 1:None 替换为值,如下所示

class CustomLayer(keras.layers.Layer):
    def __init__(self):
        super().__init__()

    def call(self, inputs):
        foo = inputs["foo"]
        baz = inputs["bar"]["baz"]
        return foo + baz


layer = CustomLayer()
inputs = {
    "foo": keras.Input(shape=(1,), name="foo"),
    "bar": {
        "baz": keras.Input(shape=(1,), name="bar"),
    },
}
layer(inputs)
<KerasTensor shape=(None, 1), dtype=float32, sparse=False, name=keras_tensor_14>

解决方案 2:使用可选参数定义 call 方法。这是此修复的一个示例

class CustomLayer(keras.layers.Layer):
    def __init__(self):
        super().__init__()

    def call(self, foo, baz=None):
        if baz is not None:
            return foo + baz
        return foo


layer = CustomLayer()
foo = keras.Input(shape=(1,), name="foo")
baz = None
layer(foo, baz=baz)
<KerasTensor shape=(None, 1), dtype=float32, sparse=False, name=keras_tensor_15>

状态构建问题

Keras 3 在创建状态(例如数值权重变量)的时间方面比 Keras 2 严格得多。Keras 3 要求在模型可以训练之前创建所有状态。这是使用 JAX 的要求(而 TensorFlow 在状态创建时机方面非常宽松)。

Keras 层应在其构造函数 (__init__() 方法) 或其 build() 方法中创建其状态。它们应避免在 call() 中创建状态。

如果您忽略此建议并在 call() 中创建状态(例如,通过调用先前未构建的层),则 Keras 将尝试在训练前使用符号输入调用 call() 方法来自动构建该层。但是,这种自动状态创建的尝试在某些情况下可能会失败。这将导致类似如下的错误

Layer 'frame_position_embedding' looks like it has unbuilt state,
but Keras is not able to trace the layer `call()` in order to build it automatically.
Possible causes:
1. The `call()` method of your layer may be crashing.
Try to `__call__()` the layer eagerly on some test input first to see if it works.
E.g. `x = np.random.random((3, 4)); y = layer(x)`
2. If the `call()` method is correct, then you may need to implement
the `def build(self, input_shape)` method on your layer.
It should create all variables used by the layer
(e.g. by calling `layer.build()` on all its children layers).

使用 JAX 后端时,您可以使用以下图层重现此错误

class PositionalEmbedding(keras.layers.Layer):
    def __init__(self, sequence_length, output_dim, **kwargs):
        super().__init__(**kwargs)
        self.position_embeddings = layers.Embedding(
            input_dim=sequence_length, output_dim=output_dim
        )
        self.sequence_length = sequence_length
        self.output_dim = output_dim

    def call(self, inputs):
        inputs = keras.ops.cast(inputs, self.compute_dtype)
        length = keras.ops.shape(inputs)[1]
        positions = keras.ops.arange(start=0, stop=length, step=1)
        embedded_positions = self.position_embeddings(positions)
        return inputs + embedded_positions

如何修复:按照错误消息的要求进行操作。首先,尝试在 eager 模式下运行该层,以查看 call() 方法是否确实正确(注意:如果它在 Keras 2 中运行正常,那么它就是正确的,不需要更改)。如果确实正确,那么您应该实现一个 build(self, input_shape) 方法来创建图层的所有状态,包括子图层的状态。以下是上述图层的修复应用(注意 build() 方法)

class PositionalEmbedding(keras.layers.Layer):
    def __init__(self, sequence_length, output_dim, **kwargs):
        super().__init__(**kwargs)
        self.position_embeddings = layers.Embedding(
            input_dim=sequence_length, output_dim=output_dim
        )
        self.sequence_length = sequence_length
        self.output_dim = output_dim

    def build(self, input_shape):
        self.position_embeddings.build(input_shape)

    def call(self, inputs):
        inputs = keras.ops.cast(inputs, self.compute_dtype)
        length = keras.ops.shape(inputs)[1]
        positions = keras.ops.arange(start=0, stop=length, step=1)
        embedded_positions = self.position_embeddings(positions)
        return inputs + embedded_positions

已删除的功能

为清理起见,Keras 3 中删除了一些使用量非常少量的旧功能

  • 已删除 keras.layers.ThresholdedReLU。您可以改用具有 threshold 参数的 ReLU 层。
  • 符号 Layer.add_loss():已删除符号 add_loss()(您仍然可以在层/模型的 call() 方法中使用 add_loss())。
  • 局部连接层(LocallyConnected1DLocallyConnected2D)由于使用量非常少而被删除。要使用局部连接层,请将层实现复制到您自己的代码库中。
  • keras.layers.experimental.RandomFourierFeatures 由于使用量非常少而被删除。要使用它,请将层实现复制到您自己的代码库中。
  • 已删除的层属性:已删除层属性 metricsdynamicmetrics 仍然可在 Model 类上使用。
  • RNN 层中的 constantstime_major 参数已删除。constants 参数是 Theano 的遗留物,使用量非常少。time_major 参数的使用量也非常少。
  • reset_metrics 参数:已从 model.*_on_batch() 方法中删除 reset_metrics 参数。此参数的使用量非常少。
  • 已删除 keras.constraints.RadialConstraint 对象。此对象的使用量非常少。

过渡到无后端 Keras 3

具有 TensorFlow 后端的 Keras 3 代码将与原生 TensorFlow API 一起工作。但是,如果您希望您的代码独立于后端,您将需要

  • 将所有 tf.* API 调用替换为其等效的 Keras API。
  • 将您的自定义 train_step/test_step 方法转换为多框架实现。
  • 确保您在层中正确使用了无状态的 keras.random Op。

让我们详细介绍每个点。

切换到 Keras Ops

在许多情况下,这是您开始能够使用 JAX 和 PyTorch 运行自定义层和指标所需进行的唯一操作:将任何 tf.*tf.math*tf.linalg.* 等替换为 keras.ops.*。大多数 TF Op 应该与 Keras 3 一致。如果名称不同,它们将在本指南中突出显示。

NumPy ops

Keras 在 keras.ops 中实现了 NumPy API。

下表仅列出了 TensorFlow 和 Keras Ops 的一小部分;未列出的 Ops 通常在这两个框架中名称相同(例如 reshapematmulcast 等)

TensorFlow Keras 3.0
tf.abs keras.ops.absolute
tf.reduce_all keras.ops.all
tf.reduce_max keras.ops.amax
tf.reduce_min keras.ops.amin
tf.reduce_any keras.ops.any
tf.concat keras.ops.concatenate
tf.range keras.ops.arange
tf.acos keras.ops.arccos
tf.asin keras.ops.arcsin
tf.asinh keras.ops.arcsinh
tf.atan keras.ops.arctan
tf.atan2 keras.ops.arctan2
tf.atanh keras.ops.arctanh
tf.convert_to_tensor keras.ops.convert_to_tensor
tf.reduce_mean keras.ops.mean
tf.clip_by_value keras.ops.clip
tf.math.conj keras.ops.conjugate
tf.linalg.diag_part keras.ops.diagonal
tf.reverse keras.ops.flip
tf.gather keras.ops.take
tf.math.is_finite keras.ops.isfinite
tf.math.is_inf keras.ops.isinf
tf.math.is_nan keras.ops.isnan
tf.reduce_max keras.ops.max
tf.reduce_mean keras.ops.mean
tf.reduce_min keras.ops.min
tf.rank keras.ops.ndim
tf.math.pow keras.ops.power
tf.reduce_prod keras.ops.prod
tf.math.reduce_std keras.ops.std
tf.reduce_sum keras.ops.sum
tf.gather keras.ops.take
tf.gather_nd keras.ops.take_along_axis
tf.math.reduce_variance keras.ops.var

其他 Ops

TensorFlow Keras 3.0
tf.nn.sigmoid_cross_entropy_with_logits keras.ops.binary_crossentropy (注意 from_logits 参数)
tf.nn.sparse_softmax_cross_entropy_with_logits keras.ops.sparse_categorical_crossentropy (注意 from_logits 参数)
tf.nn.sparse_softmax_cross_entropy_with_logits keras.ops.categorical_crossentropy(target, output, from_logits=False, axis=-1)
tf.nn.conv1d, tf.nn.conv2d, tf.nn.conv3d, tf.nn.convolution keras.ops.conv
tf.nn.conv_transpose, tf.nn.conv1d_transpose, tf.nn.conv2d_transpose, tf.nn.conv3d_transpose keras.ops.conv_transpose
tf.nn.depthwise_conv2d keras.ops.depthwise_conv
tf.nn.separable_conv2d keras.ops.separable_conv
tf.nn.batch_normalization 无直接等价项;使用 keras.layers.BatchNormalization
tf.nn.dropout keras.random.dropout
tf.nn.embedding_lookup keras.ops.take
tf.nn.l2_normalize keras.utils.normalize (不是 Op)
x.numpy keras.ops.convert_to_numpy
tf.scatter_nd_update keras.ops.scatter_update
tf.tensor_scatter_nd_update keras.ops.slice_update
tf.signal.fft2d keras.ops.fft2
tf.signal.inverse_stft keras.ops.istft
tf.image.crop_to_bounding_box keras.ops.image.crop_images
tf.image.pad_to_bounding_box keras.ops.image.pad_images

自定义 train_step() 方法

您的模型可能包含自定义的 train_step()test_step() 方法,这些方法依赖于仅 TensorFlow 的 API——例如,您的 train_step() 方法可能利用 TensorFlow 的 tf.GradientTape。要将此类模型转换为在 JAX 或 PyTorch 上运行,您需要为要支持的每个后端编写不同的 train_step() 实现。

在某些情况下,您可能可以简单地覆盖 Model.compute_loss() 方法,并使其完全独立于后端,而不是覆盖 train_step()。以下是一个具有自定义 compute_loss() 方法的层的示例,该方法可在 JAX、TensorFlow 和 PyTorch 上运行

class MyModel(keras.Model):
    def compute_loss(self, x=None, y=None, y_pred=None, sample_weight=None):
        loss = keras.ops.sum(keras.losses.mean_squared_error(y, y_pred, sample_weight))
        return loss

如果您需要修改优化机制本身,而不仅仅是损失计算,那么您需要覆盖 train_step(),并为每个后端实现一个 train_step 方法,如下所示。

有关每个后端应如何处理的详细信息,请参阅以下指南

class MyModel(keras.Model):
    def train_step(self, *args, **kwargs):
        if keras.backend.backend() == "jax":
            return self._jax_train_step(*args, **kwargs)
        elif keras.backend.backend() == "tensorflow":
            return self._tensorflow_train_step(*args, **kwargs)
        elif keras.backend.backend() == "torch":
            return self._torch_train_step(*args, **kwargs)

    def _jax_train_step(self, state, data):
        pass  # See guide: keras.io/guides/custom_train_step_in_jax/

    def _tensorflow_train_step(self, data):
        pass  # See guide: keras.io/guides/custom_train_step_in_tensorflow/

    def _torch_train_step(self, data):
        pass  # See guide: keras.io/guides/custom_train_step_in_torch/

使用 RNG 的层

Keras 3 拥有新的 keras.random 命名空间,其中包含

这些操作是无状态的,这意味着如果您传递一个 seed 参数,它们将每次返回相同的结果。如下所示

print(keras.random.normal(shape=(), seed=123))
print(keras.random.normal(shape=(), seed=123))
tf.Tensor(0.7832616, shape=(), dtype=float32)
tf.Tensor(0.7832616, shape=(), dtype=float32)

至关重要的是,这与有状态 tf.random Ops 的行为不同

print(tf.random.normal(shape=(), seed=123))
print(tf.random.normal(shape=(), seed=123))
tf.Tensor(2.4435377, shape=(), dtype=float32)
tf.Tensor(-0.6386405, shape=(), dtype=float32)

当您编写使用 RNG 的层时,例如自定义 dropout 层,您将希望在调用层时使用不同的种子值。但是,您不能仅仅递增一个 Python 整数并传递它,因为虽然这在 eager 执行时会正常工作,但在使用编译时(JAX、TensorFlow 和 PyTorch 都可用)则不会按预期工作。编译层时,层看到的第一个 Python 整数种子值将硬编码到编译后的图中。

为解决此问题,您应该将有状态 keras.random.SeedGenerator 对象的实例作为 seed 参数传递,如下所示

seed_generator = keras.random.SeedGenerator(1337)
print(keras.random.normal(shape=(), seed=seed_generator))
print(keras.random.normal(shape=(), seed=seed_generator))
tf.Tensor(0.6077996, shape=(), dtype=float32)
tf.Tensor(0.8211102, shape=(), dtype=float32)

因此,当编写一个使用 RNG 的层时,您将使用以下模式

class RandomNoiseLayer(keras.layers.Layer):
    def __init__(self, noise_rate, **kwargs):
        super().__init__(**kwargs)
        self.noise_rate = noise_rate
        self.seed_generator = keras.random.SeedGenerator(1337)

    def call(self, inputs):
        noise = keras.random.uniform(
            minval=0, maxval=self.noise_rate, seed=self.seed_generator
        )
        return inputs + noise

这样的层可以在任何环境中安全使用——在 eager 执行或编译的模型中。每次调用层都会使用不同的种子值,正如预期的那样。