1. 程式人生 > 程式設計 >Keras自定義實現帶masking的meanpooling層方式

Keras自定義實現帶masking的meanpooling層方式

Keras確實是一大神器,程式碼可以寫得非常簡潔,但是最近在寫LSTM和DeepFM的時候,遇到了一個問題:樣本的長度不一樣。對不定長序列的一種預處理方法是,首先對資料進行padding補0,然後引入keras的Masking層,它能自動對0值進行過濾。

問題在於keras的某些層不支援Masking層處理過的輸入資料,例如Flatten、AveragePooling1D等等,而其中meanpooling是我需要的一個運算。例如LSTM對每一個序列的輸出長度都等於該序列的長度,那麼均值運算就只應該除以序列長度,而不是padding後的最長長度。

例如下面這個 3x4 大小的張量,經過補零padding的。我希望做axis=1的meanpooling,則第一行應該是 (10+20)/2,第二行應該是 (10+20+30)/3,第三行應該是 (10+20+30+40)/4。

Keras自定義實現帶masking的meanpooling層方式

Keras如何自定義層

在 Keras2.0 版本中(如果你使用的是舊版本請更新),自定義一個層的方法參考這裡。具體地,你只要實現三個方法即可。

build(input_shape) : 這是你定義層引數的地方。這個方法必須設self.built = True,可以通過呼叫super([Layer],self).build()完成。如果這個層沒有需要訓練的引數,可以不定義。

call(x) : 這裡是編寫層的功能邏輯的地方。你只需要關注傳入call的第一個引數:輸入張量,除非你希望你的層支援masking。

compute_output_shape(input_shape) : 如果你的層更改了輸入張量的形狀,你應該在這裡定義形狀變化的邏輯,這讓Keras能夠自動推斷各層的形狀。

下面是一個簡單的例子:

from keras import backend as K
from keras.engine.topology import Layer
import numpy as np

class MyLayer(Layer):

 def __init__(self,output_dim,**kwargs):
 self.output_dim = output_dim
 super(MyLayer,self).__init__(**kwargs)

 def build(self,input_shape):
 # Create a trainable weight variable for this layer.
 self.kernel = self.add_weight(name='kernel',shape=(input_shape[1],self.output_dim),initializer='uniform',trainable=True)
 super(MyLayer,self).build(input_shape) # Be sure to call this somewhere!

 def call(self,x):
 return K.dot(x,self.kernel)

 def compute_output_shape(self,input_shape):
 return (input_shape[0],self.output_dim)

Keras自定義層如何允許masking

觀察了一些支援masking的層,發現他們對masking的支援體現在兩方面。

在 __init__ 方法中設定 supports_masking=True。

實現一個compute_mask方法,用於將mask傳到下一層。

部分層會在call中呼叫傳入的mask。

自定義實現帶masking的meanpooling

假設輸入是3d的。首先,在__init__方法中設定self.supports_masking = True,然後在call中實現相應的計算。

from keras import backend as K
from keras.engine.topology import Layer
import tensorflow as tf

class MyMeanPool(Layer):
 def __init__(self,axis,**kwargs):
 self.supports_masking = True
 self.axis = axis
 super(MyMeanPool,self).__init__(**kwargs)

 def compute_mask(self,input,input_mask=None):
 # need not to pass the mask to next layers
 return None

 def call(self,x,mask=None):
 if mask is not None:
 mask = K.repeat(mask,x.shape[-1])
 mask = tf.transpose(mask,[0,2,1])
 mask = K.cast(mask,K.floatx())
 x = x * mask
 return K.sum(x,axis=self.axis) / K.sum(mask,axis=self.axis)
 else:
 return K.mean(x,axis=self.axis)

 def compute_output_shape(self,input_shape):
 output_shape = []
 for i in range(len(input_shape)):
 if i!=self.axis:
 output_shape.append(input_shape[i])
 return tuple(output_shape)

使用舉例:

from keras.layers import Input,Masking
from keras.models import Model
from MyMeanPooling import MyMeanPool

data = [[[10,10],0 ],0 ]],[[10,[20,20],[30,30],[40,40]]]

A = Input(shape=[4,2]) # None * 4 * 2
mA = Masking()(A)
out = MyMeanPool(axis=1)(mA)

model = Model(inputs=[A],outputs=[out])

print model.summary()
print model.predict(data)

結果如下,每一行對應一個樣本的結果,例如第一個樣本只有第一個時刻有值,輸出結果是[10. 10. ],是正確的。

[[10. 10.]
 [15. 15.]
 [20. 20.]
 [25. 25.]]

在DeepFM中,每個樣本都是由ID構成的,多值field往往會導致樣本長度不一的情況,例如interest這樣的field,同一個樣本可能在該field中有多項取值,畢竟每個人的興趣點不止一項。採取padding的方法將每個field的特徵補長到最長的長度,則資料尺寸是 [batch_size,max_timestep],經過Embedding為每個樣本的每個特徵ID配一個latent vector,資料尺寸將變為 [batch_size,max_timestep,latent_dim]。

我們希望每一個field的Embedding之後的尺寸為[batch_size,latent_dim],然後進行concat操作橫向拼接,所以這裡就可以使用自定義的MeanPool層了。希望能給大家一個參考,也希望大家多多支援我們。