在 Keras/Tensorflow 中实现可训练的广义 Bump 函数层
Implementing a trainable generalized Bump function layer in Keras/Tensorflow
我正在尝试编写 Bump function 的以下变体,应用组件方面:
,
其中σ是可训练的;但它不起作用(下面报告的错误)。
我的尝试:
这是我到目前为止编写的代码(如果有帮助的话)。假设我有两个函数(例如):
def f_True(x):
# Compute Bump Function
bump_value = 1-tf.math.pow(x,2)
bump_value = -tf.math.pow(bump_value,-1)
bump_value = tf.math.exp(bump_value)
return(bump_value)
def f_False(x):
# Compute Bump Function
x_out = 0*x
return(x_out)
class trainable_bump_layer(tf.keras.layers.Layer):
def __init__(self, *args, **kwargs):
super(trainable_bump_layer, self).__init__(*args, **kwargs)
def build(self, input_shape):
self.threshold_level = self.add_weight(name='threshlevel',
shape=[1],
initializer='GlorotUniform',
trainable=True)
def call(self, input):
# Determine Thresholding Logic
The_Logic = tf.math.less(input,self.threshold_level)
# Apply Logic
output_step_3 = tf.cond(The_Logic,
lambda: f_True(input),
lambda: f_False(input))
return output_step_3
错误报告:
Train on 100 samples
Epoch 1/10
WARNING:tensorflow:Gradients do not exist for variables ['reconfiguration_unit_steps_3_3/threshlevel:0'] when minimizing the loss.
WARNING:tensorflow:Gradients do not exist for variables ['reconfiguration_unit_steps_3_3/threshlevel:0'] when minimizing the loss.
32/100 [========>.....................] - ETA: 3s
...
tensorflow:Gradients do not exist for variables
此外,它似乎没有按组件应用(除了不可训练的问题)。可能是什么问题?
不幸的是,检查 x
是否在 (-σ, σ)
内的操作是不可微分的,因此无法通过任何梯度下降方法学习 σ。具体来说,不可能计算关于 self.threshold_level
的梯度,因为 tf.math.less
对于条件是不可微的。
关于逐元素条件,您可以根据健康)状况。例如:
output_step_3 = tf.where(The_Logic, f_True(input), f_False(input))
注意: 我根据提供的代码回答,其中 self.threshold_level
未在 f_True
和 f_False
中使用。如果 self.threshold_level
在提供的公式中用于这些函数,则该函数当然可以相对于 self.threshold_level
.
微分
2020 年 4 月 19 日更新:感谢@今天的澄清。
我建议您尝试使用正态分布而不是凹凸分布。
在我这里的测试中,这个 bump 函数表现不佳(我找不到错误但不要丢弃它,但我的图表显示两个非常尖锐的颠簸,这对网络不利)
使用正态分布,您会得到一个规则且可区分的凸起,您可以控制其高度、宽度和中心。
所以,你可以试试这个功能:
y = a * exp ( - b * (x - c)²)
在一些图表中尝试它,看看它的行为如何。
为此:
class trainable_bump_layer(tf.keras.layers.Layer):
def __init__(self, *args, **kwargs):
super(trainable_bump_layer, self).__init__(*args, **kwargs)
def build(self, input_shape):
#suggested shape (has a different kernel for each input feature/channel)
shape = tuple(1 for _ in input_shape[:-1]) + input_shape[-1:]
#for your desired shape of only 1:
shape = tuple(1 for _ in input_shape) #all ones
#height
self.kernel_a = self.add_weight(name='kernel_a ',
shape=shape
initializer='ones',
trainable=True)
#inverse width
self.kernel_b = self.add_weight(name='kernel_b',
shape=shape
initializer='ones',
trainable=True)
#center
self.kernel_c = self.add_weight(name='kernel_c',
shape=shape
initializer='zeros',
trainable=True)
def call(self, input):
exp_arg = - self.kernel_b * K.square(input - self.kernel_c)
return self.kernel_a * K.exp(exp_arg)
我有点惊讶没有人提到给定警告的主要原因(也是唯一的)!看起来,该代码应该实现 Bump 函数的通用变体;但是,再看一下实现的功能:
def f_True(x):
# Compute Bump Function
bump_value = 1-tf.math.pow(x,2)
bump_value = -tf.math.pow(bump_value,-1)
bump_value = tf.math.exp(bump_value)
return(bump_value)
def f_False(x):
# Compute Bump Function
x_out = 0*x
return(x_out)
错误很明显:在这些函数中没有使用层的可训练权重!所以你收到消息说不存在梯度也就不足为奇了为此:你根本没有使用它,所以没有梯度来更新它!相反,这正是原始的 Bump 函数(即没有可训练的权重)。
但是,你可能会说:"at least, I used the trainable weight in the condition of tf.cond
, so there must be some gradients?!";然而,事实并非如此,让我来澄清一下:
首先,正如您也注意到的,我们对逐元素调节很感兴趣。因此,您需要使用 tf.where
.
而不是 tf.cond
另一个误解是声称因为 tf.less
被用作条件,并且因为它是不可微的,即它相对于它的输入没有梯度(这是真的:对于具有布尔输出 w.r.t 的函数没有定义的梯度。它的实数值输入!),然后导致给定的警告!
- 这是完全错误的!这里的导数将取自 层的输出 w.r.t 可训练权重,并且选择条件不存在于输出中。相反,它只是一个布尔张量,用于确定要选择的输出分支。而已!条件的导数不会被采用,也永远不需要。所以这不是给定警告的原因;原因仅是我上面提到的:可训练权重在层的输出中没有贡献。 (注意:如果关于条件的观点让你有点吃惊,那么想一个简单的例子:ReLU 函数,定义为
relu(x) = 0 if x < 0 else x
。如果条件的导数,即 x < 0
,是considered/needed,它不存在,那么我们将无法在我们的模型中使用 ReLU 并使用基于梯度的优化方法来训练它们!)
(注意:从这里开始,我会将阈值表示为 sigma,就像等式中一样)。
好的!我们找到了执行错误背后的原因。我们可以解决这个问题吗?当然!这是更新后的工作实施:
import tensorflow as tf
from tensorflow.keras.initializers import RandomUniform
from tensorflow.keras.constraints import NonNeg
class BumpLayer(tf.keras.layers.Layer):
def __init__(self, *args, **kwargs):
super(BumpLayer, self).__init__(*args, **kwargs)
def build(self, input_shape):
self.sigma = self.add_weight(
name='sigma',
shape=[1],
initializer=RandomUniform(minval=0.0, maxval=0.1),
trainable=True,
constraint=tf.keras.constraints.NonNeg()
)
super().build(input_shape)
def bump_function(self, x):
return tf.math.exp(-self.sigma / (self.sigma - tf.math.pow(x, 2)))
def call(self, inputs):
greater = tf.math.greater(inputs, -self.sigma)
less = tf.math.less(inputs, self.sigma)
condition = tf.logical_and(greater, less)
output = tf.where(
condition,
self.bump_function(inputs),
0.0
)
return output
关于此实现的几点说明:
我们已将 tf.cond
替换为 tf.where
以进行逐元素调节。
此外,如您所见,与您的实施仅检查不等式的一侧不同,我们使用 tf.math.less
、tf.math.greater
以及 tf.logical_and
找出输入值的大小是否小于 sigma
(或者,我们可以只使用 tf.math.abs
和 tf.math.less
;没有区别!)。让我们重复一遍:以这种方式使用布尔输出函数不会导致任何问题,并且与 derivatives/gradients.
无关
我们还对层学习的 sigma 值使用了非负约束。为什么?因为小于零的 sigma 值没有意义(即当 sigma 为负时,范围 (-sigma, sigma)
定义不正确)。
考虑到前一点,我们注意正确初始化 sigma 值(即初始化为一个小的非负值)。
还有,请不要做0.0 * inputs
这样的事情!它是多余的(而且有点奇怪),它等同于 0.0
;两者的梯度都是 0.0
(w.r.t. inputs
)。将零与张量相乘不会添加任何内容或解决任何现有问题,至少在这种情况下不会!
现在,让我们测试一下它是如何工作的。我们编写了一些辅助函数来根据固定的 sigma 值生成训练数据,并创建一个包含单个 BumpLayer
且输入形状为 (1,)
的模型。让我们看看它是否可以学习用于生成训练数据的 sigma 值:
import numpy as np
def generate_data(sigma, min_x=-1, max_x=1, shape=(100000,1)):
assert sigma >= 0, 'Sigma should be non-negative!'
x = np.random.uniform(min_x, max_x, size=shape)
xp2 = np.power(x, 2)
condition = np.logical_and(x < sigma, x > -sigma)
y = np.where(condition, np.exp(-sigma / (sigma - xp2)), 0.0)
dy = np.where(condition, xp2 * y / np.power((sigma - xp2), 2), 0)
return x, y, dy
def make_model(input_shape=(1,)):
model = tf.keras.Sequential()
model.add(BumpLayer(input_shape=input_shape))
model.compile(loss='mse', optimizer='adam')
return model
# Generate training data using a fixed sigma value.
sigma = 0.5
x, y, _ = generate_data(sigma=sigma, min_x=-0.1, max_x=0.1)
model = make_model()
# Store initial value of sigma, so that it could be compared after training.
sigma_before = model.layers[0].get_weights()[0][0]
model.fit(x, y, epochs=5)
print('Sigma before training:', sigma_before)
print('Sigma after training:', model.layers[0].get_weights()[0][0])
print('Sigma used for generating data:', sigma)
# Sigma before training: 0.08271004
# Sigma after training: 0.5000002
# Sigma used for generating data: 0.5
是的,它可以学习用于生成数据的 sigma 值!但是,是否可以保证它实际上适用于训练数据的所有不同值和 sigma 的初始化?答案是不!其实,有可能你运行上面的代码,训练后得到nan
作为sigma的值,或者inf
作为损失值!所以有什么问题?为什么会产生这个 nan
或 inf
值?让我们在下面讨论...
处理数值稳定性
在构建机器学习模型并使用基于梯度的优化方法对其进行训练时,需要考虑的重要事项之一是模型中运算和计算的数值稳定性。当操作或其梯度生成极大或极小的值时,几乎肯定会破坏训练过程(例如,这是在 CNN 中对图像像素值进行归一化以防止出现此问题的原因之一)。
那么,让我们来看看这个通用的凹凸函数(现在让我们放弃阈值化)。很明显,此函数在 x^2 = sigma
(即 x = sqrt(sigma)
或 x=-sqrt(sigma)
时)具有奇点(即未定义函数或其梯度的点)。下面的动画图显示了凹凸函数(红色实线)及其导数 w.r.t。 sigma(绿色虚线)和 x=sigma
和 x=-sigma
线(两条垂直的蓝色虚线),当 sigma 从零开始增加到 5 时:
如您所见,在奇点区域周围,函数对于 sigma 的所有值都表现不佳,因为函数及其导数在这些区域都取了极大的值。因此,对于特定的 sigma 值,给定这些区域的输入值,将生成爆炸式输出和梯度值,因此会出现 inf
损失值的问题。
更进一步,tf.where
存在问题行为,导致层中 sigma 变量的 nan
值问题:令人惊讶的是,如果 [= 的非活动分支中的生成值17=] 非常大或 inf
,它与 bump 函数一起导致非常大或 inf
梯度值,那么 tf.where
的梯度将是 nan
,尽管事实上,inf
在 inactive 分支中,甚至没有被选中(请参阅 Github issue,其中讨论了这一点)!!
那么 tf.where
的这种行为有什么解决方法吗?是的,实际上有一个技巧可以解决这个问题,在 中有解释:基本上我们可以使用额外的 tf.where
来防止函数应用于这些区域。换句话说,我们不是在任何输入值上应用 self.bump_function
,而是过滤那些不在 (-self.sigma, self.sigma)
范围内的值(即函数应该应用的实际范围),而不是用零(始终产生安全值,即等于 exp(-1)
):
output = tf.where(
condition,
self.bump_function(tf.where(condition, inputs, 0.0)),
0.0
)
应用此修复程序将完全解决 sigma 的 nan
值问题。让我们用不同的 sigma 值生成的训练数据值对其进行评估,看看它的表现如何:
true_learned_sigma = []
for s in np.arange(0.1, 10.0, 0.1):
model = make_model()
x, y, dy = generate_data(sigma=s, shape=(100000,1))
model.fit(x, y, epochs=3 if s < 1 else (5 if s < 5 else 10), verbose=False)
sigma = model.layers[0].get_weights()[0][0]
true_learned_sigma.append([s, sigma])
print(s, sigma)
# Check if the learned values of sigma
# are actually close to true values of sigma, for all the experiments.
res = np.array(true_learned_sigma)
print(np.allclose(res[:,0], res[:,1], atol=1e-2))
# True
它可以正确学习所有的sigma值!那很好。该解决方法有效!不过,有一个警告:如果该层的输入值大于 -1 且小于 1(即,这是我们的 generate_data
函数的默认情况),这保证可以正常工作并学习任何 sigma 值;否则,仍然存在 inf
损失值的问题,如果输入值的幅度大于 1,则可能会发生这种情况(请参见下面的点 #1 和 #2)。
这里有一些值得好奇和感兴趣的人思考的食物:
刚刚提到如果这一层的输入值大于1或者小于-1,那么可能会出问题。你能争论为什么会这样吗? (提示:使用上面的动画图并考虑 sigma > 1
且输入值介于 sqrt(sigma)
和 sigma
之间(或介于 -sigma
和 -sqrt(sigma)
.)
您能否针对第 1 点中的问题提供解决方案,即该层可以适用于所有输入值? (提示:就像 tf.where
的变通方法一样,考虑如何进一步过滤掉 不安全值 可以应用 bump 函数并生成爆炸output/gradient.)
但是,如果你不想解决这个问题,并且想在模型中使用这个层,那么你如何保证这个层的输入值是总是在 -1 和 1 之间? (提示:作为一种解决方案,有一个常用的激活函数,它产生的值正好在这个范围内,并且可以潜在地用作该层之前的层的激活函数。)
如果你看一下最后的代码片段,你会发现我们使用了 epochs=3 if s < 1 else (5 if s < 5 else 10)
。这是为什么?为什么大的 sigma 值需要更多的 epochs 来学习? (提示:再次使用动画图,并考虑随着 sigma 值的增加,输入值在 -1 和 1 之间的函数的导数。它们的大小是多少?)
我们是否还需要检查生成的训练数据是否存在任何 nan
、inf
或极大的 y
值并将其过滤掉? (提示:是的,如果 sigma > 1
和值的范围,即 min_x
和 max_x
,落在 (-1, 1)
之外;否则,不,没有必要!这是为什么?留作练习!)
我正在尝试编写 Bump function 的以下变体,应用组件方面:
其中σ是可训练的;但它不起作用(下面报告的错误)。
我的尝试:
这是我到目前为止编写的代码(如果有帮助的话)。假设我有两个函数(例如):
def f_True(x):
# Compute Bump Function
bump_value = 1-tf.math.pow(x,2)
bump_value = -tf.math.pow(bump_value,-1)
bump_value = tf.math.exp(bump_value)
return(bump_value)
def f_False(x):
# Compute Bump Function
x_out = 0*x
return(x_out)
class trainable_bump_layer(tf.keras.layers.Layer):
def __init__(self, *args, **kwargs):
super(trainable_bump_layer, self).__init__(*args, **kwargs)
def build(self, input_shape):
self.threshold_level = self.add_weight(name='threshlevel',
shape=[1],
initializer='GlorotUniform',
trainable=True)
def call(self, input):
# Determine Thresholding Logic
The_Logic = tf.math.less(input,self.threshold_level)
# Apply Logic
output_step_3 = tf.cond(The_Logic,
lambda: f_True(input),
lambda: f_False(input))
return output_step_3
错误报告:
Train on 100 samples
Epoch 1/10
WARNING:tensorflow:Gradients do not exist for variables ['reconfiguration_unit_steps_3_3/threshlevel:0'] when minimizing the loss.
WARNING:tensorflow:Gradients do not exist for variables ['reconfiguration_unit_steps_3_3/threshlevel:0'] when minimizing the loss.
32/100 [========>.....................] - ETA: 3s
...
tensorflow:Gradients do not exist for variables
此外,它似乎没有按组件应用(除了不可训练的问题)。可能是什么问题?
不幸的是,检查 x
是否在 (-σ, σ)
内的操作是不可微分的,因此无法通过任何梯度下降方法学习 σ。具体来说,不可能计算关于 self.threshold_level
的梯度,因为 tf.math.less
对于条件是不可微的。
关于逐元素条件,您可以根据健康)状况。例如:
output_step_3 = tf.where(The_Logic, f_True(input), f_False(input))
注意: 我根据提供的代码回答,其中 self.threshold_level
未在 f_True
和 f_False
中使用。如果 self.threshold_level
在提供的公式中用于这些函数,则该函数当然可以相对于 self.threshold_level
.
2020 年 4 月 19 日更新:感谢@今天的澄清。
我建议您尝试使用正态分布而不是凹凸分布。 在我这里的测试中,这个 bump 函数表现不佳(我找不到错误但不要丢弃它,但我的图表显示两个非常尖锐的颠簸,这对网络不利)
使用正态分布,您会得到一个规则且可区分的凸起,您可以控制其高度、宽度和中心。
所以,你可以试试这个功能:
y = a * exp ( - b * (x - c)²)
在一些图表中尝试它,看看它的行为如何。
为此:
class trainable_bump_layer(tf.keras.layers.Layer):
def __init__(self, *args, **kwargs):
super(trainable_bump_layer, self).__init__(*args, **kwargs)
def build(self, input_shape):
#suggested shape (has a different kernel for each input feature/channel)
shape = tuple(1 for _ in input_shape[:-1]) + input_shape[-1:]
#for your desired shape of only 1:
shape = tuple(1 for _ in input_shape) #all ones
#height
self.kernel_a = self.add_weight(name='kernel_a ',
shape=shape
initializer='ones',
trainable=True)
#inverse width
self.kernel_b = self.add_weight(name='kernel_b',
shape=shape
initializer='ones',
trainable=True)
#center
self.kernel_c = self.add_weight(name='kernel_c',
shape=shape
initializer='zeros',
trainable=True)
def call(self, input):
exp_arg = - self.kernel_b * K.square(input - self.kernel_c)
return self.kernel_a * K.exp(exp_arg)
我有点惊讶没有人提到给定警告的主要原因(也是唯一的)!看起来,该代码应该实现 Bump 函数的通用变体;但是,再看一下实现的功能:
def f_True(x):
# Compute Bump Function
bump_value = 1-tf.math.pow(x,2)
bump_value = -tf.math.pow(bump_value,-1)
bump_value = tf.math.exp(bump_value)
return(bump_value)
def f_False(x):
# Compute Bump Function
x_out = 0*x
return(x_out)
错误很明显:在这些函数中没有使用层的可训练权重!所以你收到消息说不存在梯度也就不足为奇了为此:你根本没有使用它,所以没有梯度来更新它!相反,这正是原始的 Bump 函数(即没有可训练的权重)。
但是,你可能会说:"at least, I used the trainable weight in the condition of tf.cond
, so there must be some gradients?!";然而,事实并非如此,让我来澄清一下:
首先,正如您也注意到的,我们对逐元素调节很感兴趣。因此,您需要使用
tf.where
. 而不是 另一个误解是声称因为
tf.less
被用作条件,并且因为它是不可微的,即它相对于它的输入没有梯度(这是真的:对于具有布尔输出 w.r.t 的函数没有定义的梯度。它的实数值输入!),然后导致给定的警告!- 这是完全错误的!这里的导数将取自 层的输出 w.r.t 可训练权重,并且选择条件不存在于输出中。相反,它只是一个布尔张量,用于确定要选择的输出分支。而已!条件的导数不会被采用,也永远不需要。所以这不是给定警告的原因;原因仅是我上面提到的:可训练权重在层的输出中没有贡献。 (注意:如果关于条件的观点让你有点吃惊,那么想一个简单的例子:ReLU 函数,定义为
relu(x) = 0 if x < 0 else x
。如果条件的导数,即x < 0
,是considered/needed,它不存在,那么我们将无法在我们的模型中使用 ReLU 并使用基于梯度的优化方法来训练它们!)
- 这是完全错误的!这里的导数将取自 层的输出 w.r.t 可训练权重,并且选择条件不存在于输出中。相反,它只是一个布尔张量,用于确定要选择的输出分支。而已!条件的导数不会被采用,也永远不需要。所以这不是给定警告的原因;原因仅是我上面提到的:可训练权重在层的输出中没有贡献。 (注意:如果关于条件的观点让你有点吃惊,那么想一个简单的例子:ReLU 函数,定义为
tf.cond
(注意:从这里开始,我会将阈值表示为 sigma,就像等式中一样)。
好的!我们找到了执行错误背后的原因。我们可以解决这个问题吗?当然!这是更新后的工作实施:
import tensorflow as tf
from tensorflow.keras.initializers import RandomUniform
from tensorflow.keras.constraints import NonNeg
class BumpLayer(tf.keras.layers.Layer):
def __init__(self, *args, **kwargs):
super(BumpLayer, self).__init__(*args, **kwargs)
def build(self, input_shape):
self.sigma = self.add_weight(
name='sigma',
shape=[1],
initializer=RandomUniform(minval=0.0, maxval=0.1),
trainable=True,
constraint=tf.keras.constraints.NonNeg()
)
super().build(input_shape)
def bump_function(self, x):
return tf.math.exp(-self.sigma / (self.sigma - tf.math.pow(x, 2)))
def call(self, inputs):
greater = tf.math.greater(inputs, -self.sigma)
less = tf.math.less(inputs, self.sigma)
condition = tf.logical_and(greater, less)
output = tf.where(
condition,
self.bump_function(inputs),
0.0
)
return output
关于此实现的几点说明:
我们已将
tf.cond
替换为tf.where
以进行逐元素调节。此外,如您所见,与您的实施仅检查不等式的一侧不同,我们使用
tf.math.less
、tf.math.greater
以及tf.logical_and
找出输入值的大小是否小于sigma
(或者,我们可以只使用tf.math.abs
和tf.math.less
;没有区别!)。让我们重复一遍:以这种方式使用布尔输出函数不会导致任何问题,并且与 derivatives/gradients. 无关
我们还对层学习的 sigma 值使用了非负约束。为什么?因为小于零的 sigma 值没有意义(即当 sigma 为负时,范围
(-sigma, sigma)
定义不正确)。考虑到前一点,我们注意正确初始化 sigma 值(即初始化为一个小的非负值)。
还有,请不要做
0.0 * inputs
这样的事情!它是多余的(而且有点奇怪),它等同于0.0
;两者的梯度都是0.0
(w.r.t.inputs
)。将零与张量相乘不会添加任何内容或解决任何现有问题,至少在这种情况下不会!
现在,让我们测试一下它是如何工作的。我们编写了一些辅助函数来根据固定的 sigma 值生成训练数据,并创建一个包含单个 BumpLayer
且输入形状为 (1,)
的模型。让我们看看它是否可以学习用于生成训练数据的 sigma 值:
import numpy as np
def generate_data(sigma, min_x=-1, max_x=1, shape=(100000,1)):
assert sigma >= 0, 'Sigma should be non-negative!'
x = np.random.uniform(min_x, max_x, size=shape)
xp2 = np.power(x, 2)
condition = np.logical_and(x < sigma, x > -sigma)
y = np.where(condition, np.exp(-sigma / (sigma - xp2)), 0.0)
dy = np.where(condition, xp2 * y / np.power((sigma - xp2), 2), 0)
return x, y, dy
def make_model(input_shape=(1,)):
model = tf.keras.Sequential()
model.add(BumpLayer(input_shape=input_shape))
model.compile(loss='mse', optimizer='adam')
return model
# Generate training data using a fixed sigma value.
sigma = 0.5
x, y, _ = generate_data(sigma=sigma, min_x=-0.1, max_x=0.1)
model = make_model()
# Store initial value of sigma, so that it could be compared after training.
sigma_before = model.layers[0].get_weights()[0][0]
model.fit(x, y, epochs=5)
print('Sigma before training:', sigma_before)
print('Sigma after training:', model.layers[0].get_weights()[0][0])
print('Sigma used for generating data:', sigma)
# Sigma before training: 0.08271004
# Sigma after training: 0.5000002
# Sigma used for generating data: 0.5
是的,它可以学习用于生成数据的 sigma 值!但是,是否可以保证它实际上适用于训练数据的所有不同值和 sigma 的初始化?答案是不!其实,有可能你运行上面的代码,训练后得到nan
作为sigma的值,或者inf
作为损失值!所以有什么问题?为什么会产生这个 nan
或 inf
值?让我们在下面讨论...
处理数值稳定性
在构建机器学习模型并使用基于梯度的优化方法对其进行训练时,需要考虑的重要事项之一是模型中运算和计算的数值稳定性。当操作或其梯度生成极大或极小的值时,几乎肯定会破坏训练过程(例如,这是在 CNN 中对图像像素值进行归一化以防止出现此问题的原因之一)。
那么,让我们来看看这个通用的凹凸函数(现在让我们放弃阈值化)。很明显,此函数在 x^2 = sigma
(即 x = sqrt(sigma)
或 x=-sqrt(sigma)
时)具有奇点(即未定义函数或其梯度的点)。下面的动画图显示了凹凸函数(红色实线)及其导数 w.r.t。 sigma(绿色虚线)和 x=sigma
和 x=-sigma
线(两条垂直的蓝色虚线),当 sigma 从零开始增加到 5 时:
如您所见,在奇点区域周围,函数对于 sigma 的所有值都表现不佳,因为函数及其导数在这些区域都取了极大的值。因此,对于特定的 sigma 值,给定这些区域的输入值,将生成爆炸式输出和梯度值,因此会出现 inf
损失值的问题。
更进一步,tf.where
存在问题行为,导致层中 sigma 变量的 nan
值问题:令人惊讶的是,如果 [= 的非活动分支中的生成值17=] 非常大或 inf
,它与 bump 函数一起导致非常大或 inf
梯度值,那么 tf.where
的梯度将是 nan
,尽管事实上,inf
在 inactive 分支中,甚至没有被选中(请参阅 Github issue,其中讨论了这一点)!!
那么 tf.where
的这种行为有什么解决方法吗?是的,实际上有一个技巧可以解决这个问题,在 tf.where
来防止函数应用于这些区域。换句话说,我们不是在任何输入值上应用 self.bump_function
,而是过滤那些不在 (-self.sigma, self.sigma)
范围内的值(即函数应该应用的实际范围),而不是用零(始终产生安全值,即等于 exp(-1)
):
output = tf.where(
condition,
self.bump_function(tf.where(condition, inputs, 0.0)),
0.0
)
应用此修复程序将完全解决 sigma 的 nan
值问题。让我们用不同的 sigma 值生成的训练数据值对其进行评估,看看它的表现如何:
true_learned_sigma = []
for s in np.arange(0.1, 10.0, 0.1):
model = make_model()
x, y, dy = generate_data(sigma=s, shape=(100000,1))
model.fit(x, y, epochs=3 if s < 1 else (5 if s < 5 else 10), verbose=False)
sigma = model.layers[0].get_weights()[0][0]
true_learned_sigma.append([s, sigma])
print(s, sigma)
# Check if the learned values of sigma
# are actually close to true values of sigma, for all the experiments.
res = np.array(true_learned_sigma)
print(np.allclose(res[:,0], res[:,1], atol=1e-2))
# True
它可以正确学习所有的sigma值!那很好。该解决方法有效!不过,有一个警告:如果该层的输入值大于 -1 且小于 1(即,这是我们的 generate_data
函数的默认情况),这保证可以正常工作并学习任何 sigma 值;否则,仍然存在 inf
损失值的问题,如果输入值的幅度大于 1,则可能会发生这种情况(请参见下面的点 #1 和 #2)。
这里有一些值得好奇和感兴趣的人思考的食物:
刚刚提到如果这一层的输入值大于1或者小于-1,那么可能会出问题。你能争论为什么会这样吗? (提示:使用上面的动画图并考虑
sigma > 1
且输入值介于sqrt(sigma)
和sigma
之间(或介于-sigma
和-sqrt(sigma)
.)您能否针对第 1 点中的问题提供解决方案,即该层可以适用于所有输入值? (提示:就像
tf.where
的变通方法一样,考虑如何进一步过滤掉 不安全值 可以应用 bump 函数并生成爆炸output/gradient.)但是,如果你不想解决这个问题,并且想在模型中使用这个层,那么你如何保证这个层的输入值是总是在 -1 和 1 之间? (提示:作为一种解决方案,有一个常用的激活函数,它产生的值正好在这个范围内,并且可以潜在地用作该层之前的层的激活函数。)
如果你看一下最后的代码片段,你会发现我们使用了
epochs=3 if s < 1 else (5 if s < 5 else 10)
。这是为什么?为什么大的 sigma 值需要更多的 epochs 来学习? (提示:再次使用动画图,并考虑随着 sigma 值的增加,输入值在 -1 和 1 之间的函数的导数。它们的大小是多少?)我们是否还需要检查生成的训练数据是否存在任何
nan
、inf
或极大的y
值并将其过滤掉? (提示:是的,如果sigma > 1
和值的范围,即min_x
和max_x
,落在(-1, 1)
之外;否则,不,没有必要!这是为什么?留作练习!)