此页面由 Cloud Translation API 翻译。
Switch to English

功能性API

在TensorFlow.org上查看 在GitHub上查看源代码 下载笔记本

建立

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

介绍

Keras 功能性API是一种创建模型的方法,该模型比tf.keras.Sequential API更灵活。功能性API可以处理具有非线性拓扑的模型,具有共享层的模型以及具有多个输入或输出的模型。

深度学习模型通常是层的有向无环图(DAG)的主要思想。因此,功能性API是一种构建层图的方法

考虑以下模型:

 (input: 784-dimensional vectors)
       ↧
[Dense (64 units, relu activation)]
       ↧
[Dense (64 units, relu activation)]
       ↧
[Dense (10 units, softmax activation)]
       ↧
(output: logits of a probability distribution over 10 classes)
 

这是一个具有三层的基本图形。要使用功能性API构建此模型,请先创建一个输入节点:

 inputs = keras.Input(shape=(784,))
 

数据的形状设置为784维向量。由于仅指定每个样品的形状,因此始终忽略批次大小。

例如,如果您输入的图像输入形状为(32, 32, 3) ,则可以使用:

 # Just for demonstration purposes.
img_inputs = keras.Input(shape=(32, 32, 3))
 

返回的inputs包含有关输入给模型的输入数据的形状和dtype的信息。形状如下:

 inputs.shape
 
TensorShape([None, 784])

这是dtype:

 inputs.dtype
 
tf.float32

通过在此inputs对象上调用一个图层,可以在图层图中创建一个新节点:

 dense = layers.Dense(64, activation="relu")
x = dense(inputs)
 

“图层调用”操作就像从“输入”到创建的该图层绘制箭头。您将输入“传递”到dense层,然后得到x

让我们在层图中添加更多层:

 x = layers.Dense(64, activation="relu")(x)
outputs = layers.Dense(10)(x)
 

此时,您可以通过在层图中指定Model的输入和输出来创建Model

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

让我们看看模型摘要是什么样的:

 model.summary()
 
Model: "mnist_model"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         [(None, 784)]             0         
_________________________________________________________________
dense (Dense)                (None, 64)                50240     
_________________________________________________________________
dense_1 (Dense)              (None, 64)                4160      
_________________________________________________________________
dense_2 (Dense)              (None, 10)                650       
=================================================================
Total params: 55,050
Trainable params: 55,050
Non-trainable params: 0
_________________________________________________________________

您还可以将模型绘制为图形:

 keras.utils.plot_model(model, "my_first_model.png")
 

png

并且,可选地,在绘制的图中显示每个图层的输入和输出形状:

 keras.utils.plot_model(model, "my_first_model_with_shape_info.png", show_shapes=True)
 

png

该图和代码几乎相同。在代码版本中,连接箭头由调用操作代替。

“层数图”是深度学习模型的直观心理图像,而功能性API是创建与之密切相关的模型的方法。

训练,评估和推断

对于使用功能性API构建的模型,对于Sequential模型,训练,评估和推断的工作方式完全相同。

在这里,加载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

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=2, 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])
 
Epoch 1/2
750/750 [==============================] - 2s 3ms/step - loss: 0.3458 - accuracy: 0.9013 - val_loss: 0.1860 - val_accuracy: 0.9463
Epoch 2/2
750/750 [==============================] - 2s 2ms/step - loss: 0.1588 - accuracy: 0.9524 - val_loss: 0.1242 - val_accuracy: 0.9645
313/313 - 1s - loss: 0.1297 - accuracy: 0.9604
Test loss: 0.12967276573181152
Test accuracy: 0.9603999853134155

有关更多阅读,请参阅培训和评估指南。

保存并序列化

对于使用功能性API构建的模型,保存模型和序列化的工作方式与对Sequential模型进行保存的方式相同。保存功能模型的标准方法是调用model.save()将整个模型保存为单个文件。您以后可以从该文件中重新创建相同的模型,即使构建该模型的代码不再可用。

