TensorFlow2 入门指南 | 13 Keras Functional API 官方教程

Posted AI 菌

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TensorFlow2 入门指南 | 13 Keras Functional API 官方教程相关的知识,希望对你有一定的参考价值。

前言:

本专栏在保证内容完整性的基础上,力求简洁,旨在让初学者能够更快地、高效地入门TensorFlow2 深度学习框架。如果觉得本专栏对您有帮助的话,可以给一个小小的三连,各位的支持将是我创作的最大动力!

系列文章汇总:TensorFlow2 入门指南
Github项目地址:https://github.com/Keyird/TensorFlow2-for-beginner
在这里插入图片描述


一、Keras Functional API 介绍

采用 Keras Functional API 相比 tf.keras.Sequential API 能搭建出更复杂的网络模型,比如利用 Keras Functional API 可以搭建出非线性、共享的、甚至是多输入多输出的结构,这些都是 tf.keras.Sequential API 不能完成的。

通过 Keras Functional API 来建立一个简单的三层网络:

# 网络搭建
inputs = keras.Input(shape=(784,))
x = layers.Dense(128, activation="relu")(inputs)
x = layers.Dense(64, activation="relu")(x)
outputs = layers.Dense(10)(x)

基于此,要创建一个model,需要在层图中指定其输入和输出:

model = keras.Model(inputs=inputs, outputs=outputs, name="mnist_model")

接下来,就能通过 model.summary() 打印出model的结构和参数了;也能通过 model.fit() 和 model.compile() 完成进一步地训练。

二、Keras Functional API 网络训练、验证方法

Keras Functional API 网络训练、验证方法 与 Sequential models 几乎是一样的。下面是使用 Keras Functional API 对 MNIST 数据集进行分类的一个案例:

