Python 如果Keras结果不可复制,那么';比较模型和选择超参数的最佳实践是什么?

Python 如果Keras结果不可复制,那么';比较模型和选择超参数的最佳实践是什么?,python,tensorflow,keras,reproducible-research,Python,Tensorflow,Keras,Reproducible Research,更新:这个问题是针对TensorFlow1.x的。我升级到2.0,并且(至少在下面的简单代码上)再现性问题似乎在2.0上得到了解决。这样就解决了我的问题;但我仍然很好奇,在1.x版上这个问题使用了什么“最佳实践” 在keras/tensorflow上训练完全相同的模型/参数/数据不会产生可再现的结果,每次训练模型时,损失明显不同。有很多关于stackoverflow的问题(例如),但是推荐的解决方法似乎不适合我或其他许多stackoverflow的人。好吧,就是这样 但是考虑到keras在ten

更新:这个问题是针对TensorFlow1.x的。我升级到2.0,并且(至少在下面的简单代码上)再现性问题似乎在2.0上得到了解决。这样就解决了我的问题;但我仍然很好奇,在1.x版上这个问题使用了什么“最佳实践”

在keras/tensorflow上训练完全相同的模型/参数/数据不会产生可再现的结果,每次训练模型时,损失明显不同。有很多关于stackoverflow的问题(例如),但是推荐的解决方法似乎不适合我或其他许多stackoverflow的人。好吧,就是这样

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

我不认为这个问题与我的代码有任何关系,但以防万一它会有所帮助;下面是一个示例程序:

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)

考虑到不可再现性,我如何评估我的超参数和体系结构中的更改是否有帮助?

这很狡猾,但事实上,您的代码缺少一个更好再现性的步骤:在每次运行之前重置Keras&TensorFlow图。如果没有此选项,
tf.set\u random\u seed()
将无法正常工作-请参阅下面的正确方法

我会用尽所有的选择,然后再把毛巾扔到不可复制性上;目前我只知道,这可能是一个bug。尽管如此,即使您按照所有步骤进行操作,也可能会得到明显不同的结果-在这种情况下,请参见“如果没有任何结果”,但每个步骤显然不是很有效,因此最好专注于实现再现性:

最终改进

  • 使用下面的
    重置种子(K)
  • 提高数值精度:
    K.set\u floatx('float64')
  • 在Python内核启动之前设置
    pythonhasheed
    ——例如从终端
  • 升级到TF2,其中包括一些可再现性错误修复,但请记住
  • 在单个线程上运行CPU(速度非常慢)
  • 不要从
    tf.python.keras导入-请参阅
  • 确保所有导入一致(即不要从keras.layers导入<代码>和从tensorflow.keras.optimizers导入<代码>)
  • 使用更高级的CPU—例如,Google Colab,即使使用GPU,对数字不精确性也更具鲁棒性—请参阅
另请参见关于再现性的说明


如果什么都不起作用

  • 重新运行X次w/完全相同的超参数和种子,平均结果
  • K-折叠交叉验证w/完全相同的超参数和种子,平均结果-优越的选择,但需要更多的工作

正确的重置方法

def reset_seeds(reset_graph_,_backend=None):
如果使用\u后端重置\u图形\u不是无:
K=使用\u后端重置\u图形\u
K.清除会话()
tf.compat.v1.reset\u default\u graph()
打印(“KERAS和TENSORFLOW图形重置”)#可选
np.随机种子(1)
随机种子(2)
tf.compat.v1.set\u random\u seed(3)
打印(“随机种子重置”)#可选

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

session\u conf=tf.ConfigProto(
线程内并行度=1,
线程间并行度(线程=1)
sess=tf.Session(config=Session\u conf)

您有几个选项可以稳定性能

1) 设置初始化器的种子,使其始终初始化为相同的值

2) 数据越多,收敛越稳定

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

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

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


6) 此外,您还可以选择只进行多次训练并取平均值。

这个问题似乎在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)

只要把下面的代码放进去,就行了。问题的关键非常重要,每次运行模型之前都要调用函数reset_seeds()。这样做,你将获得可复制的结果,因为我在谷歌合作检查

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() 

关键是在每次运行之前,即在创建每个模型之前,都放置重置代码。例如,对于使用
GridSearchCV
调整hyperparameter,重置代码应放在模型创建的最开始处。