如何實現(xiàn)TensorFlow 2.x 基于 Keras 的模型構(gòu)建,針對這個問題,這篇文章詳細介紹了相對應(yīng)的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
成都創(chuàng)新互聯(lián)公司成都網(wǎng)站建設(shè)定制開發(fā),是成都網(wǎng)站營銷推廣公司,為水電改造提供網(wǎng)站建設(shè)服務(wù),有成熟的網(wǎng)站定制合作流程,提供網(wǎng)站定制設(shè)計服務(wù):原型圖制作、網(wǎng)站創(chuàng)意設(shè)計、前端HTML5制作、后臺程序開發(fā)等。成都網(wǎng)站設(shè)計熱線:18982081108
Keras
是一個用 Python
編寫的高級神經(jīng)網(wǎng)絡(luò) API
,它是一個獨立的庫,能夠以 TensorFlow
, CNTK
或者 Theano
作為后端運行。 TensorFlow
從 1.0
版本開始嘗試與 Keras
做集成,到 2.0
版發(fā)布后更是深度集成了 Keras
,并緊密依賴 tf.keras
作為其中央高級 API
,官方亦高度推薦使用 keras API
來完成深度模型的構(gòu)建。
tf.keras
具有三個關(guān)鍵優(yōu)勢:
對小白用戶友好: Keras
具有簡單且一致的接口,并對用戶產(chǎn)生的錯誤有明確可行的建議去修正。 TensorFlow 2.0
之前的版本,由于其代碼編寫復(fù)雜, API
接口混亂而且各個版本之間兼容性較差,受到廣泛的批評,使用 Keras
進行統(tǒng)一化之后,會大大減少開發(fā)人員的工作量。
模塊化且可組合: Keras
模型通過可構(gòu)建的模塊連接在一起,沒有任何限制,模型結(jié)構(gòu)清晰,代碼容易閱讀。
便于擴展:當編寫新的自定義模塊時,可以非常方便的基于已有的接口進行擴展。
Keras
使得 TensorFlow
更易于使用,而且不用損失其靈活性和性能。
在 TensorFlow 2.x
版本中,可以使用三種方式來構(gòu)建 Keras
模型,分別是 Sequential
, 函數(shù)式 (Functional) API
以及自定義模型 (Subclassed)
。下面就分別介紹下這三種構(gòu)建方式。
在 Keras
中,通常是將多個層 (layer
) 組裝起來形成一個模型 (model
),最常見的一種方式就是層的堆疊,可以使用 tf.keras.Sequential
來輕松實現(xiàn)。以上圖中所示模型為例,其代碼實現(xiàn)如下:
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
參數(shù)來指定輸入數(shù)據(jù)的維度,詳見注釋部分。當指定了 input_shape
等參數(shù)后,每次 add
新的層,模型都在持續(xù)不斷地創(chuàng)建過程中,也就說此時模型中各層的權(quán)重矩陣已經(jīng)被初始化了,可以通過調(diào)用 model.weights
來打印模型的權(quán)重信息。
當然,第一層也可以不包含輸入數(shù)據(jù)的維度信息,稱之為延遲創(chuàng)建模式,也就是說此時模型還未真正創(chuàng)建,權(quán)重矩陣也不存在??梢酝ㄟ^調(diào)用 model.build(batch_input_shape)
方法手動創(chuàng)建模型。如果未手動創(chuàng)建,那么只有當調(diào)用 fit
或者其他訓(xùn)練和評估方法時,模型才會被創(chuàng)建,權(quán)重矩陣才會被初始化,此時模型會根據(jù)輸入的數(shù)據(jù)來自動推斷其維度信息。
input_shape
中沒有指定 batch
的大小而將其設(shè)置為 None
,是因為在訓(xùn)練與評估時所采用的 batch
大小可能不一致。如果設(shè)為定值,在訓(xùn)練或評估時會產(chǎn)生錯誤,而這樣設(shè)置后,可以由模型自動推斷 batch
大小并進行計算,魯棒性更強。
除了這種順序性的添加 (add
) 外,還可以通過將 layers
以參數(shù)的形式傳遞給 Sequential
來構(gòu)建模型。示例代碼如下所示:
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
的函數(shù)式 API
是比 Sequential
更為靈活的創(chuàng)建模型的方式。它可以處理具有非線性拓撲結(jié)構(gòu)的模型、具有共享層 (layers
) 的模型以及多輸入輸出的模型。深度學習的模型通常是由層 (layers
) 組成的有向無環(huán)圖,而函數(shù)式 API
就是構(gòu)建這種圖的一種有效方式。
以 Sequential Model
一節(jié)中提到的模型為例,使用函數(shù)式 API
實現(xiàn)的方式如下所示:
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
方法構(gòu)建模型的不同之處在于,函數(shù)式 API
通過 keras.Input
指定了輸入 inputs
并通過函數(shù)調(diào)用
的方式生成了輸出 outputs
,最后使用 keras.Model
方法構(gòu)建了整個模型。
為什么叫函數(shù)式 API
,從代碼中可以看到,可以像函數(shù)調(diào)用一樣來使用各種層 (layers
),比如定義好了 dense
層,可以直接將 inputs
作為 dense
的輸入而得到一個輸出 x
,然后又將 x
作為下一層的輸入,最后的函數(shù)返回值就是整個模型的輸出。
函數(shù)式 API
可以將同一個層 (layers
) 作為多個模型的組成部分,示例代碼如下所示:
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
層之前的所有層。
函數(shù)式 API
生成的所有模型 (models
) 都可以像層 (layers
) 一樣被調(diào)用。還以自編碼器 (autoencoder
) 為例,現(xiàn)在將它分成編碼器 (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
模型時,使用了模型函數(shù)調(diào)用
的方式,直接將 autoencoder_input
和 encoded
分別作為 encoder
和 decoder
兩個模型的輸入,并最終得到 autoencoder
模型。
函數(shù)式 API
可以很容易處理多輸入和多輸出的模型,這是 Sequential API
無法實現(xiàn)的。比如我們的模型輸入有一部分是類別型特征
,一般需要經(jīng)過 Embedding
處理,還有一部分是數(shù)值型特征
,一般無需特殊處理,顯然無法將這兩種特征直接合并作為單一輸入共同處理,此時就會用到多輸入。而有時我們希望模型返回多個輸出,以供后續(xù)的計算使用,此時就會用到多輸出模型。多輸入與多輸出模型的示例代碼如下所示:
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
,經(jīng)過不同的處理層后,二者通過 Concatenate
結(jié)合到一起,最后又經(jīng)過不同的處理層得到了兩個輸出 binary_pred
和 categorical_pred
。該模型的結(jié)構(gòu)圖如下圖所示:
函數(shù)式 API
另一個好的用法是模型的層共享,也就是在一個模型中,層被多次重復(fù)使用,它從不同的輸入學習不同的特征。一種常見的共享層是嵌入層 (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
。該模型的結(jié)構(gòu)圖如下圖所示:
tf.keras
模塊下包含了許多內(nèi)置的層 (layers
),比如上面我們用到的 Dense
, Embedding
, Reshape
等。有時我們會發(fā)現(xiàn)這些內(nèi)置的層并不能滿足我們的需求,此時可以很方便創(chuàng)建自定義的層來進行擴展。自定義的層通過繼承 tf.keras.Layer
類來實現(xiàn),且該子類要實現(xiàn)父類的 build
和 call
方法。對于內(nèi)置的 Dense
層,使用自定義層來實現(xiàn)的話,其代碼如下所示:
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__
方法用來初始化一些構(gòu)建該層所需的基本參數(shù), build
方法用來創(chuàng)建該層所需的權(quán)重矩陣 w
和偏差矩陣 b
, call
方法則是層構(gòu)建的真正執(zhí)行者,它將輸入轉(zhuǎn)為輸出并返回。其實權(quán)重矩陣等的創(chuàng)建也可以在 __init__
方法中完成,但是在很多情況下,我們不能提前預(yù)知輸入數(shù)據(jù)的維度,需要在實例化層的某個時間點來延遲創(chuàng)建權(quán)重矩陣,因此需要在 build
方法中根據(jù)輸入數(shù)據(jù)的維度信息 input_shape
來動態(tài)創(chuàng)建權(quán)重矩陣。
以上三個方法的調(diào)用順序為 __init__
, build
, call
,其中 __init__
在實例化層時即被調(diào)用,而 build
和 call
是在確定了輸入后才被調(diào)用。其實 Layer
類中有一個內(nèi)置方法 __call__
,在層構(gòu)建時首先會調(diào)用該方法,而在方法內(nèi)部會調(diào)用 build
和 call
,并且只有第一次調(diào)用 __call__
時才會觸發(fā) build
,也就是說 build
中的變量只能被創(chuàng)建一次,而 call
是可以被調(diào)用多次的,比如訓(xùn)練,評估時都會被調(diào)用。
如果需要對該層提供序列化的支持,則需要實現(xiàn)一個 get_config
方法來以字典的形式返回該層實例的構(gòu)造函數(shù)參數(shù)。在給定 config
的字典后,可以通過調(diào)用該層的類方法 (classmethod) from_config
來重新創(chuàng)建該層, from_config
的默認實現(xiàn)如代碼所示,層的重新創(chuàng)建見 layer recreate
代碼部分,當然也可以重寫 from_config
類方法來提供新的創(chuàng)建方式。而重新創(chuàng)建新模型 (model
) 的代碼與 layer
重建的代碼有所不同,它需要借助于 keras.Model.from_config
方法來完成構(gòu)建,詳見 model recreate
代碼部分。
自定義的層是可以遞歸組合的,也就是說一個層可以作為另一個層的屬性。一般推薦在 __init__
方法中創(chuàng)建子層,因為子層自己的 build
方法會在外層 build
調(diào)用時被觸發(fā)而去執(zhí)行權(quán)重矩陣的構(gòu)建任務(wù),無需在父層中顯示創(chuàng)建。還以 Sequential Model
一節(jié)提到的模型為例作為說明,代碼如下:
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
的構(gòu)建,可以達到與 Sequential Model
中一樣的效果,而且所有子層的權(quán)重矩陣都會作為新層的權(quán)重矩陣而存在。
層 (layers
) 在構(gòu)建的過程中,會去遞歸地收集在此創(chuàng)建過程中生成的損失 (losses
)。在重寫 call
方法時,可通過調(diào)用 add_loss
方法來增加自定義的損失。層的所有損失中也包括其子層的損失,而且它們都可以通過 layer.losses
屬性來進行獲取,該屬性是一個列表 (list
),需要注意的是正則項的損失會自動包含在內(nèi)。示例代碼如下所示:
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
方法預(yù)置有一個 training
參數(shù),它是一個 bool
類型的變量,表示是否處于訓(xùn)練狀態(tài),它會根據(jù)調(diào)用的方法來設(shè)置值,訓(xùn)練時為 True
, 評估時為 False
。因為有一些層像 BatchNormalization
和 Dropout
一般只會用在訓(xùn)練過程中,而在評估和預(yù)測的過程中一般是不會使用的,所以可以通過該參數(shù)來控制模型在不同狀態(tài)下所執(zhí)行的不同計算過程。
自定義模型與自定義層的實現(xiàn)方式比較相似,不過模型需要繼承自 tf.keras.Model
, Model
類的有些 API
是與 Layer
類相同的,比如自定義模型也要實現(xiàn) __init__
, build
和 call
方法。不過兩者也有不同之處,首先 Model
具有訓(xùn)練,評估以及預(yù)測接口,其次它可以通過 model.layers
查看所有內(nèi)置層的信息,另外 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)
上述代碼實現(xiàn)了一個 AutoEncoder Model
類,它由兩層組成,分別為 Encoder
和 Decoder
,而這兩層也是自定義的。通過調(diào)用 model.weights
可以查看該模型所有的權(quán)重信息,當然這里包含子層中的所有權(quán)重信息。
對于自定義的層或模型,在調(diào)用其 summary
, weights
, variables
, trainable_weights
, losses
等方法或?qū)傩詴r,要先確保層或模型已經(jīng)被創(chuàng)建,不然可能報錯或返回為空,在模型調(diào)試時要注意這一點。
在 tf.keras.layers
模塊下面有很多預(yù)定義的層,這些層大多都具有相同的構(gòu)造函數(shù)參數(shù)。下面介紹一些常用的參數(shù),對于每個層的獨特參數(shù)以及參數(shù)的含義,可以在使用時查詢官方文檔即可,文檔的解釋一般會很詳細。
activation
指激活函數(shù),可以設(shè)置為字符串如 relu
或 activations
對象 tf.keras.activations.relu()
,默認情況下為 None
,即表示線性關(guān)系。
kernel_initializer
和 bias_initializer
,表示層中權(quán)重矩陣和偏差矩陣的初始化方式,可以設(shè)置為字符串如 Glorotuniform
或者 initializers
對象 tf.keras.initializers.GlorotUniform()
,默認情況下即為 Glorotuniform
初始化方式。
kernel_regularizer
和 bias_regularizer
,表示權(quán)重矩陣和偏差矩陣的正則化方式,上面介紹過,可以是 L1
或 L2
正則化,如 tf.keras.regularizers.l2(1e-3)
,默認情況下是沒有正則項的。
當構(gòu)建比較簡單的模型,使用 Sequential
方式當然是最方便快捷的,可以利用現(xiàn)有的 Layer
完成快速構(gòu)建、驗證的過程。
如果模型比較復(fù)雜,則最好使用函數(shù)式 API
或自定義模型。通常函數(shù)式 API
是更高級、更容易以及更安全的實現(xiàn)方式,它還具有一些自定義模型所不具備的特性。但是,當構(gòu)建不容易表示為有向無環(huán)圖的模型時,自定義模型提供了更大的靈活性。
函數(shù)式 API
可以提前做模型校驗,因為它通過 Input
方法提前指定了模型的輸入維度,所以當輸入不合規(guī)范會更早的發(fā)現(xiàn),有助于我們調(diào)試,而自定義模型開始是沒有指定輸入數(shù)據(jù)的維度的,它是在運行過程中根據(jù)輸入數(shù)據(jù)來自行推斷的。
使用函數(shù)式 API
編寫代碼模塊化不強,閱讀起來有些吃力,而通過自定義模型,可以非常清楚的了解該模型的整體結(jié)構(gòu),易于理解。
在實際使用中,可以將函數(shù)式 API
和自定義模型結(jié)合使用,來滿足我們各式各樣的模型構(gòu)建需求。
在編寫模型代碼時,可以多參考借鑒別人的模型構(gòu)建方式,有時會有不小的收獲。
在查找所需的 tensorflow
方法時,如果 keras
模塊下有提供實現(xiàn)則優(yōu)先使用該方法,如果沒有則找 tf
模塊下的方法即可,這樣可使得代碼的兼容性以及魯棒性更強。
在模型創(chuàng)建過程中,多使用模型和層的內(nèi)置方法和屬性,如 summary
, weights
等,這樣可以從全局角度來審視模型的結(jié)構(gòu),有助于發(fā)現(xiàn)一些潛在的問題。
因為 TensorFlow 2.x
模型默認使用 Eager Execution
動態(tài)圖機制來運行代碼,所以可以在代碼的任意位置直接打印 Tensor
來查看其數(shù)值以及維度等信息,在模型調(diào)試時十分有幫助。
關(guān)于如何實現(xiàn)TensorFlow 2.x 基于 Keras 的模型構(gòu)建問題的解答就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道了解更多相關(guān)知識。