如果 Keras 结果不可重现,那么比较模型和选择超参数的最佳做法是啥?

Posted

技术标签:

【中文标题】如果 Keras 结果不可重现,那么比较模型和选择超参数的最佳做法是啥?【英文标题】:If Keras results are not reproducible, what's the best practice for comparing models and choosing hyper parameters?如果 Keras 结果不可重现,那么比较模型和选择超参数的最佳做法是什么? 【发布时间】:2020-03-23 07:22:32 【问题描述】:

更新:这个问题是针对 Tensorflow 1.x 的。我升级到 2.0 并且(至少在下面的简单代码中)重现性问题似乎在 2.0 上已修复。这样就解决了我的问题;但我仍然很好奇在 1.x 上针对此问题使用了哪些“最佳实践”。

在 keras/tensorflow 上训练完全相同的模型/参数/数据不会给出可重现的结果,并且每次训练模型时损失都显着不同。有很多关于这个的 *** 问题(例如,How to get reproducible results in keras),但推荐的解决方法似乎对我或 *** 上的许多其他人不起作用。好吧,就是这样。

但是考虑到 keras 在 tensorflow 上的不可重现性的限制——比较模型和选择超参数的最佳实践是什么?我正在测试不同的架构和激活,但由于损失估计每次都不同,我永远不确定一个模型是否比另一个更好。有没有处理这个问题的最佳实践?

我认为这个问题与我的代码无关,但以防万一;这是一个示例程序:

import os
#*** says turning off the GPU helps reproducibility, but it doesn't help for me
os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID"
os.environ["CUDA_VISIBLE_DEVICES"] = ""
os.environ['PYTHONHASHSEED']=str(1)

import tensorflow as tf
import tensorflow.keras as keras
import tensorflow.keras.layers 
import random
import pandas as pd
import numpy as np

#*** says this is needed for reproducibility but it doesn't help for me
from tensorflow.keras import backend as K
config = tf.ConfigProto(intra_op_parallelism_threads=1,inter_op_parallelism_threads=1)
sess = tf.Session(graph=tf.get_default_graph(), config=config)
K.set_session(sess)

#make some random data
NUM_ROWS = 1000
NUM_FEATURES = 10
random_data = np.random.normal(size=(NUM_ROWS, NUM_FEATURES))
df = pd.DataFrame(data=random_data, columns=['x_' + str(ii) for ii in range(NUM_FEATURES)])
y = df.sum(axis=1) + np.random.normal(size=(NUM_ROWS))

def run(x, y):
    #*** says you have to set the seeds but it doesn't help for me
    tf.set_random_seed(1)
    np.random.seed(1)
    random.seed(1)
    os.environ['PYTHONHASHSEED']=str(1)

    model = keras.Sequential([
            keras.layers.Dense(40, input_dim=df.shape[1], activation='relu'),
            keras.layers.Dense(20, activation='relu'),
            keras.layers.Dense(10, activation='relu'),
            keras.layers.Dense(1, activation='linear')
        ])
    NUM_EPOCHS = 500
    model.compile(optimizer='adam', loss='mean_squared_error')
    model.fit(x, y, epochs=NUM_EPOCHS, verbose=0)
    predictions = model.predict(x).flatten()
    loss = model.evaluate(x,  y) #This prints out the loss by side-effect

#Each time we run it gives a wildly different loss. :-(
run(df, y)
run(df, y)
run(df, y)

鉴于不可重复性,我如何评估我的超参数和架构的变化是否有帮助?

【问题讨论】:

【参考方案1】:

这是偷偷摸摸的,但实际上,您的代码确实缺少提高可重复性的步骤:在每次运行之前重置 Keras 和 TensorFlow 图。没有这个,tf.set_random_seed() 将无法正常工作 - 请参阅下面的正确方法。

我会用尽所有选项,然后再将毛巾扔到不可再现性上;目前我只知道one such instance,这可能是一个错误。尽管如此,即使您完成所有步骤,您也可能会得到明显不同的结果 - 在这种情况下,请参阅“如果没有任何效果”,但每个步骤显然都不是很有成效,因此最好专注于获得可重复性:

彻底的改进

在下方使用reset_seeds(K) 增加数值精度:K.set_floatx('float64') 设置PYTHONHASHSEED Python 内核启动之前 - 例如从终端 升级到 TF 2,其中包括一些可重复性错误修复,但请注意 performance 在单线程上运行 CPU(非常慢) tf.python.keras 导入 - 请参阅here 确保所有导入一致(即不要执行from keras.layers import ...from tensorflow.keras.optimizers import ...) 使用高级 CPU - 例如,Google Colab,即使使用 GPU,对数值不精确也更加稳健 - 请参阅 this SO

