解决Keras中Embedding层masking与Concatenate层不可调和的问题
作者:蕉叉熵 发布时间:2022-06-02 17:02:16
问题描述
我在用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
至此,问题成功解决了。
来源:https://blog.csdn.net/songbinxu/article/details/80242211
猜你喜欢
- 客户强烈要求使用淘宝的首页商品分类效果,很BT~,没辙就满足一下人家的需求。通过淘宝案例,立即想到了显示/隐藏层的效果,于是在DW中画了几个
- Oracle的Nvl函数nvl( ) 函数从两个表达式返回一个非null 值。语法NVL(eExpression1, eExpression
- 前言最近发现一个问题,在一次爬虫实战中,需要将字典加入列表中,意外的情况出现了!!!下面简单分析一下出现的状况:list = []dic =
- 本文实例讲述了python通过apply使用元祖和列表调用函数的方法。分享给大家供大家参考。具体实现方法如下:def my_fuc(a, b
- 一般情况下,导出超时可能都是以下三种情况:一、sql语句复杂,查询时间过长;二、处理查询后数据逻辑冗余;三、数据量过大导致响应超时。接下来分
- 感谢AKA及作者。Perl 中的正则表达式正则表达式的三种形式正则表达式中的常用模式正则表达式的 8 大原则 &nbs
- 在python中启动和关闭线程:首先导入threadingimport threading然后定义一个方法def serial_read()
- ord是unicode ordinal的缩写,即编号chr是character的缩写,即字符ord和chr是互相对应转换的.但是由于chr局
- 具体代码和说明如下:upload.asp<form action=http://<%= Request.&n
- 一、简单介绍正则表达式是一种小型的、高度专业化的编程语言,并不是python * 有的,是许多编程语言中基础而又重要的一部分。在python中
- 前记在Python3.7后官方库出现了contextvars模块, 它的主要功能就是可以为多线程以及asyncio生态添加上下文功能,即使程
- 从有道词典网页获取某单词的中文解释。import reimport urllibword=raw_input('input a wo
- 昨天晚上睡觉前突然想到的,在此记一笔。传统方式以前我们做文章系统或新闻发布系统的时候,做文章内链(标签)的时候,通常是通过以下方式来实现的:
- 利用Python OpenCV中的 cv.Resize(源,目标,变换方法)就可以实现变换为想要的尺寸了源文件:就不用说了目标:你可以对图像
- 本篇文章主要通过一个简单的例子来实现神经网络。训练数据是随机产生的模拟数据集,解决二分类问题。下面我们首先说一下,训练神经网络的一般过程:1
- 仿google的asp分页代码index.asp(文件1)<%@LANGUAGE="VBSCRIPT" CODEP
- 提示:本文多图,请手机端注意流量。前言利用python做图片识别,识别提取图片中的文字会有很多方法,但是想要简单一点怎么办,那就可以使用te
- 首先,了解下原理。1,提供文本框进行查询内容的输入2,将查询信息提交页面程序处理3,程序页主要作用:接受查询信息,根据此信息调用特定的SQL
- 前言:IPython 是 Python 的原生交互式 shell 的增强版,可以完成许多不同寻常的任务,比如帮助实现并行化计算;主要使用它提
- 今天在看文档的时候,发现pytorch 的conv操作不是很明白,于是有了一下记录首先提出两个问题:1.输入图片是单通道情况下的filter