如何使用TensorFlow后端屏蔽Keras中的损失函数?

如何使用TensorFlow后端屏蔽Keras中的损失函数?,keras,lstm,masking,loss-function,Keras,Lstm,Masking,Loss Function,我正在尝试使用Keras的LSTM和TensorFlow后端实现一个序列到序列的任务。输入是长度可变的英语句子。为了构建一个具有二维形状的数据集,我在行尾添加了EOF,并在每个句子中填充足够的占位符,例如。。然后将句子中的每个字符转换为一个热向量,这样数据集就具有三维形状[批次号、最大句子长度、字符号]。在LSTM编码器和解码器层之后,计算输出和目标之间的softmax交叉熵 为了消除模型训练中的填充效应,可以对输入和损失函数进行掩蔽。KERA中的掩码输入可以通过使用layers.core.Ma

我正在尝试使用Keras的LSTM和TensorFlow后端实现一个序列到序列的任务。输入是长度可变的英语句子。为了构建一个具有二维形状的数据集,我在行尾添加了
EOF
,并在每个句子中填充足够的占位符,例如
。然后将句子中的每个字符转换为一个热向量,这样数据集就具有三维形状
[批次号、最大句子长度、字符号]
。在LSTM编码器和解码器层之后,计算输出和目标之间的softmax交叉熵

为了消除模型训练中的填充效应,可以对输入和损失函数进行掩蔽。KERA中的掩码输入可以通过使用
layers.core.Masking
完成。在TensorFlow中,损失函数的掩蔽可以按如下方式进行:

但是,我没有找到在Keras中实现它的方法,因为Keras中的用户定义损失函数只接受参数
y\u true
y\u pred
。那么,如何将真正的
序列长度
输入到丢失函数和掩码中呢

此外,我在
\keras\engine\training.py
中找到了一个函数
\u weighted\u masked\u objective(fn)
。它的定义是

为目标函数添加对掩蔽和样本权重的支持

但函数似乎只能接受
fn(y\u true,y\u pred)
。有没有办法用这个函数来解决我的问题

具体来说,我修改了俞阳的例子

from keras.models import Model
from keras.layers import Input, Masking, LSTM, Dense, RepeatVector, TimeDistributed, Activation
import numpy as np
from numpy.random import seed as random_seed
random_seed(123)

max_sentence_length = 5
character_number = 3 # valid character 'a, b' and placeholder '#'

input_tensor = Input(shape=(max_sentence_length, character_number))
masked_input = Masking(mask_value=0)(input_tensor)
encoder_output = LSTM(10, return_sequences=False)(masked_input)
repeat_output = RepeatVector(max_sentence_length)(encoder_output)
decoder_output = LSTM(10, return_sequences=True)(repeat_output)
output = Dense(3, activation='softmax')(decoder_output)

model = Model(input_tensor, output)
model.compile(loss='categorical_crossentropy', optimizer='adam')
model.summary()

X = np.array([[[0, 0, 0], [0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 1, 0]],
          [[0, 0, 0], [0, 1, 0], [1, 0, 0], [0, 1, 0], [0, 1, 0]]])
y_true = np.array([[[0, 0, 1], [0, 0, 1], [1, 0, 0], [0, 1, 0], [0, 1, 0]], # the batch is ['##abb','#babb'], padding '#'
          [[0, 0, 1], [0, 1, 0], [1, 0, 0], [0, 1, 0], [0, 1, 0]]])

y_pred = model.predict(X)
print('y_pred:', y_pred)
print('y_true:', y_true)
print('model.evaluate:', model.evaluate(X, y_true))
# See if the loss computed by model.evaluate() is equal to the masked loss
import tensorflow as tf
logits=tf.constant(y_pred, dtype=tf.float32)
target=tf.constant(y_true, dtype=tf.float32)
cross_entropy = tf.reduce_mean(-tf.reduce_sum(target * tf.log(logits),axis=2))
losses = -tf.reduce_sum(target * tf.log(logits),axis=2)
sequence_lengths=tf.constant([3,4])
mask = tf.reverse(tf.sequence_mask(sequence_lengths,maxlen=max_sentence_length),[0,1])
losses = tf.boolean_mask(losses, mask)
masked_loss = tf.reduce_mean(losses)
with tf.Session() as sess:
    c_e = sess.run(cross_entropy)
    m_c_e=sess.run(masked_loss)
    print("tf unmasked_loss:", c_e)
    print("tf masked_loss:", m_c_e)
Keras和TensorFlow的输出比较如下:


如上所示,在某些类型的层之后禁用掩蔽。那么,当添加这些层时,如何掩盖Keras中的损失函数呢

如果你没有像俞阳的回答那样使用面具,你可以试试这个

如果您的目标数据
Y
具有长度并填充了掩码值,则可以:

