keras提供了Sequential
線性的模型,但是有些網絡需要多個輸入,有些網絡有多個輸出,更甚之層與層之間有內部分支,這使得網絡看起來像是層構成的圖,而不是線性的堆疊。有些場景需要多模態的輸入,這些的輸入來源於不同的數據,例如下面的例子
而有些場景是多個輸出,例如給定一部小說,希望將其自動分類(比如愛情、驚悚),同時還希望預測其寫作的日期。當然可以訓練兩個獨立的模型,但由於這些屬性並非是統計無關的,你可以構造一個更好的模型,進行聯合訓練輸出想要的結果。
那么如何該用keras實現這類模型呢?
函數式API可是用於構建具有多個輸入輸出的模型,通常情況下,這種模型會在某一時刻用一個可以組合多個張量層將不同的輸入分支合並、相加和連接等。
多輸入模型
假設一個問答模型有兩個輸入:一個自然語言描述的問題和一個文本片段,后者提供用於回答問題的信息。然后模型要生成一個回答,在最簡單的情況下,這個回答只包含一個詞,可以通過對某個預定義的詞表做softmax得到。
import numpy as np
from keras import layers
from keras.models import Model
from keras import Input
from keras.utils import plot_model
import keras
text_vocabulary_size = 10000
question_vocabulary_size = 10000
answer_vocabulary_size = 500
# 文本輸入是一個長度可變的整數序列,注意name為可選
text_input = Input(shape=(None,), dtype='int32', name='text')
# 將輸入映射為一個64的向量
embedded_text = layers.Embedding(text_vocabulary_size, 64)(text_input)
#利用lstm將向量編碼為單個向量
encoded_text = layers.LSTM(32)(embedded_text)
question_input = Input(shape=(None,),dtype='int32',name='question')
embedded_question = layers.Embedding(question_vocabulary_size, 32)(question_input)
encoded_question = layers.LSTM(16)(embedded_question)
concatenated = layers.concatenate([encoded_text, encoded_question],axis=-1)
# 拼接
answer = layers.Dense(answer_vocabulary_size,activation='softmax')(concatenated)
#在模型實例化時,指定兩個輸入和輸出
model = Model([text_input, question_input], answer)
model.compile(optimizer='rmsprop',loss='categorical_crossentropy', metrics=['acc'])
看一下模型的架構
可以通過plot_model(model,show_shapes=True,to_file='model.png')
內置方法將模型的結構輸出出來
接下來怎么訓練這個雙輸入模型呢?有兩個可用的API:1、向模型中輸入一個由numpy組成的列表 2、輸入一個將輸入名稱映射為numpy數組的字典
num_samples = 1000
max_length = 100
text = np.random.randint(1, text_vocabulary_size, size=(num_samples, max_length))
question = np.random.randint(1, question_vocabulary_size, size=(num_samples, max_length))
answers = np.random.randint(answer_vocabulary_size, size=(num_samples))
answers = keras.utils.to_categorical(answers, answer_vocabulary_size)
#model.fit([text, question], answers, epochs=10, batch_size=128)
model.fit({'text': text, 'question': question}, answers,epochs=10, batch_size=128)
多輸出模型
一個簡單的例子:輸入某人的一些列社交發帖,預測這個人的年齡、性別和收入水平
代碼如下:
vocabulary_size = 50000
num_income_groups = 10
posts_input = Input(shape=(None,), dtype='int32', name='posts')
embedded_posts = layers.Embedding(256, vocabulary_size)(posts_input)
x = layers.Conv1D(128, 5, activation='relu')(embedded_posts)
x = layers.MaxPooling1D(5)(x)
x = layers.Conv1D(256, 5, activation='relu')(x)
x = layers.Conv1D(256, 5, activation='relu')(x)
x = layers.MaxPooling1D(5)(x)
x = layers.Conv1D(256, 5, activation='relu')(x)
x = layers.Conv1D(256, 5, activation='relu')(x)
x = layers.GlobalMaxPooling1D()(x)
x = layers.Dense(128, activation='relu')(x)
age_prediction = layers.Dense(1, name='age')(x)
income_prediction = layers.Dense(num_income_groups,activation='softmax',name='income')(x)
gender_prediction = layers.Dense(1, activation='sigmoid', name='gender')(x)
model = Model(posts_input, [age_prediction, income_prediction, gender_prediction])
model.summary()
plot_model(model,show_shapes=True,to_file='model.png')
#model.compile(optimizer='rmsprop',\
# loss=['mse', 'categorical_crossentropy', 'binary_crossentropy'])
model.compile(optimizer='rmsprop',\
loss=['mse', 'categorical_crossentropy', 'binary_crossentropy'],\
loss_weights=[0.25, 1., 10.])
對於這些多數出(多頭)的模型該怎么訓練呢?預測年齡是一個回歸問題,性別是一個分類問題。為了能夠進行訓練我們必須將這些損失合並為單個標量。在合並不同的損失函數的時候,最簡單的方法就是對所有的函數加權求和。在 Keras 中,你可以在編譯時使用損失組成的列表或 字典來為不同輸出指定不同損失,然后將得到的損失值相加得到一個全局損失,並在訓練過程 中將這個損失最小化。注意,在嚴重不平衡的損失會導致模型單獨針對單個損失最大的任務進行優化,而忽略了其他的任務。為了解決這一問題可以對每個損失指定一個權重。
有向無環圖
利用函數是API不僅能夠方便的構建多輸入或多輸出模型,而且可以實現內部更為復雜的拓撲結構。Keras中的神經網絡可以是層組成的任意有向五環圖(directed acyclic graph,DAG)
代碼如下:
input_x = Input(shape=(250,250,1), dtype='float32', name='X')
branch_a = layers.Conv2D(128, 1,activation='relu', strides=2,padding='same')(input_x)
branch_b = layers.Conv2D(128, 1, activation='relu',padding='same')(input_x)
branch_b = layers.Conv2D(128, 3, activation='relu',strides=2,padding='same')(branch_b)
branch_c = layers.AveragePooling2D(3, strides=2,padding='same')(input_x)
branch_c = layers.Conv2D(128, 3, activation='relu',padding='same')(branch_c)
branch_d = layers.Conv2D(128, 1, activation='relu',padding='same')(input_x)
branch_d = layers.Conv2D(128, 3, activation='relu',padding='same')(branch_d)
branch_d = layers.Conv2D(128, 3, activation='relu', strides=2,padding='same')(branch_d)
output = layers.concatenate([branch_a, branch_b, branch_c, branch_d], axis=-1)
model = Model(input_x, output)
plot_model(model,show_shapes=True,to_file='model.png')
模型的效果圖如下

權重共享
函數是API有一個重要的特性,那就是能夠多次使用一層實例。如果對一個實例的層調用兩次,而不是每次都實例化一個新層,那么每次調用都重復使用這個權重,這樣就可以構建共享分支的模型了。據一個例子求A、B句子的相似度。A對於B等於B對於A的相似度。
lstm = layers.LSTM(32)
left_input = Input(shape=(None, 128))
left_output = lstm(left_input)
right_input = Input(shape=(None, 128))
right_output = lstm(right_input)
merged = layers.concatenate([left_output, right_output], axis=-1)
predictions = layers.Dense(1, activation='sigmoid')(merged)
model = Model([left_input, right_input], predictions)
plot_model(model,show_shapes=True,to_file='model.png')
模型如下: