keras函數式編程(多任務學習,共享網絡層)


https://keras.io/zh/

https://keras.io/zh/getting-started/functional-api-guide/

https://github.com/keras-team/keras/tree/master/examples

Keras 函數式 API 是定義復雜模型(如多輸出模型、有向無環圖,或具有共享層的模型)的方法。  函數式API: https://keras.io/zh/models/model/

  • 網絡層的實例是可調用的,它以張量為參數,並且返回一個張量
  • 輸入和輸出均為張量,它們都可以用來定義一個模型(Model
  • 這樣的模型同 Keras 的 Sequential 模型一樣,都可以被訓練
from keras.layers import Input, Dense
from keras.models import Model

# 這部分返回一個張量
inputs = Input(shape=(784,))

# 層的實例是可調用的,它以張量為參數,並且返回一個張量
x = Dense(64, activation='relu')(inputs)
x = Dense(64, activation='relu')(x)
predictions = Dense(10, activation='softmax')(x)

# 這部分創建了一個包含輸入層和三個全連接層的模型
model = Model(inputs=inputs, outputs=predictions)
model.compile(optimizer='rmsprop',
              loss='categorical_crossentropy',
              metrics=['accuracy'])
model.fit(data, labels)  # 開始訓練

 

函數式API用途一:多輸入多輸出模型(多任務學習MTL)

預測 Twitter 上的一條新聞標題有多少轉發和點贊數。模型的主要輸入將是新聞標題本身,即一系列詞語,同時還添加了其他的輔助輸入來接收額外的數據,例如新聞標題的發布的時間等。 該模型也將通過兩個損失函數進行監督學習。

模型結構:

 => 通過函數式API實現該模型。

主要輸入接收新聞標題本身,即一個整數序列(每個整數編碼一個詞)。 這些整數在 1 到 10,000 之間(10,000 個詞的詞匯表),且序列長度為 100 個詞。

from keras.layers import Input, Embedding, LSTM, Dense
from keras.models import Model

# 標題輸入:接收一個含有 100 個整數的序列,每個整數在 1 到 10000 之間。
# 注意我們可以通過傳遞一個 "name" 參數來命名任何層。
main_input = Input(shape=(100,), dtype='int32', name='main_input')

# Embedding 層將輸入序列編碼為一個稠密向量的序列,
# 每個向量維度為 512。  x為向量序列,100個向量,每個向量維度為512
x = Embedding(output_dim=512, input_dim=10000, input_length=100)(main_input)

# LSTM 層把向量序列轉換成單個向量,輸出32維向量
# 它包含整個序列的上下文信息
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]) # 這里是如何連接的? 行向量與列向量。后續可以輸出測試數據看一下 # 堆疊多個全連接網絡層
x = Dense(64, activation='relu')(x)
x = Dense(64, activation='relu')(x)
x = Dense(64, activation='relu')(x)

# 最后添加主要的邏輯回歸層
main_output = Dense(1, activation='sigmoid', name='main_output')(x)

定義一個具有兩個輸入和兩個輸出的模型:

model = Model(inputs=[main_input, auxiliary_input], outputs=[main_output, auxiliary_output])

編譯模型,並給輔助損失分配一個 0.2 的權重。如果要為不同的輸出指定不同的 loss_weights或 loss,可以使用列表或字典。 在這里,我們給 loss 參數傳遞單個損失函數,這個損失將用於所有的輸出:

model.compile(optimizer='rmsprop',
              loss={'main_output': 'binary_crossentropy', 'aux_output': 'binary_crossentropy'},
              loss_weights={'main_output': 1., 'aux_output': 0.2})

# 然后使用以下方式訓練:
model.fit({'main_input': headline_data, 'aux_input': additional_data},
          {'main_output': labels, 'aux_output': labels},
          epochs=50, batch_size=32)

 

輔助損失函數反向傳播

(多任務學習中)輔助損失函數記為loss_0,主損失函數記為loss_1, 有兩種反向傳播方法:1、用loss=loss_0+loss_1,然后再梯度反傳; 2、分別loss_0和loss_1去反向傳播; 一般情況下會設置兩個loss的比例,作為一個超參數進行調整,上述直接相加就相當於1:1。如果兩個loss分別訓練, 就是給問題兩個優化目標, 如果兩個loss的優化方向有差別, 可能導致優化結果波動,難以收斂。

 

