【译】Effective TensorFlow Chapter12——TensorFlow中的数值稳定性

【译】Effective TensorFlow Chapter12——TensorFlow中的数值稳定性

TensorFlow小彩虹2021-08-26 9:39:28210A+A-

本文翻译自: 《Numerical stability in TensorFlow》, 如有侵权请联系删除,仅限于学术交流,请勿商用。如有谬误,请联系指出。

当使用任何数值计算库(如NumPy或TensorFlow)时,值得注意的是,编写出正确的数学计算代码对于计算出正确结果并不是必须的。你同样需要确保整个计算过程是稳定的。

让我们从一个例子入手。小学的时候我们就知道,对于任意一个非0的数x,都有x*y/y=x。但是让我们在实践中看看是否如此:

import numpy as np

x = np.float32(1)

y = np.float32(1e-50)  # y would be stored as zero
z = x * y / y

print(z)  # prints nan

错误的原因是:y是float32类型的数字,所能表示的数值太小。当y太大时会出现类似的问题:

y = np.float32(1e39)  # y would be stored as inf
z = x * y / y

print(z)  # prints 0

float32类型可以表示的最小正值是1.4013e-45,任何低于该值的数都将存储为零。此外,任何超过3.40282e + 38的数都将存储为inf。

print(np.nextafter(np.float32(0), np.float32(1)))  # prints 1.4013e-45
print(np.finfo(np.float32).max)  # print 3.40282e+38

为了保证计算的稳定性,你需要避免使用绝对值非常小或非常大的值。可能听起来这种问题比较低级,但这些问题可能会让程序变得难以调试,尤其是在TensorFlow中进行梯度下降时。这是因为你不仅需要确保正向传递中的所有值都在数据类型的有效范围内,而且反向传播时同样如此(在梯度运算期间)。

让我们看一个真实的例子。我们想要在logits向量上计算其softmax的值。一个too navie的实现方式就像这样:

import tensorflow as tf

def unstable_softmax(logits):
    exp = tf.exp(logits)
    return exp / tf.reduce_sum(exp)

tf.Session().run(unstable_softmax([1000., 0.]))  # prints [ nan, 0.]

注意一下,计算相对较小的数的对数,将会得到一个超出float32范围的大数。对于我们的naive softmax实现来说,最大的有效对数是ln(3.40282e+38) = 88.7,如果超过这个值,就会导致nan结果。

但是我们怎样才能使它更稳定呢?解决办法相当简单。很容易看到,exp (x - c) /∑exp (x - c) = exp (x) /∑exp (x)。因此,我们可以从逻辑中减去任何常数,结果还是一样的。我们选择这个常数作为逻辑的最大值。这样,指数函数的定义域将被限制为[-inf, 0],因此其范围将为[0.0,1.0],这是可取的:

import tensorflow as tf

def softmax(logits):
    exp = tf.exp(logits - tf.reduce_max(logits))
    return exp / tf.reduce_sum(exp)

tf.Session().run(softmax([1000., 0.]))  # prints [ 1., 0.]

让我们来看一个复杂点案例。假设我们有一个分类问题,并且使用softmax函数从我们的逻辑中产生概率。然后我们定义一个真实值和预测值之间的交叉熵损失函数。回想一下,交叉熵的分类分布可以简单地定义为xe(p, q) = -∑ p_i log(q_i),所以一个简单的交叉熵代码是这样的:

def unstable_softmax_cross_entropy(labels, logits):
    logits = tf.log(softmax(logits))
    return -tf.reduce_sum(labels * logits)

labels = tf.constant([0.5, 0.5])
logits = tf.constant([1000., 0.])

xe = unstable_softmax_cross_entropy(labels, logits)

print(tf.Session().run(xe))  # prints inf

请注意,在这个代码中,当softmax输出接近0时,输出将会接近无穷,这将导致我们的计算不稳定。我们可以通过扩展softmax函数并做一些简化来重写它:

def softmax_cross_entropy(labels, logits):
    scaled_logits = logits - tf.reduce_max(logits)
    normalized_logits = scaled_logits - tf.reduce_logsumexp(scaled_logits)
    return -tf.reduce_sum(labels * normalized_logits)

labels = tf.constant([0.5, 0.5])
logits = tf.constant([1000., 0.])

xe = softmax_cross_entropy(labels, logits)

print(tf.Session().run(xe))  # prints 500.0

我们也可以验证梯度计算也是正确的:

g = tf.gradients(xe, logits)
print(tf.Session().run(g))  # prints [0.5, -0.5]

再次提醒一下,在做梯度下降的时候务必格外小心,以确保函数以及每一层的梯度值都在一个有效的范围内。指数函数和对数函数在使用时也要格外的注意,因为它们可以将小数字映射为大数字,反之亦然。

点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示 | 支付宝红包
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1
本网站由 提供CDN/云存储服务

联系我们