如何在Keras中获得可重复的结果

110
我每次运行Keras框架中的imdb_lstm.py示例(https://github.com/fchollet/keras/blob/master/examples/imdb_lstm.py)时,都会得到不同的结果(测试精度)。代码在顶部包含np.random.seed(1337),在任何keras导入之前。它应该防止它为每个运行生成不同的数字。我错过了什么吗?
更新:如何复制:
1.安装Keras(http://keras.io/) 2.执行https://github.com/fchollet/keras/blob/master/examples/imdb_lstm.py几次。它将训练模型并输出测试精度。 预期结果:每次运行时测试精度相同。 实际结果:每次运行时测试精度不同。
更新2:我正在Windows 8.1上使用MinGW / msys运行它,模块版本如下: theano 0.7.0 numpy 1.8.1 scipy 0.14.0c1
更新3:我已经缩小了问题的范围。如果我在GPU上运行示例(设置Theano标志设备= gpu0),则每次都会得到不同的测试准确性,但如果我在CPU上运行,则一切都按预期工作。我的显卡是:NVIDIA GeForce GT 635)

也许问题在于我使用的是Windows操作系统。 numpy.random.uniform运行良好,总是产生相同的结果。 - Pavel Surmenok
我将问题缩小了一点。如果我使用GPU运行示例(设置Theano标志设备= gpu0),则每次获得不同的测试准确度,但是如果我在CPU上运行它,则一切都符合预期。我的显卡:NVIDIA GeForce GT 635。 - Pavel Surmenok
5
GPU的代码必须大量使用类似于“SIMD”的指令。这可能会导致随机数生成器以随机顺序被调用。此外,GPU是相当自主的实体,它可能使用自己的随机数生成器。毕竟,在GPU上运行任何想要的代码并不容易。 - u354356007
1
请查看以下链接: http://deeplearning.net/software/theano/library/sandbox/rng_mrg.html 和 http://deeplearning.net/software/theano/tutorial/examples.html。 为了在GPU上每次获得相同的结果,您需要添加一些keras函数以便能够在GPU上正确地进行种子设置。 - James Tobin
3
你使用了哪个CUDA版本?你安装了cuDNN吗?我认为后者在速度上做出了一些牺牲,导致GPU上出现非确定性行为。(应该很微小,我认为这与反向传播时计算原子操作有关,但你可能不会每次得到相同的值。) - o1lo01ol1o
显示剩余4条评论
11个回答

79

您可以在Keras文档中找到答案:https://keras.io/getting-started/faq/#how-can-i-obtain-reproducible-results-using-keras-during-development

简而言之,为了确保您在一台计算机/笔记本电脑的CPU上使用Python脚本可以得到可重复的结果,您需要执行以下操作:

  1. PYTHONHASHSEED环境变量设置为固定值
  2. python内置伪随机生成器设置为固定值
  3. numpy伪随机生成器设置为固定值
  4. tensorflow伪随机生成器设置为固定值
  5. 配置新的全局tensorflow会话

按照顶部的Keras链接,我正在使用以下源代码:

# Seed value
# Apparently you may use different seed values at each stage
seed_value= 0

# 1. Set the `PYTHONHASHSEED` environment variable at a fixed value
import os
os.environ['PYTHONHASHSEED']=str(seed_value)

# 2. Set the `python` built-in pseudo-random generator at a fixed value
import random
random.seed(seed_value)

# 3. Set the `numpy` pseudo-random generator at a fixed value
import numpy as np
np.random.seed(seed_value)

# 4. Set the `tensorflow` pseudo-random generator at a fixed value
import tensorflow as tf
tf.random.set_seed(seed_value)
# for later versions: 
# tf.compat.v1.set_random_seed(seed_value)

# 5. Configure a new global `tensorflow` session
from keras import backend as K
session_conf = tf.ConfigProto(intra_op_parallelism_threads=1, inter_op_parallelism_threads=1)
sess = tf.Session(graph=tf.get_default_graph(), config=session_conf)
K.set_session(sess)
# for later versions:
# session_conf = tf.compat.v1.ConfigProto(intra_op_parallelism_threads=1, inter_op_parallelism_threads=1)
# sess = tf.compat.v1.Session(graph=tf.compat.v1.get_default_graph(), config=session_conf)
# tf.compat.v1.keras.backend.set_session(sess)

毋须多言,您无需在使用Python脚本中的numpyscikit-learntensorflow/keras函数时指定任何seedrandom_state,因为通过上面的源代码我们已经将它们的伪随机生成器全局设置为固定值。


2
对于TensorFlow的较新版本,如果遇到错误,请使用*tf.random.set_random_seed(seed_value)*。 - Kalpit
谢谢,这对我很有效!只是为了确认一下:在运行脚本后,有没有什么需要我做的来“恢复随机性到正常状态”?或者设置seed_values只有“一次性效果”? - Frank
嘿@Frank,我认为如果你像那样设置种子值,除非你重新启动内核等(或自己设置不同的种子值等),否则它不会恢复正常。 - Outcast
@Outcast 在你发送的 Keras 文档链接中,写到了 首先,在程序启动之前(而不是在程序内部),需要将 PYTHONHASHSEED 环境变量设置为 0。那么你的回答是否是在程序内部设置了 PYTHONHASHSEEDos.environ['PYTHONHASHSEED']=str(seed_value))? - fabda01

16

在Tensorflow 2.0中,您可以像这样设置随机种子:

import tensorflow as tf
tf.random.set_seed(221)