另请参阅related SO 的可重复性


如果没有任何效果

重新运行 X 次 使用完全相同的超参数和种子,平均结果 K 折交叉验证 具有完全相同的超参数和种子,平均结果 - 更好的选择,但需要更多的工作

正确的重置方法

def reset_seeds(reset_graph_with_backend=None):
    if reset_graph_with_backend is not None:
        K = reset_graph_with_backend
        K.clear_session()
        tf.compat.v1.reset_default_graph()
        print("KERAS AND TENSORFLOW GRAPHS RESET")  # optional

    np.random.seed(1)
    random.seed(2)
    tf.compat.v1.set_random_seed(3)
    print("RANDOM SEEDS RESET")  # optional

在单 CPU 线程上运行 TF:(仅限 TF1 的代码)

session_conf = tf.ConfigProto(
      intra_op_parallelism_threads=1,
      inter_op_parallelism_threads=1)
sess = tf.Session(config=session_conf)

【讨论】:

真正的关键是在每次运行之前,即在创建每个模型之前,放置重置代码。例如,使用GridSearchCV 调整超参数,重置代码应该放在模型创建的最开始。【参考方案2】:

这个问题似乎在 Tensorflow 2.0 中得到了解决(至少在简单模型上)!这是一个似乎可以产生可重复结果的代码 sn-p。

import os
####*IMPORANT*: Have to do this line *before* importing tensorflow
os.environ['PYTHONHASHSEED']=str(1)

import tensorflow as tf
import tensorflow.keras as keras
import tensorflow.keras.layers 
import random
import pandas as pd
import numpy as np

def reset_random_seeds():
   os.environ['PYTHONHASHSEED']=str(1)
   tf.random.set_seed(1)
   np.random.seed(1)
   random.seed(1)

#make some random data
reset_random_seeds()
NUM_ROWS = 1000
NUM_FEATURES = 10
random_data = np.random.normal(size=(NUM_ROWS, NUM_FEATURES))
df = pd.DataFrame(data=random_data, columns=['x_' + str(ii) for ii in range(NUM_FEATURES)])
y = df.sum(axis=1) + np.random.normal(size=(NUM_ROWS))

def run(x, y):
    reset_random_seeds()

    model = keras.Sequential([
            keras.layers.Dense(40, input_dim=df.shape[1], activation='relu'),
            keras.layers.Dense(20, activation='relu'),
            keras.layers.Dense(10, activation='relu'),
            keras.layers.Dense(1, activation='linear')
        ])
    NUM_EPOCHS = 500
    model.compile(optimizer='adam', loss='mean_squared_error')
    model.fit(x, y, epochs=NUM_EPOCHS, verbose=0)
    predictions = model.predict(x).flatten()
    loss = model.evaluate(x,  y) #This prints out the loss by side-effect

#With Tensorflow 2.0 this is now reproducible! 
run(df, y)
run(df, y)
run(df, y)

【讨论】:

【参考方案3】:

只放下面的代码,它就可以工作。问题的关键,非常重要,是在每次运行模型之前调用函数 reset_seeds() 。这样做您将获得可重现的结果,正如我在 Google Collab 中检查的那样。

import numpy as np
import tensorflow as tf
import random as python_random

def reset_seeds():
   np.random.seed(123) 
   python_random.seed(123)
   tf.random.set_seed(1234)

reset_seeds() 

【讨论】:

【参考方案4】:

您有多种选择来稳定性能...

1) 为您的初始化程序设置种子,以便它们始终初始化为相同的值。

2) 更多的数据通常会导致更稳定的收敛。

3) 较低的学习率和较大的批次大小也有利于更可预测的学习。

4) 基于固定数量的 epoch 进行训练,而不是在训练期间使用回调来修改超参数。

5) 在不同子集上训练的 K 折验证。这些折叠的平均值应该会产生一个相当可预测的指标。

6) 您也可以选择只训练多次并取其平均值。

【讨论】:

以上是关于如果 Keras 结果不可重现,那么比较模型和选择超参数的最佳做法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

JSONDecodeError:加载 tf.Keras 模型时的期望值

如何在 keras 中获得可重现的结果

RandomizedSearchCV 和 GridsearchCV 结果不可重现

使用 Keras 和 Theano 进行模型选择需要很长时间

keras 模型上奇怪的分析结果:越复杂越快

如果我将层传递给两个 Keras 模型并且只训练一个模型,那么在前者训练后两个模型会共享权重吗