life‘s_a_struggle

导航

功能丰富的API -- The Functional API

功能丰富的API

搭建

1 import numpy as np
2 import tensorflow as tf
3 from tensorflow import keras
4 from tensorflow.keras import layers

介绍

Keras functional API 比 tf.keras.Sequential API 自由度更高。这个API可以解决非线性特殊模型结构共享层甚至多输入多输出

深度学习的主要思想是层的有向无环图(DGA)。所以函数式方法就这大家DGA的一种途径。这点事序贯模型做不到的。

例如,下面的模型:

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

这是一个基础的三层图。用函数式方法构建这个模型,从创建输入节点开始:

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

数据的尺寸被设置为一个784维的向量。由于指定了每个样本的尺寸,所以批大小通常省略。

如果,举个例子,你有一张图片尺寸是(32,32,3),你将这么定义:

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

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

inputs.shape
TensorShape([None, 784])
inputs.dtype

 

tf.float32

通过调用这个输入对象上的一个层,你可以在层图中创建一个新节点:

dense = layers.Dense(64, activation="relu")
x = dense(inputs)
#亦或者

dense = layers.Dense(64, activation="relu")(inputs)
#之所以称之为函数式,也是因为他的形式像f(x)

 

“图层调用”动作就像从“输入”到你创建的这个图层绘制一个箭头。您将输入“传递”到密集层(dense layer),然后得到x作为输出。

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

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: "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_with_shape_info.png", show_shapes=True)

 

 

这个数字和代码几乎是相同的。在代码版本中,连接箭头被调用操作替换。

对于深度学习模型来说,“层图”是一种直观的心理图像,而功能性API是一种创建与此密切相关的模型的方式。

Training, evaluation, and inference 训练、评估、判断

 对于使用功能API构建的模型,训练、评估和推理的工作方式与序列模型完全相同。

Model类提供了一个内置的训练循环(fit()方法)和一个内置的评估循环(evaluate()方法)。请注意,您可以轻松地自定义这些循环来实现监督学习之外的训练例程(例如GANs)。

在这里,加载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 2ms/step - loss: 0.5648 - accuracy: 0.8473 - val_loss: 0.1793 - val_accuracy: 0.9474
Epoch 2/2
750/750 [==============================] - 1s 1ms/step - loss: 0.1686 - accuracy: 0.9506 - val_loss: 0.1398 - val_accuracy: 0.9576
313/313 - 0s - loss: 0.1401 - accuracy: 0.9580
Test loss: 0.14005452394485474
Test accuracy: 0.9580000042915344

 For further reading, see the training and evaluation guide.

保存并且序列化

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

这个保存的文件包括:-模型架构-模型权重值(在训练期间学到的)-模型训练配置,如果有(通过编译)-优化器和它的状态,如果有(重新开始训练)

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

 

 INFO:tensorflow:Assets written to: path_to_my_model/assets

 For details, read the model serialization & saving guide.

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

在函数式API中,通过在层图中指定它们的输入和输出来创建模型。这意味着一个图层图可以用来生成多个模型。

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

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层的逆向2是UpSampling2D层。

 所有的模型都是可调用的,就像层一样

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

下面是一个不同的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()

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 (Functional)         (None, 16)                18672     
_________________________________________________________________
decoder (Functional)         (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使操作多个输入和输出变得很容易。这是序贯模型做不到的。

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

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

这个模型将有两个输出:

  • 优先级得分介于0和1之间(标量sigmoid输出)
  • 以及应该处理票据的部门(部门集上的softmax输出)。
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)

 

 

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

 

 

 

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 [==============================] - 3s 21ms/step - loss: 1.2713 - priority_loss: 0.7000 - department_loss: 2.8567
Epoch 2/2
40/40 [==============================] - 1s 22ms/step - loss: 1.2947 - priority_loss: 0.6990 - department_loss: 2.9786

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

当使用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})。

 For more detailed explanation, refer to the training and evaluation guide.

一个玩具ResNet模型

除了具有多个输入和输出的模型之外,函数式API还使操作非线性连接拓扑变得容易——这些模型的层不是顺序连接的,而顺序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
__________________________________________________________________________________________________

流程图:

 

 

 

 现在训练模型:

(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)
13/13 [==============================] - 2s 103ms/step - loss: 2.3218 - acc: 0.1291 - val_loss: 2.3014 - val_acc: 0.1150

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

共享层

函数式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)

提取并重用层图中的节点

因为您正在操作的层图是一个静态数据结构,所以可以访问和检查它。这就是如何将功能模型画成图像的方法。

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

让我们看一个例子。这是一个在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)

This comes in handy for tasks like neural style transfer, among other things.

使用自定义层扩展API

tf.Keras包含了广泛的内置层,例如:

  • Convolutional layers: Conv1DConv2DConv3DConv2DTranspose

  • Pooling layers: MaxPooling1DMaxPooling2DMaxPooling3DAveragePooling1D

  • RNN layers: GRULSTMConvLSTM2D

  • BatchNormalizationDropoutEmbedding, etc.

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

  • 调用方法,该方法指定由层完成的计算。
  • Build方法,它创建该层的权重(这只是一个样式约定,因为你也可以在__init__中创建权重)。

要了解更多关于从头创建层的知识,请阅读自定义层和模型指南。custom layers and models

以下是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),当给定配置字典重新创建一个层实例时使用该方法。from_config的默认实现是:

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

 

何时使用函数式API

您应该使用Keras函数式API来创建一个新模型,还是只是直接子类化model类?一般来说,函数式API更高级、更简单、更安全,并且具有许多子类模型不支持的特性。

然而,当构建不容易表示为层的有向无环图的模型时,模型子类化提供了更大的灵活性。例如,您不能使用函数式API实现Tree-RNN,并且必须直接子类化Model。

要深入了解函数式API和模型子类之间的区别,请阅读TensorFlow 2.0中的什么是符号和命令式API ?What are Symbolic and Imperative APIs in TensorFlow 2.0?

函数式API的优点:

下面的属性对于顺序模型(也是数据结构)也是正确的,但是对于子类模型(是Python字节码,而不是数据结构)不是正确的。

Less verbose

There is no super(MyClass, self).__init__(...), no def call(self, ...):, etc.

比较:

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)

 

函数式模型可以序列化或克隆

因为功能模型是一种数据结构,而不是一段代码,所以它是安全的可序列化的,可以保存为单个文件,允许您在没有访问任何原始代码的情况下重新创建完全相同的模型。See the serialization & saving guide.

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

API的弱点:

它不支持动态架构

功能性API将模型视为层的DAGs。这适用于大多数深度学习体系结构,但并非所有——例如,递归网络或树型rnn不遵循这一假设,不能在功能性API中实现。

API混搭风格

在函数API和模型的子类之间进行选择并不是一个二元决策,它将您限制在一个模型类别中。tf里的所有型号。keras API可以相互交互,无论它们是顺序模型、功能模型还是从头编写的子类模型。

您可以始终使用功能模型或序列模型作为子类模型或层的一部分:

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(self, inputs, **kwargs) -- 其中输入是一个张量或张量的嵌套结构(例如,一组张量),并且**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)))

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

posted on 2021-05-18 12:22  life‘s_a_struggle  阅读(209)  评论(0编辑  收藏  举报