「 導語」TensorFlow 2.0 版發佈以來,Keras 已經被深度集成於 TensorFlow 框架中,Keras API 也成爲了構建深度網絡模型的第一選擇。使用 Keras 進行模型開發與迭代是每個數據開發人員都須要掌握的一項基本技能,讓咱們一塊兒走進 Keras 的世界一探究竟。
Keras
是一個用 Python
編寫的高級神經網絡 API
,它是一個獨立的庫,可以以 TensorFlow
, CNTK
或者 Theano
做爲後端運行。 TensorFlow
從 1.0
版本開始嘗試與 Keras
作集成,到 2.0
版發佈後更是深度集成了 Keras
,並緊密依賴 tf.keras
做爲其中央高級 API
,官方亦高度推薦使用 keras API
來完成深度模型的構建。tf.keras
具備三個關鍵優點:python
Keras
具備簡單且一致的接口,並對用戶產生的錯誤有明確可行的建議去修正。 TensorFlow 2.0
以前的版本,因爲其代碼編寫複雜, API
接口混亂並且各個版本之間兼容性較差,受到普遍的批評,使用 Keras
進行統一化以後,會大大減小開發人員的工做量。Keras
模型經過可構建的模塊鏈接在一塊兒,沒有任何限制,模型結構清晰,代碼容易閱讀。Keras
使得 TensorFlow
更易於使用,並且不用損失其靈活性和性能。在 TensorFlow 2.x
版本中,可使用三種方式來構建 Keras
模型,分別是 Sequential
, 函數式 (Functional) API
以及自定義模型 (Subclassed)
。下面就分別介紹下這三種構建方式。後端
在 Keras
中,一般是將多個層 (layer
) 組裝起來造成一個模型 (model
),最多見的一種方式就是層的堆疊,可使用 tf.keras.Sequential
來輕鬆實現。以上圖中所示模型爲例,其代碼實現以下:安全
import tensorflow as tf from tensorflow.keras import layers model = tf.keras.Sequential() # Adds a densely-connected layer with 64 units to the model: model.add(layers.Dense(64, activation='relu', input_shape=(16,))) # This is identical to the following: # model.add(layers.Dense(64, activation='relu', input_dim=16)) # model.add(layers.Dense(64, activation='relu', batch_input_shape=(None, 16))) # Add another: model.add(layers.Dense(64, activation='relu')) # Add an output layer with 10 output units: model.add(layers.Dense(10)) # model.build((None, 16)) print(model.weights)
Sequential
添加的第一層,能夠包含一個 input_shape
或 input_dim
或 batch_input_shape
參數來指定輸入數據的維度,詳見註釋部分。當指定了 input_shape
等參數後,每次 add
新的層,模型都在持續不斷地建立過程當中,也就說此時模型中各層的權重矩陣已經被初始化了,能夠經過調用 model.weights
來打印模型的權重信息。model.build(batch_input_shape)
方法手動建立模型。若是未手動建立,那麼只有當調用 fit
或者其餘訓練和評估方法時,模型纔會被建立,權重矩陣纔會被初始化,此時模型會根據輸入的數據來自動推斷其維度信息。input_shape
中沒有指定 batch
的大小而將其設置爲 None
,是由於在訓練與評估時所採用的 batch
大小可能不一致。若是設爲定值,在訓練或評估時會產生錯誤,而這樣設置後,能夠由模型自動推斷 batch
大小並進行計算,魯棒性更強。除了這種順序性的添加 (add
) 外,還能夠經過將 layers
以參數的形式傳遞給 Sequential
來構建模型。示例代碼以下所示:網絡
import tensorflow as tf from tensorflow.keras import layers model = tf.keras.Sequential([ layers.Dense(64, activation='relu', input_shape=(16, )), layers.Dense(64, activation='relu'), layers.Dense(10) ]) # model.build((None, 16)) print(model.weights)
Keras
的函數式 API
是比 Sequential
更爲靈活的建立模型的方式。它能夠處理具備非線性拓撲結構的模型、具備共享層 (layers
) 的模型以及多輸入輸出的模型。深度學習的模型一般是由層 (layers
) 組成的有向無環圖,而函數式 API
就是構建這種圖的一種有效方式。以 Sequential Model
一節中提到的模型爲例,使用函數式 API
實現的方式以下所示:框架
from tensorflow import keras from tensorflow.keras import layers inputs = keras.Input(shape=(16, )) dense = layers.Dense(64, activation='relu') x = dense(inputs) x = layers.Dense(64, activation='relu')(x) outputs = layers.Dense(10)(x) model = keras.Model(inputs=inputs, outputs=outputs, name='model') model.summary()
Sequential
方法構建模型的不一樣之處在於,函數式 API
經過 keras.Input
指定了輸入 inputs
並經過函數調用
的方式生成了輸出 outputs
,最後使用 keras.Model
方法構建了整個模型。函數式 API
,從代碼中能夠看到,能夠像函數調用同樣來使用各類層 (layers
),好比定義好了 dense
層,能夠直接將 inputs
做爲 dense
的輸入而獲得一個輸出 x
,而後又將 x
做爲下一層的輸入,最後的函數返回值就是整個模型的輸出。函數式 API
能夠將同一個層 (layers
) 做爲多個模型的組成部分,示例代碼以下所示:dom
from tensorflow import keras from tensorflow.keras import layers encoder_input = keras.Input(shape=(16, ), name='encoder_input') x = layers.Dense(32, activation='relu')(encoder_input) x = layers.Dense(64, activation='relu')(x) encoder_output = layers.Dense(128, activation='relu')(x) encoder = keras.Model(encoder_input, encoder_output, name='encoder') encoder.summary() x = layers.Dense(64, activation='relu')(encoder_output) x = layers.Dense(32, activation='relu')(x) decoder_output = layers.Dense(16, activation='relu')(x) autoencoder = keras.Model(encoder_input, decoder_output, name='autoencoder') autoencoder.summary()
代碼中包含了兩個模型,一個編碼器 (encoder
) 和一個自編碼器 (autoencoder
),能夠看到兩個模型共用了 encoder_out
層,固然也包括了 encoder_out
層以前的全部層。ide
函數式 API
生成的全部模型 (models
) 均可以像層 (layers
) 同樣被調用。還以自編碼器 (autoencoder
) 爲例,如今將它分紅編碼器 (encoder
) 和解碼器 (decoder
) 兩部分,而後用 encoder
和 decoder
生成 autoencoder
,代碼以下:模塊化
from tensorflow import keras from tensorflow.keras import layers encoder_input = keras.Input(shape=(16, ), name='encoder_input') x = layers.Dense(32, activation='relu')(encoder_input) x = layers.Dense(64, activation='relu')(x) encoder_output = layers.Dense(128, activation='relu')(x) encoder = keras.Model(encoder_input, encoder_output, name='encoder') encoder.summary() decoder_input = keras.Input(shape=(128, ), name='decoder_input') x = layers.Dense(64, activation='relu')(decoder_input) x = layers.Dense(32, activation='relu')(x) decoder_output = layers.Dense(16, activation='relu')(x) decoder = keras.Model(decoder_input, decoder_output, name='decoder') decoder.summary() autoencoder_input = keras.Input(shape=(16), name='autoencoder_input') encoded = encoder(autoencoder_input) autoencoder_output = decoder(encoded) autoencoder = keras.Model( autoencoder_input, autoencoder_output, name='autoencoder', ) autoencoder.summary()
代碼中首先生成了兩個模型 encoder
和 decoder
,而後在生成 autoencoder
模型時,使用了模型函數調用
的方式,直接將 autoencoder_input
和 encoded
分別做爲 encoder
和 decoder
兩個模型的輸入,並最終獲得 autoencoder
模型。函數
函數式 API
能夠很容易處理多輸入和多輸出的模型,這是 Sequential API
沒法實現的。好比咱們的模型輸入有一部分是類別型特徵
,通常須要通過 Embedding
處理,還有一部分是數值型特徵
,通常無需特殊處理,顯然沒法將這兩種特徵直接合並做爲單一輸入共同處理,此時就會用到多輸入。而有時咱們但願模型返回多個輸出,以供後續的計算使用,此時就會用到多輸出模型。多輸入與多輸出模型的示例代碼以下所示:性能
from tensorflow import keras from tensorflow.keras import layers categorical_input = keras.Input(shape=(16, )) numeric_input = keras.Input(shape=(32, )) categorical_features = layers.Embedding( input_dim=100, output_dim=64, input_length=16, )(categorical_input) categorical_features = layers.Reshape([16 * 64])(categorical_features) numeric_features = layers.Dense(64, activation='relu')(numeric_input) x = layers.Concatenate(axis=-1)([categorical_features, numeric_features]) x = layers.Dense(128, activation='relu')(x) binary_pred = layers.Dense(1, activation='sigmoid')(x) categorical_pred = layers.Dense(3, activation='softmax')(x) model = keras.Model( inputs=[categorical_input, numeric_input], outputs=[binary_pred, categorical_pred], ) model.summary()
代碼中有兩個輸入 categorical_input
和 numeric_input
,通過不一樣的處理層後,兩者經過 Concatenate
結合到一塊兒,最後又通過不一樣的處理層獲得了兩個輸出 binary_pred
和 categorical_pred
。該模型的結構圖以下圖所示:
函數式 API
另外一個好的用法是模型的層共享,也就是在一個模型中,層被屢次重複使用,它從不一樣的輸入學習不一樣的特徵。一種常見的共享層是嵌入層 (Embedding
),代碼以下:
from tensorflow import keras from tensorflow.keras import layers categorical_input_one = keras.Input(shape=(16, )) categorical_input_two = keras.Input(shape=(24, )) shared_embedding = layers.Embedding(100, 64) categorical_features_one = shared_embedding(categorical_input_one) categorical_features_two = shared_embedding(categorical_input_two) categorical_features_one = layers.Reshape([16 * 64])(categorical_features_one) categorical_features_two = layers.Reshape([16 * 64])(categorical_features_two) x = layers.Concatenate(axis=-1)([ categorical_features_one, categorical_features_two, ]) x = layers.Dense(128, activation='relu')(x) outputs = layers.Dense(1, activation='sigmoid')(x) model = keras.Model( inputs=[categorical_input_one, categorical_input_two], outputs=outputs, ) model.summary()
代碼中有兩個輸入 categorical_input_one
和 categorical_input_two
,它們共享了一個 Embedding
層 shared_embedding
。該模型的結構圖以下圖所示:
tf.keras
模塊下包含了許多內置的層 (layers
),好比上面咱們用到的 Dense
, Embedding
, Reshape
等。有時咱們會發現這些內置的層並不能知足咱們的需求,此時能夠很方便建立自定義的層來進行擴展。自定義的層經過繼承 tf.keras.Layer
類來實現,且該子類要實現父類的 build
和 call
方法。對於內置的 Dense
層,使用自定義層來實現的話,其代碼以下所示:
import tensorflow as tf from tensorflow import keras from tensorflow.keras import layers class CustomDense(layers.Layer): def __init__(self, units=32): super().__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} @classmethod def from_config(cls, config): return cls(**config) inputs = keras.Input((4, )) layer = CustomDense(10) outputs = layer(inputs) model = keras.Model(inputs, outputs) model.summary() # layer recreate config = layer.get_config() new_layer = CustomDense.from_config(config) new_outputs = new_layer(inputs) print(new_layer.weights) print(new_layer.non_trainable_weights) print(new_layer.trainable_weights) # model recreate config = model.get_config() new_model = keras.Model.from_config( config, custom_objects={'CustomDense': CustomDense}, ) new_model.summary()
__init__
方法用來初始化一些構建該層所需的基本參數, build
方法用來建立該層所需的權重矩陣 w
和誤差矩陣 b
, call
方法則是層構建的真正執行者,它將輸入轉爲輸出並返回。其實權重矩陣等的建立也能夠在 __init__
方法中完成,可是在不少狀況下,咱們不能提早預知輸入數據的維度,須要在實例化層的某個時間點來延遲建立權重矩陣,所以須要在 build
方法中根據輸入數據的維度信息 input_shape
來動態建立權重矩陣。__init__
, build
, call
,其中 __init__
在實例化層時即被調用,而 build
和 call
是在肯定了輸入後才被調用。其實 Layer
類中有一個內置方法 __call__
,在層構建時首先會調用該方法,而在方法內部會調用 build
和 call
,而且只有第一次調用 __call__
時纔會觸發 build
,也就是說 build
中的變量只能被建立一次,而 call
是能夠被調用屢次的,好比訓練,評估時都會被調用。get_config
方法來以字典的形式返回該層實例的構造函數參數。在給定 config
的字典後,能夠經過調用該層的類方法 (classmethod) from_config
來從新建立該層, from_config
的默認實現如代碼所示,層的從新建立見 layer recreate
代碼部分,固然也能夠重寫 from_config
類方法來提供新的建立方式。而從新建立新模型 (model
) 的代碼與 layer
重建的代碼有所不一樣,它須要藉助於 keras.Model.from_config
方法來完成構建,詳見 model recreate
代碼部分。自定義的層是能夠遞歸組合的,也就是說一個層能夠做爲另外一個層的屬性。通常推薦在 __init__
方法中建立子層,由於子層本身的 build
方法會在外層 build
調用時被觸發而去執行權重矩陣的構建任務,無需在父層中顯示建立。還以 Sequential Model
一節提到的模型爲例做爲說明,代碼以下:
from tensorflow import keras from tensorflow.keras import layers class MLP(layers.Layer): def __init__(self): super().__init__() self.dense_1 = layers.Dense(64, activation='relu') self.dense_2 = layers.Dense(64, activation='relu') self.dense_3 = layers.Dense(10) def call(self, inputs): x = self.dense_1(inputs) x = self.dense_2(x) x = self.dense_3(x) return x inputs = keras.Input((16, )) mlp = MLP() y = mlp(inputs) print('weights:', len(mlp.weights)) print('trainable weights:', len(mlp.trainable_weights))
從代碼中能夠看到,咱們將三個 Dense
層做爲 MLP
的子層,而後利用它們來完成 MLP
的構建,能夠達到與 Sequential Model
中同樣的效果,並且全部子層的權重矩陣都會做爲新層的權重矩陣而存在。
層 (layers
) 在構建的過程當中,會去遞歸地收集在此建立過程當中生成的損失 (losses
)。在重寫 call
方法時,可經過調用 add_loss
方法來增長自定義的損失。層的全部損失中也包括其子層的損失,並且它們均可以經過 layer.losses
屬性來進行獲取,該屬性是一個列表 (list
),須要注意的是正則項的損失會自動包含在內。示例代碼以下所示:
import tensorflow as tf from tensorflow import keras from tensorflow.keras import layers class CustomLayer(layers.Layer): def __init__(self, rate=1e-2, l2_rate=1e-3): super().__init__() self.rate = rate self.l2_rate = l2_rate self.dense = layers.Dense( units=32, kernel_regularizer=keras.regularizers.l2(self.l2_rate), ) def call(self, inputs): self.add_loss(self.rate * tf.reduce_sum(inputs)) return self.dense(inputs) inputs = keras.Input((16, )) layer = CustomLayer() x = layer(inputs) print(layer.losses)
call
方法預置有一個 training
參數,它是一個 bool
類型的變量,表示是否處於訓練狀態,它會根據調用的方法來設置值,訓練時爲 True
, 評估時爲 False
。由於有一些層像 BatchNormalization
和 Dropout
通常只會用在訓練過程當中,而在評估和預測的過程當中通常是不會使用的,因此能夠經過該參數來控制模型在不一樣狀態下所執行的不一樣計算過程。自定義模型與自定義層的實現方式比較類似,不過模型須要繼承自 tf.keras.Model
, Model
類的有些 API
是與 Layer
類相同的,好比自定義模型也要實現 __init__
, build
和 call
方法。不過二者也有不一樣之處,首先 Model
具備訓練,評估以及預測接口,其次它能夠經過 model.layers
查看全部內置層的信息,另外 Model
類還提供了模型保存和序列化的接口。以 AutoEncoder
爲例,一個完整的自定義模型的示例代碼以下所示:
from tensorflow import keras from tensorflow.keras import layers class Encoder(layers.Layer): def __init__(self, l2_rate=1e-3): super().__init__() self.l2_rate = l2_rate def build(self, input_shape): self.dense1 = layers.Dense( units=32, activation='relu', kernel_regularizer=keras.regularizers.l2(self.l2_rate), ) self.dense2 = layers.Dense( units=64, activation='relu', kernel_regularizer=keras.regularizers.l2(self.l2_rate), ) self.dense3 = layers.Dense( units=128, activation='relu', kernel_regularizer=keras.regularizers.l2(self.l2_rate), ) def call(self, inputs): x = self.dense1(inputs) x = self.dense2(x) x = self.dense3(x) return x class Decoder(layers.Layer): def __init__(self, l2_rate=1e-3): super().__init__() self.l2_rate = l2_rate def build(self, input_shape): self.dense1 = layers.Dense( units=64, activation='relu', kernel_regularizer=keras.regularizers.l2(self.l2_rate), ) self.dense2 = layers.Dense( units=32, activation='relu', kernel_regularizer=keras.regularizers.l2(self.l2_rate), ) self.dense3 = layers.Dense( units=16, activation='relu', kernel_regularizer=keras.regularizers.l2(self.l2_rate), ) def call(self, inputs): x = self.dense1(inputs) x = self.dense2(x) x = self.dense3(x) return x class AutoEncoder(keras.Model): def __init__(self): super().__init__() self.encoder = Encoder() self.decoder = Decoder() def call(self, inputs): x = self.encoder(inputs) x = self.decoder(x) return x model = AutoEncoder() model.build((None, 16)) model.summary() print(model.layers) print(model.weights)
上述代碼實現了一個 AutoEncoder Model
類,它由兩層組成,分別爲 Encoder
和 Decoder
,而這兩層也是自定義的。經過調用 model.weights
能夠查看該模型全部的權重信息,固然這裏包含子層中的全部權重信息。
summary
, weights
, variables
, trainable_weights
, losses
等方法或屬性時,要先確保層或模型已經被建立,否則可能報錯或返回爲空,在模型調試時要注意這一點。在 tf.keras.layers
模塊下面有不少預約義的層,這些層大多都具備相同的構造函數參數。下面介紹一些經常使用的參數,對於每一個層的獨特參數以及參數的含義,能夠在使用時查詢官方文檔便可,文檔的解釋通常會很詳細。
activation
指激活函數,能夠設置爲字符串如 relu
或 activations
對象 tf.keras.activations.relu()
,默認狀況下爲 None
,即表示線性關係。kernel_initializer
和 bias_initializer
,表示層中權重矩陣和誤差矩陣的初始化方式,能夠設置爲字符串如 Glorotuniform
initializers
對象 tf.keras.initializers.GlorotUniform()
,默認狀況下即爲 Glorotuniform
初始化方式。kernel_regularizer
和 bias_regularizer
,表示權重矩陣和誤差矩陣的正則化方式,上面介紹過,能夠是 L1
或 L2
正則化,如 tf.keras.regularizers.l2(1e-3)
,默認狀況下是沒有正則項的。Sequential
方式固然是最方便快捷的,能夠利用現有的 Layer
完成快速構建、驗證的過程。函數式 API
或自定義模型。一般函數式 API
是更高級、更容易以及更安全的實現方式,它還具備一些自定義模型所不具有的特性。可是,當構建不容易表示爲有向無環圖的模型時,自定義模型提供了更大的靈活性。函數式 API
能夠提早作模型校驗,由於它經過 Input
方法提早指定了模型的輸入維度,因此當輸入不合規範會更早的發現,有助於咱們調試,而自定義模型開始是沒有指定輸入數據的維度的,它是在運行過程當中根據輸入數據來自行推斷的。函數式 API
編寫代碼模塊化不強,閱讀起來有些吃力,而經過自定義模型,能夠很是清楚的瞭解該模型的總體結構,易於理解。函數式 API
和自定義模型結合使用,來知足咱們各式各樣的模型構建需求。tensorflow
方法時,若是 keras
模塊下有提供實現則優先使用該方法,若是沒有則找 tf
模塊下的方法便可,這樣可以使得代碼的兼容性以及魯棒性更強。summary
, weights
等,這樣能夠從全局角度來審視模型的結構,有助於發現一些潛在的問題。TensorFlow 2.x
模型默認使用 Eager Execution
動態圖機制來運行代碼,因此能夠在代碼的任意位置直接打印 Tensor
來查看其數值以及維度等信息,在模型調試時十分有幫助。