TensorFlow 篇 | TensorFlow 2.x 基於 Keras 的模型構建

導語」TensorFlow 2.0 版發佈以來,Keras 已經被深度集成於 TensorFlow 框架中,Keras API 也成爲了構建深度網絡模型的第一選擇。使用 Keras 進行模型開發與迭代是每個數據開發人員都須要掌握的一項基本技能,讓咱們一塊兒走進 Keras 的世界一探究竟。

Keras 介紹

  1. Keras 是一個用 Python 編寫的高級神經網絡 API ,它是一個獨立的庫,可以以 TensorFlowCNTK 或者 Theano 做爲後端運行。 TensorFlow1.0 版本開始嘗試與 Keras 作集成,到 2.0 版發佈後更是深度集成了 Keras ,並緊密依賴 tf.keras 做爲其中央高級 API ,官方亦高度推薦使用 keras API 來完成深度模型的構建。
  2. tf.keras 具備三個關鍵優點:python

    1. 對小白用戶友好: Keras 具備簡單且一致的接口,並對用戶產生的錯誤有明確可行的建議去修正。 TensorFlow 2.0 以前的版本,因爲其代碼編寫複雜, API 接口混亂並且各個版本之間兼容性較差,受到普遍的批評,使用 Keras 進行統一化以後,會大大減小開發人員的工做量。
    2. 模塊化且可組合: Keras 模型經過可構建的模塊鏈接在一塊兒,沒有任何限制,模型結構清晰,代碼容易閱讀。
    3. 便於擴展:當編寫新的自定義模塊時,能夠很是方便的基於已有的接口進行擴展。
  3. Keras 使得 TensorFlow 更易於使用,並且不用損失其靈活性和性能。

Keras 模型構建

TensorFlow 2.x 版本中,可使用三種方式來構建 Keras 模型,分別是 Sequential函數式 (Functional) API 以及自定義模型 (Subclassed)。下面就分別介紹下這三種構建方式。後端

Sequential Model

模型結構圖

  1. 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)
  2. 注意對於 Sequential 添加的第一層,能夠包含一個 input_shapeinput_dimbatch_input_shape 參數來指定輸入數據的維度,詳見註釋部分。當指定了 input_shape 等參數後,每次 add 新的層,模型都在持續不斷地建立過程當中,也就說此時模型中各層的權重矩陣已經被初始化了,能夠經過調用 model.weights 來打印模型的權重信息。
  3. 固然,第一層也能夠不包含輸入數據的維度信息,稱之爲延遲建立模式,也就是說此時模型還未真正建立,權重矩陣也不存在。能夠經過調用 model.build(batch_input_shape) 方法手動建立模型。若是未手動建立,那麼只有當調用 fit 或者其餘訓練和評估方法時,模型纔會被建立,權重矩陣纔會被初始化,此時模型會根據輸入的數據來自動推斷其維度信息。
  4. input_shape 中沒有指定 batch 的大小而將其設置爲 None ,是由於在訓練與評估時所採用的 batch 大小可能不一致。若是設爲定值,在訓練或評估時會產生錯誤,而這樣設置後,能夠由模型自動推斷 batch 大小並進行計算,魯棒性更強。
  5. 除了這種順序性的添加 (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)

