代码之家  ›  专栏  ›  技术社区  ›  Roman Starkov

如果省略输入_形状,Keras模型的结构是什么?为什么它的性能更好?

  •  2
  • Roman Starkov  · 技术社区  · 7 年前

    我漏掉了那封信 input_shape 在我的Keras模型的第一层中。最终,我注意到了这一点,并对其进行了修复——我的模型的性能急剧下降。

    查看模型的结构(带和不带) multiple . 而且,用 plot_model 显示层之间没有连接:

    enter image description here

    就性能而言,我所理解的模型(带有输入_形状)在使用我的测试代码(如下所示)进行了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))
    ...
    

    (不必在意细节,这只是一个模型,演示了使用和不使用输入形状时的性能差异)

    是什么 倍数 ? 这些层是如何真正连接起来的?我如何在创建相同模型的同时指定 ?

    完整脚本:

    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])
    
    1 回复  |  直到 7 年前
        1
  •  4
  •   a_guest    7 年前

    TL;博士

    结果不同的原因是两个模型的初始权重不同。其中一个表现(显著)优于另一个的事实纯属偶然,正如@today所提到的,他们获得的结果大致相似。

    作为 tf.set_random_seed 解释说,随机操作使用两个种子 和 操作特定种子 ; tf.set_随机_种子

    依赖随机种子的操作实际上从两个种子派生:图形级种子和操作级种子。这将设置图形级别的种子。

    让我们来看看 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_随机_种子

    1. 如果设置了图形级种子,但未设置操作种子:系统将确定地选择操作种子和图形级种子,以便获得唯一的随机序列。

    现在回到原来的问题,如果 input_shape 定义与否。再看一点源代码,我们发现 Sequential.add 只有 输入波形 被指定;否则,它只存储层列表( model._layers ); 比较 model.inputs, model.outputs calling the layers directly 派往 Layer.__call__ . 这个包装器构建层,设置层的输入和输出,并向输出添加一些元数据;它还使用 ops.name_scope 分组操作。我们可以从 Tensorboard Input -> Dense -> Dropout -> Dense ):

    Tensorboard visualization

    现在我们没有具体说明 输入波形 compile 模型实际上是 not compiled (仅设置优化器等属性)。相反,当第一次将数据传递到模型时,它是“动态”编译的。这种情况发生在 model._standardize_weights :模型输出通过以下方式获得: self.call(dummy_input_values, training=training) builds the layers (请注意,模型尚未构建)然后 computes the output incrementally Layer.call (不是 __call__

    Tensorboard visualization

    展开这两个图,我们会发现它们包含相同的操作,分组方式不同。然而,这样做的效果是 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 为确保随机化方面的再现性,
    • 设置随机种子 稠密的 和 Dropout 变量初始化,
    • 远离的 validation_split 因为拆分发生在“动态”编译模型之前,没有
    • 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标准偏差:

    Performance per epoch

    我验证了两个版本的初始权重和初始预测(拟合前)是相同的:

    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
    

    [ ! ] 我在删除所有文件后检查了性能 辍学者 在这种情况下,性能实际上是相等的。因此,关键似乎在于辍学层。实际上,没有退出层的模型的性能与模型的性能相同 具有 退出层,但 没有 指定 输入波形 输入波形

    基本上,这两个版本的区别在于 __召唤__ call 输入波形 未指定。这可能是由于 training=False

    指定 输入波形 :

    Dropout node

    未指定 :

    Dropout node

    switch

    Dropout node, connection to switch

    核实 training kwarg让我们来定义子类 辍学者 :

    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__] 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)
    

    所以我怀疑问题出在你的内心深处 __召唤__

    系统

    我使用的是Ubuntu 16.04、Python 3.6.7和Tensorflow 1.12.0 conda (不支持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* ,然后重新安装 张量流 ,差异消失了。即使在重新安装之后 凯拉斯 pip ; 这里也没有差异。现在我再也无法重现这种差异了。一定是tensorflow的装置坏了。