# 数据集准备
(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
x_train = x_train.reshape(60000, 784).astype("float32") / 255
x_test = x_test.reshape(10000, 784).astype("float32") / 255

# 网络搭建
inputs = keras.Input(shape=(784,))
x = layers.Dense(128, activation="relu")(inputs)
x = layers.Dense(64, activation="relu")(x)
outputs = layers.Dense(10)(x)
model = keras.Model(inputs=inputs, outputs=outputs, name="mnist_model")
model.summary()

# 网络装配
model.compile(
    loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
    optimizer=keras.optimizers.RMSprop(),
    metrics=["accuracy"],)

# 网络训练
history = model.fit(x_train, y_train, batch_size=64, epochs=10, validation_split=0.2)
test_scores = model.evaluate(x_test, y_test, verbose=2)
print("Test loss:", test_scores[0])
print("Test accuracy:", test_scores[1])

网络训练结果:
在这里插入图片描述

三、模型保存以及加载

对于使用 Keras Functional API 构建的模型,保存模型和序列化的工作方式与 Sequential models 相同。保存函数模型的标准方法是调用 model.save() 将整个模型保存为单个文件。您以后可以从这个文件重新创建相同的模型,即使构建模型的代码不再可用。这个保存的文件包括:

  • 模型结构
  • 模型权重值(训练过程中学习到的)
  • 模型训练配置(as passed to compile)
  • 优化器以及训练的状态 (to restart training where you left off)

模型保存以及加载方法:

model.save("path_to_my_model")
del model
# Recreate the exact same model purely from the file:
model = keras.models.load_model("path_to_my_model")

四、使用同一 graph of layers 定义不同模型

在 Keras Functional API 中,通过在 graph of layers 中指定它们的输入和输出来创建模型。这意味着一个 graph of layers 可以用来生成多个模型。

在下面的示例中,使用相同的层堆栈实例化两个模型:一个将图像输入转换为16维向量的编码器模型encoder ,以及一个用于训练的端到端自动编码器模型autoencoder。

encoder_input = keras.Input(shape=(28, 28, 1), name="img")
x = layers.Conv2D(16, 3, activation="relu")(encoder_input)
x = layers.Conv2D(32, 3, activation="relu")(x)
x = layers.MaxPooling2D(3)(x)
x = layers.Conv2D(32, 3, activation="relu")(x)
x = layers.Conv2D(16, 3, activation="relu")(x)
encoder_output = layers.GlobalMaxPooling2D()(x)
# 编码器模型encoder 
encoder = keras.Model(encoder_input, encoder_output, name="encoder")
encoder.summary()

x = layers.Reshape((4, 4, 1))(encoder_output)
x = layers.Conv2DTranspose(16, 3, activation="relu")(x)
x = layers.Conv2DTranspose(32, 3, activation="relu")(x)
x = layers.UpSampling2D(3)(x)
x = layers.Conv2DTranspose(16, 3, activation="relu")(x)
decoder_output = layers.Conv2DTranspose(1, 3, activation="relu")(x)
# 自动编码器模型autoencoder
autoencoder = keras.Model(encoder_input, decoder_output, name="autoencoder")
autoencoder.summary()

五、任何 model 可以像层一样被调用

(1)模型嵌套

你可以将 model 视为一个层,在输入或另一层的输出上调用任何模型。你不仅重用了模型的体系结构,还重用了它的权重。下面是一个不同的 autoencoder 示例,它创建了一个编码器模型,一个解码器模型,并将它们链在两个调用中以获得 autoencoder 模型:

encoder_input = keras.Input(shape=(28, 28, 1), name="original_img")
x = layers.Conv2D(16, 3, activation="relu")(encoder_input)
x = layers.Conv2D(32, 3, activation="relu")(x)
x = layers.MaxPooling2D(3)(x)
x = layers.Conv2D(32, 3, activation="relu")(x)
x = layers.Conv2D(16, 3, activation="relu")(x)
encoder_output = layers.GlobalMaxPooling2D()(x)
# 编码器模型
encoder = keras.Model(encoder_input, encoder_output, name="encoder")
encoder.summary()

decoder_input = keras.Input(shape=(16,), name="encoded_img")
x = layers.Reshape((4, 4, 1))(decoder_input)
x = layers.Conv2DTranspose(16, 3, activation="relu")(x)
x = layers.Conv2DTranspose(32, 3, activation="relu")(x)
x = layers.UpSampling2D(3)(x)
x = layers.Conv2DTranspose(16, 3, activation="relu")(x)
decoder_output = layers.Conv2DTranspose(1, 3, activation="relu")(x)
# 解码器模型
decoder = keras.Model(decoder_input, decoder_output, name="decoder")
decoder.summary()

将已定义好的 encoder、decoder 模型以层的方式进行调用:

autoencoder_input = keras.Input(shape=(28, 28, 1), name="img")
# 将 model 当做 layers 的方式,可以被调用
encoded_img = encoder(autoencoder_input)
decoded_img = decoder(encoded_img)
# 自动编码器模型autoencoder
autoencoder = keras.Model(autoencoder_input, decoded_img, name="autoencoder")
autoencoder.summary()

(2)模型集成

如上所见,模型可以嵌套:模型可以包含子模型(因为模型就像一个层)。模型嵌套的一个常见用例是集成。例如,下面是如何将一组模型集成到一个单一模型中,对它们的预测进行平均:

def get_model():
    inputs = keras.Input(shape=(128,))
    outputs = layers.Dense(1)(inputs)
    return keras.Model(inputs, outputs)
    
model1 = get_model()
model2 = get_model()
model3 = get_model()

inputs = keras.Input(shape=(128,))
y1 = model1(inputs)
y2 = model2(inputs)
y3 = model3(inputs)
outputs = layers.average([y1, y2, y3])
ensemble_model = keras.Model(inputs=inputs, outputs=outputs)

六、构建复杂的网络结构

(1)多输入多输出模型

Keras Functional API 使操作多个输入和输出变得很容易,这是 Sequential API 不能实现的。

例如,如果您正在构建一个根据优先级对客户问题票据进行排序并将其路由到正确的部门的系统,那么该模型将有三个输入:

  • 票的标题(文本输入)
  • 票据的文本正文(文本输入)
  • 用户添加的任何标记(分类输入)

这个模型将有两个输出:

  • 优先级得分介于0和1之间(标量sigmoid输出)
  • 应处理票据的部门(部门集上的softmax输出)

要搭建一个结构图如下图所示的网络模型:

在这里插入图片描述

对于这个问题,你可以用 Keras Functional API 用几行代码构建这个模型:

import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

num_tags = 12  # Number of unique issue tags
num_words = 10000  # Size of vocabulary obtained when preprocessing text data
num_departments = 4  # Number of departments for predictions

title_input = keras.Input(shape=(None,), name="title")  # Variable-length sequence of ints
body_input = keras.Input(shape=(None,), name="body")  # Variable-length sequence of ints
tags_input = keras.Input(shape=(num_tags,), name="tags")  # Binary vectors of size `num_tags`

# Embed each word in the title into a 64-dimensional vector
title_features = layers.Embedding(num_words, 64)(title_input)
# Embed each word in the text into a 64-dimensional vector
body_features = layers.Embedding(num_words, 64)(body_input)

# Reduce sequence of embedded words in the title into a single 128-dimensional vector
title_features = layers.LSTM(128)(title_features)
# Reduce sequence of embedded words in the body into a single 32-dimensional vector
body_features = layers.LSTM(32)(body_features)

# Merge all available features into a single large vector via concatenation
x = layers.concatenate([title_features, body_features, tags_input])

# Stick a logistic regression for priority prediction on top of the features
priority_pred = layers.Dense(1, name="priority")(x)
# Stick a department classifier on top of the features
department_pred = layers.Dense(num_departments, name="department")(x)

# Instantiate an end-to-end model predicting both priority and department
model = keras.Model(
    inputs=[title_input, body_input, tags_input],
    outputs=[priority_pred, department_pred],
)

在装配这个模型时,您可以为每个输出分配不同的损失。你甚至可以为每次损失分配不同的权重——以调整它们对总训练损失的贡献:

model.compile(
    optimizer=keras.optimizers.RMSprop(1e-3),
    loss={
        "priority": keras.losses.BinaryCrossentropy(from_logits=True),
        "department": keras.losses.CategoricalCrossentropy(from_logits=True),
    },
    loss_weights=[1.0, 0.2],
)

通过传递输入和目标的NumPy数组列表来训练模型:

# Dummy input data
title_data = np.random.randint(num_words, size=(1280, 10))
body_data = np.random.randint(num_words, size=(1280, 100))
tags_data = np.random.randint(2, size=(1280, num_tags)).astype("float32")

# Dummy target data
priority_targets = np.random.random(size=(1280, 1))
dept_targets = np.random.randint(2, size=(1280, num_departments))

# 模型的训练
model.fit(
    {"title": title_data, "body": body_data, "tags": tags_data},
    {"priority": priority_targets, "department": dept_targets},
    epochs=2,
    batch_size=32,
)

(2)搭建 ResNet 结构

Keras Functional API 除了能搭建多输入多输出模型之外,还能轻松搭建非线性拓扑结构的模型,而 Sequential API 无法处理这些层。一个常见的用例是Resnet结构中的残差连接,下面是Resnet的网络结构图:

在这里插入图片描述

采用 Keras Functional API 搭建如上图所示结构图:

inputs = keras.Input(shape=(32, 32, 3), name="img")
x = layers.Conv2D(32, 3, activation="relu")(inputs)
x = layers.Conv2D(64, 3, activation="relu")(x)
block_1_output = layers.MaxPooling2D(3)(x)

x = layers.Conv2D(64, 3, activation="relu", padding="same")(block_1_output)
x = layers.Conv2D(64, 3, activation="relu", padding="same")(x)
block_2_output = layers.add([x, block_1_output])  # 残差链接

x = layers.Conv2D(64, 3, activation="relu", padding="same")(block_2_output)
x = layers.Conv2D(64, 3, activation="relu", padding="same")(x)
block_3_output = layers.add([x, block_2_output])  # 残差链接

x = layers.Conv2D(64, 3, activation="relu")(block_3_output)
x = layers.GlobalAveragePooling2D()(x)
x = layers.Dense(256, activation="relu")(x)
x = layers.Dropout(0.5)(x)
outputs = layers.Dense(10)(x)

model = keras.Model(inputs, outputs, name="toy_resnet")
model.summary()

七、共享层

函数式 API 的另一个很好的用途是使用共享层的模型。共享层是在同一模型中被多次重用的层实例——它们学习对应于层图中多个路径的特征。

共享层通常用于从相似的空间(例如,具有相似词汇表的两段不同文本)对输入进行编码。它们可以跨这些不同的输入共享信息,并且可以在更少的数据上训练这样的模型。如果在某个输入中看到给定的单词,那么将有利于处理通过共享层的所有输入。

要在函数式API中共享一个层,需要多次调用同一层实例。例如,这是一个跨两个不同的文本输入共享的嵌入层:

# Embedding for 1000 unique words mapped to 128-dimensional vectors
shared_embedding = layers.Embedding(1000, 128)

# Variable-length sequence of integers
text_input_a = keras.Input(shape=(None,), dtype="int32")

# Variable-length sequence of integers
text_input_b = keras.Input(shape=(None,), dtype="int32")

# Reuse the same layer to encode both inputs
encoded_input_a = shared_embedding(text_input_a)
encoded_input_b = shared_embedding(text_input_b)

八、提取并重用网络图中的节点

graph of layers(网络结构图)是一个静态数据结构,所以可以访问和检查它。这也意味着你可以访问中间层(网络图中的“节点”)的激活,并在其他地方重用它们,这对于特征提取等非常有用。

让我们看一个例子。这是一个在 ImageNet 上预训练权重的VGG19模型:

vgg19 = tf.keras.applications.VGG19()

这些是通过查询图数据结构获得的模型的中间激活:

features_list = [layer.output for layer in vgg19.layers]

使用这些特性创建一个新的特征提取模型,返回中间层激活的值:

feat_extraction_model = keras.Model(inputs=vgg19.input, outputs=features_list)
img = np.random.random((1, 224, 224, 3)).astype("float32")
extracted_features = feat_extraction_model(img)

九、使用自定义层扩展API

tf.keras 包括广泛的内置层,例如:

  • 卷积层: Conv1D, Conv2D, Conv3D, Conv2DTranspose
  • 池化层: MaxPooling1D, MaxPooling2D, MaxPooling3D, AveragePooling1D
  • RNN 层:GRU, LSTM, ConvLSTM2D
  • 其它层:BatchNormalization, Dropout, Embedding, 等等

但如果没有找到所需的内容,可以通过创建自己的层来扩展API。所有层继承Layer类并实现:

  • call():它指定了该层完成的计算;
  • build():这会创建层的权重(这只是一个样式约定,因为你也可以在__init__中创建权重)。

更多可了解:https://tensorflow.google.cn/guide/keras/custom_layers_and_models

以下是 tf.keras.layers.Dense 的基本实现:

class CustomDense(layers.Layer):
    def __init__(self, units=32

以上是关于TensorFlow2 入门指南 | 13 Keras Functional API 官方教程的主要内容,如果未能解决你的问题,请参考以下文章

TensorFlow2 入门指南 | 06 TensorFLow2 高阶操作汇总

TensorFlow2 入门指南 | 06 TensorFLow2 高阶操作汇总

TensorFlow2 入门指南 | 10 TensorBoard可视化

TensorFlow2 入门指南 | 10 TensorBoard可视化

《30天吃掉那只 TensorFlow2.0》 3-3 高阶API示范

TensorFlow2 入门指南 | 19 模型文件的保存与加载