import keras.backend as K
def custom_loss(yTrue,yPred):

    #find which values in yTrue (target) are the mask value
    isMask = K.equal(yTrue, maskValue) #true for all mask values

    #since y is shaped as (batch, length, features), we need all features to be mask values
    isMask = K.all(isMask, axis=-1) #the entire output vector must be true
        #this second line is only necessary if the output features are more than 1

    #transform to float (0 or 1) and invert
    isMask = K.cast(isMask, dtype=K.floatx())
    isMask = 1 - isMask #now mask values are zero, and others are 1

    #multiply this by the inputs:
       #maybe you might need K.expand_dims(isMask) to add the extra dimension removed by K.all
     yTrue = yTrue * isMask   
     yPred = yPred * isMask

     return someLossFunction(yTrue,yPred)

如果您仅对输入数据进行填充,或者如果Y没有长度,则可以在函数外部使用自己的掩码:

masks = [
   [1,1,1,1,1,1,0,0,0],
   [1,1,1,1,0,0,0,0,0],
   [1,1,1,1,1,1,1,1,0]
]
 #shape (samples, length). If it fails, make it (samples, length, 1). 

import keras.backend as K

masks = K.constant(masks)
由于掩码取决于您的输入数据,因此您可以使用掩码值来知道在何处放置零,例如:

masks = np.array((X_train == maskValue).all(), dtype='float64')    
masks = 1 - masks

#here too, if you have a problem with dimensions in the multiplications below
#expand masks dimensions by adding a last dimension = 1.
并使您的函数从外部获取掩码(如果更改输入数据,则必须重新创建丢失函数):


有人知道keras是否会自动屏蔽损失功能吗??
因为它提供了一个屏蔽层,对输出没有任何说明,也许它会自动执行?

如果模型中有一个屏蔽,它会逐层传播,并最终应用于损耗。因此,如果以正确的方式填充和屏蔽序列,则填充占位符的丢失将被忽略

一些细节: 解释整个过程有点复杂,所以我将把它分解为几个步骤:

  • compile()
  • weighted_loss=[_weighted_masked_objective(fn),用于损失函数中的fn]
    #准备输出掩码。
    掩码=self.compute\u掩码(self.inputs,掩码=None)
    如果掩码为“无”:
    掩码=[self.outputs中的u无]
    如果不存在(掩码,列表):
    面具=[面具]
    #计算总损失。
    总损失=无
    使用K.name_范围(“损失”):
    对于范围内的i(len(自输出)):
    y_true=自我目标[i]
    y_pred=自输出[i]
    加权损失=加权损失[i]
    样本重量=样本重量[i]
    面具=面具[i]
    使用K.name_作用域(self.output_names[i]+''u loss'):
    输出损耗=加权损耗(y_真,y_pred,
    样品(重量、面罩)
    
  • Model.compute\u mask()
    内部,调用
    run\u internal\u graph()
  • run\u internal\u graph()
    内部,通过调用
    layer.compute\u mask()
    ,将模型中的掩码从模型的输入逐层传播到输出
  • 因此,如果在模型中使用
    掩蔽
    层,则不必担心填充占位符的丢失。这些条目的损失将被掩盖,正如您可能已经在
    \u weighted\u masked\u objective()
    中看到的那样

    一个小例子:
    最大句子长度=5
    字符数=2
    输入\张量=输入(形状=(最大句子长度、字符数))
    屏蔽输入=屏蔽(屏蔽值=0)(输入张量)
    输出=LSTM(3,返回序列=真)(屏蔽输入)
    模型=模型(输入张量,输出)
    compile(loss='mae',optimizer='adam')
    X=np.数组([[0,0],[0,0],[1,0],[0,1],[0,1]],
    [[0, 0], [0, 1], [1, 0], [0, 1], [0, 1]]])
    y_true=np.one((2,最大句子长度,3))
    y_pred=模型预测(X)
    打印(y_pred)
    [[[ 0.          0.          0.        ]
    [ 0.          0.          0.        ]
    [-0.11980877  0.05803877  0.07880752]
    [-0.00429189  0.13382857  0.19167568]
    [ 0.06817091  0.19093043  0.26219055]]
    [[ 0.          0.          0.        ]
    [ 0.0651961   0.10283815  0.12413475]
    [-0.04420842  0.137494    0.13727818]
    [ 0.04479844  0.17440712  0.24715884]
    [ 0.11117355  0.21645413  0.30220413]]]
    #查看model.evaluate()计算的损失是否等于掩盖的损失
    无遮罩损失=np.abs(1-y_pred).mean()
    掩盖损失=np.abs(1-y_pred[y_pred!=0])。平均值()
    打印(模型评估(X,y_真))
    0.881977558136
    打印(掩蔽丢失)
    0.881978
    打印(无遮罩丢失)
    0.917384
    
    从本例中可以看出,屏蔽部分的损失(在
    y_pred
    中的零)被忽略,
    model.evaluate()
    的输出等于
    masked_损失


    编辑: 如果存在具有
    return\u sequences=False
    的重复层,则掩码停止传播(即,返回的掩码为
    None
    )。在
    RNN.compute\u mask()中
    
    def customLoss(yTrue,yPred):
    
        yTrue = masks*yTrue
        yPred = masks*yPred
    
        return someLossFunction(yTrue,yPred)
    
    model.evaluate: 1.08339476585
    tf unmasked_loss: 1.08989
    tf masked_loss: 1.08339