我正在尝试从this paper用Keras编写自定义损失函数。也就是说,我要造成的损失是这样的:
这是针对多类别多标签问题的排名损失类型。详细信息如下:
Y_i = set of positive labels for sample i
Y_i^bar = set of negative labels for sample i (complement of Y_i)
c_j^i = prediction on i^th sample at label j
随后,y_true
和y_pred
的尺寸均为18。
def multilabel_loss(y_true, y_pred):
""" Multi-label loss function.
More complete description here...
"""
zero = K.tf.constant(0, dtype=tf.float32)
where_one = K.tf.not_equal(y_true, zero)
where_zero = K.tf.equal(y_true, zero)
Y_p = K.tf.where(where_one)
Y_n = K.tf.where(where_zero)
n = K.tf.shape(y_true)[0]
loss = 0
for i in range(n):
# Here i is the ith sample; for a specific i, I find all locations
# where Y_p, Y_n belong to the ith sample; axis 0 denotes
# the sample index space
Y_p_i = K.tf.equal(Y_p[:,0], K.tf.constant(i, dtype=tf.int64))
Y_n_i = K.tf.equal(Y_n[:,0], K.tf.constant(i, dtype=tf.int64))
# Here I plug in those locations to get the values
Y_p_i = K.tf.where(Y_p_i)
Y_n_i = K.tf.where(Y_n_i)
# Here I get the indices of the values above
Y_p_ind = K.tf.gather(Y_p[:,1], Y_p_i)
Y_n_ind = K.tf.gather(Y_n[:,1], Y_n_i)
# Here I compute Y_i and its complement
yi = K.tf.shape(Y_p_ind)[0]
yi_not = K.tf.shape(Y_n_ind)[0]
# The value to normalize the inner summation
normalizer = K.tf.divide(1, K.tf.multiply(yi, yi_not))
# This creates a matrix of all combinations of indices k, l from the
# above equation; then it is reshaped
prod = K.tf.map_fn(lambda x: K.tf.map_fn(lambda y: K.tf.stack( [ x, y ] ), Y_n_ind ), Y_p_ind )
prod = K.tf.reshape(prod, [-1, 2, 1])
prod = K.tf.squeeze(prod)
# Next, the indices are fed into the corresponding prediction
# matrix, where the values are then exponentiated and summed
y_pred_gather = K.tf.gather(y_pred[i,:].T, prod)
s = K.tf.cast(K.sum(K.tf.exp(K.tf.subtract(y_pred_gather[:,0], y_pred_gather[:,1]))), tf.float64)
loss = loss + K.tf.multiply(normalizer, s)
return loss
我的问题如下:
n
的错误。即TypeError: 'Tensor' object cannot be interpreted as an integer
。我环顾四周,但找不到解决办法。我的直觉是,我需要完全避免for循环,这将我带到了Y_i
及其补码对于每个i
都可能具有不同的大小。如果您想让我详细说明我的代码,请告诉我。很高兴这样做。
更新3
根据@Parag S. Chandakkar的建议,我有以下内容:
def multi_label_loss(y_true, y_pred):
# set consistent casting
y_true = tf.cast(y_true, dtype=tf.float64)
y_pred = tf.cast(y_pred, dtype=tf.float64)
# this get all positive predictions and negative predictions
# it also exponentiates them in their respective Y_i classes
PT = K.tf.multiply(y_true, tf.exp(-y_pred))
PT_complement = K.tf.multiply((1-y_true), tf.exp(y_pred))
# this step gets the weight vector that we'll normalize by
m = K.shape(y_true)[0]
W = K.tf.multiply(K.sum(y_true, axis=1), K.sum(1-y_true, axis=1))
W_inv = 1./W
W_inv = K.reshape(W_inv, (m,1))
# this step computes the outer product of two tensors
def outer_product(inputs):
"""
inputs: list of two tensors (of equal dimensions,
for which you need to compute the outer product
"""
x, y = inputs
batchSize = K.shape(x)[0]
outerProduct = x[:,:, np.newaxis] * y[:,np.newaxis,:]
outerProduct = K.reshape(outerProduct, (batchSize, -1))
# returns a flattened batch-wise set of tensors
return outerProduct
# set up inputs to outer product
inputs = [PT, PT_complement]
# compute final loss
loss = K.sum(K.tf.multiply(W_inv, outer_product(inputs)))
return loss
答案 0 :(得分:2)
这不是答案,而是更像我的思考过程,应该可以帮助您编写简洁的代码。
首先,我不认为您现在应该担心该错误,因为在消除for循环时,您的代码可能看起来非常不同。
现在,我还没有看过这篇论文,但是预测c_j^i
应该是来自最后一个非softmax层(这就是我的假设)的原始值。
因此,您可以添加一个额外的exp
层并为每个预测计算exp(c_j^i)
。现在,由于求和,for循环来了。如果仔细观察,它所要做的就是首先对所有标签形成对,然后减去它们相应的预测。现在,首先将减法表示为exp(c_l^i) * exp(-c_k^i)
。要查看发生了什么,请举一个简单的例子。
import numpy as np
a = [1, 2, 3]
a = np.reshape(a, (3,1))
按照上面的说明,您需要以下结果。
r1 = sum([1 * 2, 1 * 3, 2 * 3]) = sum([2, 3, 6]) = 11
通过矩阵乘法可以获得相同的结果,这是消除循环的一种方法。
r2 = a * a.T
# r2 = array([[1, 2, 3],
# [2, 4, 6],
# [3, 6, 9]])
Extract the upper triangular part,即2, 3, 6
,然后对数组求和以得到11
,这就是您想要的结果。现在,可能会有一些差异,例如,您可能需要详尽地形成所有对。您应该能够以矩阵乘法的形式对其进行转换。
一旦您考虑了总和项,如果您为每个样本|Y_i|
预计算了量\bar{Y_i}
和i
,就可以轻松计算归一化项。将它们作为输入数组传递,并将它们作为y_pred
的一部分传递给损失。 i
的最终求和将由Keras完成。
编辑1:即使|Y_i|
和\bar{Y_i}
取不同的值,您也应该能够建立一个通用公式来提取上三角部分,而与矩阵无关预先计算了|Y_i|
和\bar{Y_i}
后的大小。
编辑2:我认为您并不完全了解我。我认为,NumPy完全不应在损失函数中使用。 (大多数)仅使用Tensorflow是可行的。在保留我之前的解释的同时,我将再次解释。
我现在知道在正标记和负标记之间(即分别为|Y_i|
和\bar{Y_i}
)存在笛卡尔积。因此,首先,在原始预测之后添加一个layer of exp
(在TF中,而不是在Numpy中)。
现在,您需要知道y_true
的18个维度中的哪些索引对应于正数,哪些索引对应于负数。如果您使用的是一种热编码,则可以使用tf.where
和tf.gather
(请参阅here)即时找到。
现在,您应该知道与正负标签相对应的索引j
(在c_j^i
中)。您所需要做的就是为\sum_(k, l) {exp(c_k^i) * (1 / exp(c_l^i))}
对计算(k, l)
。您需要做的就是形成一个由exp(c_k^i) for all k
组成的张量(称为A
)和另一个由exp(c_l^i) for all l
组成的张量(称为B
)。然后计算sum(A * B^T)
。如果要使用笛卡尔积,也无需提取上三角部分。到现在为止,您应该具有最里面求和的结果。
与我之前所说的相反,我认为您也可以从y_true
即时计算归一化因子。
您只需要弄清楚如何将其扩展到三个维度即可处理多个样本。
注意:通过使用tf.py_func
,Numpy的使用是probably possible,但此处似乎没有必要。只需使用TF的功能即可。