开发者指南 / 将 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

使用 TensorFlow 后端从 Keras 2 迁移到 Keras 3

首先,替换您的导入

  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 中,在 GPU 上,Model 构造函数的 jit_compile 参数的默认值已设置为 True。这意味着默认情况下,模型将在 GPU 上使用即时 (JIT) 编译进行编译。

JIT 编译可以提高某些模型的性能。但是,它可能不适用于所有 TensorFlow 操作。如果您正在使用自定义模型或层,并且看到与 XLA 相关的错误,则可能需要将 jit_compile 参数设置为 False。这是使用 XLA 和 TensorFlow 时遇到的已知问题列表。除了这些问题之外,还有一些 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,或者将 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 的 SavedModel – 它适用于任何 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 中,默认情况下,在自定义层的 call() 方法上启用 TF autograph。在 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 操作

在函数式模型构建期间,不允许对 Keras 张量使用 TF 操作:“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 中的等效操作。

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 和 metrics 中的类似条目。但是,在 keras 3.0 中,这些条目不会自动添加到 metrics 中。它们必须在每个单独输出的指标列表中显式提供。

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

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 变量跟踪

与 Keras 2 不同,将 tf.Variable 设置为 Keras 3 层或模型的属性不会自动跟踪变量。以下代码片段将显示 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 条目

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

如果参数中的 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

如何修复:完全按照错误消息的要求进行操作。首先,尝试紧急运行该层以查看 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 已删除。相反,您只需使用带有参数 thresholdReLU 层。
  • 符号式 Layer.add_loss():符号式 add_loss() 已被移除(您仍然可以在层/模型的 call() 方法内部使用 add_loss())。
  • 局部连接层 (LocallyConnected1DLocallyConnected2D) 由于使用率极低已被移除。要使用局部连接层,请将层的实现复制到您自己的代码库中。
  • keras.layers.experimental.RandomFourierFeatures 由于使用率极低已被移除。要使用它,请将层的实现复制到您自己的代码库中。
  • 已移除的层属性:层属性 metricsdynamic 已被移除。metrics 仍然可以在 Model 类上使用。
  • RNN 层中的 constantstime_major 参数已被移除。constants 参数是 Theano 的遗留物,使用率极低。time_major 参数的使用率也很低。
  • reset_metrics 参数:reset_metrics 参数已从 model.*_on_batch() 方法中移除。此参数的使用率极低。
  • keras.constraints.RadialConstraint 对象已被移除。此对象的使用率极低。

过渡到后端无关的 Keras 3

使用 TensorFlow 后端的 Keras 3 代码将与原生 TensorFlow API 一起工作。但是,如果您希望您的代码与后端无关,您将需要

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

让我们详细了解每个要点。

切换到 Keras 操作

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

NumPy 操作

Keras 将 NumPy API 作为 keras.ops 的一部分实现。

下表仅列出了一小部分 TensorFlow 和 Keras 操作;未列出的操作通常在两个框架中具有相同的名称(例如 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

其他操作

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(不是操作)
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 操作的行为不同

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 整数并传递它,因为虽然这在急切执行时可以正常工作,但在使用编译时(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

这样的层在任何设置(急切执行或编译模型)中都可以安全使用。每次层调用都将按预期使用不同的种子值。