在 Keras 中,使用 SGD,为什么 model.fit() 训练顺利,但逐步训练方法给出爆炸梯度和损失
In Keras, using SGD, why model.fit() trains smoothly, but step wise training method gives exploding gradient and loss
因为这种爆炸式的梯度和爆炸式的损失发生在网络很大的时候,所以我不在这里打扰post整个网络。但我已经尽力了,在过去的两周里,我深入研究了源代码的细节以监控一些权重,手动编写更新步骤以监控损失、权重、更新、梯度和超参数以与内部进行比较状态。我想我在这里问之前做了一些功课。
问题是有两种使用 Keras 的训练方法 API,一种是 model.fit()
,第二种是针对更复杂的训练和网络定制的一种方法,但我几乎保持一切相同彼此, model.fit()
没有爆炸损失,但自定义方法给出了爆炸。有趣的是,当我在一个小得多的网络下监控许多细节时,两种方法看起来一切都一样。
环境:
# tensorflow 1.14
import tensorflow as tf
from tensorflow.keras import backend as K
对于model.fit()
方法:
# I skipped the details of the below two lines as I couldn't share the very details. but x is [10000, 32, 32, 3] image data, y is [10000, 10, 1] label. model is regular Keras model.
x_train, y_train, x_test, y_test = get_data()
model = get_keras_model()
loss_fn = tf.keras.losses.CategoricalCrossentropy()
sgd = tf.keras.optimizers.SGD(lr=.1, momentum=0.9, nesterov=True)
model.compile(loss=loss_fn, optimizer=sgd, metrics=['accuracy'])
history = model.fit(x_train, y_train, batch_size=128, epochs=100, validation_data=(x_test, y_test))
自定义方法:
x_train, y_train, x_test, y_test = get_data()
model = get_keras_model()
input = model.inputs[0]
y_true = tf.placeholder(dtype = tf.int32, shape = [None, 10])
y_pred = model.outputs[0]
loss_fn = tf.keras.losses.CategoricalCrossentropy()
loss = loss_fn(y_true, y_pred)
weights = model.trainable_weights
sgd = tf.keras.optimizers.SGD(lr=.1, momentum=0.9, nesterov=True)
training_updates = sgd.get_updates(loss, weights)
training_fn = K.function([y_true, input], [loss], training_updates)
num_train = 10000
steps_per_epoch = int(num_train / 128) # batch size 128
total_steps = steps_per_epoch * 100 # epoch 100
for step in total_steps:
idx = np.random.randint(0, 10000, 128)
input_img = x_train[idx]
ground_true = y_train[idx]
cur_loss = training_fn([ground_true, input_img])
简而言之,相同的模型、相同的损失函数、相同的优化器 SGD、相同的图像馈送(我确实控制图像馈送顺序,尽管这里的代码是从训练数据中随机选择的)。 model.fit()
的内部过程中是否有任何东西可以防止损失或梯度爆炸?
深挖源码后,我找到了梯度爆炸的原因,正确的代码(最小改动如下):
x_train, y_train, x_test, y_test = get_data()
model = get_keras_model()
input = model.inputs[0]
y_true = tf.placeholder(dtype = tf.int32, shape = [None, 10])
y_pred = model.outputs[0]
loss_fn = tf.keras.losses.CategoricalCrossentropy()
loss = loss_fn(y_true, y_pred)
weights = model.trainable_weights
sgd = tf.keras.optimizers.SGD(lr=.1, momentum=0.9, nesterov=True)
training_updates = sgd.get_updates(loss, weights)
# Correct:
training_fn = K.function([y_true, input, K.symbolic_learning_phase()], [loss], training_updates)
# Before:
# training_fn = K.function([y_true, input], [loss], training_updates)
num_train = 10000
steps_per_epoch = int(num_train / 128) # batch size 128
total_steps = steps_per_epoch * 100 # epoch 100
for step in total_steps:
idx = np.random.randint(0, 10000, 128)
input_img = x_train[idx]
ground_true = y_train[idx]
# Correct:
cur_loss = training_fn([ground_true, input_img, True])
# Before:
# cur_loss = training_fn([ground_true, input_img])
我对这个特定 Tensor K.symbolic_learning_phase()
的理解是它有默认值要设置 False
(如果你在初始化时检查源代码),BatchNormalization
和 Dropout
层等在训练阶段和测试阶段表现不同。在这种情况下,BatchNormalization
层是梯度爆炸的原因(现在有些帖子提到他们使用 BatchNormalization
层进行梯度爆炸是有道理的)这是因为它的两个可训练权重 batch_normalization_1/gamma:0
并且 batch_normalization_1/beta:0
取决于此 Tensor 并使用默认值 False
他们没有学习并且他们的权重在训练期间变得 nan
非常快。
我注意到使用这种 training_updates
方法的 Keras 代码并没有真正将 K.symbolic_learning_phase()
放入他们的代码中,但是,这是 Keras 的 API 在幕后所做的。
因为这种爆炸式的梯度和爆炸式的损失发生在网络很大的时候,所以我不在这里打扰post整个网络。但我已经尽力了,在过去的两周里,我深入研究了源代码的细节以监控一些权重,手动编写更新步骤以监控损失、权重、更新、梯度和超参数以与内部进行比较状态。我想我在这里问之前做了一些功课。
问题是有两种使用 Keras 的训练方法 API,一种是 model.fit()
,第二种是针对更复杂的训练和网络定制的一种方法,但我几乎保持一切相同彼此, model.fit()
没有爆炸损失,但自定义方法给出了爆炸。有趣的是,当我在一个小得多的网络下监控许多细节时,两种方法看起来一切都一样。
环境:
# tensorflow 1.14
import tensorflow as tf
from tensorflow.keras import backend as K
对于model.fit()
方法:
# I skipped the details of the below two lines as I couldn't share the very details. but x is [10000, 32, 32, 3] image data, y is [10000, 10, 1] label. model is regular Keras model.
x_train, y_train, x_test, y_test = get_data()
model = get_keras_model()
loss_fn = tf.keras.losses.CategoricalCrossentropy()
sgd = tf.keras.optimizers.SGD(lr=.1, momentum=0.9, nesterov=True)
model.compile(loss=loss_fn, optimizer=sgd, metrics=['accuracy'])
history = model.fit(x_train, y_train, batch_size=128, epochs=100, validation_data=(x_test, y_test))
自定义方法:
x_train, y_train, x_test, y_test = get_data()
model = get_keras_model()
input = model.inputs[0]
y_true = tf.placeholder(dtype = tf.int32, shape = [None, 10])
y_pred = model.outputs[0]
loss_fn = tf.keras.losses.CategoricalCrossentropy()
loss = loss_fn(y_true, y_pred)
weights = model.trainable_weights
sgd = tf.keras.optimizers.SGD(lr=.1, momentum=0.9, nesterov=True)
training_updates = sgd.get_updates(loss, weights)
training_fn = K.function([y_true, input], [loss], training_updates)
num_train = 10000
steps_per_epoch = int(num_train / 128) # batch size 128
total_steps = steps_per_epoch * 100 # epoch 100
for step in total_steps:
idx = np.random.randint(0, 10000, 128)
input_img = x_train[idx]
ground_true = y_train[idx]
cur_loss = training_fn([ground_true, input_img])
简而言之,相同的模型、相同的损失函数、相同的优化器 SGD、相同的图像馈送(我确实控制图像馈送顺序,尽管这里的代码是从训练数据中随机选择的)。 model.fit()
的内部过程中是否有任何东西可以防止损失或梯度爆炸?
深挖源码后,我找到了梯度爆炸的原因,正确的代码(最小改动如下):
x_train, y_train, x_test, y_test = get_data()
model = get_keras_model()
input = model.inputs[0]
y_true = tf.placeholder(dtype = tf.int32, shape = [None, 10])
y_pred = model.outputs[0]
loss_fn = tf.keras.losses.CategoricalCrossentropy()
loss = loss_fn(y_true, y_pred)
weights = model.trainable_weights
sgd = tf.keras.optimizers.SGD(lr=.1, momentum=0.9, nesterov=True)
training_updates = sgd.get_updates(loss, weights)
# Correct:
training_fn = K.function([y_true, input, K.symbolic_learning_phase()], [loss], training_updates)
# Before:
# training_fn = K.function([y_true, input], [loss], training_updates)
num_train = 10000
steps_per_epoch = int(num_train / 128) # batch size 128
total_steps = steps_per_epoch * 100 # epoch 100
for step in total_steps:
idx = np.random.randint(0, 10000, 128)
input_img = x_train[idx]
ground_true = y_train[idx]
# Correct:
cur_loss = training_fn([ground_true, input_img, True])
# Before:
# cur_loss = training_fn([ground_true, input_img])
我对这个特定 Tensor K.symbolic_learning_phase()
的理解是它有默认值要设置 False
(如果你在初始化时检查源代码),BatchNormalization
和 Dropout
层等在训练阶段和测试阶段表现不同。在这种情况下,BatchNormalization
层是梯度爆炸的原因(现在有些帖子提到他们使用 BatchNormalization
层进行梯度爆炸是有道理的)这是因为它的两个可训练权重 batch_normalization_1/gamma:0
并且 batch_normalization_1/beta:0
取决于此 Tensor 并使用默认值 False
他们没有学习并且他们的权重在训练期间变得 nan
非常快。
我注意到使用这种 training_updates
方法的 Keras 代码并没有真正将 K.symbolic_learning_phase()
放入他们的代码中,但是,这是 Keras 的 API 在幕后所做的。