目录
1、 nan 和inf产生原因
搭建神经网络后产生的,在训练早期,模型参数可能不是很合适,会出现梯度消失和爆炸的情况,特别是有lstm,rnn这类网络的情况。nan 是not a number ,inf是无穷大。比如求损失函数会用到log,如果输入接近0,那么结果就是inf。
2、解决方法
2.1、学习率要设置小一些
2.2、使用带上限的激活函数,如tf.nn.relu6
也就是对输出做了限制
2.3、检查输入数据中是否有nan或inf
x = tf.constant([5.0, 4.8, 6.8, np.inf, np.nan])
x=tf.math.is_finite(x) #[True, True, True, False, False]
test=tf.reduce_all(x) # False
assert test
pytorch
torch.isnan(x)
2.4、对loss部分做处理
loss中出现nan或inf时,用loss对参数求导显然是有问题的
logits处做处理
训练深度学习网络时候,出现Nan是什么原因,怎么才能避免? - 知乎
可以看到有一个nan,处理方法是:
y_=[0.0,1]
y_conv=[0.0,1]
y_conv=tf.clip_by_value(y_conv,1e-10,1.0) #使用这个数值来做处理
cross_entropy1 = y_*tf.math.log(y_conv)
cross_entropy2 =-tf.reduce_sum(cross_entropy1)
另一种处理方式:
def mask_nan(x,value):
'''
用value值来代替nan 或inf
'''
x_values= tf.add(tf.zeros_like(x),value)
mask = tf.math.is_finite(x)
y = tf.where(mask,x,x_values)
return y
y_=[0.0,1]
y_conv=[0.0,1]
cross_entropy1 = y_*tf.math.log(y_conv)
cross_entropy1 = mask_nan(cross_entropy1,3)#输出<tf.Tensor: id=34332, shape=(2,), dtype=float32, numpy=array([3., 0.], dtype=float32)>
cross_entropy2 =-tf.reduce_sum(cross_entropy1)
2.5、对梯度的处理
def log1pexp(x):
return tf.math.log(1 + tf.exp(x))
def grad_log1pexp(x):
with tf.GradientTape() as tape:
tape.watch(x)
value = log1pexp(x)
return tape.gradient(value, x)
grad_log1pexp(tf.constant(0.)).numpy()# 输出0.5
grad_log1pexp(tf.constant(100.)).numpy()#输出nan
#或者使用
theoretical, numerical=tf.test.compute_gradient(log1pexp,[tf.constant(0.)])
print(theoretical, numerical)#输出(array([[0.5]], dtype=float32),) (array([[0.49996373]], dtype=float32),)
theoretical, numerical=tf.test.compute_gradient(log1pexp,[tf.constant(100.)])
print(theoretical, numerical)#输出(array([[nan]], dtype=float32),) (array([[nan]], dtype=float32),)
可以看到x = 100 由于数值的不稳定性而失败。
所以在求出梯度后,反向传播前,我们可以对gradient处理:
def log1pexp(x):
return tf.math.log(1 + tf.exp(x))
def grad_log1pexp(x):
with tf.GradientTape() as tape:
tape.watch(x)
value = log1pexp(x)
gradients = tape.gradient(value, x)
return gradients
#有三种处理梯度的方法,先用合适的处理就行。
gradients,_ = tf.clip_by_global_norm(gradients,clip_norm=1)
gradients = tf.clip_by_value(gradients,clip_value_min=0,clip_value_max=1)
gradients = tf.clip_by_norm(gradients,clip_norm=1)
#处理完之后,做如下类似操作
optimizer = tf.keras.optimizers.Adam(1e-3)
optimizer.apply_gradients(zip(gradients, variables))
以上是tensorflow的方法,其它框架类似处理。
2.6 手动梯度重写
@tf.custom_gradient
def log1pexp(x):
e = tf.exp(x)
def grad(dy):
return dy * (1 - 1 / (1 + e)) #dy是上一层的梯度,用链式法则,后边乘的是自己产生的梯度
return tf.math.log(1 + e), grad
def grad_log1pexp(x):
with tf.GradientTape() as tape:
tape.watch(x)
value = log1pexp(x)
return tape.gradient(value, x)
2.7、其它操作
限制梯度更新过程中,梯度的最大范数是2. 还有其它的constrants参见官方文档
from tensorflow.keras.constraints import max_norm
model.add(Dense(64, kernel_constraint=max_norm(2.)))
这个操作不是对梯度的操作,而是在反向更新后,kernel的norm变的不符合kernel_constraint中的要求后,直接对kernel作改变。
还有一种方法:
tf.keras.optimizers.Adam(
learning_rate=0.001,
beta_1=0.9,
beta_2=0.999,
epsilon=1e-07,
amsgrad=False,
name='Adam',
**kwargs
)
比如设置clipnorm=1,意味着,如查一个变量的梯度的范数超过了1,那么这个梯度就会用梯度正则化,grads/norm(grads),来使梯度的范数变成1.
比如clipvalue=0.5,意味着,梯度中的值小于-0.5,就会置为-0.5,如果大于0.5那么就置为0.5.
来个例子:
from sklearn.datasets import make_regression
from keras.layers import Dense
from keras.models import Sequential
from keras.optimizers import SGD
from matplotlib import pyplot
# generate regression dataset
X, y = make_regression(n_samples=1000, n_features=20, noise=0.1, random_state=1)
# split into train and test
n_train = 500
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]
# define model
model = Sequential()
model.add(Dense(25, input_dim=20, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(1, activation='linear'))
# compile model
opt = SGD(lr=0.01, momentum=0.9, clipvalue=5.0)
model.compile(loss='mean_squared_error', optimizer=opt)
# fit model
history = model.fit(trainX, trainy, validation_data=(testX, testy), epochs=100, verbose=0)
# evaluate the model
train_mse = model.evaluate(trainX, trainy, verbose=0)
test_mse = model.evaluate(testX, testy, verbose=0)
print('Train: %.3f, Test: %.3f' % (train_mse, test_mse))
# plot loss during training
pyplot.title('Mean Squared Error')
pyplot.plot(history.history['loss'], label='train')
pyplot.plot(history.history['val_loss'], label='test')
pyplot.legend()
pyplot.show()
opt = SGD(lr=0.01, momentum=0.9, clipvalue=5.0) 其中加入clipvalue是可以收敛训练的,但如果不加则loss等于Nan
但同时发现个问题,在keras配合tensorflow1.x时这个参数是起作用的,但在tensorflow 2.x中使用tf.keras则没有效果。
所以 tensorflow2.x中 对梯度进行clip的方法是,如下:
import tensorflow as tf
import numpy as np
from sklearn.datasets import make_regression
from tensorflow.keras.layers import Dense
from tensorflow.keras.models import Sequential
from tensorflow.keras.optimizers import SGD
from matplotlib import pyplot
# generate regression dataset
X, y = make_regression(n_samples=1000, n_features=20, noise=0.1, random_state=1)
X = X.astype(np.float32)
y = y.astype(np.float32)
# split into train and test
n_train = 500
trainX, testX = X[:n_train, :], X[n_train:, :]
trainy, testy = y[:n_train], y[n_train:]
# define model
model = Sequential()
model.add(Dense(25, input_dim=20, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(1, activation='linear'))
# compile model
# opt = SGD(lr=0.01, momentum=0.9)
opt = SGD(lr=0.01, momentum=0.9)
train_ds = tf.data.Dataset.from_tensor_slices((trainX,trainy)).shuffle(10000).batch(32)
test_ds = tf.data.Dataset.from_tensor_slices((testX,testy)).batch(32)
train_loss = tf.keras.metrics.Mean()
test_loss = tf.keras.metrics.Mean()
epochs = 100
for epoch in range(epochs):
for x,y in train_ds:
with tf.GradientTape() as tape:
y_=model(x)
loss = tf.keras.losses.MeanSquaredError()(y,y_)
gradients = tape.gradient(loss,model.trainable_variables)
gradients = [tf.clip_by_norm(gradient,tf.constant(1.)) for gradient in gradients]# 这一行很重要
opt.apply_gradients(zip(gradients,model.trainable_variables))
train_loss(loss)
print(train_loss.result().numpy())
for x,y,in test_ds:
y_=model(x)
loss = tf.keras.losses.MeanSquaredError()(y,y_)
test_loss(loss)
print(test_loss.result().numpy())
train_loss.reset_states()
test_loss.reset_states()
总结
以后肯定会向tensorflow2.x走,所以可以使用tf.clip_by_XXX来专门来对梯度处理,或都是对loss使用我定义的mask_nan这种方式来处理。总的处理方式可以按照我写的博客来依次使用。