如果 Keras 结果不可重现,那么比较模型和选择超参数的最佳做法是什么?
If Keras results are not reproducible, what's the best practice for comparing models and choosing hyper parameters?
更新:这个问题是针对 Tensorflow 1.x 的。我升级到 2.0 并且(至少在下面的简单代码中)重现性问题似乎已在 2.0 上修复。这样就解决了我的问题;但我仍然很好奇 "best practices" 在 1.x.
上用于此问题的内容
在 keras/tensorflow 上训练完全相同的 model/parameters/data 不会给出可重现的结果,并且每次训练模型时损失都大不相同。有很多关于此的 Whosebug 问题(例如 How to get reproducible results in keras ),但推荐的解决方法似乎对我或 Whosebug 上的许多其他人不起作用。好的,就是这样。
但考虑到 keras 在 tensorflow 上的不可再现性限制——比较模型和选择超参数的最佳做法是什么?我正在测试不同的架构和激活,但由于每次损失估计都不同,所以我不确定一个模型是否比另一个更好。是否有处理此问题的最佳做法?
我认为这个问题与我的代码无关,但以防万一;这是一个示例程序:
import os
#Whosebug 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
#Whosebug 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):
#Whosebug 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_random_seed()
将无法正常工作 - 请参阅下面的正确方法。
我会用尽所有的选择,然后再放弃不可再现性;目前我只知道 one such instance,这可能是一个错误。尽管如此,即使您完成了所有步骤,您也可能会得到明显不同的结果 - 在这种情况下,请参阅 "If nothing works",但每个步骤显然都不是很有成效,因此最好集中精力实现可重复性:
最终改进:
- 使用下面的
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,也能更稳健地应对数字不精确 - 请参阅
另见 重现性
如果没有效果:
- Re运行 X 次 w/ 完全相同的超参数和种子,平均结果
- 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)
您有几个稳定性能的选项...
1) 为您的初始化器设置种子,以便它们始终被初始化为相同的值。
2) 更多的数据通常会导致更稳定的收敛。
3) 较低的学习率和较大的批量大小也有利于更可预测的学习。
4) 基于固定数量的 epoch 进行训练,而不是在训练期间使用回调来修改超参数。
5) K-fold 验证以训练不同的子集。这些折叠的平均值应该会产生一个相当可预测的指标。
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() 。这样做你将获得可重现的结果,正如我在 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()
更新:这个问题是针对 Tensorflow 1.x 的。我升级到 2.0 并且(至少在下面的简单代码中)重现性问题似乎已在 2.0 上修复。这样就解决了我的问题;但我仍然很好奇 "best practices" 在 1.x.
上用于此问题的内容在 keras/tensorflow 上训练完全相同的 model/parameters/data 不会给出可重现的结果,并且每次训练模型时损失都大不相同。有很多关于此的 Whosebug 问题(例如 How to get reproducible results in keras ),但推荐的解决方法似乎对我或 Whosebug 上的许多其他人不起作用。好的,就是这样。
但考虑到 keras 在 tensorflow 上的不可再现性限制——比较模型和选择超参数的最佳做法是什么?我正在测试不同的架构和激活,但由于每次损失估计都不同,所以我不确定一个模型是否比另一个更好。是否有处理此问题的最佳做法?
我认为这个问题与我的代码无关,但以防万一;这是一个示例程序:
import os
#Whosebug 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
#Whosebug 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):
#Whosebug 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_random_seed()
将无法正常工作 - 请参阅下面的正确方法。
我会用尽所有的选择,然后再放弃不可再现性;目前我只知道 one such instance,这可能是一个错误。尽管如此,即使您完成了所有步骤,您也可能会得到明显不同的结果 - 在这种情况下,请参阅 "If nothing works",但每个步骤显然都不是很有成效,因此最好集中精力实现可重复性:
最终改进:
- 使用下面的
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,也能更稳健地应对数字不精确 - 请参阅
另见
如果没有效果:
- Re运行 X 次 w/ 完全相同的超参数和种子,平均结果
- 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)
您有几个稳定性能的选项...
1) 为您的初始化器设置种子,以便它们始终被初始化为相同的值。
2) 更多的数据通常会导致更稳定的收敛。
3) 较低的学习率和较大的批量大小也有利于更可预测的学习。
4) 基于固定数量的 epoch 进行训练,而不是在训练期间使用回调来修改超参数。
5) K-fold 验证以训练不同的子集。这些折叠的平均值应该会产生一个相当可预测的指标。
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() 。这样做你将获得可重现的结果,正如我在 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()