from tensorflow import keras
from tensorflow.keras import layers


model = keras.Sequential( [ 
layers.Dense(2,name = 'one'),
layers.Dense(3,activation = 'sigmoid', name = 'two'),
layers.Dense(2,name = 'three')])

x = tf.random.uniform((12,12))
model(x)

13
Theano的文档documentation讨论了随机变量种子的困难以及为什么要使用它自己的随机数生成器来为每个图实例提供种子。在不同的{{{RandomOp}}}实例之间共享随机数生成器会使得在图中产生相同的流变得困难,并且保持{{{RandomOps}}}的隔离性。因此,图中的每个{{{RandomOp}}}实例将有自己的随机数生成器。该随机数生成器是函数的输入。在典型的用法中,我们将使用函数输入的新特性({{{value}}},{{{update}}})来传递和更新每个{{{RandomOp}}}的rng。通过将RNG作为输入传递,可以使用访问函数输入的常规方法来访问每个{{{RandomOp}}}的rng。在这种方法中,没有预先存在的机制来处理整个图的组合随机数状态。因此,建议通过辅助函数({{{seed, getstate, setstate}}})提供缺失的功能(最后三个要求)。
他们还提供了示例,介绍如何种植所有的随机数生成器。

您也可以通过该对象的种子方法来为由RandomStreams对象分配的所有随机变量提供种子。这个种子将用于种植一个临时的随机数生成器,然后生成每个随机变量的种子。

>>> srng.seed(902340)  # seeds rv_u and rv_n with different seeds each

11
为了生成这些种子,我们需要访问 Theano 的随机对象,这些对象将被 Keras 使用。你能否通过 Keras API 实现这一点? - max

12

我终于用我的代码得到了可重复的结果,这是我在网上看到的答案的结合体。第一件事是按照 @alex 的建议:

  1. 设置 numpy.random.seed
  2. 对于 Python 3,使用 PYTHONHASHSEED=0

然后,你需要解决 @user2805751 指出的有关 cuDNN 的问题,通过以下附加的 THEANO_FLAGS 调用你的 Keras 代码来解决:

  1. dnn.conv.algo_bwd_filter=deterministic,dnn.conv.algo_bwd_data=deterministic

最后,你需要根据此评论的说明修补你的 Theano 安装,基本上包括以下几点:

  1. theano/sandbox/cuda/opt.py 中将所有对 *_dev20 运算符的调用替换为其常规版本。

这样就可以通过相同的种子获得相同的结果。

请注意可能会有一些减慢。我看到运行时间增加了约 10%。


8

现在,在Tensorflow 2.0中已经解决了这个问题!我在使用TF 1.x时也遇到了同样的问题(请参见如果Keras的结果不可重现,选择超参数、比较模型的最佳实践是什么?),但是

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)

5
这对我有效:
SEED = 123456
import os
import random as rn
import numpy as np
from tensorflow import set_random_seed

os.environ['PYTHONHASHSEED']=str(SEED)
np.random.seed(SEED)
set_random_seed(SEED)
rn.seed(SEED)

4

这并不像看起来的那么难。只需要放置这个,它就能运行:

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

问题的关键非常重要,就是在运行模型之前每次调用reset_seeds()函数。这样做可以获得可重复的结果,正如我在Google Collab中检查的一样。


这种方法对我几乎有效。我不得不在函数体的开头添加 os.environ["PYTHONHASHSEED"] = str(seed_value),然后它就可以工作了。 - michen00

3

我使用Keras训练和测试了Sequential()类型的神经网络。我对嘈杂的语音数据进行了非线性回归。我使用以下代码来生成随机种子:

import numpy as np
seed = 7
np.random.seed(seed)

我每次在相同的数据上训练和测试时,都会得到完全相同的val_loss结果。

1
你用过GPU吗?使用的后端是Theano还是TensorFlow? - Pavel Surmenok
我使用了Theano后端的CPU。 - tauseef_CuriousGuy
1
明白了。CPU 对我来说很好,只有在使用 GPU 时才会出现问题。 - Pavel Surmenok

2

我想在之前的回答中补充一些内容。如果您使用python 3,并且希望每次运行都获得可重复的结果,则需要:

  1. 在代码开头设置numpy.random.seed
  2. 将PYTHONHASHSEED=0作为参数提供给python解释器

0

与之前所说的不同,只有Tensorflow种子对权重的随机生成有影响(最新版本Tensorflow 2.6.0和Keras 2.6.0)

以下是一个小测试,您可以运行它来检查每个种子的影响(其中np代表numpy,tf代表tensorflow,random代表Python的随机库):

# Testing how seeds influence results
# -----------------------------------

print("Seed specification")

my_seed = 36
# To vary python hash, numpy random, python random and tensorflow random seeds
a, b, c, d = 0, 0, 0, 0

os.environ['PYTHONHASHSEED'] = str(my_seed+a) # Has no effect
np.random.seed(my_seed+b) # Has no effect
random.seed(my_seed+c) # Has no effect
tf.random.set_seed(my_seed+d) # Has an effect

print("Making ML model")

keras.mixed_precision.set_global_policy('float64')

model = keras.Sequential([
    layers.Dense(2, input_shape=input_shape),#, activation='relu'),
    layers.Dense(output_nb, activation=None),
    ])
#
weights_save = model.get_weights()

print("Some weights:", weights_save[0].flatten())

我们注意到变量abc对结果没有影响,只有d对结果有影响。
因此,在最新版本的Tensorflow中,只有Tensorflow随机种子对权重的随机选择产生影响

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