1. 程式人生 > 實用技巧 >Keras中自定義複雜的loss函式

Keras中自定義複雜的loss函式

Keras中自定義複雜的loss函式

loss = Lambda(lambda x: K.relu(margin+x[0]-x[1]))([wrong_cos,right_cos])

from keras.layers import Input,Embedding,LSTM,Dense,Lambda
from keras.layers.merge import dot
from keras.models import Model
from keras import backend as K

word_size = 128
nb_features 
= 10000 nb_classes = 10 encode_size = 64 margin = 0.1 embedding = Embedding(nb_features,word_size) lstm_encoder = LSTM(encode_size) def encode(input): return lstm_encoder(embedding(input)) q_input = Input(shape=(None,)) a_right = Input(shape=(None,)) a_wrong = Input(shape=(None,)) q_encoded = encode(q_input) a_right_encoded
= encode(a_right) a_wrong_encoded = encode(a_wrong) q_encoded = Dense(encode_size)(q_encoded)
#一般的做法是,直接將問題和答案用同樣的方法encode成向量後直接匹配,但我認為這是不合理的,我認為至少經過某個變換。 right_cos = dot([q_encoded,a_right_encoded], -1, normalize=True) wrong_cos = dot([q_encoded,a_wrong_encoded], -1, normalize=True) loss = Lambda(lambda
x: K.relu(margin+x[0]-x[1
]))([wrong_cos,right_cos]) model_train = Model(inputs=[q_input,a_right,a_wrong], outputs=loss) model_q_encoder = Model(inputs=q_input, outputs=q_encoded) model_a_encoder = Model(inputs=a_right, outputs=a_right_encoded) model_train.compile(optimizer='adam', loss=lambda y_true,y_pred: y_pred) model_q_encoder.compile(optimizer='adam', loss='mse') model_a_encoder.compile(optimizer='adam', loss='mse') model_train.fit([q,a1,a2], y, epochs=10) #其中q,a1,a2分別是問題、正確答案、錯誤答案的batch,y是任意形狀為(len(q),1)的矩陣

這個程式碼包含了Keras中實現最一般模型的思路:把目標當成一個輸入,構成多輸入模型,把loss寫成一個層,作為最後的輸出,搭建模型的時候,就只需要將模型的output定義為loss,而compile的時候,直接將loss設定為y_pred(因為模型的輸出就是loss,所以y_pred就是loss),無視y_true,訓練的時候,y_true隨便扔一個符合形狀的陣列進去就行了。最後我們得到的是問題和答案的編碼器,也就是問題和答案都分別編碼出一個向量來,我們只需要比較coscos,就可以選擇最優答案了。

from keras.layers import Input,Conv2D, MaxPooling2D,Flatten,Dense,Embedding,Lambda
from keras.models import Model
from keras import backend as K

nb_classes = 100
feature_size = 32

input_image = Input(shape=(224,224,3))
cnn = Conv2D(10, (2,2))(input_image)
cnn = MaxPooling2D((2,2))(cnn)
cnn = Flatten()(cnn)
feature = Dense(feature_size, activation='relu')(cnn)
predict = Dense(nb_classes, activation='softmax', name='softmax')(feature) #至此,得到一個常規的softmax分類模型

input_target = Input(shape=(1,))
centers = Embedding(nb_classes, feature_size)(input_target) #Embedding層用來存放中心
l2_loss = Lambda(lambda x: K.sum(K.square(x[0]-x[1][:,0]), 1, keepdims=True), name='l2_loss')([feature,centers])

model_train = Model(inputs=[input_image,input_target], outputs=[predict,l2_loss])
model_train.compile(optimizer='adam', loss=['sparse_categorical_crossentropy',lambda y_true,y_pred: y_pred], loss_weights=[1.,0.2], metrics={'softmax':'accuracy'})

model_predict = Model(inputs=input_image, outputs=predict)
model_predict.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])

model_train.fit([train_images,train_targets], [train_targets,random_y], epochs=10)
#TIPS:這裡用的是sparse交叉熵,這樣我們直接輸入整數的類別編號作為目標,而不用轉成one hot形式。所以Embedding層的輸入,
#跟softmax的目標,都是train_targets,都是類別編號,而random_y是任意形狀為(len(train_images),1)的矩陣。

為什麼不像第二部分的triplet loss模型那樣,將整體的loss寫成一個單一的輸出,然後搭建模型,而是要像目前這樣變成雙輸出呢?

事實上,Keras愛好者鍾情於Keras,其中一個很重要的原因就是它的進度條——能夠實時顯示訓練loss、訓練準確率。如果像第二部分那樣寫,那麼就不能設定metrics引數,那麼訓練過程中就不能顯示準確率了,這不能說是一個小遺憾。而目前這樣寫,我們就依然能夠在訓練過程中看到訓練準確率,還能分別看到交叉熵loss、l2_loss、總的loss分別是多少,非常舒服

Keras的設計是:有多少個輸出就有多少個loss,每個loss是獨立的,不能互動,要互動的話放到keras層裡邊去,或者是用add_loss方法:
https://kexue.fm/archives/6311#%E5%8F%AF%E4%BB%A5%E4%B8%8D%E8%A6%81%E8%BE%93%E5%87%BA

“讓Keras更酷一些!”:隨意的輸出和靈活的歸一化