重新加载后,Keras模型参数全部为“NaN”

我使用了Resnet50进行迁移学习。我基于Keras提供的预训练模型(’imagenet’)创建了一个新模型。

训练我的新模型后,我按以下方式保存它:

# 保存Siamese网络结构siamese_model_json = siamese_network.to_json()with open("saved_model/siamese_network_arch.json", "w") as json_file:    json_file.write(siamese_model_json)# 保存Siamese网络模型权重siamese_network.save_weights('saved_model/siamese_model_weights.h5')

之后,我按以下方式重新加载模型以进行一些预测:

json_file = open('saved_model/siamese_network_arch.json', 'r')loaded_model_json = json_file.read()json_file.close()siamese_network = model_from_json(loaded_model_json)# 将权重加载到新模型中siamese_network.load_weights('saved_model/siamese_model_weights.h5')

然后,我检查权重是否合理(来自其中一层):

print("bn3d_branch2c:\n",      siamese_network.get_layer('model_1').get_layer('bn3d_branch2c').get_weights())

如果我只训练我的网络1个epoch,我会看到合理的值…

但是,如果我训练我的模型18个epoch(由于我的电脑非常慢,这需要5-6个小时),我只会看到NaN值如下:

bn3d_branch2c: [array([nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan,       nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan,       nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan,       nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan,       nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan,       nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan,       ...

这里有什么技巧吗?

补充1:

这是我创建模型的方式。

在这里,我有一个triplet_loss函数,稍后我会用到它。

def triplet_loss(inputs, dist='euclidean', margin='maxplus'):    anchor, positive, negative = inputs    positive_distance = K.square(anchor - positive)    negative_distance = K.square(anchor - negative)    if dist == 'euclidean':        positive_distance = K.sqrt(K.sum(positive_distance, axis=-1, keepdims=True))        negative_distance = K.sqrt(K.sum(negative_distance, axis=-1, keepdims=True))    elif dist == 'sqeuclidean':        positive_distance = K.sum(positive_distance, axis=-1, keepdims=True)        negative_distance = K.sum(negative_distance, axis=-1, keepdims=True)    loss = positive_distance - negative_distance    if margin == 'maxplus':        loss = K.maximum(0.0, 2 + loss)    elif margin == 'softplus':        loss = K.log(1 + K.exp(loss))    returned_loss = K.mean(loss)    return returned_loss

以下是我从头到尾构建模型的方式。我提供完整的代码以给出准确的图景。

model = ResNet50(weights='imagenet')# 移除最后一层(为了稍后能够创建Siamese网络模型)model.layers.pop()# 首先冻结ResNet50的所有层。将应用迁移学习。for layer in model.layers:    layer.trainable = False# 所有批量归一化层仍然需要可训练,以便使用新的训练数据更新“均值”和“标准差(std)”参数model.get_layer('bn_conv1').trainable = Truemodel.get_layer('bn2a_branch2a').trainable = Truemodel.get_layer('bn2a_branch2b').trainable = Truemodel.get_layer('bn2a_branch2c').trainable = Truemodel.get_layer('bn2a_branch1').trainable = Truemodel.get_layer('bn2b_branch2a').trainable = Truemodel.get_layer('bn2b_branch2b').trainable = Truemodel.get_layer('bn2b_branch2c').trainable = Truemodel.get_layer('bn2c_branch2a').trainable = Truemodel.get_layer('bn2c_branch2b').trainable = Truemodel.get_layer('bn2c_branch2c').trainable = Truemodel.get_layer('bn3a_branch2a').trainable = Truemodel.get_layer('bn3a_branch2b').trainable = Truemodel.get_layer('bn3a_branch2c').trainable = Truemodel.get_layer('bn3a_branch1').trainable = Truemodel.get_layer('bn3b_branch2a').trainable = Truemodel.get_layer('bn3b_branch2b').trainable = Truemodel.get_layer('bn3b_branch2c').trainable = Truemodel.get_layer('bn3c_branch2a').trainable = Truemodel.get_layer('bn3c_branch2b').trainable = Truemodel.get_layer('bn3c_branch2c').trainable = Truemodel.get_layer('bn3d_branch2a').trainable = Truemodel.get_layer('bn3d_branch2b').trainable = Truemodel.get_layer('bn3d_branch2c').trainable = Truemodel.get_layer('bn4a_branch2a').trainable = Truemodel.get_layer('bn4a_branch2b').trainable = Truemodel.get_layer('bn4a_branch2c').trainable = Truemodel.get_layer('bn4a_branch1').trainable = Truemodel.get_layer('bn4b_branch2a').trainable = Truemodel.get_layer('bn4b_branch2b').trainable = Truemodel.get_layer('bn4b_branch2c').trainable = Truemodel.get_layer('bn4c_branch2a').trainable = Truemodel.get_layer('bn4c_branch2b').trainable = Truemodel.get_layer('bn4c_branch2c').trainable = Truemodel.get_layer('bn4d_branch2a').trainable = Truemodel.get_layer('bn4d_branch2b').trainable = Truemodel.get_layer('bn4d_branch2c').trainable = Truemodel.get_layer('bn4e_branch2a').trainable = Truemodel.get_layer('bn4e_branch2b').trainable = Truemodel.get_layer('bn4e_branch2c').trainable = Truemodel.get_layer('bn4f_branch2a').trainable = Truemodel.get_layer('bn4f_branch2b').trainable = Truemodel.get_layer('bn4f_branch2c').trainable = Truemodel.get_layer('bn5a_branch2a').trainable = Truemodel.get_layer('bn5a_branch2b').trainable = Truemodel.get_layer('bn5a_branch2c').trainable = Truemodel.get_layer('bn5a_branch1').trainable = Truemodel.get_layer('bn5b_branch2a').trainable = Truemodel.get_layer('bn5b_branch2b').trainable = Truemodel.get_layer('bn5b_branch2c').trainable = Truemodel.get_layer('bn5c_branch2a').trainable = Truemodel.get_layer('bn5c_branch2b').trainable = Truemodel.get_layer('bn5c_branch2c').trainable = True# 编译Siamese网络时使用def identity_loss(y_true, y_pred):    return K.mean(y_pred - 0 * y_true)  # 创建Siamese网络x = model.get_layer('flatten_1').output # 层'flatten_1'是模型的最后一层model_out = Dense(128, activation='relu',  name='model_out')(x)model_out = Lambda(lambda  x: K.l2_normalize(x,axis=-1))(model_out)new_model = Model(inputs=model.input, outputs=model_out)anchor_input = Input(shape=(224, 224, 3), name='anchor_input')pos_input = Input(shape=(224, 224, 3), name='pos_input')neg_input = Input(shape=(224, 224, 3), name='neg_input')encoding_anchor   = new_model(anchor_input)encoding_pos      = new_model(pos_input)encoding_neg      = new_model(neg_input)loss = Lambda(triplet_loss)([encoding_anchor, encoding_pos, encoding_neg])siamese_network = Model(inputs  = [anchor_input, pos_input, neg_input],                         outputs = loss) # 注意,模型的输出是上面triplet_loss函数的返回值siamese_network.compile(optimizer=Adam(lr=.0001), loss=identity_loss)

需要注意的是,我使所有批量归一化层“可训练”,以便BN相关的参数可以使用我的训练数据进行更新。这产生了很多行,但我找不到更简短的解决方案。


回答:

解决方案的灵感来自于@Gurmeet Singh的推荐。

显然,在训练过程中一段时间后,可训练层的权重变得非常大,所有这些权重都被设置为NaN,这让我以为我保存和重新加载模型的方式有问题,但问题实际上是梯度爆炸。

我在github讨论中也看到了类似的问题,可以在这里查看:github.com/keras-team/keras/issues/2378 在github该线程的底部,建议使用较低的学习率来避免这个问题。

在这个链接中(Keras ML库:如何在梯度更新后进行权重裁剪?TensorFlow后端),讨论了两种解决方案:- 使用优化器中的clipvalue参数,它简单地按配置切割计算出的梯度值。但这不是推荐的解决方案。(在另一个线程中解释。)- 第二种方法是使用clipnorm参数,当计算出的梯度值的L2范数超过用户设定的值时,它会简单地裁剪这些值。

我也考虑过使用输入归一化(以避免梯度爆炸),但后来发现这已经在preprocess_input(..)函数中完成了。(详细信息请查看此链接:https://www.tensorflow.org/api_docs/python/tf/keras/applications/resnet50/preprocess_input)虽然可以将mode参数设置为“tf”(否则默认设置为“caffe”),这可能会进一步帮助(因为mode=”tf”设置将像素缩放到-1到1之间),但我没有尝试过。

总结一下,我在编译将要训练的模型时更改了两个东西:

更改的行如下:

更改前:

siamese_network.compile(optimizer=Adam(**lr=.0001**),                         loss=identity_loss)

更改后:

siamese_network.compile(optimizer=Adam(**lr=.00004**, **clipnorm=1.**),                        loss=identity_loss)

1) 使用较小的学习率,使梯度更新稍微小一些2) 使用clipnorm参数来归一化计算出的梯度并进行裁剪。

然后我再次训练了我的网络10个epoch。损失按预期降低,但现在速度更慢了。并且在保存和存储我的模型时我没有遇到任何问题。(至少在10个epoch之后(在我的电脑上需要时间)。)

请注意,我将clipnorm的值设置为1。这意味着首先计算梯度的L2范数,如果计算出的归一化梯度超过“1”的值,则裁剪梯度。我认为这是一个可以优化的超参数,它影响训练模型所需的时间,同时有助于避免梯度爆炸问题。

Related Posts

Keras Dense层输入未被展平

这是我的测试代码: from keras import…

无法将分类变量输入随机森林

我有10个分类变量和3个数值变量。我在分割后直接将它们…

如何在Keras中对每个输出应用Sigmoid函数?

这是我代码的一部分。 model = Sequenti…

如何选择类概率的最佳阈值?

我的神经网络输出是一个用于多标签分类的预测类概率表: …

在Keras中使用深度学习得到不同的结果

我按照一个教程使用Keras中的深度神经网络进行文本分…

‘MatMul’操作的输入’b’类型为float32,与参数’a’的类型float64不匹配

我写了一个简单的TensorFlow代码,但不断遇到T…

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注