代码示例 / 快速Keras食谱 / 使用 TensorFlow NumPy 编写 Keras 模型

使用 TensorFlow NumPy 编写 Keras 模型

作者: lukewood
创建日期: 2021/08/28
最后修改: 2021/08/28
描述: 概述如何使用 TensorFlow NumPy API 编写 Keras 模型。

在 Colab 中查看 GitHub 源码


介绍

NumPy 是一个非常成功的 Python 线性代数库。

TensorFlow 最近推出了 tf_numpy,这是 TensorFlow 对 NumPy API 大部分子集的实现。 得益于 tf_numpy,你可以按照 NumPy 风格编写 Keras 层或模型!

TensorFlow NumPy API 与 TensorFlow 生态系统完全集成。 自动微分、TensorBoard、Keras 模型回调、 TPU 分布以及模型导出等功能均受支持。

让我们通过几个示例来了解一下。


设置

import os

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

import tensorflow as tf
import tensorflow.experimental.numpy as tnp
import keras
from keras import layers

为了测试我们的模型,我们将使用波士顿房价回归数据集。

(x_train, y_train), (x_test, y_test) = keras.datasets.boston_housing.load_data(
    path="boston_housing.npz", test_split=0.2, seed=113
)
input_dim = x_train.shape[1]


def evaluate_model(model: keras.Model):
    loss, percent_error = model.evaluate(x_test, y_test, verbose=0)
    print("训练前的平均绝对百分比误差: ", percent_error)
    model.fit(x_train, y_train, epochs=200, verbose=0)
    loss, percent_error = model.evaluate(x_test, y_test, verbose=0)
    print("训练后的平均绝对百分比误差:", percent_error)
从 https://storage.googleapis.com/tensorflow/tf-keras-datasets/california_housing.npz 下载数据
 743530/743530 ━━━━━━━━━━━━━━━━━━━━ 0s 0us/step

使用 TNP 子类化 keras.Model

利用 Keras API 最灵活的方法是子类化 [keras.Model](/api/models/model#model-class) 类。子类化模型类 使你能够完全自定义训练循环中的内容。这使得 子类化模型成为研究人员的热门选择。

在这个例子中,我们将实现一个 Model 子类,该子类使用 TNP API 对波士顿房价数据集执行回归。请注意,当使用 TNP API 与 Keras 一起时,微分和梯度下降是自动处理的。

首先,让我们定义一个简单的 TNPForwardFeedRegressionNetwork 类。

class TNPForwardFeedRegressionNetwork(keras.Model):
    def __init__(self, blocks=None, **kwargs):
        super().__init__(**kwargs)
        if not isinstance(blocks, list):
            raise ValueError(f"blocks 必须是一个列表,获取到的 blocks={blocks}")
        self.blocks = blocks
        self.block_weights = None
        self.biases = None

    def build(self, input_shape):
        current_shape = input_shape[1]
        self.block_weights = []
        self.biases = []
        for i, block in enumerate(self.blocks):
            self.block_weights.append(
                self.add_weight(
                    shape=(current_shape, block),
                    trainable=True,
                    name=f"block-{i}",
                    initializer="glorot_normal",
                )
            )
            self.biases.append(
                self.add_weight(
                    shape=(block,),
                    trainable=True,
                    name=f"bias-{i}",
                    initializer="zeros",
                )
            )
            current_shape = block

        self.linear_layer = self.add_weight(
            shape=(current_shape, 1),
            name="linear_projector",
            trainable=True,
            initializer="glorot_normal",
        )

    def call(self, inputs):
        activations = inputs
        for w, b in zip(self.block_weights, self.biases):
            activations = tnp.matmul(activations, w) + b
            # ReLu 激活函数
            activations = tnp.maximum(activations, 0.0)

        return tnp.matmul(activations, self.linear_layer)

就像任何其他 Keras 模型一样,我们可以利用任何支持的优化器、损失、指标或我们想要的回调。

让我们来看看模型的表现!

model = TNPForwardFeedRegressionNetwork(blocks=[3, 3])
model.compile(
    optimizer="adam",
    loss="mean_squared_error",
    metrics=[keras.metrics.MeanAbsolutePercentageError()],
)
evaluate_model(model)
WARNING: 所有在 absl::InitializeLog() 调用之前的日志消息都写入 STDERR
I0000 00:00:1699909864.025985   48611 device_compiler.h:187] 使用 XLA 编译的集群!  此行最多在进程的生命周期内记录一次。