函數式API用途二:共享網絡層

建立一個模型來分辨兩條推文是否來自同一個人(例如,通過推文的相似性來對用戶進行比較)。實現這個目標的一種方法是建立一個模型,將兩條推文編碼成兩個向量,連接向量,然后添加邏輯回歸層;這將輸出兩條推文來自同一作者的概率。模型將接收一對對正負表示的推特數據。

由於這個問題是對稱的,編碼第一條推文的機制應該被完全重用來編碼第二條推文(權重及其他全部)。這里我們使用一個共享的 LSTM 層來編碼推文。 

首先我們將一條推特轉換為一個尺寸為 (280, 256) 的矩陣,即每條推特 280 字符,每個字符為 256 維的 one-hot 編碼向量 (取 256 個常用字符)。

import keras
from keras.layers import Input, LSTM, Dense
from keras.models import Model

tweet_a = Input(shape=(280, 256))
tweet_b = Input(shape=(280, 256))

要在不同的輸入上共享同一個層,只需實例化該層一次,然后根據需要傳入你想要的輸入即可:

# 這一層可以輸入一個矩陣,並返回一個 64 維的向量
shared_lstm = LSTM(64)

# 當我們重用相同的圖層實例多次,圖層的權重也會被重用 (它其實就是同一層)
encoded_a = shared_lstm(tweet_a)
encoded_b = shared_lstm(tweet_b)

# 然后再連接兩個向量:
merged_vector = keras.layers.concatenate([encoded_a, encoded_b], axis=-1)

# 再在上面添加一個邏輯回歸層
predictions = Dense(1, activation='sigmoid')(merged_vector)

# 定義一個連接推特輸入和預測的可訓練的模型
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)

 

層(節點)的概念 : 獲取多輸入layer的輸出

每當你在某個輸入上調用一個層時,都將創建一個新的張量(層的輸出),並且為該層添加一個「節點」,將輸入張量連接到輸出張量。當多次調用同一個圖層時,該圖層將擁有多個節點索引 (0, 1, 2...)。

在之前版本的 Keras 中,可以通過 layer.get_output() 來獲得層實例的輸出張量,或者通過 layer.output_shape 來獲取其輸出形狀。現在你依然可以這么做(除了 get_output() 已經被 output 屬性替代)。但是如果一個層與多個輸入連接呢?

只要一個層僅僅連接到一個輸入,就不會有困惑,.output 會返回層的唯一輸出:

a = Input(shape=(280, 256))

lstm = LSTM(32)
encoded_a = lstm(a)

assert lstm.output == encoded_a

但是如果該層有多個輸入,那就會出現問題:

a = Input(shape=(280, 256))
b = Input(shape=(280, 256))

lstm = LSTM(32)
encoded_a = lstm(a)
encoded_b = lstm(b)

lstm.output
>> AttributeError: Layer lstm_1 has multiple inbound nodes,
hence the notion of "layer output" is ill-defined.
Use `get_output_at(node_index)` instead.

通過下面的方法可以解決:

assert lstm.get_output_at(0) == encoded_a
assert lstm.get_output_at(1) == encoded_b

input_shape 和 output_shape 這兩個屬性也是如此:只要該層只有一個節點,或者只要所有節點具有相同的輸入/輸出尺寸,那么「層輸出/輸入尺寸」的概念就被很好地定義,並且將由 layer.output_shape / layer.input_shape 返回。但是比如說,如果將一個 Conv2D 層先應用於尺寸為 (32,32,3) 的輸入,再應用於尺寸為 (64, 64, 3) 的輸入,那么這個層就會有多個輸入/輸出尺寸,你將不得不通過指定它們所屬節點的索引來獲取它們:

a = Input(shape=(32, 32, 3))
b = Input(shape=(64, 64, 3))

conv = Conv2D(16, (3, 3), padding='same')
conved_a = conv(a)

# 到目前為止只有一個輸入,以下可行:
assert conv.input_shape == (None, 32, 32, 3)

conved_b = conv(b)
# 現在 `.input_shape` 屬性不可行,但是這樣可以:
assert conv.get_input_shape_at(0) == (None, 32, 32, 3)
assert conv.get_input_shape_at(1) == (None, 64, 64, 3)

 

更多的一些例子

inception模型、殘差網絡、共享視覺模型、視覺問答模型等

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM