我错误地省略了Keras模型第一层中的input_shape
。最终,我注意到并修复了该问题–模型的性能急剧下降。
查看带有和不带有input_shape
的模型的结构,我发现性能更好的模型的输出形状为multiple
。此外,使用plot_model
进行绘制会显示各层之间没有任何连接:
在性能方面,我理解的模型(带有input_shape)在用我的测试代码(下图)经过10个周期后达到了4.0513(MSE)的验证损失,而“怪异”模型管理的是1.3218 –仅区别在于随着更多的时代而增加。
模型定义:
model = keras.Sequential()
model.add(keras.layers.Dense(64, activation=tf.nn.relu, input_shape=(1001,)))
# add or remove this ^^^^^^^^^^^^^^^^^^^
model.add(keras.layers.Dropout(0.05))
...
(不必担心细节,这只是一个模型,该模型演示了有无input_shape时性能的差异)
那么,性能更好的模型中发生了什么?什么是multiple
?各层之间如何真正连接?在指定input_shape
的同时如何建立相同的模型?
完整脚本:
import tensorflow as tf
from tensorflow import keras
import numpy as np
from collections import deque
import math, random
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)
np.random.seed(1)
tf.set_random_seed(1)
random.seed(1)
model = keras.Sequential()
model.add(keras.layers.Dense(64, activation=tf.nn.relu, input_shape=(1001,)))
# add or remove this ^^^^^^^^^^^^^^^^^^^
model.add(keras.layers.Dropout(0.05))
model.add(keras.layers.Dense(64, activation=tf.nn.relu))
model.add(keras.layers.Dropout(0.05))
model.add(keras.layers.Dense(64, activation=tf.nn.relu))
model.add(keras.layers.Dropout(0.05))
model.add(keras.layers.Dense(64, activation=tf.nn.relu))
model.add(keras.layers.Dropout(0.05))
model.add(keras.layers.Dense(1))
model.compile(
loss = 'mse',
optimizer = tf.train.RMSPropOptimizer(0.0005),
metrics = ['mae', 'mse'])
inputs, outputs = get_data()
hist = model.fit(inputs, outputs, epochs=10, validation_split=0.1)
print("Final val_loss is", hist.history['val_loss'][-1])
答案 0 :(得分:2)
结果不同的原因是因为两个模型的初始权重不同。一个人的表现(明显)好于另一个人的事实纯粹是偶然的,正如@today提到的,他们获得的结果大致相似。
正如tf.set_random_seed
的文档所述,随机运算使用两个种子,即图级种子和特定于操作的种子; tf.set_random_seed
设置图形级种子:
依赖于随机种子的操作实际上是从两个种子派生的:图形级种子和操作级种子。这将设置图级种子。
看一下Dense
的定义,我们看到the default kernel initializer是'glorot_uniform'
(这里只考虑内核初始化程序,而偏置初始化程序也是如此)。深入研究源代码,我们最终会发现这将使用默认参数来获取GlorotUniform
。具体来说,该 操作(即权重初始化)的random number generator seed设置为None
。现在,如果我们检查该种子在何处使用,我们发现它例如传递给了random_ops.truncated_normal
。这又(与所有随机操作一样)现在获取两个种子,一个是图级种子,另一个是特定于操作的种子:seed1, seed2 = random_seed.get_seed(seed)
。我们可以检查get_seed
函数的定义,发现如果没有给出特定于操作的种子(在我们的情况下),则它是从当前图的属性op_seed = ops.get_default_graph()._last_id
派生的。 tf.set_random_seed
文档的相应部分为:
- 如果设置了图级别的种子,但未设置操作种子:系统确定性地选择一个操作种子和图级别的种子,以便获得唯一的随机序列。
现在回到原始问题,无论是否定义input_shape
,图结构都会有所不同。再次查看一些源代码,我们发现Sequential.add
仅在指定input_shape
的情况下以 增量方式构建网络的输入和输出。否则,它仅存储层列表(model._layers
);比较model.inputs, model.outputs
的两个定义。输出由calling the layers directly增量构建,该输出调度到Layer.__call__
。该包装器将构建该层,设置该层的输入和输出,并向输出中添加一些元数据。它还使用ops.name_scope
对操作进行分组。我们可以从Tensorboard提供的可视化效果中看到这一点(Input -> Dense -> Dropout -> Dense
的简化模型架构示例):
现在,在我们未指定input_shape
的情况下,所有模型都只有一层列表。即使在调用compile
之后,该模型实际上仍是not compiled(仅设置了诸如优化器之类的属性)。取而代之的是,当第一次将数据传递到模型中时,它是“即时”编译的。这发生在model._standardize_weights
中:通过self.call(dummy_input_values, training=training)
获得模型输出。检查此方法,我们发现它builds the layers(注意尚未建立模型),然后通过使用Layer.call
(不是__call__
)computes the output incrementally。这样就省去了所有元数据以及操作的分组,因此导致了图的结构不同(尽管其计算操作都相同)。再次检查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的代码进行了一些修改:
Dense
和Dropout
变量初始化的随机种子,validation_split
,因为拆分是在没有input_shape
的模型“即时”编译之前发生的,因此可能会干扰种子,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 std。开发人员:
我验证了两个版本的初始权重和初始预测(拟合之前)是相同的:
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)
和
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层的模型的性能与带有 掉落层的模型的性能相同,但是没有的模型指定input_shape
。因此,似乎没有input_shape
的Dropout层是无效的。
基本上,两个版本之间的区别在于,一个版本使用__call__
,而另一个版本使用call
计算输出(如上所述)。由于性能类似于没有Dropout层时的性能,可能的解释可能是未指定input_shape
时Dropout层不会掉落。这可能是由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__
内,但是现在我不知道是什么。
我正在通过conda
使用Ubuntu 16.04,Python 3.6.7和Tensorflow 1.12.0(不支持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
我还安装了keras
和keras-base
(keras-applications
需要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*
和tensorflow*
,然后重新安装tensorflow
之后,差异消失了。即使重新安装keras
,结果仍然相似。我还检查了另一个虚拟环境,其中virtualenv通过pip
安装了tensorflow;这里也没有差异。现在,我再也无法重现这种差异。一定是张量流的破损安装。