問題描述
我在用Keras的Embedding層做nlp相關的實現時,發現了一個神奇的問題,先上代碼:
1
2
3
4
5
6
7
8
|
a = Input (shape = [ 15 ]) # None*15 b = Input (shape = [ 30 ]) # None*30 emb_a = Embedding( 10 , 5 , mask_zero = True )(a) # None*15*5 emb_b = Embedding( 20 , 5 , mask_zero = False )(b) # None*30*5 cat = Concatenate(axis = 1 )([emb_a, emb_b]) # None*45*5 model = Model(inputs = [a, b], outputs = [cat]) print model.summary() |
我有兩個Embedding層,當其中一個設置mask_zero=True,而另一個為False時,會報如下錯誤。
ValueError: Dimension 0 in both shapes must be equal, but are 1 and 5.
Shapes are [1] and [5]. for 'concatenate_1/concat_1' (op: 'ConcatV2')
with input shapes: [?,15,1], [?,30,5], [] and with computed input tensors: input[2] = <1>.
什么意思呢?是說在concatenate時發現兩個矩陣的第三維一個是1,一個是5,這就很神奇了,加了個mask_zero=True還會改變矩陣維度的嗎?
尋找問題根源
為了檢驗Embedding層輸出的正確性,我把代碼改成了:
1
2
3
|
a = Input (shape = [ 30 ]) ... cat = Concatenate(axis = 2 )([emb_a, emb_b]) |
運行成功了,并且summary顯示兩個Embedding層輸出矩陣的第三維都是5。
這就很奇怪了,明明沒有改變維度,為什么會報那樣的錯誤?
然后我仔細追溯了一下前面的各項error,發現這么一句:
File ".../keras/layers/merge.py", line 374, in compute_mask
concatenated = K.concatenate(masks, axis=self.axis)
難道是mask的拼接有問題?
于是我修改了/keras/layers/merge.py里的Concatenate類的compute_mask函數(sudo vim就可以修改),在返回前輸出一下masks:
1
2
3
4
5
|
def compute_mask( self , inputs, mask = None ): ... for x in masks: print x return ... |
Tensor("concatenate_1/ExpandDims:0", shape=(?, 30, 1), dtype=bool)
Tensor("concatenate_1/Cast:0", shape=(?, 30, 5), dtype=bool)
發現了!有一個叫concatenate_1/ExpandDims:0的mask它的第三維度是1!
那么這個ExpandDims是什么鬼,觀察一下compute_mask代碼,發現了:
1
2
3
4
5
|
... elif K.ndim(mask_i) < K.ndim(input_i): # Mask is smaller than the input, expand it masks.append(K.expand_dims(mask_i)) ... |
意思是當mask_i的維度比input_i的維度小時,擴展一維,這下知道第三維的1是怎么來的了,那么可以預計compute_mask函數輸入的mask尺寸應該是(None, 30),輸出一下試試:
1
2
3
|
def compute_mask( self , inputs, mask = None ): print mask ... |
[<tf.Tensor 'embedding_1/NotEqual:0' shape=(?, 30) dtype=bool>, None]
果然如此,總結一下問題的所在:
Embedding層的輸出會比輸入多一維,但Embedding生成的mask的維度與輸入一致。在Concatenate中,沒有mask的Embedding輸出被分配一個與該輸出相同維度的全1的mask,比有mask的Embedding的mask多一維。
提出解決方案
那么,Embedding層的mask到底是如何起作用的呢?是直接在Embedding層中起作用,還是在后續的層中起作用呢?縱觀embeddings.py,mask_zero只在compute_mask函數被用到:
1
2
3
4
5
|
def compute_mask( self , inputs, mask = None ): if not self .mask_zero: return None else : return K.not_equal(inputs, 0 ) |
可見,Embedding層的mask是記錄了Embedding輸入中非零元素的位置,并且傳給后面的支持masking的層,在后面的層里起作用。
一種最簡單的解決方案:
給所有參與Concatenate的Embedding層都設置mask_zero=True。
但是,我想到了一種更靈活的解決方案:
修改embedding.py的compute_mask函數,使得輸出的mask從2維變成3維,且第三維等于output_dim。
1
2
3
4
5
6
7
8
9
10
|
import tensorflow as tf ... def compute_mask( self , inputs, mask = None ): if not self .mask_zero: return None else : mask = K.repeat(K.not_equal(inputs, 0 ), self .output_dim) # [?,output_dim,n] mask = tf.transpose(mask, [ 0 , 2 , 1 ]) # [?,n,output_dim] return mask ... |
驗證解決方案
為了驗證這個改動是否正確,我需要設計幾個小實驗。
實驗一:mask的正確性
我把輸出的mask做了改動,不知道mask是否是正確的。
如下所示,數據是一個帶有3個樣本、樣本長度最長為3的補零padding過的矩陣,我分別讓Embedding層的mask_zero為False和True(為True時input_dim=|va|+2所以是5)。然后分別將Embedding的輸出在axis=1用MySumLayer進行求和。為了方便觀察,我用keras.initializers.ones()把Embedding層的權值全部初始化為1。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
# data data = np.array([[ 1 , 0 , 0 ], [ 1 , 2 , 0 ], [ 1 , 2 , 3 ]]) init = keras.initializers.ones() # network a = Input (shape = [ 3 ]) # None*3 emb1 = Embedding( 4 , 5 , embeddings_initializer = init, mask_zero = False )(a) # None*3*5 emb2 = Embedding( 5 , 5 , embeddings_initializer = init, mask_zero = True )(a) # None*3*5 sum1 = MySumLayer(axis = 1 )(emb1) # None*5 sum2 = MySumLayer(axis = 1 )(emb2) # None*5 model = Model(inputs = [a], outputs = [sum1, sum2]) # prediciton out = model.predict(data) for x in out: print x |
結果如下:
1
2
3
4
5
6
7
|
[[ 3. 3. 3. 3. 3. ] [ 3. 3. 3. 3. 3. ] [ 3. 3. 3. 3. 3. ]] [[ 1. 1. 1. 1. 1. ] [ 2. 2. 2. 2. 2. ] [ 3. 3. 3. 3. 3. ]] |
這個結果是正確的,這里解釋一波:
(1)當mask_True=False時,輸入矩陣中的0也會被認為是正確的index,從而從權值矩陣中抽出第0行作為該index的Embedding,而我的權值都是1,因此所有Embedding都是1,對axis=1求和,實際上是對word length這一軸求和,輸入的word length最長為3,以致于輸出矩陣的元素都是3.
(2)當mask_True=True時,輸入矩陣中的0會被mask掉,而這個mask的操作是體現在MySumLayer中的,將輸入(3, 3, 5)與mask(3, 3, 5)逐元素相乘,再相加。第一個樣本只有一項非零,第二個有兩項,第三個三項,因此MySumLayer輸出的矩陣,各行元素分別是1,2,3.
另外附上MySumLayer的代碼,它的功能是指定一個axis將Tensor進行求和:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
from keras import backend as K from keras.engine.topology import Layer import tensorflow as tf class MySumLayer(Layer): def __init__( self , axis, * * kwargs): self .supports_masking = True self .axis = axis super (MySumLayer, self ).__init__( * * kwargs) def compute_mask( self , input , input_mask = None ): # do not pass the mask to the next layers return None def call( self , x, mask = None ): if mask is not None : # mask (batch, time) mask = K.cast(mask, K.floatx()) if K.ndim(x)! = K.ndim(mask): mask = K.repeat(mask, x.shape[ - 1 ]) mask = tf.transpose(mask, [ 0 , 2 , 1 ]) x = x * mask return K. sum (x, axis = self .axis) else : return K. sum (x, axis = self .axis) def compute_output_shape( self , input_shape): # remove temporal dimension if self .axis = = 1 : return input_shape[ 0 ], input_shape[ 2 ] if self .axis = = 2 : return input_shape[ 0 ], input_shape[ 1 ] |
實驗二:一個mask_zero=True和一個mask_zero=False的Embedding是否能夠拼接
1
2
3
4
5
6
7
8
|
a = Input (shape = [ 3 ]) # None*3 b = Input (shape = [ 4 ]) # None*4 emba = Embedding( 4 , 5 , embeddings_initializer = init, mask_zero = False )(a) # None*3*5 embb = Embedding( 6 , 5 , embeddings_initializer = init, mask_zero = True )(b) # None*4*5 cat = Concatenate(axis = 1 )([emba, embb]) # None*7*5 model = Model(inputs = [a,b], outputs = [cat]) print model.summary() |
沒有報錯!而且輸出的shape正是(None, 7, 5)。
實驗三:兩個mask_zero=True的Embedding拼接是否會報錯
1
2
3
4
5
6
7
8
|
a = Input (shape = [ 3 ]) # None*3 b = Input (shape = [ 4 ]) # None*4 emba = Embedding( 4 , 5 , embeddings_initializer = init, mask_zero = True )(a) # None*3*5 embb = Embedding( 6 , 5 , embeddings_initializer = init, mask_zero = True )(b) # None*4*5 cat = Concatenate(axis = 1 )([emba, embb]) # None*7*5 model = Model(inputs = [a,b], outputs = [cat]) print model.summary() |
沒有報錯!
實驗四:兩個mask_zero=True的Embedding拼接結果是否正確
如下所示,第一個矩陣是一個帶有4個樣本、樣本長度最長為3的補零padding過的矩陣,第二個矩陣是一個帶有4個樣本、樣本長度最長為4的補零padding過的矩陣。為什么這里要求樣本個數一致呢,因為一般來說需要這種拼接操作的都是同一批樣本的不同特征。兩者的Embedding都設置mask_zero=True,在axis=1拼接后,用MySumLayer在axis=1加起來。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
# data data1 = np.array([[ 1 , 0 , 0 ], [ 1 , 2 , 0 ], [ 1 , 2 , 3 ], [ 1 , 2 , 3 ]]) data2 = np.array([[ 1 , 0 , 0 , 0 ], [ 1 , 2 , 0 , 0 ], [ 1 , 2 , 3 , 0 ], [ 1 , 2 , 3 , 4 ]]) init = keras.initializers.ones() # network a = Input (shape = [ 3 ]) # None*3 b = Input (shape = [ 4 ]) # None*4 emba = Embedding( 4 , 5 , embeddings_initializer = init, mask_zero = True )(a) # None*3*5 embb = Embedding( 6 , 5 , embeddings_initializer = init, mask_zero = True )(b) # None*3*5 cat = Concatenate(axis = 1 )([emba, embb]) su = MySumLayer(axis = 1 )(cat) model = Model(inputs = [a,b], outputs = [su]) # prediction print model.predict([data1, data2]) |
輸出如下
1
2
3
4
|
[[ 2. 2. 2. 2. 2. ] [ 4. 4. 4. 4. 4. ] [ 6. 6. 6. 6. 6. ] [ 7. 7. 7. 7. 7. ]] |
這個結果是正確的,解釋一波,其實兩個矩陣橫向拼接起來是下面這樣的,4個樣本分別有2、4、6、7個非零index,而Embedding層權值都是1,所以最終輸出的就是上面這個樣子。
1
2
3
4
5
|
# index 1 0 0 1 0 0 0 1 2 0 1 2 0 0 1 2 3 1 2 3 0 1 2 3 1 2 3 4 |
至此,問題成功解決了。
以上這篇解決Keras中Embedding層masking與Concatenate層不可調和的問題就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支持服務器之家。
原文鏈接:https://blog.csdn.net/songbinxu/article/details/80242211