解决Keras中Embedding层masking与Concatenate层不可调和的问题

问题描述

我在用Keras的Embedding层做nlp相关的实现时,发现了一个神奇的问题,先上代码:

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层输出的正确性,我把代码改成了:

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:

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代码,发现了:

...
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),输出一下试试:

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函数被用到:

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。

 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。

# 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

结果如下:

[[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进行求和:

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是否能够拼接

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拼接是否会报错

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加起来。

# 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])

输出如下

[[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,所以最终输出的就是上面这个样子。

# 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层不可调和的问题就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Keras—embedding嵌入层的用法详解

    最近在工作中进行了NLP的内容,使用的还是Keras中embedding的词嵌入来做的. Keras中embedding层做一下介绍. 中文文档地址:https://keras.io/zh/layers/embeddings/ 参数如下: 其中参数重点有input_dim,output_dim,非必选参数input_length. 初始化方法参数设置后面会单独总结一下. demo使用预训练(使用百度百科(word2vec)的语料库)参考 embedding使用的demo参考: def creat

  • Keras实现将两个模型连接到一起

    神经网络玩得越久就越会尝试一些网络结构上的大改动. 先说意图 有两个模型:模型A和模型B.模型A的输出可以连接B的输入.将两个小模型连接成一个大模型,A-B,既可以同时训练又可以分离训练. 流行的算法里经常有这么关系的两个模型,对GAN来说,生成器和判别器就是这样子:对VAE来说,编码器和解码器就是这样子:对目标检测网络来说,backbone和整体也是可以拆分的.所以,应用范围还是挺广的. 实现方法 首先说明,我的实现方法不一定是最佳方法.也是实在没有借鉴到比较好的方法,所以才自己手动写了一个.

  • Keras中的多分类损失函数用法categorical_crossentropy

    from keras.utils.np_utils import to_categorical 注意:当使用categorical_crossentropy损失函数时,你的标签应为多类模式,例如如果你有10个类别,每一个样本的标签应该是一个10维的向量,该向量在对应有值的索引位置为1其余为0. 可以使用这个方法进行转换: from keras.utils.np_utils import to_categorical categorical_labels = to_categorical(int_

  • 解决Keras中Embedding层masking与Concatenate层不可调和的问题

    问题描述 我在用Keras的Embedding层做nlp相关的实现时,发现了一个神奇的问题,先上代码: 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(axi

  • 解决Keras 中加入lambda层无法正常载入模型问题

    刚刚解决了这个问题,现在记录下来 问题描述 当使用lambda层加入自定义的函数后,训练没有bug,载入保存模型则显示Nonetype has no attribute 'get' 问题解决方法: 这个问题是由于缺少config信息导致的.lambda层在载入的时候需要一个函数,当使用自定义函数时,模型无法找到这个函数,也就构建不了. m = load_model(path,custom_objects={"reduce_mean":self.reduce_mean,"sli

  • 解决Keras中CNN输入维度报错问题

    想要写分类器对图片进行分类,用到了CNN.然而,在运行程序时,一直报错: ValueError: Negative dimension size caused by subtracting 5 from 1 for 'conv2d_1/convolution' (op: 'Conv2D') with input shapes: [?,1,28,28], [5,5,28,30]. 这部分提到的代码是这样的,这是我的分类器的输入层: model.add(Conv2D(30,(5, 5), input

  • 解决Keras中循环使用K.ctc_decode内存不释放的问题

    如下一段代码,在多次调用了K.ctc_decode时,会发现程序占用的内存会越来越高,执行速度越来越慢. data = generator(...) model = init_model(...) for i in range(NUM): x, y = next(data) _y = model.predict(x) shape = _y.shape input_length = np.ones(shape[0]) * shape[1] ctc_decode = K.ctc_decode(_y,

  • Keras自定义实现带masking的meanpooling层方式

    Keras确实是一大神器,代码可以写得非常简洁,但是最近在写LSTM和DeepFM的时候,遇到了一个问题:样本的长度不一样.对不定长序列的一种预处理方法是,首先对数据进行padding补0,然后引入keras的Masking层,它能自动对0值进行过滤. 问题在于keras的某些层不支持Masking层处理过的输入数据,例如Flatten.AveragePooling1D等等,而其中meanpooling是我需要的一个运算.例如LSTM对每一个序列的输出长度都等于该序列的长度,那么均值运算就只应该

  • Keras实现支持masking的Flatten层代码

    不知道为什么,我总是需要实现某种骚操作,而这种骚操作往往是Keras不支持的.例如,我有一个padding过的矩阵,那么它一定是带masking的,然后我想要把它Flatten,再输入到Dense层.然而Keras的Flatten层不支持masking. Keras原本Flatten的实现 class Flatten(Layer): def __init__(self, **kwargs): super(Flatten, self).__init__(**kwargs) self.input_s

  • 浅谈keras中的Merge层(实现层的相加、相减、相乘实例)

    [题目]keras中的Merge层(实现层的相加.相减.相乘) 详情请参考: Merge层 一.层相加 keras.layers.Add() 添加输入列表的图层. 该层接收一个相同shape列表张量,并返回它们的和,shape不变. Example import keras input1 = keras.layers.Input(shape=(16,)) x1 = keras.layers.Dense(8, activation='relu')(input1) input2 = keras.la

  • 升级keras解决load_weights()中的未定义skip_mismatch关键字问题

    1.问题描述 在用yolov3训练自己的数据集时,尝试加载预训练的权重,在冻结前154层的基础上,利用自己的数据集finetune. 出现如下错误: load_weights(),got an unexpected keyword argument skip_mismatch 2.解决方法 因为keras旧版本没有这一定义,在新的版本中有这一关键字的定义,因此,更新keras版本至2.1.5即可解决. source activate env pip uninstall keras pip ins

  • 解决Keras 自定义层时遇到版本的问题

    在2.2.0版本前, from keras import backend as K from keras.engine.topology import Layer class MyLayer(Layer): def __init__(self, output_dim, **kwargs): self.output_dim = output_dim super(MyLayer, self).__init__(**kwargs) def build(self, input_shape): # 为该层

  • 解决Keras TensorFlow 混编中 trainable=False设置无效问题

    这是最近碰到一个问题,先描述下问题: 首先我有一个训练好的模型(例如vgg16),我要对这个模型进行一些改变,例如添加一层全连接层,用于种种原因,我只能用TensorFlow来进行模型优化,tf的优化器,默认情况下对所有tf.trainable_variables()进行权值更新,问题就出在这,明明将vgg16的模型设置为trainable=False,但是tf的优化器仍然对vgg16做权值更新 以上就是问题描述,经过谷歌百度等等,终于找到了解决办法,下面我们一点一点的来复原整个问题. trai

随机推荐