如何使用Keras获得可重现的结果
神经网络算法是随机的。
这意味着它们利用了随机性,例如初始化为随机权重,反过来,在相同数据上训练的相同网络可以产生不同的结果。
这可能会让初学者感到困惑,因为算法看起来不稳定,实际上他们是被设计出来的。随机初始化允许网络学习正被学习的函数的良好近似值。
然而,有时每次对相同的数据训练相同的网络时,你都需要完全相同的结果。例如用于教程,或者可能在操作上。
在本教程中,你将了解如何为随机数生成器设置种子,以便每次都可以从相同数据的相同网络中获得相同的结果。
我们开始吧。
教程概述
本教程分为6个部分。它们是:
- 为什么我每次都得到不同的结果?
- 不同结果的演示。
- 解决方案。
- 使用Theano后端的种子随机数。
- 使用TensorFlow后端的种子随机数。
- 如果我仍然得到不同的结果怎么办?
环境
本教程假设你已经安装了Python SciPy环境。在本例中,你可以使用Python2或Python3。
本教程假设你安装了Kera(v2.0.3+)和TensorFlow(v1.1.0+)或Theano(v0.9+)后端。
本教程还假设你已经安装了scikit-learn、Pandas、NumPy和Matplotlib。
如果你需要帮助设置Python环境,请参阅此帖子:
为什么我每次都得到不同的结果?
这是我从初学者到神经网络和深度学习领域的一个常见问题。
这种误解也可能以如下问题的形式出现:
- 如何获得稳定的结果?
- 如何获得可重复的结果?
- 我应该用什么种子呢?
神经网络通过设计随机性来确保它们有效地学习问题的近似函数。之所以使用随机性,是因为这类机器学习算法在使用随机性时比不使用随机性时执行得更好。
神经网络中使用的最常见的随机性形式是网络权重的随机初始化。虽然随机性可以用于其他领域,但这里只列出了一个简短的列表:
- 初始化中的随机性,例如权重。
- 正则化中的随机性,例如辍学。
- 层中的随机性,例如单词嵌入。
- 优化中的随机性,如随机优化。
这些随机性的来源,甚至更多,意味着当你在完全相同的数据上运行完全相同的神经网络算法时,你肯定会得到不同的结果。
有关随机算法背后的原因的更多信息,请参见帖子:
不同结果的演示
我们可以用一个小例子来说明神经网络的随机性。
在本节中,我们将开发一个多层感知器模型来学习从0.0到0.9递增0.1的短数字序列。给定0.0,模型必须预测0.1;给定0.1,模型必须输出0.2;依此类推。
下面列出了准备数据的代码。
# create sequence length = 10 sequence = [i/float(length) for i in range(length)] # create X/y pairs df = DataFrame(sequence) df = concat([df.shift(1), df], axis=1) df.dropna(inplace=True) # convert to MLPfriendly format values = df.values X, y = values[:,0], values[:,1]
我们将使用一个具有1个输入、10个隐层神经元和1个输出的网络。该网络将使用均方误差损失函数,并将使用有效的ADAM算法进行训练。
网络需要大约1000个纪元才能有效地解决这个问题,但我们只会训练100个纪元。这是为了确保我们得到一个在进行预测时会出错的模型。
网络训练完成后,我们将对数据集进行预测,并打印均方误差。
下面列出了该网络的代码。
# design network model = Sequential() model.add(Dense(10, input_dim=1)) model.add(Dense(1)) model.compile(loss='mean_squared_error', optimizer='adam') # fit network model.fit(X, y, epochs=100, batch_size=len(X), verbose=0) # forecast yhat = model.predict(X, verbose=0) print(mean_squared_error(y, yhat[:,0]))
在本例中,我们将创建10次网络并打印10个不同的网络分数。
下面提供了完整的代码清单。
from pandas import DataFrame from pandas import concat from keras.models import Sequential from keras.layers import Dense from sklearn.metrics import mean_squared_error # fit MLP to dataset and print error def fit_model(X, y): # design network model = Sequential() model.add(Dense(10, input_dim=1)) model.add(Dense(1)) model.compile(loss='mean_squared_error', optimizer='adam') # fit network model.fit(X, y, epochs=100, batch_size=len(X), verbose=0) # forecast yhat = model.predict(X, verbose=0) print(mean_squared_error(y, yhat[:,0])) # create sequence length = 10 sequence = [i/float(length) for i in range(length)] # create X/y pairs df = DataFrame(sequence) df = concat([df.shift(1), df], axis=1) df.dropna(inplace=True) # convert to MLP friendly format values = df.values X, y = values[:,0], values[:,1] # repeat experiment repeats = 10 for _ in range(repeats): fit_model(X, y)
运行该示例将在每行中打印不同的精度。
你的具体结果会有所不同。下面提供了示例输出。
0.0282584265697 0.0457025913022 0.145698137198 0.0873461454407 0.0309397604521 0.046649185173 0.0958450337178 0.0130660263779 0.00625176026631 0.00296055161492
解决方案
这是两个主要的解决方案。
解决方案1:重复你的实验
解决此问题的传统且实用的方法是多次运行你的网络(30次以上),并使用统计数据来总结你的模型的性能,然后将你的模型与其他模型进行比较。
我强烈推荐这种方法,但由于某些型号的训练时间很长,这并不总是可行的。
有关此方法的详细信息,请参阅:
解决方案2:为随机数生成器设定种子
或者,另一种解决方案是对随机数生成器使用固定种子。
使用伪随机数生成器生成随机数。随机数生成器是一种数学函数,它将生成足够随机的长数字序列,用于一般用途,例如在机器学习算法中。
随机数生成器需要种子来启动该过程,并且在大多数实现中通常使用以毫秒为单位的当前时间作为默认值。这是为了确保在默认情况下,每次运行代码时都会生成不同的随机数序列。
还可以使用特定数字(如“1”)指定此种子,以确保每次运行代码时都会生成相同的随机数序列。
特定的种子值并不重要,只要它在代码的每次运行中保持相同即可。
根据后端的不同,设置随机数生成器的具体方式也不同,我们将在Theano和TensorFlow中了解如何设置随机数生成器。
使用Theano后端的种子随机数
通常,Keras的随机性来源来自NumPy随机数生成器。
在很大程度上,Theano后端也是如此。
我们可以通过从Random模块调用seed()函数来为NumPy随机数生成器设定种子,如下所示:
from numpy.random import seed seed(1)
种子函数的导入和调用最好在代码文件的顶部完成。
这是一种最佳实践,因为在将各种Keras或Theano(或其他)库作为其初始化的一部分导入时,甚至在直接使用它们之前,也可能会使用一些随机性。
我们可以将这两行添加到上面示例的顶部,然后运行两次。
每次运行代码时,你应该会看到相同的均方误差值列表(可能会因为不同机器的精度而略有不同),如下所示:
0.169326527063 2.75750621228e-05 0.0183287291562 1.93553737255e-07 0.0549871087449 0.0906326807824 0.00337575114075 0.00414857518259 8.14587362008e-08 0.0522927019639
你的结果应该与我的一致(忽略精度的细微差异)。
使用TensorFlow后端的种子随机数
Keras的随机性来源确实来自NumPy随机数生成器,因此无论你使用的是Theano还是TensorFlow后端,这都必须是种子。
在任何其他导入或其他代码之前,必须通过调用文件顶部的seed()函数为其设定种子。
from numpy.random import seed seed(1)
此外,TensorFlow有自己的随机数生成器,也必须通过在NumPy随机数生成器之后立即调用 set_random_seed()函数来设定种子,如下所示:
from tensorflow import set_random_seed set_random_seed(2)
为清晰起见,代码文件的顶部必须在任何其他行之前包含以下4行;
from numpy.random import seed seed(1) from tensorflow import set_random_seed set_random_seed(2)
你可以对这两种种子使用相同的种子,也可以对不同的种子使用不同的种子。我不认为这会有太大的不同,因为随机性的来源会进入不同的过程。
将这4行添加到上面的示例中将允许代码在每次运行时产生相同的结果。你应该会看到与下面列出的值相同的均方误差值(可能会因不同机器的精度而略有不同):
0.224045112999 0.00154879478823 0.00387589994044 0.0292376881968 0.00945528404353 0.013305765525 0.0206255228201 0.0359538356108 0.00441943512128 0.298706569397
你的结果应该与我的一致(忽略精度的细微差异)。
如果我仍然得到不同的结果怎么办?
要重复,报告结果和比较模型的最可靠方法是多次重复你的实验(30多次)并使用汇总统计数据。
如果这是不可能的,你可以通过播种代码使用的随机数生成器来获得100%可重复的结果。上面的解决方案应该涵盖大多数情况,但不是全部。
如果你遵循了上述说明,但在相同的数据上使用相同的算法仍然得到不同的结果,该怎么办呢?
有可能还有其他的随机性来源,你没有考虑到。
来自第三方库的随机性
可能你的代码正在使用一个附加库,该库使用一个也必须是种子的不同随机数生成器。
尝试将你的代码削减到所需的最低限度(例如,一个数据样本、一个训练期等)。并仔细阅读API文档,努力缩小引入随机性的其他第三方库的范围。
使用GPU带来的随机性
以上所有示例都假设代码在CPU上运行。
使用GPU训练模型时,后端可能会配置为使用复杂的GPU库堆栈,其中一些可能会引入你自己的随机性来源,而你可能无法考虑这些随机性来源。
例如,有一些证据表明,如果你在堆栈中使用Nvidia cuDNN,这可能会引入额外的随机性来源,并阻碍结果的精确重现性。
复杂模型的随机性
由于你模型的复杂性和训练的并行性,你可能会得到不可重现的结果。
这很可能是因为后端库提高了效率,而且可能无法跨内核使用随机数序列。
我自己没有看到这一点,但在一些GitHub问题和StackOverflow问题中看到了迹象。
你可以尝试降低模型的复杂性,以查看这是否会影响结果的重现性,即使只是为了缩小原因范围。
我建议阅读一下你的后端是如何使用随机性的,看看有没有什么可供你选择的。
在Theano中,请参见:
在TensorFlow中,请参见:
此外,考虑寻找其他有相同问题的人,以获得进一步的洞察力。一些值得搜索的好地方包括:
摘要
在本教程中,你了解了如何在Kera中获得神经网络模型的可重现结果。
具体地说,你了解到:
- 神经网络在设计上是随机的,并且随机性的来源可以固定,以使结果可重现。
- 你可以在NumPy和TensorFlow中设定随机数生成器的种子,这将使大多数Keras代码100%可重现。
- 在某些情况下,有额外的随机性来源,你有办法找出它们,也许还能修复它们。