如何在自定义损失函数中迭代张量?

Posted

技术标签:

【中文标题】如何在自定义损失函数中迭代张量?【英文标题】:How to iterate through tensors in custom loss function? 【发布时间】:2018-12-26 13:55:45 【问题描述】:

我正在使用带有 tensorflow 后端的 keras。我的目标是在 custom loss 函数中查询当前批次的batchsize。这是计算自定义损失函数的值所必需的,这些值取决于特定观察的索引。鉴于下面的最小可重复示例,我想更清楚地说明这一点。

(顺便说一句:我当然可以使用为训练过程定义的批量大小,并在定义自定义损失函数时插入它的值,但是这可能会有所不同,特别是如果epochsize % batchsize(epochsize modulo batchsize)不等零,那么最后一批epoch的大小不同。我在***中没有找到合适的方法,尤其是例如 Tensor indexing in custom loss function 和 Tensorflow custom loss function in Keras - loop over tensor 和 Looping over a tensor 因为显然在构建图形时无法推断出任何张量的形状,这是损失函数的情况 - 只有在评估给定数据时才能进行形状推断,这只是可能给定图表。因此,我需要告诉自定义损失函数在不知道维度长度的情况下沿某个维度对特定元素执行某些操作。

(这在所有示例中都是相同的)

from keras.models import Sequential
from keras.layers import Dense, Activation

# Generate dummy data
import numpy as np
data = np.random.random((1000, 100))
labels = np.random.randint(2, size=(1000, 1))

model = Sequential()
model.add(Dense(32, activation='relu', input_dim=100))
model.add(Dense(1, activation='sigmoid'))

示例 1:没有什么特别的问题,没有自定义损失

model.compile(optimizer='rmsprop',
              loss='binary_crossentropy',
              metrics=['accuracy'])    

# Train the model, iterating on the data in batches of 32 samples
model.fit(data, labels, epochs=10, batch_size=32)

(省略输出,运行良好)

示例 2:没什么特别的,有一个相当简单的自定义损失

def custom_loss(yTrue, yPred):
    loss = np.abs(yTrue-yPred)
    return loss

model.compile(optimizer='rmsprop',
              loss=custom_loss,
              metrics=['accuracy'])

# Train the model, iterating on the data in batches of 32 samples
model.fit(data, labels, epochs=10, batch_size=32)

(省略输出,运行良好)

示例 3:问题

def custom_loss(yTrue, yPred):
    print(yPred) # Output: Tensor("dense_2/Sigmoid:0", shape=(?, 1), dtype=float32)
    n = yPred.shape[0]
    for i in range(n): # TypeError: __index__ returned non-int (type NoneType)
        loss = np.abs(yTrue[i]-yPred[int(i/2)])
    return loss

model.compile(optimizer='rmsprop',
              loss=custom_loss,
              metrics=['accuracy'])

# Train the model, iterating on the data in batches of 32 samples
model.fit(data, labels, epochs=10, batch_size=32)

当然,张量还没有形状信息,这在构建图形时无法推断,只能在训练时推断。因此for i in range(n) 出现错误。有没有办法做到这一点?

输出的回溯:

-------

顺便说一句,如果有任何问题,这是我真正的自定义损失函数。为了清楚和简单起见,我在上面跳过了它。

def neg_log_likelihood(yTrue,yPred):
    yStatus = yTrue[:,0]
    yTime = yTrue[:,1]    
    n = yTrue.shape[0]    
    for i in range(n):
        s1 = K.greater_equal(yTime, yTime[i])
        s2 = K.exp(yPred[s1])
        s3 = K.sum(s2)
        logsum = K.log(y3)
        loss = K.sum(yStatus[i] * yPred[i] - logsum)
    return loss

这是 cox 比例风险模型的部分负对数似然图。

这是为了澄清 cmets 中的一个问题以避免混淆。我认为没有必要详细了解这一点来回答这个问题。

【问题讨论】:

答案是:不要迭代。我会帮忙,但是你的损失函数中有很多奇怪的东西,我无法理解。但是您知道yTrueyPred 具有始终相同的形状,对吧?以你为例,没有yTrue[:,1] 我添加了损失函数的图片。尽管确实没有必要克服这个问题(示例 3),但我想避免混淆。 亲爱的丹尼尔,感谢您的时间和精力。我发现了两种解决方案(1)一种使用循环的非高效解决方案(2)一种使用 tensorflow 后端及其矢量化的解决方案。在我做了一些改进后,我会在这里发布并回答你所有的问题。 @Thomas,你是怎么解决这个问题的?可以发在这里吗?我真的很想知道。 @DanielMöller,其实y_pred和y_true可以有不同的形状…… 【参考方案1】:

像往常一样,不要循环。存在严重的性能缺陷和错误。除非完全无法避免(通常并非无法避免),否则仅使用后端函数


示例 3 的解决方案:

所以,这里有一件很奇怪的事情……

您真的想简单地忽略模型的一半预测吗? (例3)

假设这是真的,只需在最后一个维度复制您的张量,展平并丢弃其中的一半。你有你想要的确切效果。

def custom_loss(true, pred):
    n = K.shape(pred)[0:1]

    pred = K.concatenate([pred]*2, axis=-1) #duplicate in the last axis
    pred = K.flatten(pred)                  #flatten 
    pred = K.slice(pred,                    #take only half (= n samples)
                   K.constant([0], dtype="int32"), 
                   n) 

    return K.abs(true - pred)

损失函数的解决方案:

如果您将时间从大到小排序,只需进行累计。

警告:如果每个样本只有一次,则无法使用 mini-batches 进行训练!!!batch_size = len(labels)

在一个额外的维度(每个样本很多次)中有时间是有意义的,就像在循环和一维卷积网络中所做的那样。无论如何,考虑到您表达的示例,即yTime 的形状(samples_equal_times,)

def neg_log_likelihood(yTrue,yPred):
    yStatus = yTrue[:,0]
    yTime = yTrue[:,1]    
    n = K.shape(yTrue)[0]    


    #sort the times and everything else from greater to lower:
    #obs, you can have the data sorted already and avoid doing it here for performance

    #important, yTime will be sorted in the last dimension, make sure its (None,) in this case
    # or that it's (None, time_length) in the case of many times per sample
    sortedTime, sortedIndices = tf.math.top_k(yTime, n, True)    
    sortedStatus = K.gather(yStatus, sortedIndices)
    sortedPreds = K.gather(yPred, sortedIndices)

    #do the calculations
    exp = K.exp(sortedPreds)
    sums = K.cumsum(exp)  #this will have the sum for j >= i in the loop
    logsums = K.log(sums)

    return K.sum(sortedStatus * sortedPreds - logsums)

【讨论】:

好吧,既然你回答了最初的问题,我必须给你赏金,但不幸的是,它不适用于我的情况。您可以将我的案例视为每个样本具有可变次数,我用它来设置形状为 (num_times_true, num_times_pred) 的动态规划矩阵。这可能最好在另一个问题中回答。 通常乘以一个掩码(即一个与您想要使用/丢弃的内容相对应的零和一的矩阵)可以完成这项工作。我不相信你可以有不同大小的批次。因此,对于每批这样的配方可能工作正常。 循环的一种可能性是使用tf.split 作为批处理维度,然后循环每个生成的张量,但这对性能来说很糟糕。 我明白了。感谢您的想法!

以上是关于如何在自定义损失函数中迭代张量?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Keras 自定义损失函数中使用张量?

keras中的加权mse自定义损失函数

自定义损失函数中的张量索引

Keras 在自定义损失函数中访问单个值

每个张量组的 Keras 自定义损失函数

GPU 上的 Keras 模型:在自定义损失函数中使用 Pandas