TL;DR
结果不同的原因是两个模型的初始权重不同。一个人的表现(明显)比另一个人好这一事实纯粹是偶然的,正如 @today 提到的,他们获得的结果大致相似。
Details
作为文档tf.set_random_seed https://www.tensorflow.org/api_docs/python/tf/random/set_random_seed解释说,随机操作使用两个种子,图级种子和操作特定种子; tf.set_random_seed
设置图级种子:
依赖随机种子的操作实际上源自两个种子:图级种子和操作级种子。这设置了图级种子。
看一下 的定义Dense
我们看到默认内核初始化程序 https://github.com/tensorflow/tensorflow/blob/v1.12.0/tensorflow/python/keras/layers/core.py#L904 is 'glorot_uniform'
(这里我们只考虑内核初始化器,但偏置初始化器也是如此)。进一步浏览源代码,我们最终会发现这获取了GlorotUniform https://github.com/tensorflow/tensorflow/blob/v1.12.0/tensorflow/python/ops/init_ops.py#L1120使用默认参数。具体来说随机数生成器种子 https://github.com/tensorflow/tensorflow/blob/v1.12.0/tensorflow/python/ops/init_ops.py#L1138为了那个原因specific操作(即权重初始化)设置为None
。现在,如果我们检查该种子的使用位置,我们会发现它被传递到random_ops.truncated_normal https://github.com/tensorflow/tensorflow/blob/v1.12.0/tensorflow/python/ops/init_ops.py#L477例如。这反过来(就像所有随机操作一样)现在获取two种子,一个是图级种子,另一个是特定于操作的种子:seed1, seed2 = random_seed.get_seed(seed) https://github.com/tensorflow/tensorflow/blob/v1.12.0/tensorflow/python/ops/random_ops.py#L173。我们可以检查一下定义get_seed
函数,我们发现如果没有给出操作特定的种子(这是我们的情况),那么它是从当前图的属性导出的:op_seed = ops.get_default_graph()._last_id https://github.com/tensorflow/tensorflow/blob/v1.12.0/tensorflow/python/framework/random_seed.py#L70。的相应部分tf.set_random_seed
文档内容如下:
- 如果设置了图级种子,但未设置操作种子:系统确定性地选择与图级种子结合的操作种子,以便获得唯一的随机序列。
现在回到原来的问题,如果input_shape
已定义或未定义。再次查看一些源代码我们发现Sequential.add https://github.com/tensorflow/tensorflow/blob/v1.12.0/tensorflow/python/keras/engine/sequential.py#L123增量构建网络的输入和输出only if input_shape
已指定;否则它只存储图层列表(model._layers
);比较model.inputs, model.outputs
对于这两个定义。输出是通过增量构建的直接调用层 https://github.com/tensorflow/tensorflow/blob/v1.12.0/tensorflow/python/keras/engine/sequential.py#L175发送到Layer.__call__ https://github.com/tensorflow/tensorflow/blob/v1.12.0/tensorflow/python/keras/engine/base_layer.py#L664。该包装器构建层,设置层的输入和输出,并向输出添加一些元数据;它还使用一个ops.name_scope https://github.com/tensorflow/tensorflow/blob/v1.12.0/tensorflow/python/keras/engine/base_layer.py#L712进行分组操作。我们可以从提供的可视化中看到这一点张量板 https://www.tensorflow.org/guide/graph_viz(简化模型架构的示例Input -> Dense -> Dropout -> Dense
):
现在,在我们没有指定的情况下input_shape
模型拥有的只是一个图层列表。即使在打电话之后compile
该模型实际上是未编译 https://github.com/tensorflow/tensorflow/blob/v1.12.0/tensorflow/python/keras/engine/training.py#L434(仅设置优化器等属性)。相反,当第一次将数据传递到模型时,它会“动态”编译。这发生在model._standardize_weights https://github.com/tensorflow/tensorflow/blob/v1.12.0/tensorflow/python/keras/engine/training.py#L1007:模型输出通过以下方式获得self.call(dummy_input_values, training=training) https://github.com/tensorflow/tensorflow/blob/v1.12.0/tensorflow/python/keras/engine/training.py#L1330。检查这个方法我们发现它构建层 https://github.com/tensorflow/tensorflow/blob/v1.12.0/tensorflow/python/keras/engine/sequential.py#L237(注意模型还没有建立)然后增量计算输出 https://github.com/tensorflow/tensorflow/blob/v1.12.0/tensorflow/python/keras/engine/sequential.py#L250通过使用Layer.call
(not __call__
)。这省略了所有元数据以及操作分组,因此导致图的结构不同(尽管其计算操作都是相同的)。再次检查 Tensorboard 我们发现:
展开这两个图,我们会发现它们包含相同的操作,但以不同的方式分组在一起。然而,这会导致keras.backend.get_session().graph._last_id
两个定义都不同,因此会产生不同的随机操作种子:
# With `input_shape`:
>>> keras.backend.get_session().graph._last_id
303
# Without `input_shape`:
>>> keras.backend.get_session().graph._last_id
7
绩效结果
我使用了 OP 的代码并进行了一些修改,以便进行类似的随机操作:
- 添加了描述的步骤here https://keras.io/getting-started/faq/#how-can-i-obtain-reproducible-results-using-keras-during-development确保随机化方面的可重复性,
- 设置随机种子
Dense
and Dropout
变量初始化,
- Removed
validation_split
因为分割发生在模型的“动态”编译之前,没有input_shape
因此可能会干扰种子,
- Set
shuffle = False
因为这可能使用单独的操作特定种子。
这是完整的代码(另外我还执行了export PYTHONHASHSEED=0
运行脚本之前):
from collections import deque
from functools import partial
import math
import random
import sys
import numpy as np
import tensorflow as tf
from tensorflow import keras
seed = int(sys.argv[1])
np.random.seed(1)
tf.set_random_seed(seed)
random.seed(1)
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)
keras.backend.set_session(sess)
def func(x):
return math.sin(x)*5 + math.sin(x*1.8)*4 + math.sin(x/4)*5
def get_data():
x = 0
dx = 0.1
q = deque()
r = 0
data = np.zeros((100000, 1002), np.float32)
while True:
x = x + dx
sig = func(x)
q.append(sig)
if len(q) < 1000:
continue
arr = np.array(q, np.float32)
for k in range(10):
xx = random.uniform(0.1, 9.9)
data[r, :1000] = arr[:1000]
data[r, 1000] = 5*xx #scale for easier fitting
data[r, 1001] = func(x + xx)
r = r + 1
if r >= data.shape[0]:
break
if r >= data.shape[0]:
break
q.popleft()
inputs = data[:, :1001]
outputs = data[:, 1001]
return (inputs, outputs)
Dense = partial(keras.layers.Dense, kernel_initializer=keras.initializers.glorot_uniform(seed=1))
Dropout = partial(keras.layers.Dropout, seed=1)
model = keras.Sequential()
model.add(Dense(64, activation=tf.nn.relu,
# input_shape=(1001,)
))
model.add(Dropout(0.05))
model.add(Dense(64, activation=tf.nn.relu))
model.add(Dropout(0.05))
model.add(Dense(64, activation=tf.nn.relu))
model.add(Dropout(0.05))
model.add(Dense(64, activation=tf.nn.relu))
model.add(Dropout(0.05))
model.add(Dense(1))
model.compile(
loss = 'mse',
optimizer = tf.train.RMSPropOptimizer(0.0005)
)
inputs, outputs = get_data()
shuffled = np.arange(len(inputs))
np.random.shuffle(shuffled)
inputs = inputs[shuffled]
outputs = outputs[shuffled]
hist = model.fit(inputs, outputs[:, None], epochs=10, shuffle=False)
np.save('without.{:d}.loss.npy'.format(seed), hist.history['loss'])
通过这段代码,我实际上希望这两种方法都能获得类似的结果,但事实证明它们并不相等:
for i in $(seq 1 10)
do
python run.py $i
done
绘制平均损失 +/- 1 标准。开发人员:
初始权重和初始预测
我验证了两个版本的初始权重和初始预测(拟合之前)是相同的:
inputs, outputs = get_data()
mode = 'without'
pred = model.predict(inputs)
np.save(f'{mode}.prediction.npy', pred)
for i, layer in enumerate(model.layers):
if isinstance(layer, keras.layers.Dense):
w, b = layer.get_weights()
np.save(f'{mode}.{i:d}.kernel.npy', w)
np.save(f'{mode}.{i:d}.bias.npy', b)
and
for i in 0 2 4 8
do
for data in bias kernel
do
diff -q "with.$i.$data.npy" "without.$i.$data.npy"
done
done
辍学的影响
[ ! ]删除所有内容后我检查了性能Dropout
层,在这种情况下,性能实际上是相同的。所以问题的关键似乎在于 Dropout 层。实际上,没有 Dropout 层的模型的性能与有 Dropout 层的模型的性能相同with丢弃层但是without指定input_shape
。所以看来没有input_shape
Dropout 层无效。
基本上这两个版本之间的区别在于一个使用__call__
和其他用途call
计算输出(如上所述)。由于性能与没有 Dropout 层类似,可能的解释可能是 Dropout 层在以下情况下不会下降:input_shape
没有指定。这可能是由于training=False
,即各层无法识别它们处于训练模式。但是我不明白为什么会发生这种情况。我们还可以再次考虑 Tensorboard 图。
指定input_shape
:
未指定input_shape
:
哪里的switch
还取决于学习阶段(如前所述):
为了验证training
kwarg 让我们子类化Dropout
:
class Dropout(keras.layers.Dropout):
def __init__(self, rate, noise_shape=None, seed=None, **kwargs):
super().__init__(rate, noise_shape=noise_shape, seed=1, **kwargs)
def __call__(self, inputs, *args, **kwargs):
training = kwargs.get('training')
if training is None:
training = keras.backend.learning_phase()
print('[__call__] training: {}'.format(training))
return super().__call__(inputs, *args, **kwargs)
def call(self, inputs, training=None):
if training is None:
training = keras.backend.learning_phase()
print('[call] training: {}'.format(training))
return super().call(inputs, training)
我获得了两个版本的类似输出,但是调用__call__
失踪时input_shape
未指定:
[__call__] training: Tensor("keras_learning_phase:0", shape=(), dtype=bool)
[call] training: Tensor("keras_learning_phase:0", shape=(), dtype=bool)
[__call__] training: Tensor("keras_learning_phase:0", shape=(), dtype=bool)
[call] training: Tensor("keras_learning_phase:0", shape=(), dtype=bool)
[__call__] training: Tensor("keras_learning_phase:0", shape=(), dtype=bool)
[call] training: Tensor("keras_learning_phase:0", shape=(), dtype=bool)
[__call__] training: Tensor("keras_learning_phase:0", shape=(), dtype=bool)
[call] training: Tensor("keras_learning_phase:0", shape=(), dtype=bool)
所以我怀疑问题出在内部某个地方__call__
但现在我不知道它是什么。
System
我正在使用 Ubuntu 16.04、Python 3.6.7 和 Tensorflow 1.12.0conda
(不支持 GPU):
$ uname -a
Linux MyPC 4.4.0-141-generic #167-Ubuntu SMP Wed Dec 5 10:40:15 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
$ python --version
Python 3.6.7 :: Anaconda, Inc.
$ conda list | grep tensorflow
tensorflow 1.12.0 mkl_py36h69b6ba0_0
tensorflow-base 1.12.0 mkl_py36h3c3e929_0
Edit
我也有过keras
and keras-base
安装(keras-applications
and keras-preprocessing
需要由tensorflow
):
$ conda list | grep keras
keras 2.2.4 0
keras-applications 1.0.6 py36_0
keras-base 2.2.4 py36_0
keras-preprocessing 1.0.5 py36_0
全部删除后,keras*
and tensorflow*
,然后重新安装tensorflow
,差异消失了。即使重新安装后keras
结果仍然相似。我还检查了不同的 virtualenv,其中通过安装了tensorflowpip
;这里也没有差异。现在我无法再重现这种差异。这肯定是张量流的安装损坏了。