作者: Divyashree Sreepathihalli
创建日期 2023/10/23
最后修改日期 2023/10/30
描述: 指导和故障排除,用于将您的 Keras 2 代码迁移到多后端 Keras 3。
本指南将帮助您将仅限 TensorFlow 的 Keras 2 代码迁移到多后端 Keras 3 代码。迁移的开销很小。迁移完成后,您可以在 JAX、TensorFlow 或 PyTorch 上运行 Keras 工作流。
本指南分为两部分
让我们开始吧。
首先,安装 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
[[34;49mnotice[1;39;49m][39;49m A new release of pip is available: [31;49m23.3.1[39;49m -> [32;49m24.0
[[34;49mnotice[1;39;49m][39;49m To update, run: [32;49mpip install --upgrade pip
首先,替换您的导入
from tensorflow import keras 替换为 import kerasfrom tensorflow.keras import xyz(例如 from tensorflow.keras import layers)替换为 from keras import xyz(例如 from keras import layers)tf.keras.* 替换为 keras.*接下来,开始运行您的测试。大多数情况下,您的代码在 Keras 3 上都能正常执行。您可能遇到的所有问题都将在下面详细介绍,并附带修复方法。
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)
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)
通过 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>
在 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 会包含
以下代码片段将重现上述行为
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]
将 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())。LocallyConnected1D、LocallyConnected2D)由于使用量非常少而被删除。要使用局部连接层,请将层实现复制到您自己的代码库中。keras.layers.experimental.RandomFourierFeatures 由于使用量非常少而被删除。要使用它,请将层实现复制到您自己的代码库中。metrics、dynamic。metrics 仍然可在 Model 类上使用。constants 和 time_major 参数已删除。constants 参数是 Theano 的遗留物,使用量非常少。time_major 参数的使用量也非常少。reset_metrics 参数:已从 model.*_on_batch() 方法中删除 reset_metrics 参数。此参数的使用量非常少。keras.constraints.RadialConstraint 对象。此对象的使用量非常少。具有 TensorFlow 后端的 Keras 3 代码将与原生 TensorFlow API 一起工作。但是,如果您希望您的代码独立于后端,您将需要
tf.* API 调用替换为其等效的 Keras API。train_step/test_step 方法转换为多框架实现。keras.random Op。让我们详细介绍每个点。
在许多情况下,这是您开始能够使用 JAX 和 PyTorch 运行自定义层和指标所需进行的唯一操作:将任何 tf.*、tf.math*、tf.linalg.* 等替换为 keras.ops.*。大多数 TF Op 应该与 Keras 3 一致。如果名称不同,它们将在本指南中突出显示。
Keras 在 keras.ops 中实现了 NumPy API。
下表仅列出了 TensorFlow 和 Keras Ops 的一小部分;未列出的 Ops 通常在这两个框架中名称相同(例如 reshape、matmul、cast 等)
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/
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 执行或编译的模型中。每次调用层都会使用不同的种子值,正如预期的那样。