训练前的平均绝对百分比误差:  99.96772766113281
训练后的平均绝对百分比误差:40.94866180419922

太好了!我们的模型似乎有效地学习了手头的问题。

我们还可以使用 TNP 编写我们自己的自定义损失函数。

def tnp_mse(y_true, y_pred):
    return tnp.mean(tnp.square(y_true - y_pred), axis=0)


keras.backend.clear_session()
model = TNPForwardFeedRegressionNetwork(blocks=[3, 3])
model.compile(
    optimizer="adam",
    loss=tnp_mse,
    metrics=[keras.metrics.MeanAbsolutePercentageError()],
)
evaluate_model(model)
训练前的平均绝对百分比误差:  99.99896240234375
训练后的平均绝对百分比误差:52.533199310302734

使用 TNP 实现基于 Keras 层的模型

如有需要,TNP 也可以在以层为导向的 Keras 代码结构中使用。 让我们实现相同的模型,但采用分层的方法!

def tnp_relu(x):
    return tnp.maximum(x, 0)


class TNPDense(keras.layers.Layer):
    def __init__(self, units, activation=None):
        super().__init__()
        self.units = units
        self.activation = activation

    def build(self, input_shape):
        self.w = self.add_weight(
            name="weights",
            shape=(input_shape[1], self.units),
            initializer="random_normal",
            trainable=True,
        )
        self.bias = self.add_weight(
            name="bias",
            shape=(self.units,),
            initializer="zeros",
            trainable=True,
        )

    def call(self, inputs):
        outputs = tnp.matmul(inputs, self.w) + self.bias
        if self.activation:
            return self.activation(outputs)
        return outputs


def create_layered_tnp_model():
    return keras.Sequential(
        [
            TNPDense(3, activation=tnp_relu),
            TNPDense(3, activation=tnp_relu),
            TNPDense(1),
        ]
    )


model = create_layered_tnp_model()
model.compile(
    optimizer="adam",
    loss="mean_squared_error",
    metrics=[keras.metrics.MeanAbsolutePercentageError()],
)
model.build((None, input_dim))
model.summary()

evaluate_model(model)
模型: "sequential"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ 层 (类型)                      输出形状                   参数 # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ tnp_dense (TNPDense)            │ (None, 3)                 │         27 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ tnp_dense_1 (TNPDense)          │ (None, 3)                 │         12 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ tnp_dense_2 (TNPDense)          │ (None, 1)                 │          4 │
└─────────────────────────────────┴───────────────────────────┴────────────┘
 总参数: 43 (172.00 B)
 可训练参数: 43 (172.00 B)
 不可训练参数: 0 (0.00 B)
训练前的平均绝对百分比误差:  100.00006866455078
训练后的平均绝对百分比误差: 43.57806396484375

你也可以无缝切换TNP层和原生Keras层!

def create_mixed_model():
    return keras.Sequential(
        [
            TNPDense(3, activation=tnp_relu),
            # 模型将可以无缝使用普通的Dense层
            layers.Dense(3, activation="relu"),
            # ... 或者切换回tnp层!
            TNPDense(1),
        ]
    )


model = create_mixed_model()
model.compile(
    optimizer="adam",
    loss="mean_squared_error",
    metrics=[keras.metrics.MeanAbsolutePercentageError()],
)
model.build((None, input_dim))
model.summary()

evaluate_model(model)
模型: "sequential_1"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ 层 (类型)                     输出形状                  参数 # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ tnp_dense_3 (TNPDense)          │ (None, 3)                 │         27 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ dense (Dense)                   │ (None, 3)                 │         12 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ tnp_dense_4 (TNPDense)          │ (None, 1)                 │          4 │
└─────────────────────────────────┴───────────────────────────┴────────────┘
 总参数: 43 (172.00 B)
 可训练参数: 43 (172.00 B)
 不可训练参数: 0 (0.00 B)
