解决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笔记(叁)继续学习
Oct 24 Python
python中使用xlrd、xlwt操作excel表格详解
Jan 29 Python
Python中的localtime()方法使用详解
May 22 Python
python和bash统计CPU利用率的方法
Jul 10 Python
python安装numpy&amp;安装matplotlib&amp; scipy的教程
Nov 02 Python
Python Pandas找到缺失值的位置方法
Apr 12 Python
Python 利用scrapy爬虫通过短短50行代码下载整站短视频
Oct 29 Python
Pycharm如何打断点的方法步骤
Jun 13 Python
django的auth认证,authenticate和装饰器功能详解
Jul 25 Python
python实现图像外边界跟踪操作
Jul 13 Python
python 5个顶级异步框架推荐
Sep 09 Python
Pycharm 跳转回之前所在页面的操作
Feb 05 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在XP下IIS和Apache2服务器上的安装
2006/09/05 PHP
第十节 抽象方法和抽象类 [10]
2006/10/09 PHP
php购物网站支付paypal使用方法
2010/11/28 PHP
PHP实现二维数组按某列进行排序的方法
2016/11/18 PHP
PHP PDOStatement::nextRowset讲解
2019/02/01 PHP
Laravel 实现关系模型取出需要的字段
2019/10/10 PHP
TP5框架实现一次选择多张图片并预览的方法示例
2020/04/04 PHP
PHP实现计算器小功能
2020/08/28 PHP
在JavaScript中,为什么要尽可能使用局部变量?
2009/04/06 Javascript
Array 重排序方法和操作方法的简单实例
2014/01/24 Javascript
js中用cssText设置css样式的简单方法
2016/09/19 Javascript
Bootstrap 树控件使用经验分享(图文解说)
2017/11/06 Javascript
在element-ui的el-tree组件中用render函数生成el-button的实例代码
2018/11/05 Javascript
JavaScript队列结构Queue实现过程解析
2020/03/07 Javascript
在vue中使用cookie记住用户上次选择的实例(本次例子中为下拉框)
2020/09/11 Javascript
Vue 修改网站图标的方法
2020/12/31 Vue.js
Python调用C语言的方法【基于ctypes模块】
2018/01/22 Python
Python实现统计英文文章词频的方法分析
2019/01/28 Python
Python3实现的简单工资管理系统示例
2019/03/12 Python
python爬虫 urllib模块url编码处理详解
2019/08/20 Python
python中的RSA加密与解密实例解析
2019/11/18 Python
python反爬虫方法的优缺点分析
2020/11/25 Python
使用HTML5拍照示例代码
2013/08/06 HTML / CSS
英国最大的在线快递公司之一:ParcelHero
2019/11/04 全球购物
党员年终民主评议的自我评价
2013/11/05 职场文书
工商管理专业大学生职业生涯规划范文
2014/03/09 职场文书
2014全国两会学习心得体会1000字
2014/03/10 职场文书
2014年会策划方案
2014/05/11 职场文书
2014年教师业务学习材料
2014/05/12 职场文书
大学生优秀班干部事迹材料
2014/05/26 职场文书
党的群众路线整改落实情况汇报
2014/10/28 职场文书
2014年底工作总结
2014/12/15 职场文书
整改通知书
2015/04/20 职场文书
2015小学教师年度考核工作总结
2015/05/12 职场文书
Windows Server 2008 修改远程登录端口以及配置防火墙
2022/04/28 Servers
Win11怎么添加用户?Win11添加用户账户的方法
2022/07/15 数码科技