在 TF2/Keras 中正确实现 Autoencoder MSE 损失函数

Posted

技术标签:

【中文标题】在 TF2/Keras 中正确实现 Autoencoder MSE 损失函数【英文标题】:Correct implementation of Autoencoder MSE loss function in TF2/Keras 【发布时间】:2021-04-23 13:50:09 【问题描述】:

谁能解释一下以下两者的区别?

假设一个带有实值输入的普通自动编码器,根据this 和this 来源,它的损失函数应该如下。换句话说,a) 对于示例中的每个元素,我们计算平方差,b) 我们对示例的所有元素执行求和,c) 我们对所有示例取平均值。

def MSE_custom(y_true, y_pred):
    return tf.reduce_mean(
        0.5 * tf.reduce_sum(
            tf.square(tf.subtract(y_true, y_pred)),
            axis=1
            )
        )

但是,在我看到的大多数实现中:autoencoder.compile(loss='mse', ...)

我看不出两者是如何相同的。考虑这个例子:

y_true = [[0.0, 1.0, 0.0], [0.0, 0.0, 1.0], [1.0, 1.0, 1.0]]
y_pred = [[0.0, 0.8, 0.9], [0.5, 0.7, 0.6], [0.8, 0.7, 0.5]]

result1 = MSE_custom(y_true, y_pred)  # 0.355 

mse = tf.keras.losses.MeanSquaredError(reduction=tf.keras.losses.Reduction.AUTO)
result2 = mse(y_true, y_pred)  # 0.237

我错过了什么?非常感谢!

【问题讨论】:

【参考方案1】:

正如Tensorflow documentation 中所解释的,MSE 是通过对整个张量大小(SUM 损失减少)或整个批次大小(SUM_OVER_BATCH_SIZE 损失减少)的平方误差进行平均来计算的。下面的代码显示了如何复制两个 MSE 计算的一些示例。

import tensorflow as tf

y_true = [[0.0, 1.0, 0.0], [0.8, 0.9, 1.0], [1.0, 1.0, 1.0], [1.0, 0.0, 0.0]]
y_pred = [[0.0, 0.8, 0.9], [0.5, 0.7, 0.6], [0.8, 0.7, 0.5], [0.9, 0.1, 0.3]]

##############################################
# Loss reduction: "SUM"
##############################################
reduction = tf.keras.losses.Reduction.SUM

mse_1 = tf.keras.losses.MeanSquaredError(reduction=reduction)
print(mse_1(y_true, y_pred))
# tf.Tensor(0.54333335, shape=(), dtype=float32)

def MSE_1(y_true, y_pred):
    x = tf.reduce_sum(tf.square(tf.subtract(y_true, y_pred)))
    y = tf.cast(tf.shape(y_true)[1], tf.float32) # divide by the shape of the tensor
    return tf.divide(x, y)

print(MSE_1(y_true, y_pred))
# tf.Tensor(0.54333335, shape=(), dtype=float32)

##############################################
# Loss reduction: "SUM_OVER_BATCH_SIZE"
##############################################
reduction = tf.keras.losses.Reduction.SUM_OVER_BATCH_SIZE

mse_2 = tf.keras.losses.MeanSquaredError(reduction=reduction)
print(mse_2(y_true, y_pred))
# tf.Tensor(0.13583334, shape=(), dtype=float32)

def MSE_2(y_true, y_pred):
    x = tf.reduce_sum(tf.square(tf.subtract(y_true, y_pred)))
    y = tf.cast(tf.multiply(tf.shape(y_true)[0], tf.shape(y_true)[1]), tf.float32) # divide by the size of the tensor
    return tf.divide(x, y)

print(MSE_2(y_true, y_pred))
# tf.Tensor(0.13583334, shape=(), dtype=float32)

##############################################
# Loss reduction: "NONE"
##############################################
reduction = tf.keras.losses.Reduction.NONE

mse_3 = tf.keras.losses.MeanSquaredError(reduction=reduction)
print(mse_3(y_true, y_pred))
# tf.Tensor([0.28333333 0.09666666 0.12666667 0.03666667], shape=(4,), dtype=float32)

def MSE_3(y_true, y_pred):
    x = tf.reduce_sum(tf.square(tf.subtract(y_true, y_pred)), axis=1)
    y = tf.cast(tf.shape(y_true)[1], tf.float32) # divide by the shape of the tensor
    return tf.divide(x, y)

print(MSE_3(y_true, y_pred))
# tf.Tensor([0.28333333 0.09666666 0.12666667 0.03666667], shape=(4,), dtype=float32)

# recover "SUM" loss reduction
print(tf.reduce_sum(mse_3(y_true, y_pred)))
# tf.Tensor(0.54333335, shape=(), dtype=float32)

print(tf.reduce_sum(MSE_3(y_true, y_pred)))
# tf.Tensor(0.54333335, shape=(), dtype=float32)

# recover "SUM_OVER_BATCH_SIZE" loss reduction
print(tf.divide(tf.reduce_sum(mse_3(y_true, y_pred)), tf.cast(tf.shape(y_true)[0], tf.float32)))
# tf.Tensor(0.13583334, shape=(), dtype=float32)

print(tf.divide(tf.reduce_sum(MSE_3(y_true, y_pred)), tf.cast(tf.shape(y_true)[0], tf.float32)))
# tf.Tensor(0.13583334, shape=(), dtype=float32)

【讨论】:

【参考方案2】:

有两个区别。

    Keras 损失平均在所有维度上,即您的 reduce_sum 应替换为 reduce_mean。 Keras 损失乘以 0.5。

在您的情况下,您有三个维度,因此我们可以通过除以 3(模拟平均)并乘以 2 从您的结果中得出 Keras 损失。事实证明,0.355 * 2/3 == 0.237(大致)。

这些变化可能会让你失望,但它们最终是无关紧要的,因为除以 N 和乘以 2 都是常数因子,因此也只能为梯度提供一个常数因子。

编辑:以下计算应该给您与 Keras 损失相同的结果:

mse_custom = tf.reduce_mean((y_true - y_pred)**2)

为了简单起见,我使用重载的 Python 运算符而不是 TF 运算(减法/平方)。这只是一次对整个 2D 矩阵进行平均,这与计算轴 1 上的平均值然后在轴 0 上平均 相同。

【讨论】:

感谢您的及时回复!如果我理解正确,如果我根据您的回答定义 MSE_custom2: MSE_custom2(y_true, y_pred): return tf.reduce_mean( tf.reduce_mean( tf.square(tf.subtract(y_true, y_pred)), axis=1 ) ) 那么在 autoencoder.compile() 中有 loss=MSE_custom2 或 loss='mse' 是相同的并且都是正确的,对吧? 确实如此。事实上,您应该能够在所有轴上只使用一个 tf.reduce_mean(尽管这有点“混淆”了对维度进行平均和对批次进行平均之间的差异,但它给出了相同的结果)。 您能详细说明一下吗?如何使用单个 tf.reduce_mean 仍然得到相同的结果?

以上是关于在 TF2/Keras 中正确实现 Autoencoder MSE 损失函数的主要内容,如果未能解决你的问题,请参考以下文章

如何在c#中正确实现等待异步[重复]

如何在python的类中正确实现辅助函数

在这种情况下,如何在 Flutter 中正确实现 FutureBuilder?

在 Android 中正确实现 ViewPager2

在PHP中正确实现模型类

如何在php中正确实现结构化菜单