函數式 API

  1. Keras函數式 API 是比 Sequential 更爲靈活的建立模型的方式。它能夠處理具備非線性拓撲結構的模型、具備共享層 (layers) 的模型以及多輸入輸出的模型。深度學習的模型一般是由層 (layers) 組成的有向無環圖,而函數式 API 就是構建這種圖的一種有效方式。
  2. 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()
  3. 與使用 Sequential 方法構建模型的不一樣之處在於,函數式 API 經過 keras.Input 指定了輸入 inputs 並經過函數調用的方式生成了輸出 outputs ,最後使用 keras.Model 方法構建了整個模型。
  4. 爲何叫函數式 API ,從代碼中能夠看到,能夠像函數調用同樣來使用各類層 (layers),好比定義好了 dense 層,能夠直接將 inputs 做爲 dense 的輸入而獲得一個輸出 x ,而後又將 x 做爲下一層的輸入,最後的函數返回值就是整個模型的輸出。
  5. 函數式 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

  6. 函數式 API 生成的全部模型 (models) 均可以像層 (layers) 同樣被調用。還以自編碼器 (autoencoder) 爲例,如今將它分紅編碼器 (encoder) 和解碼器 (decoder) 兩部分,而後用 encoderdecoder 生成 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()

    代碼中首先生成了兩個模型 encoderdecoder ,而後在生成 autoencoder 模型時,使用了模型函數調用的方式,直接將 autoencoder_inputencoded 分別做爲 encoderdecoder 兩個模型的輸入,並最終獲得 autoencoder 模型。函數

  7. 函數式 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_inputnumeric_input ,通過不一樣的處理層後,兩者經過 Concatenate 結合到一塊兒,最後又通過不一樣的處理層獲得了兩個輸出 binary_predcategorical_pred 。該模型的結構圖以下圖所示:

    多輸入輸出結構圖

  8. 函數式 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_onecategorical_input_two ,它們共享了一個 Embeddingshared_embedding 。該模型的結構圖以下圖所示:

    共享層結構圖

自定義 Keras 層和模型

  1. tf.keras 模塊下包含了許多內置的層 (layers),好比上面咱們用到的 DenseEmbeddingReshape 等。有時咱們會發現這些內置的層並不能知足咱們的需求,此時能夠很方便建立自定義的層來進行擴展。自定義的層經過繼承 tf.keras.Layer 類來實現,且該子類要實現父類的 buildcall 方法。對於內置的 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()
    1. 其中 __init__ 方法用來初始化一些構建該層所需的基本參數, build 方法用來建立該層所需的權重矩陣 w 和誤差矩陣 bcall 方法則是層構建的真正執行者,它將輸入轉爲輸出並返回。其實權重矩陣等的建立也能夠在 __init__ 方法中完成,可是在不少狀況下,咱們不能提早預知輸入數據的維度,須要在實例化層的某個時間點來延遲建立權重矩陣,所以須要在 build 方法中根據輸入數據的維度信息 input_shape 來動態建立權重矩陣。
    2. 以上三個方法的調用順序爲 __init__buildcall ,其中 __init__ 在實例化層時即被調用,而 buildcall 是在肯定了輸入後才被調用。其實 Layer 類中有一個內置方法 __call__ ,在層構建時首先會調用該方法,而在方法內部會調用 buildcall ,而且只有第一次調用 __call__ 時纔會觸發 build ,也就是說 build 中的變量只能被建立一次,而 call 是能夠被調用屢次的,好比訓練,評估時都會被調用。
    3. 若是須要對該層提供序列化的支持,則須要實現一個 get_config 方法來以字典的形式返回該層實例的構造函數參數。在給定 config 的字典後,能夠經過調用該層的類方法 (classmethod) from_config 來從新建立該層, from_config 的默認實現如代碼所示,層的從新建立見 layer recreate 代碼部分,固然也能夠重寫 from_config 類方法來提供新的建立方式。而從新建立新模型 (model) 的代碼與 layer 重建的代碼有所不一樣,它須要藉助於 keras.Model.from_config 方法來完成構建,詳見 model recreate 代碼部分。
  2. 自定義的層是能夠遞歸組合的,也就是說一個層能夠做爲另外一個層的屬性。通常推薦在 __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 中同樣的效果,並且全部子層的權重矩陣都會做爲新層的權重矩陣而存在。

  3. 層 (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)
  4. 層或模型的 call 方法預置有一個 training 參數,它是一個 bool 類型的變量,表示是否處於訓練狀態,它會根據調用的方法來設置值,訓練時爲 True , 評估時爲 False 。由於有一些層像 BatchNormalizationDropout 通常只會用在訓練過程當中,而在評估和預測的過程當中通常是不會使用的,因此能夠經過該參數來控制模型在不一樣狀態下所執行的不一樣計算過程。
  5. 自定義模型與自定義層的實現方式比較類似,不過模型須要繼承自 tf.keras.ModelModel 類的有些 API 是與 Layer 類相同的,好比自定義模型也要實現 __init__buildcall 方法。不過二者也有不一樣之處,首先 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 類,它由兩層組成,分別爲 EncoderDecoder ,而這兩層也是自定義的。經過調用 model.weights 能夠查看該模型全部的權重信息,固然這裏包含子層中的全部權重信息。

  6. 對於自定義的層或模型,在調用其 summary, weights, variables, trainable_weightslosses 等方法或屬性時,要先確保層或模型已經被建立,否則可能報錯或返回爲空,在模型調試時要注意這一點。

