如果Keras的结果无法复现,那么在比较模型和选择超参数时最佳实践是什么?

5

更新:此问题针对Tensorflow 1.x。我升级到了2.0版本,至少在下面的简单代码中,可重复性问题似乎在2.0上已经解决了。所以这解决了我的问题;但我仍然很好奇在1.x版本中如何使用“最佳实践”处理此问题。

在keras/tensorflow上使用相同的模型/参数/数据进行训练并不能产生可重复的结果,并且每次训练模型时损失值显著不同。有许多关于这个问题的stackoverflow问题(例如,如何在keras中获得可重复的结果),但推荐的解决方法似乎对我或许多其他人都不起作用。好吧,就是这样。

但是,鉴于在tensorflow上keras无法实现可重复性的局限性,比较模型并选择超参数的最佳实践是什么?我正在测试不同的架构和激活函数,但由于每次损失估计都不同,我从来不确定哪个模型比另一个更好。有没有任何处理这个问题的最佳实践?

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

import os
#stackoverflow 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

#StackOverflow 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):
    #StackOverflow 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)

在不可重复性的情况下,如何评估我的超参数和架构变化是否有所帮助?

4个回答

10

虽然有点隐蔽,但您的代码确实缺少一个更好的可重现性步骤:在每次运行之前重置Keras和TensorFlow图形。 如果没有这样做,tf.set_random_seed() 将无法正常工作-请参见下面的正确方法。

在放弃无法重现性之前,我会尝试所有选项;目前我只知道一个这样的例子,而且很可能是一个错误。 尽管如此,即使您遵循了所有步骤,也可能得到明显不同的结果-在那种情况下,请参见“如果没有任何作用”,但每个选项显然都不太生产效益,因此最好专注于获得可重现性:

明确的改进

  • 使用以下 reset_seeds(K)
  • 增加数字精度:K.set_floatx('float64')
  • 在Python内核启动之前设置 PYTHONHASHSEED - 例如从终端
  • 升级到TF 2,其中包括一些可重现性错误修复,但要注意性能
  • 使用单线程运行CPU(非常缓慢)
  • 不要tf.python.keras导入 - 请参见此处
  • 确保所有导入是一致的(即不要from keras.layers import ...from tensorflow.keras.optimizers import ...
  • 使用更好的CPU-例如,Google Colab即使使用GPU,也比较鲁棒,可以防止数值不精确-请参见此SO

还请参见关于可重现性的相关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”调整超参数时,应该将重置代码放置在模型创建的最开始位置。 - Moore

4

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

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)

1

只需使用下面的代码,它就能正常工作。问题的关键,非常重要的是,在运行模型之前每次调用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() 

0

你有几种选项来稳定性能...

1) 为你的初始化器设置种子,这样它们总是被初始化为相同的值。

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

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

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

5) K-fold验证以在不同的子集上进行训练。这些折叠的平均值应该会得到一个相当可预测的指标。

6) 另外,你可以选择多次训练并取平均值。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接