如何处理其中包含 (1/(1-exp(-x))-1/x) 的自定义损失函数?

问题描述 投票:0回答:1

我正在研究一个具有不规则张量的深度学习模型,其中自定义损失函数与以下内容相关:

f(x)+f(x+50)

当 x!=0 时 f(x)=1/(1-exp(-x))-1/x,当 x=0 时 f(x)=0.5。

f(x) 的范围在 0 和 1 之间,并且对于所有 x 都是连续且可微的。下面是 f(x) 的图表

enter image description here

我首先尝试将此函数实现为

tf.where(tf.abs(x)<0.1, 0.5+x/12, 1/(1-exp(-x))-1/x)
,因为 x=0 处的梯度为 1/12。但问题是,经过如下所示的拟合后,损失变成了 nan:

Epoch: 0    train_loss: 0.072233    val_loss: 0.052703
Epoch: 10   train_loss: 0.008087    val_loss: 0.041443
Epoch: 20   train_loss: 0.005942    val_loss: 0.029767
Epoch: 30   train_loss: 0.005200    val_loss: 0.026407
Epoch: 40   train_loss: nan val_loss: nan
Epoch: 50   train_loss: nan val_loss: nan

我尝试解决这个问题,但都失败了。

  1. 我也让代码单独计算x<-10 and x>10时的f(x),即:
tf.where(tf.abs(x)<0.1, 0.5+x/12,
         tf.where(x<-10., -1/x,
                  tf.where(x>10., 1-1/x, 1/(1-tf.exp(-x))-1/x)))

但它给出了相同的结果。

  1. 降低学习率并更改优化器会得到相同的结果,并开始以与上述类似的训练损失给予 nan。

  2. 我通过

    tf.keras.backend.set_floatx('float64')
    将默认的float设置为float64。它设法进一步训练模型,但同样,它开始以较低的训练损失给出相同的结果:

Epoch: 0    train_loss: 0.043096    val_loss: 0.050407
Epoch: 10   train_loss: 0.006179    val_loss: 0.034259
Epoch: 20   train_loss: 0.005841    val_loss: 0.034110
...
Epoch: 210  train_loss: 0.003594    val_loss: 0.026524
Epoch: 220  train_loss: nan val_loss: nan
Epoch: 230  train_loss: nan val_loss: nan
  1. 用 sigmoid 函数替换 f(x) 解决了问题。但我真的很想使用 f(x),因为它对我正在做的事情确实很有意义。

我猜在计算梯度时发生了一些inf/inf、0/0或inf-inf,但我不是那么专家,无法获得更详细的线索。如果您知道如何解决这个问题或者知道我需要做什么来解决这个问题,我将非常感激。

tensorflow tensorflow2.0 loss-function
1个回答
0
投票

这主要是一个灾难性的数字抵消问题 - 你不能只使用代数形式计算 IEEE754 数字中的某些内容,并期望它适用于非常小或很大的数字。

您对 f(x) 的定义是:

f(x)=1/(1-exp(-x))-1/x when x!=0, f(x)=0.5 when x=0.

在许多语言中,都提供了一个函数 expxm1,它可以将“exp(x)-1”计算为全机器精度(可以追溯到 x87 数字协处理器的硬件实现,甚至可能更早)。这可能足以解决您眼前的问题,以避免 x 值较小时被零除 (<1e-7 for floats, <2e-16 for doubles).

Tensorflow 似乎有这样一个 expxm1 并且它保持精度的目的在帮助中解释了。

但是您可能可以通过乘以并计算公分母来获得更好的准确性,从而做得更好。

f(x) = (x - (1-exp(-x))/(x*(1-exp(x))  x !=0

评价为

f(x) = -(x + expxm1(-x))/(x*expxm1(-x))

对于一些非常小的 x 值,它仍然会损失精度并返回零,但当它达到精度限制时,它不应该再生成 Nans。

如果您确实需要它对任何 x 连续工作,无论多小,那么修复方法是返回分子的第一项,当输入

x^2/2
很小时,该分子的第一项实际上不会抵消
x

 f(x) = -x^2/(2*x*expm1(-x))   for x << 1e-16

评价为

 f(x) = -x/(2*expxm1(-x))     x != 0

此类问题在数值计算中很常见,其中两个几乎相等的分量被减去。有各种经典的重新排列技巧可以避免灾难性的取消。

© www.soinside.com 2019 - 2024. All rights reserved.