函數式(Functional)模型
我們起初將Functional一詞譯作泛型,想要表達該類模型能夠表達任意張量映射的含義,但表達的不是很精確,在Keras2里我們將這個詞改移為“函數式”,函數式模型稱作Functional,但它的類名是Model,因此有時候也用Model來代表函數式模型。
Keras函數式模型接口是用戶定義多輸出模型、非循環有向模型或具有共享層的模型等復雜模型的途徑。一句話,只要你的模型不是類似VGG一樣一條路走到黑的模型,或者你的模型需要多於一個的輸出,那么你總應該選擇函數式模型。函數式模型是最廣泛的一類模型,序貫(Sequential)模型只是它的一種特殊情況。
第一個模型:全連接網絡
Sequential當然是實現全連接網絡的最好方式,但我們從簡單的全連接網絡開始,有助於我們學習這部分的內容。在開始前,有幾個概念需要澄清:
- 層對象接受張量作為參數,返回一個張量。
- 輸入是張量,輸出也是張量的一個框架就是一個模型,通過Model定義。
- 這樣的模型可以向Keras的Sequential一樣被訓練
from keras.layer import Input,Dense from keras.models import Model #This returns a tensor inputs = Input(shape=(784,)) #a Layer instance is callable on a tensor , and returns a tensor x = Dense(64 , activation='relu')(inputs) x = Dense(64,activation='relu')(x) predictions = Dense(10,activation='softmax')(x) #This creates a model that includes #the Input layer and three Dense layers model = Model(inputs=inputs,outputs=predictions) model.compile(optimizer='rmsprop',loss='categorical_crossentropy',metrics=['accuracy']) model.fit(data,labels) #starts training
所有的模型都是可調用的,就像層一樣
利用函數式模型的接口,我們可以很容易的重用已經訓練好的模型:你可以把模型當做一個層一樣,通過提供一個tensor來調用它。注意當你調用一個模型時,你不僅僅重用了它的結構,也重用了它的權重。
x = Input(shape=(784,)) #This works, and returns the 10-way softmax we defined above y = model(x)
這種方式可以允許你快速的創建能處理序列信號的模型,你可以很快將一個圖像分類的模型變為一個對視屏分類的模型,只需要一行代碼:
from keras.layers import TimeDistributed #Input tensor for sequences of 20 timesteps #each containing a 784-dimensional vector input_sequences = Input(shape=(20,784)) #This applies our previous model to every timestep in the input sequences. #the output of the previous model was a 10-way softmax #so the output of the layer below will be a sequence of 20 vectors of size 10. processed_sequences = TimeDistributed(model)(input_sequences)
多輸入和多輸出模型
使用函數式模型的一個典型場景是搭建多輸入、多輸出的模型。
考慮這樣一個模型。我們希望預測Twitter上一條新聞會被轉發和點贊多少次。模型的主要輸入是新聞本身,也就是一個詞語的序列。但我們還可以擁有額外的輸入,如新聞發布的日期等。這個模型的損失函數將有兩部分組成,輔助的損失函數評估僅僅基於新聞本身做出預測的情況,主損失函數評估基於新聞的額外信息的預測的情況,即使來自主損失函數的梯度發生彌散,來自輔導損失函數的信息也能夠訓練Embedding和LSTM層。在模型中早點使用主要的損失函數是對於深度網絡的一個良好的正則方法。總而言之,該模型的框圖如下:
讓我們用函數式模型來實現這個框圖
主要的輸入接收新聞本身,即一個整數的序列(每個整數編碼了一個詞)。這些整數位於1到10,000之間(即我們的字典有10,000)個詞。
from keras.layer import Input , Embedding , LSTM , Dense from keras.models import Model #Headline input: meant to receive sequences of 100 integers ,between 1 and 10000. #Note that we can name any layer by passing it a "name" argument. main_input = Input(shape=(100,), dtype='int32' , name='main_input') #This embedding layer will encode the input Sequence #into a sequence of dense 512-dimensional vectors x = Embedding(output_dim=512, input_dim=10000, input_length=100)(main_input) #A LSTM will transform the vector sequence into a single vector, #containing information about the entire sequence lstm_out = LSTM(32)(x)
然后,我們插入一個額外的損失,使得即使在主損失很高的情況下,LSTM和Embedding層也可以平滑的訓練。
auxiliary_output = Dense(1, activation='sigmoid' name='aux_output')(lstm_out)
再然后,我們將LSTM與額外的輸入數據串聯起來組成輸入,送入模型中:
auxiliary_input = Input(shape(5,),name='aux_input') x = keras.layers.concatenate([lstm_out, auxiliary_input]) #We stack a deep densely-connected network on top x = Dense(64,activation='relu')(x) x = Dense(64,activation='relu')(x) x = Dense(64,activation='relu')(x) #And finally we add the main logistic regression layer main_output = Dense(1,activation='sigmoid', name='main_output')(x)
最后,我們定義整個2輸入,2輸出的模型:
model = Model(inputs=[main_input,auxiliary_input], outputs=[main_output, auxiliary_output])
模型的定義完畢,下一步編譯模型。我們給額外的損失賦0.2的權重。我們可以通過關鍵字參數loss_weights或loss來為不同的輸出設置不同的損失函數或權值。這兩個參數均可以為python的列表或字典。這里我們給loss傳遞單個損失函數,這個損失函數會被應用於所有輸出上。
model.compile(optimizer='rmsprop' , loss='binary_crossentropy',loss_weights=[1.,0.2])
編譯完成后,我們通過傳遞訓練數據和目標值訓練該模型:
model.fit([headline_data, additional_data],[labels,labels],epochs=50,batch_size=32)
因為我們的輸入和輸出時被命名過的(在定義時傳遞了“name”參數,我們也可以用下面的方式編譯和訓練模型)
model.compile(optimizer='rmsprop',loss={'main_output': 'binary_crossentropy','aux_output':'binary_crossentropy'},loss_weights={'main_output':1.,'aux_output':0.2}) #And trained it via: model.fit({'main_input': headline_data,'aux_input': addtional_data},{'main_output':labels,'aux_output':labels},epochs=50,batch_size=32)
共享層
另一個使用函數式模型的場合是使用共享層的時候。
考慮微博數據,我們希望建立模型來判別兩條微博是否是來自同一個用戶,這個需求同樣可以用來判斷一個用戶的兩條微博的相似性。
一種實現方式是,我們建立一個模型,它分別將兩條微博的數據映射到兩個特征向量上,然后將該特征向量串聯並加一個logistic回歸曾,輸出她們來自同一個用戶的概率。這種模型的訓練數據是一度對的微博。
因為這個問題是對稱的,所以處理第一條微博的模型當然也能沖用於處理第二條微博,所以這里我們使用一個共享LSTM層來進行映射。
首先,我們將微博的數據轉為(140,256)的矩陣,即每條微博有140個字符,每個單詞的特征由256維的詞向量表示,向量的每個元素為1表示某個字符出現,為0表示不出現,這是一個one-hot編碼。之所以是(140,256),是因為一條微博最多有140個字符,而擴展的ASCII碼表編碼了常見的256個字符。如果考慮中文字符,那一個單詞的詞向量就不止256了,此處的微博應為Twitter
import keras from keras.layers import Input , LSTM,Dense from keras.models import Model tweet_a = Input(shape=(140,256)) tweet_b = Input(shape=(140,256))
若要對不同的輸入共享同一層,就初始化該層一次,然后多次調用它
#This Layer can take as input a matrix #and will return a vector of size 64 shared_lstm = LSTM(64) #When we reuse the same layer instance #multiple times, the weights of the layer #are also being reused #(it is effectively * the same * layer) encode_a = shared_lstm(tweet_a) encode_b = shared_lstm(tweet_b) #we can then concatenate the two vectors: merget_vector = keras.layers.cocatenate([encoded_a,encodd_b], axis=-1) # And add a logistic regression on top predictions = Dense(1, activation='sigmoid')(merged_vector) # We define a trainable model linking the # tweet inputs to the predictions model = Model(inputs=[tweet_a, tweet_b], outputs=predictions) model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['accuracy']) model.fit([data_a, data_b], labels, epochs=10)