该保存的文件包括:

  • 模型架构
  • 模型权重值(在训练过程中获悉)
  • 模型训练配置(如果有的话)(传递给compile
  • 优化程序及其状态(如果有的话)(从上次中断的地方重新开始训练)
 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")
 
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/ops/resource_variable_ops.py:1817: calling BaseResourceVariable.__init__ (from tensorflow.python.ops.resource_variable_ops) with constraint is deprecated and will be removed in a future version.
Instructions for updating:
If using Keras pass *_constraint arguments to layers.
INFO:tensorflow:Assets written to: path_to_my_model/assets

有关详细信息,请阅读模型序列化和保存指南。

使用相同的图层图定义多个模型

在功能性API中,通过在层图中指定模型的输入和输出来创建模型。这意味着可以使用单个层图来生成多个模型。

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

 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 = 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 = keras.Model(encoder_input, decoder_output, name="autoencoder")
autoencoder.summary()
 
Model: "encoder"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
img (InputLayer)             [(None, 28, 28, 1)]       0         
_________________________________________________________________
conv2d (Conv2D)              (None, 26, 26, 16)        160       
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 24, 24, 32)        4640      
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 8, 8, 32)          0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 6, 6, 32)          9248      
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 4, 4, 16)          4624      
_________________________________________________________________
global_max_pooling2d (Global (None, 16)                0         
=================================================================
Total params: 18,672
Trainable params: 18,672
Non-trainable params: 0
_________________________________________________________________
Model: "autoencoder"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
img (InputLayer)             [(None, 28, 28, 1)]       0         
_________________________________________________________________
conv2d (Conv2D)              (None, 26, 26, 16)        160       
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 24, 24, 32)        4640      
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 8, 8, 32)          0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 6, 6, 32)          9248      
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 4, 4, 16)          4624      
_________________________________________________________________
global_max_pooling2d (Global (None, 16)                0         
_________________________________________________________________
reshape (Reshape)            (None, 4, 4, 1)           0         
_________________________________________________________________
conv2d_transpose (Conv2DTran (None, 6, 6, 16)          160       
_________________________________________________________________
conv2d_transpose_1 (Conv2DTr (None, 8, 8, 32)          4640      
_________________________________________________________________
up_sampling2d (UpSampling2D) (None, 24, 24, 32)        0         
_________________________________________________________________
conv2d_transpose_2 (Conv2DTr (None, 26, 26, 16)        4624      
_________________________________________________________________
conv2d_transpose_3 (Conv2DTr (None, 28, 28, 1)         145       
=================================================================
Total params: 28,241
Trainable params: 28,241
Non-trainable params: 0
_________________________________________________________________

在此,解码架构与编码架构严格对称,因此输出形状与输入形状(28, 28, 1)

一的反向Conv2D层是Conv2DTranspose层,和一个相反MaxPooling2D层是UpSampling2D层。

所有模型都可以调用,就像图层一样

您可以通过在另一层的Input或输出上调用任何模型,将其视为层。通过调用模型,您不仅可以重用模型的体系结构,还可以重用其权重。

为了实际操作,这是对自动编码器示例的另一种处理方式,该示例创建一个编码器模型,一个解码器模型,并将它们链接到两个调用中以获得自动编码器模型:

 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()

autoencoder_input = keras.Input(shape=(28, 28, 1), name="img")
encoded_img = encoder(autoencoder_input)
decoded_img = decoder(encoded_img)
autoencoder = keras.Model(autoencoder_input, decoded_img, name="autoencoder")
autoencoder.summary()
 
Model: "encoder"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
original_img (InputLayer)    [(None, 28, 28, 1)]       0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 26, 26, 16)        160       
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 24, 24, 32)        4640      
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 8, 8, 32)          0         
_________________________________________________________________
conv2d_6 (Conv2D)            (None, 6, 6, 32)          9248      
_________________________________________________________________
conv2d_7 (Conv2D)            (None, 4, 4, 16)          4624      
_________________________________________________________________
global_max_pooling2d_1 (Glob (None, 16)                0         
=================================================================
Total params: 18,672
Trainable params: 18,672
Non-trainable params: 0
_________________________________________________________________
Model: "decoder"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
encoded_img (InputLayer)     [(None, 16)]              0         
_________________________________________________________________
reshape_1 (Reshape)          (None, 4, 4, 1)           0         
_________________________________________________________________
conv2d_transpose_4 (Conv2DTr (None, 6, 6, 16)          160       
_________________________________________________________________
conv2d_transpose_5 (Conv2DTr (None, 8, 8, 32)          4640      
_________________________________________________________________
up_sampling2d_1 (UpSampling2 (None, 24, 24, 32)        0         
_________________________________________________________________
conv2d_transpose_6 (Conv2DTr (None, 26, 26, 16)        4624      
_________________________________________________________________
conv2d_transpose_7 (Conv2DTr (None, 28, 28, 1)         145       
=================================================================
Total params: 9,569
Trainable params: 9,569
Non-trainable params: 0
_________________________________________________________________
Model: "autoencoder"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
img (InputLayer)             [(None, 28, 28, 1)]       0         
_________________________________________________________________
encoder (Model)              (None, 16)                18672     
_________________________________________________________________
decoder (Model)              (None, 28, 28, 1)         9569      
=================================================================
Total params: 28,241
Trainable params: 28,241
Non-trainable params: 0
_________________________________________________________________

如您所见,模型可以嵌套:模型可以包含子模型(因为模型就像一层一样)。模型嵌套的一个常见用例是集合 。例如,以下是将一组模型整合为一个平均其预测的模型的方法:

 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)
 

处理复杂的图拓扑

具有多个输入和输出的模型

功能性API使操作多个输入和输出变得容易。这不能使用Sequential API处理。

例如,如果您要构建一个按优先级对自定义发行故障单排序并将其路由到正确部门的系统,那么该模型将具有三个输入:

  • 票证的标题(文本输入),
  • 票证的文本正文(文本输入),以及
  • 用户添加的任何标签(分类输入)

该模型将具有两个输出:

  • 优先级分数介于0和1之间(标量S型输出),以及
  • 应该处理票证的部门(部门范围内的softmax输出)。

您可以使用功能性API几行构建此模型:

 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],
)
 

现在绘制模型:

 keras.utils.plot_model(model, "multi_input_and_output_model.png", show_shapes=True)
 

png

编译此模型时,可以为每个输出分配不同的损耗。您甚至可以为每个损失分配不同的权重-调整它们对总训练损失的贡献。

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

由于输出层具有不同的名称,因此您还可以指定以下损失:

 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,
)
 
Epoch 1/2
40/40 [==============================] - 0s 12ms/step - loss: 1.3372 - priority_loss: 0.7055 - department_loss: 3.1586
Epoch 2/2
40/40 [==============================] - 0s 11ms/step - loss: 1.3285 - priority_loss: 0.6998 - department_loss: 3.1438

<tensorflow.python.keras.callbacks.History at 0x7f6ad8133160>

当使用Dataset对象调用fit时,它应产生一个列表元组,例如([title_data, body_data, tags_data], [priority_targets, dept_targets])或者是一个字典元组,例如({'title': title_data, 'body': body_data, 'tags': tags_data}, {'priority': priority_targets, 'department': dept_targets})

有关详细说明,请参阅培训和评估指南。

玩具ResNet模型

除了具有多个输入和输出的模型外,功能性API还使操作非线性连接拓扑变得容易-这些模型的层没有顺序连接。 Sequential API无法处理的某些操作。

常见的用例是残余连接。让我们为CIFAR10建立一个玩具ResNet模型来演示这一点:

 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()
 
Model: "toy_resnet"
__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
img (InputLayer)                [(None, 32, 32, 3)]  0                                            
__________________________________________________________________________________________________
conv2d_8 (Conv2D)               (None, 30, 30, 32)   896         img[0][0]                        
__________________________________________________________________________________________________
conv2d_9 (Conv2D)               (None, 28, 28, 64)   18496       conv2d_8[0][0]                   
__________________________________________________________________________________________________
max_pooling2d_2 (MaxPooling2D)  (None, 9, 9, 64)     0           conv2d_9[0][0]                   
__________________________________________________________________________________________________
conv2d_10 (Conv2D)              (None, 9, 9, 64)     36928       max_pooling2d_2[0][0]            
__________________________________________________________________________________________________
conv2d_11 (Conv2D)              (None, 9, 9, 64)     36928       conv2d_10[0][0]                  
__________________________________________________________________________________________________
add (Add)                       (None, 9, 9, 64)     0           conv2d_11[0][0]                  
                                                                 max_pooling2d_2[0][0]            
__________________________________________________________________________________________________
conv2d_12 (Conv2D)              (None, 9, 9, 64)     36928       add[0][0]                        
__________________________________________________________________________________________________
conv2d_13 (Conv2D)              (None, 9, 9, 64)     36928       conv2d_12[0][0]                  
__________________________________________________________________________________________________
add_1 (Add)                     (None, 9, 9, 64)     0           conv2d_13[0][0]                  
                                                                 add[0][0]                        
__________________________________________________________________________________________________
conv2d_14 (Conv2D)              (None, 7, 7, 64)     36928       add_1[0][0]                      
__________________________________________________________________________________________________
global_average_pooling2d (Globa (None, 64)           0           conv2d_14[0][0]                  
__________________________________________________________________________________________________
dense_6 (Dense)                 (None, 256)          16640       global_average_pooling2d[0][0]   
__________________________________________________________________________________________________
dropout (Dropout)               (None, 256)          0           dense_6[0][0]                    
__________________________________________________________________________________________________
dense_7 (Dense)                 (None, 10)           2570        dropout[0][0]                    
==================================================================================================
Total params: 223,242
Trainable params: 223,242
Non-trainable params: 0
__________________________________________________________________________________________________

绘制模型:

 keras.utils.plot_model(model, "mini_resnet.png", show_shapes=True)
 

png

现在训练模型:

 (x_train, y_train), (x_test, y_test) = keras.datasets.cifar10.load_data()

x_train = x_train.astype("float32") / 255.0
x_test = x_test.astype("float32") / 255.0
y_train = keras.utils.to_categorical(y_train, 10)
y_test = keras.utils.to_categorical(y_test, 10)

model.compile(
    optimizer=keras.optimizers.RMSprop(1e-3),
    loss=keras.losses.CategoricalCrossentropy(from_logits=True),
    metrics=["acc"],
)
# We restrict the data to the first 1000 samples so as to limit execution time
# on Colab. Try to train on the entire dataset until convergence!
model.fit(x_train[:1000], y_train[:1000], batch_size=64, epochs=1, validation_split=0.2)
 
Downloading data from https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
170500096/170498071 [==============================] - 6s 0us/step
13/13 [==============================] - 0s 25ms/step - loss: 2.3091 - acc: 0.1037 - val_loss: 2.2991 - val_acc: 0.1300

<tensorflow.python.keras.callbacks.History at 0x7f6ac00f31d0>

共享层

功能API的另一个好用法是使用共享层的模型。共享层是在同一模型中多次重复使用的层实例-它们学习与层图中的多个路径相对应的特征。

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

要共享功能性API中的图层,请多次调用同一图层实例。例如,这是一个在两个不同的文本输入之间共享的Embedding层:

 # 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)
 

提取和重用层图中的节点

由于要处理的层图是静态数据结构,因此可以对其进行访问和检查。这就是将功能模型绘制为图像的方式。

这也意味着您可以访问中间层的激活(图中的“节点”)并在其他地方重用它们-这对于诸如特征提取之类的操作非常有用。

让我们来看一个例子。这是一个VGG19模型,其权重在ImageNet上进行了预训练:

 vgg19 = tf.keras.applications.VGG19()
 
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/vgg19/vgg19_weights_tf_dim_ordering_tf_kernels.h5
574717952/574710816 [==============================] - 5s 0us/step

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

 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包含各种内置层,例如:

  • 卷积层: Conv1DConv2DConv3DConv2DTranspose
  • 池化层: MaxPooling1DMaxPooling2DMaxPooling3DAveragePooling1D
  • RNN层: GRULSTMConvLSTM2D
  • BatchNormalizationDropoutEmbedding

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

  • call方法,它指定由图层完成的计算。
  • build方法,它创建图层的权重(这只是一种样式约定,因为您也可以在__init__创建权重)。

要了解有关从头开始创建图层的更多信息,请阅读自定义图层和模型指南。

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

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

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

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


inputs = keras.Input((4,))
outputs = CustomDense(10)(inputs)

model = keras.Model(inputs, outputs)
 

为了在您的自定义层中支持序列化,请定义一个get_config方法,该方法返回该层实例的构造函数参数:

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

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

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

    def get_config(self):
        return {"units": self.units}


inputs = keras.Input((4,))
outputs = CustomDense(10)(inputs)

model = keras.Model(inputs, outputs)
config = model.get_config()

new_model = keras.Model.from_config(config, custom_objects={"CustomDense": CustomDense})
 

(可选)实现类方法from_config(cls, config) ,该方法在给定其config字典的情况下重新创建图层实例时使用。 from_config的默认实现是:

 def from_config(cls, config):
  return cls(**config)
 

何时使用功能性API

什么时候应该使用Keras功能API创建新模型,或者仅直接对Model类进行子类化?通常,功能性API是更高级别的,更容易且更安全的API,并且具有许多子类化模型不支持的功能。

但是,当构建不容易表示为有向无环图的模型时,模型子类提供了更大的灵活性。例如,您无法使用功能性API来实现Tree-RNN,而必须直接继承Model

要深入了解功能API和模型子类之间的区别,请阅读TensorFlow 2.0中的符号和命令式API是什么?

功能性API的优势:

以下属性对于顺序模型(也是数据结构)也适用,但对于子类模型(Python字节码而不是数据结构)则不适用。

不太冗长

没有super(MyClass, self).__init__(...) ,没有def call(self, ...):

比较:

 inputs = keras.Input(shape=(32,))
x = layers.Dense(64, activation='relu')(inputs)
outputs = layers.Dense(10)(x)
mlp = keras.Model(inputs, outputs)
 

带有子类版本:

 class MLP(keras.Model):

  def __init__(self, **kwargs):
    super(MLP, self).__init__(**kwargs)
    self.dense_1 = layers.Dense(64, activation='relu')
    self.dense_2 = layers.Dense(10)

  def call(self, inputs):
    x = self.dense_1(inputs)
    return self.dense_2(x)

# Instantiate the model.
mlp = MLP()
# Necessary to create the model's state.
# The model doesn't have a state until it's called at least once.
_ = mlp(tf.zeros((1, 32)))
 

定义连接图时进行模型验证

在功能性API中,将预先创建输入规范(shape和dtype)(使用Input )。每次调用图层时,该图层都会检查传递给它的规范是否符合其假设,否则将引发有用的错误消息。

这样可以保证可以使用功能性API构建的任何模型都可以运行。除与收敛相关的调试外,所有调试均在模型构建过程中静态发生,而不是在执行时发生。这类似于编译器中的类型检查。

功能模型是可绘制和可检查的

您可以将模型绘制为图形,并且可以轻松访问此图形中的中间节点。例如,要提取并重用中间层的激活(如前面的示例所示):

 features_list = [layer.output for layer in vgg19.layers]
feat_extraction_model = keras.Model(inputs=vgg19.input, outputs=features_list)
 

功能模型可以序列化或克隆

因为功能模型是数据结构而不是一段代码,所以它可以安全地序列化,并且可以保存为单个文件,从而使您可以重新创建完全相同的模型,而无需访问任何原始代码。请参阅序列化和保存指南

要序列化一个子类化的模型,实现者有必要在模型级别指定一个get_config()from_config()方法。

功能性API的弱点:

它不支持动态架构

功能性API将模型视为层的DAG。对于大多数深度学习架构而言,这是正确的,但并非全部。例如,递归网络或Tree RNN不遵循此假设,并且无法在功能性API中实现。

混搭API样式

在功能性API或模型子类之间进行选择不是一个将您限制为一类模型的二元决策。 tf.keras API中的所有模型都可以彼此交互,无论它们是Sequential模型,功能模型还是从头开始编写的子类模型。

您始终可以将功能模型或Sequential模型用作子类化模型或层的一部分:

 units = 32
timesteps = 10
input_dim = 5

# Define a Functional model
inputs = keras.Input((None, units))
x = layers.GlobalAveragePooling1D()(inputs)
outputs = layers.Dense(1)(x)
model = keras.Model(inputs, outputs)


class CustomRNN(layers.Layer):
    def __init__(self):
        super(CustomRNN, self).__init__()
        self.units = units
        self.projection_1 = layers.Dense(units=units, activation="tanh")
        self.projection_2 = layers.Dense(units=units, activation="tanh")
        # Our previously-defined Functional model
        self.classifier = model

    def call(self, inputs):
        outputs = []
        state = tf.zeros(shape=(inputs.shape[0], self.units))
        for t in range(inputs.shape[1]):
            x = inputs[:, t, :]
            h = self.projection_1(x)
            y = h + self.projection_2(state)
            state = y
            outputs.append(y)
        features = tf.stack(outputs, axis=1)
        print(features.shape)
        return self.classifier(features)


rnn_model = CustomRNN()
_ = rnn_model(tf.zeros((1, timesteps, input_dim)))
 
(1, 10, 32)

您可以在功能性API中使用任何子类化的层或模型,只要它实现遵循以下模式之一的call方法即可:

  • call(self, inputs, **kwargs) - inputs是张量或张量的嵌套结构(例如张量列表),而**kwargs是非张量参数(非输入)。
  • call(self, inputs, training=None, **kwargs) -其中training是一个布尔值,指示该层是否应在训练模式和推断模式下运行。
  • call(self, inputs, mask=None, **kwargs) -其中mask是一个布尔值掩码张量(例如,对RNN有用)。
  • call(self, inputs, training=None, mask=None, **kwargs) -当然,您可以同时具有屏蔽和特定于训练的行为。

此外,如果在自定义图层或模型上实现get_config方法,则创建的功能模型仍将可序列化和克隆。

这是在功能模型中使用的从头开始编写的自定义RNN的快速示例:

 units = 32
timesteps = 10
input_dim = 5
batch_size = 16


class CustomRNN(layers.Layer):
    def __init__(self):
        super(CustomRNN, self).__init__()
        self.units = units
        self.projection_1 = layers.Dense(units=units, activation="tanh")
        self.projection_2 = layers.Dense(units=units, activation="tanh")
        self.classifier = layers.Dense(1)

    def call(self, inputs):
        outputs = []
        state = tf.zeros(shape=(inputs.shape[0], self.units))
        for t in range(inputs.shape[1]):
            x = inputs[:, t, :]
            h = self.projection_1(x)
            y = h + self.projection_2(state)
            state = y
            outputs.append(y)
        features = tf.stack(outputs, axis=1)
        return self.classifier(features)


# Note that you specify a static batch size for the inputs with the `batch_shape`
# arg, because the inner computation of `CustomRNN` requires a static batch size
# (when you create the `state` zeros tensor).
inputs = keras.Input(batch_shape=(batch_size, timesteps, input_dim))
x = layers.Conv1D(32, 3)(inputs)
outputs = CustomRNN()(x)

model = keras.Model(inputs, outputs)

rnn_model = CustomRNN()
_ = rnn_model(tf.zeros((1, 10, 5)))