本文是 tf.keras 系列教程的第七篇。介紹了 Keras function API 可以實現的功能。
Keras function API 是一種創建模型的方法,該模型比 tf.keras.Sequential
API 更靈活。function API 可以處理具有非線性拓撲的模型,具有共享層的模型以及具有多個輸入或輸出的模型。深度學習模型通常是層的有向無環圖(Directed Acyclic Graph, DAG)的主要思想。因此,功能性API是一種構建層圖的方法。
文章目錄
代碼環境:
python version: 3.7.6
tensorflow version: 2.1.0
導入必要的包:
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
tf.keras.backend.clear_session() # 重置keras的狀態
1. 概要
考慮以下模型:
(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(function API)構建此模型,需要先創建一個輸入節點:
inputs = keras.Input(shape=(784,))
數據的形狀設置爲784維向量。shape 只需指定每個樣本的shape即可,不需要考慮batch_size的大小。如果輸入的是shape爲(32, 32, 3)的圖像,則可以使用:
img_inputs = keras.Input(shape=(32, 32, 3))
inputs 返回輸入的shape和數據類型dtype:
inputs.shape # TensorShape([None, 784])
inputs.dtype # tf.float32
通過在 inputs
對象上調用一個layer,可以在圖層圖中創建一個新節點:
dense = layers.Dense(64, activation='relu')
x = dense(inputs)
“圖層調用”操作就像從“輸入”到創建的該圖層繪製箭頭。將輸入“傳遞”到Dense層,然後得到x。在層圖中添加更多層:
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: "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',dpi=150)
繪圖時,通過設置可選參數,顯示模型的輸入輸出尺寸以及每層的名稱:
keras.utils.plot_model(model, 'my_first_model_with_shape_info.png', show_layer_names=True, show_shapes=True, dpi=150)
該圖和代碼幾乎相同。在代碼版本中,連接箭頭由調用操作代替。
2. 模型訓練,評估和推斷
使用功能性API構建的模型,其訓練,評估和推斷的工作方式與順序模型Sequential完全相同。
在這裏,加載MNIST圖像數據,將其整形爲矢量(784),將模型擬合到數據上(同時設置驗證集的比例爲0.2,並監視其準確率指標),然後在測試數據上評估模型:
(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=5,
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])
輸出:
Train on 48000 samples, validate on 12000 samples
Epoch 1/5
48000/48000 [==============================] - 4s 93us/sample - loss: 0.3414 - accuracy: 0.9040 - val_loss: 0.1884 - val_accuracy: 0.9439
Epoch 2/5
48000/48000 [==============================] - 3s 67us/sample - loss: 0.1616 - accuracy: 0.9513 - val_loss: 0.1532 - val_accuracy: 0.9578
Epoch 3/5
48000/48000 [==============================] - 3s 65us/sample - loss: 0.1197 - accuracy: 0.9648 - val_loss: 0.1253 - val_accuracy: 0.9640
Epoch 4/5
48000/48000 [==============================] - 3s 65us/sample - loss: 0.0947 - accuracy: 0.9715 - val_loss: 0.1185 - val_accuracy: 0.9668
Epoch 5/5
48000/48000 [==============================] - 3s 64us/sample - loss: 0.0801 - accuracy: 0.9762 - val_loss: 0.1061 - val_accuracy: 0.9704
10000/10000 - 1s - loss: 0.0919 - accuracy: 0.9745
Test loss: 0.091879672590876
Test accuracy: 0.9745
3. 保存並序列化模型
使用功能性API構建的模型,保存模型和序列化的方法與順序模型Sequential進行保存的方式相同。保存功能模型的標準方法是調用 model.save()
將整個模型保存爲單個文件。之後可以從該文件重新創建相同的模型。保存的文件包括:
- 模型架構
- 模型權重值(在訓練過程中獲悉)
- 模型訓練配置(compile)
- 優化器及其狀態(如果有的話)(從上次中斷的地方重新開始訓練)
model.save('path_to_my_model')
del model
# 從文件重新創建完全相同的模型:
model = keras.models.load_model('path_to_my_model')
4. 使用同一層圖定義多個模型
在功能性API中,通過在層圖中指定模型的輸入和輸出來創建模型。這意味着可以使用單個層圖來生成多個模型。
下面的示例中,使用相同的圖層來實例化兩個模型:一個encoder將圖像輸入轉換爲16維向量的編碼器模型,以及一個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 = 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層。
5. 模型嵌套調用
模型也可以在任意層的輸入或輸出上調用,就像調用層一樣。通過調用模型,不僅可以重用模型的體系結構,還可以重用其權重。下例是自動編碼器示例的另一種處理方式,該示例創建一個編碼器模型,一個解碼器模型,並將它們鏈接到兩個調用中以獲得自動編碼器模型:
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)
ensemble_model.summary()
輸出:
Model: "model_3"
__________________________________________________________________________________________________
Layer (type) Output Shape Param # Connected to
==================================================================================================
input_6 (InputLayer) [(None, 128)] 0
__________________________________________________________________________________________________
model (Model) (None, 1) 129 input_6[0][0]
__________________________________________________________________________________________________
model_1 (Model) (None, 1) 129 input_6[0][0]
__________________________________________________________________________________________________
model_2 (Model) (None, 1) 129 input_6[0][0]
__________________________________________________________________________________________________
average (Average) (None, 1) 0 model[1][0]
model_1[1][0]
model_2[1][0]
==================================================================================================
Total params: 387
Trainable params: 387
Non-trainable params: 0
6. 複雜的網絡拓撲結構模型
6.1 多輸入、多輸出模型
功能性API使操作多個輸入和輸出變得容易。SequentialAPI 無法處理此問題。
假設要構建一個按優先級對自定義發行票證排序並將其路由到正確部門的系統,則該模型將具有三個輸入:
- 票證的標題(文本輸入)
- 票證的文本正文(文本輸入)
- 用戶添加的標籤(分類輸入)
此模型將具有兩個輸出:
- 0和1之間的優先級分數(標量sigmoid輸出)
- 應處理票證的部門(部門範圍內的softmax輸出)
可以使用功能性API構建此模型:
num_tags = 12 # 問題標籤的數量
num_words = 10000 # 預處理文本數據時獲得的詞彙量
num_departments = 4 # 預測部門數
title_input = keras.Input(shape=(None,), name='title') # 可變長度的整數序列
body_input = keras.Input(shape=(None,), name='body') # 可變長度的整數序列
tags_input = keras.Input(shape=(num_tags,), name='tags') # 大小爲num_tags的二進制向量
# 將標題中的每個單詞嵌入到64維向量中
title_features = layers.Embedding(num_words, 64)(title_input)
# 將文本中的每個單詞嵌入到64維向量中
body_features = layers.Embedding(num_words, 64)(body_input)
# 將標題中嵌入單詞的序列減少爲單個128維向量
title_features = layers.LSTM(128)(title_features)
# 將body內嵌入詞的序列化爲單個32維向量
body_features = layers.LSTM(32)(body_features)
# 通過concatenate(級聯)將所有可用功能合併到單個向量中
# 它以張量列表作爲輸入,除了級聯軸外,它們均具有相同的形狀,並返回單個張量,即所有輸入的級聯。
x = layers.concatenate([title_features, body_features, tags_input])
# 通過特徵使用邏輯迴歸以進行優先級預測
priority_pred = layers.Dense(1, name='priority')(x)
# 通過特徵對部分進行分類
department_pred = layers.Dense(num_departments, name='department')(x)
# 實例化預測優先級和部門的端到端模型
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, dpi=150)
編譯此模型時,可以爲每個輸出分配不同的損失,也可以爲每個損失分配不同的權重,以調整它們對總訓練損失的貢獻。
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.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.2])
通過傳遞輸入和期望輸出的NumPy數組列表來訓練模型:
# 構造虛擬輸入數據
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')
# 構造期望輸出數據
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)
6.2 ResNet 模型(非線性模型)
功能性API 除了具有多個輸入和輸出的模型之外,還使操作非線性連接拓撲變得更加容易,這些模型的層不是順序連接,因此使用SequentialAPI無法處理。
爲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()
繪製網絡結構:
keras.utils.plot_model(model, 'mini_resnet.png', show_shapes=True, dpi=150)
訓練:
(x_train, y_train), (x_test, y_test) = keras.datasets.cifar10.load_data()
x_train = x_train.astype('float32') / 255.
x_test = x_test.astype('float32') / 255.
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'])
model.fit(x_train, y_train,
batch_size=64,
epochs=1,
validation_split=0.2)
7. 共享層
功能API的另一個很好的用途是使用共享層(share layers)的模型。共享層是在同一模型中多次重複使用的層實例。它們學習與層圖中的多個路徑相對應的特徵。
共享層通常用於編碼相似空間中的輸入(例如,兩個具有相似詞彙的不同文本)。它們使得能夠在這些不同的輸入之間共享信息,並且使得有可能在更少的數據上訓練這種模型。如果在輸入之一中看到給定的單詞,這將有利於處理通過共享層的所有輸入。
要在功能API中共享層,即多次調用同一層實例。下例是一個在兩個不同的文本輸入之間共享的嵌入層:
# 嵌入映射到128維向量的1000個單詞
shared_embedding = layers.Embedding(1000, 128)
text_input_a = keras.Input(shape=(None,), dtype='int32')
text_input_b = keras.Input(shape=(None,), dtype='int32')
# 重用同一層來編碼兩個輸入
encoded_input_a = shared_embedding(text_input_a)
encoded_input_b = shared_embedding(text_input_b)
8. 提取和重用層圖中的節點
由於要處理的層圖是靜態數據結構,因此可以對其進行訪問和檢查。這就是將功能模型繪製爲圖像的方式。
這也意味着可以訪問中間層(圖中的“節點”)的激活並將它們在其他地方重用。這對於諸如特徵提取之類的操作非常有用。
下例是一個在ImageNet上預訓練權重的VGG19模型:
vgg19 = tf.keras.applications.VGG19()
通過查詢圖形數據結構獲得模型的中間激活:
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)
9. 使用自定義層擴展API
tf.keras 包括各種內置層,例如:
- 卷積層:Conv1D,Conv2D,Conv3D,Conv2DTranspose
- 池化層:MaxPooling1D,MaxPooling2D,MaxPooling3D,AveragePooling1D
- RNN層:GRU,LSTM,ConvLSTM2D
- BatchNormalization,Dropout,Embedding,等。
但是,如果找不到所需的內容,則可以通過創建自己的圖層來擴展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})
10. function API 的相關問題
1.什麼時候應該使用Keras功能API創建新模型,或者直接將其子Model類化?
通常,功能性API是更高級別的,更容易且更安全的API,並且具有許多子類化模型不支持的功能。
但是,當構建不容易表示爲有向無環圖的模型時,模型子類提供了更大的靈活性。例如,無法使用功能性API來實現Tree-RNN,而必須使用Model直接進行子類化。
2.定義時進行模型驗證
在功能性API中,輸入(shape和dtype)是預先創建的(使用Input)。每次調用圖層時,該圖層都會檢查傳遞給它的規範是否符合其假設,如果不符合,它將引發有用的錯誤消息。
這樣可以保證可以使用功能性API構建的任何模型都可以運行。除了與收斂有關的調試外,所有調試都在模型構建過程中靜態發生,而不是在執行時發生。這類似於編譯器中的類型檢查。
3.功能模型是可繪製和可檢查的
可以將模型繪製爲圖形,並且可以輕鬆訪問此圖形中的中間節點。例如,要提取並重用中間層的激活(如前面的示例所示):
features_list = [layer.output for layer in vgg19.layers]
feat_extraction_model = keras.Model(inputs=vgg19.input, outputs=features_list)
4.功能模型可以序列化或克隆
因爲功能模型是數據結構而不是一段代碼,所以它可以安全地序列化,並且可以保存爲單個文件,從而可以重新創建完全相同的模型,而無需訪問任何原始代碼。
5.功能性API的弱點
(1)不支持動態架構
功能性API將模型視爲層的DAG(有向無環圖)。對於大多數深度學習架構而言,這是正確的,但並非全部適用。例如,遞歸網絡或Tree RNN不遵循此假設,並且無法在功能性API中實現。
(2)需要從頭開始編寫
在編寫高級體系結構時,可能想做超出DAG層範圍的事情,必須使用模型子類在模型實例上編寫多個自定義訓練和推斷方法。