训练前的平均绝对百分比误差:  100.0
训练后的平均绝对百分比误差: 55.646610260009766

Keras API提供了多种层。将它们与NumPy代码一起使用,将在项目中节省大量时间。


分布策略

TensorFlow NumPy和Keras与 TensorFlow分布策略集成。 这使得在多个GPU上或整个TPU Pod上进行分布式训练变得简单。

gpus = tf.config.list_logical_devices("GPU")
if gpus:
    strategy = tf.distribute.MirroredStrategy(gpus)
else:
    # 我们可以回退到一个无操作的 CPU 策略。
    strategy = tf.distribute.get_strategy()
print("Running with strategy:", str(strategy.__class__.__name__))

with strategy.scope():
    model = create_layered_tnp_model()
    model.compile(
        optimizer="adam",
        loss="mean_squared_error",
        metrics=[keras.metrics.MeanAbsolutePercentageError()],
    )
    model.build((None, input_dim))
    model.summary()
    evaluate_model(model)
INFO:tensorflow:使用 MirroredStrategy,设备为 ('/job:localhost/replica:0/task:0/device:GPU:0',)
使用策略:MirroredStrategy
模型: "sequential_2"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ 层 (类型)                     输出形状                  参数 # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ tnp_dense_5 (TNPDense)          │ (None, 3)                 │         27 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ tnp_dense_6 (TNPDense)          │ (None, 3)                 │         12 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ tnp_dense_7 (TNPDense)          │ (None, 1)                 │          4 │
└─────────────────────────────────┴───────────────────────────┴────────────┘
 总参数: 43 (172.00 B)
 可训练参数: 43 (172.00 B)
 不可训练参数: 0 (0.00 B)
训练前的平均绝对百分比误差:  100.0
训练后的平均绝对百分比误差: 44.573463439941406

TensorBoard 集成

使用 Keras API 的许多好处之一是能够通过 TensorBoard 监控训练。 将 TensorFlow NumPy API 与 Keras 一起使用,使您可以轻松利用 TensorBoard。

keras.backend.clear_session()

要从 Jupyter notebook 加载 TensorBoard,您可以运行以下魔法命令:

%load_ext tensorboard
models = [
    (
        TNPForwardFeedRegressionNetwork(blocks=[3, 3]),
        "TNPForwardFeedRegressionNetwork",
    ),
    (create_layered_tnp_model(), "layered_tnp_model"),
    (create_mixed_model(), "mixed_model"),
]
for model, model_name in models:
    model.compile(
        optimizer="adam",
        loss="mean_squared_error",
        metrics=[keras.metrics.MeanAbsolutePercentageError()],
    )
    model.fit(
        x_train,
        y_train,
        epochs=200,
        verbose=0,
        callbacks=[keras.callbacks.TensorBoard(log_dir=f"logs/{model_name}")],
    )
/opt/conda/envs/keras-tensorflow/lib/python3.10/site-packages/keras/src/callbacks/tensorboard.py:676: 用户警告: 模型未能序列化为 JSON。忽略... 无效的格式说明符
  warnings.warn(f"Model failed to serialize as JSON. Ignoring... {exc}")

要从 Jupyter notebook 加载 TensorBoard,您可以使用 %tensorboard 魔法命令:

%tensorboard --logdir logs

TensorBoard 监控指标并检查训练曲线。

Tensorboard 训练图

TensorBoard 还允许您探索模型使用的计算图。

Tensorboard 图探索

检查您的模型的能力在调试过程中可以非常有价值。


结论

将现有的NumPy代码移植到使用 tensorflow_numpy API 的Keras模型是很简单的! 通过与Keras集成,您可以使用现有的Keras回调、指标和优化器,轻松分发您的训练并使用Tensorboard。

将更复杂的模型,如ResNet,迁移到TensorFlow NumPy API将是一个很好的后续学习练习。

有几个开源的NumPy ResNet实现可在网上找到。