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


Posted in Python onJune 18, 2020

问题描述

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

Python 相关文章推荐
浅谈python socket函数中,send与sendall的区别与使用方法
May 09 Python
使用Eclipse如何开发python脚本
Apr 11 Python
基于python3实现socket文件传输和校验
Jul 28 Python
使用python批量化音乐文件格式转换的实例
Jan 09 Python
基于python实现自动化办公学习笔记(CSV、word、Excel、PPT)
Aug 06 Python
django实现类似触发器的功能
Nov 15 Python
python实现矩阵和array数组之间的转换
Nov 29 Python
使用pandas的box_plot去除异常值
Dec 10 Python
Python+OpenCV+图片旋转并用原底色填充新四角的例子
Dec 12 Python
Python3 把一个列表按指定数目分成多个列表的方式
Dec 25 Python
Python交互环境下打印和输入函数的实例内容
Feb 16 Python
python执行js代码的方法
May 13 Python
Win10下用Anaconda安装TensorFlow(图文教程)
Jun 18 #Python
python中tab键是什么意思
Jun 18 #Python
python中可以声明变量类型吗
Jun 18 #Python
tensorflow之读取jpg图像长和宽实例
Jun 18 #Python
Python叠加矩形框图层2种方法及效果
Jun 18 #Python
python中rb含义理解
Jun 18 #Python
python如何输出反斜杠
Jun 18 #Python
You might like
用PHP制作静态网站的模板框架(四)
2006/10/09 PHP
关于js和php对url编码的处理方法
2014/03/04 PHP
PHP扩展模块Pecl、Pear以及Perl的区别
2014/04/09 PHP
PHP将回调函数作用到给定数组单元的方法
2014/08/19 PHP
实例讲解PHP面向对象之多态
2014/08/20 PHP
又一个PHP实现的冒泡排序算法分享
2014/08/21 PHP
一个完整的php文件上传类实例讲解
2015/10/27 PHP
Win10 下安装配置IIS + MySQL + nginx + php7.1.7
2017/08/04 PHP
php 使用mpdf实现指定字段配置字体样式的方法
2019/07/29 PHP
javascript操作JSON的要领总结
2012/12/09 Javascript
基于KMP算法JavaScript的实现方法分析
2013/05/03 Javascript
jsp js鼠标移动到指定区域显示选项卡离开时隐藏示例
2013/06/14 Javascript
js,jquery滚动/跳转页面到指定位置的实现思路
2014/06/03 Javascript
js星星评分效果
2014/07/24 Javascript
jQuery中document与window以及load与ready 区别详解
2014/12/29 Javascript
jQuery如何解决IE输入框不能输入的问题
2016/10/08 Javascript
微信小程序实现拖拽 image 触摸事件监听的实例
2017/08/17 Javascript
vue+web端仿微信网页版聊天室功能
2019/04/30 Javascript
vue计算属性无法监听到数组内部变化的解决方案
2019/11/06 Javascript
通过javascript实现扫雷游戏代码实例
2020/02/09 Javascript
浅谈使用nodejs搭建web服务器的过程
2020/07/20 NodeJs
python使用标准库根据进程名如何获取进程的pid详解
2017/10/31 Python
python实现Adapter模式实例代码
2018/02/09 Python
Django Rest framework之认证的实现代码
2018/12/17 Python
深入解析Python小白学习【操作列表】
2019/03/23 Python
基于SpringBoot构造器注入循环依赖及解决方式
2020/04/26 Python
餐厅总经理岗位职责
2013/12/31 职场文书
护士见习期自我鉴定
2014/02/08 职场文书
运动会入场词200字
2014/02/15 职场文书
读书演讲主持词
2014/03/18 职场文书
2014购房个人委托书范本
2014/10/12 职场文书
乡镇干部学习心得体会
2016/01/23 职场文书
2019运动会广播加油稿汇总
2019/08/21 职场文书
nginx实现动静分离的方法示例
2021/11/07 Servers
详解JS数组方法
2021/11/20 Javascript
go goth封装第三方认证库示例详解
2022/08/14 Golang