配置層 (layer)

tf.keras.layers 模塊下面有不少預約義的層,這些層大多都具備相同的構造函數參數。下面介紹一些經常使用的參數,對於每一個層的獨特參數以及參數的含義,能夠在使用時查詢官方文檔便可,文檔的解釋通常會很詳細。

  1. activation 指激活函數,能夠設置爲字符串如 reluactivations 對象 tf.keras.activations.relu() ,默認狀況下爲 None ,即表示線性關係。
  2. kernel_initializerbias_initializer ,表示層中權重矩陣和誤差矩陣的初始化方式,能夠設置爲字符串如 Glorotuniform
    或者 initializers 對象 tf.keras.initializers.GlorotUniform() ,默認狀況下即爲 Glorotuniform 初始化方式。
  3. kernel_regularizerbias_regularizer ,表示權重矩陣和誤差矩陣的正則化方式,上面介紹過,能夠是 L1L2 正則化,如 tf.keras.regularizers.l2(1e-3) ,默認狀況下是沒有正則項的。

模型建立方式對比

  1. 當構建比較簡單的模型,使用 Sequential 方式固然是最方便快捷的,能夠利用現有的 Layer 完成快速構建、驗證的過程。
  2. 若是模型比較複雜,則最好使用函數式 API 或自定義模型。一般函數式 API 是更高級、更容易以及更安全的實現方式,它還具備一些自定義模型所不具有的特性。可是,當構建不容易表示爲有向無環圖的模型時,自定義模型提供了更大的靈活性。
  3. 函數式 API 能夠提早作模型校驗,由於它經過 Input 方法提早指定了模型的輸入維度,因此當輸入不合規範會更早的發現,有助於咱們調試,而自定義模型開始是沒有指定輸入數據的維度的,它是在運行過程當中根據輸入數據來自行推斷的。
  4. 使用函數式 API 編寫代碼模塊化不強,閱讀起來有些吃力,而經過自定義模型,能夠很是清楚的瞭解該模型的總體結構,易於理解。
  5. 在實際使用中,能夠將函數式 API 和自定義模型結合使用,來知足咱們各式各樣的模型構建需求。

Keras 模型建立技巧

  1. 在編寫模型代碼時,能夠多參考借鑑別人的模型構建方式,有時會有不小的收穫。
  2. 在查找所需的 tensorflow 方法時,若是 keras 模塊下有提供實現則優先使用該方法,若是沒有則找 tf 模塊下的方法便可,這樣可以使得代碼的兼容性以及魯棒性更強。
  3. 在模型建立過程當中,多使用模型和層的內置方法和屬性,如 summaryweights 等,這樣能夠從全局角度來審視模型的結構,有助於發現一些潛在的問題。
  4. 由於 TensorFlow 2.x 模型默認使用 Eager Execution 動態圖機制來運行代碼,因此能夠在代碼的任意位置直接打印 Tensor 來查看其數值以及維度等信息,在模型調試時十分有幫助。

參考資料

  1. Keras Sequential 模型
  2. Keras 函數式 API
  3. Keras 編寫自定義層和模型
相關文章
相關標籤/搜索