如果省略 input_shape,Keras 模型的结构是什么?为什么它的性能更好?

2024-03-03

我省略了input_shape错误地出现在我的 Keras 模型的第一层中。最终我注意到了这一点并修复了它——我的模型的性能急剧下降。

查看有和没有的模型结构input_shape,我发现性能更好的模型的输出形状为multiple。此外,将其绘制为plot_model显示层之间没有连接:

就性能而言,我理解的模型(使用 input_shape)在使用我的测试代码(如下)进行 10 个 epoch 后实现了 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])

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文档内容如下:

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

现在回到原来的问题,如果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_shapeDropout 层无效。

基本上这两个版本之间的区别在于一个使用__call__和其他用途call计算输出(如上所述)。由于性能与没有 Dropout 层类似,可能的解释可能是 Dropout 层在以下情况下不会下降:input_shape没有指定。这可能是由于training=False,即各层无法识别它们处于训练模式。但是我不明白为什么会发生这种情况。我们还可以再次考虑 Tensorboard 图。

指定input_shape:

未指定input_shape:

哪里的switch还取决于学习阶段(如前所述):

为了验证trainingkwarg 让我们子类化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;这里也没有差异。现在我无法再重现这种差异。这肯定是张量流的安装损坏了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如果省略 input_shape,Keras 模型的结构是什么?为什么它的性能更好? 的相关文章

随机推荐

  • 如何以编程方式在 jupyter 笔记本中添加幻灯片单元格元数据?

    Jupyter 笔记本有一种可视化单元格元数据的方法 以便您能够使用以下命令参数化当您将笔记本导出到幻灯片时单元格的外观nbconvert Example 例如 我想以编程方式将该元数据添加到单元格中 而不使用 GUI 以便我可以自动创建幻
  • 在 sparql IN 子句中使用另一个 sparql

    我正在使用 SPARQL 我想知道是否可以在 in 子句中放入 sparql 更具体地说 我需要获取具有此 sparql 查询特定条件的实体 s1 s2 s1 在某个字段上的聚合值大于 5 select s1 x s2 WHERE s1 r
  • 没有模型的 MVC 3 验证

    我有一个关于 mvc3 验证的问题 内置的验证看起来很棒 然而 在一种情况下 我不得不使用 javascript 导致它的外观和感觉不一致 警报窗口与漂亮的红色文本 我们有一个表单 其中包含一些供用户输入的字段 提交后 一些 ajax 代码
  • 在我的 WPF 应用程序中捕获没有焦点的按键事件

    我在 WPF 中开发了一个屏幕键盘 我需要夺取钥匙 新闻事件 通过键盘 以跟踪 Caps Lock Shift 等等 无论它们是否被按下 请注意 当任何其他应用程序时 我的应用程序会失去焦点 比如记事本 被打开 谁能建议如何在 WPF 中实
  • 写入会话数据失败(memcache)

    我注意到一个间歇性的我们的 Memcached 会话处理程序存在问题 出现的错误是 未知 无法写入会话数据 memcache 请确认 session save path 的当前设置是正确的 Notes 对于不同的用户来说 这似乎是一个间歇性
  • 具有多个元素的数组的真值是不明确的。 numpy 错误

    具有多个元素的数组的真值是不明确的 使用 a any 或 a all 我收到此代码的上述错误 几个月前我没有遇到这种错误 但我现在明白了 我该如何解决这个问题 t np linspace np zeros 2 2 np ones 2 2 2
  • 创建 sqlite3.lib 文件/在 Visual Studios 中使用 sqlite3 [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我一生都无法编译这段代码 我让它在所有其他计算机上运行良好 但我必须切换到 Windows 计算机 并且我试图将 sqlite3 添加回包
  • 当结构体开头的名称和结构体末尾的名称不同时,这意味着什么?

    当结构体开头的名称和结构体末尾的名称不同时 这意味着什么 例如 struct book char title 50 int year boo 或者例如 typedef struct book char title 50 int year b
  • 如何为库编写 CMake 安装?

    这是我的尝试 IF NOT WIN32 INSTALL TARGETS LIB INSTALL DIR tinyscheme nix BINARY DIR libtinyscheme so INSTALL TARGETS tinyschem
  • 第一个 Android 密钥库证书过期后会发生什么?

    就目前我所记得的 10 years是在 Eclipse 中创建密钥库时的默认设置 我认为 大约 5 年前 Android 开发已经成为主流 所以我预计很多应用程序在 5 年内将无法在 Play 商店中更新 到目前为止 我还没有找到如何 延长
  • 如何在微调器的底部添加一行,就像android中的edittext一样

    你好 我正在制作我正在使用的演示应用程序EditText其中底部出现一条线是可以的 但在旋转器的情况下则不然 这怎么可能做到 布局 xml
  • 如何添加表格作为标题?

    我正在与iTextSharp尝试向生成的 PDF 添加页眉和页脚 但是 如果我尝试添加宽度为页面 100 的页眉 则会遇到一些问题 所以我做了以下事情 1 我创建了一个名为PDF页眉页脚扩展了 iTextSharpPdfPageEventH
  • 带有 javascript_include_tag 的 Ruby On Rails 错误

    所以 据我所知 我的问题似乎是 ruby 没有加载所有的 javascript 文件 因为我没有收到确认消息 并且 link to with method delete 对我没有任何作用 使用提供给我的默认设置rails new views
  • 如何使用 xlrd 版本 1.1.0 读取 Excel 中的字体和背景颜色

    实际上我使用的是xlrd模块1 1 0版本 但我不知道如何读取单元格属性 如背景颜色 字体以及单元格是否被锁定 我尝试使用 import xlrd book xlrd open workbook sample xls formatting
  • 如何为我的 C# XNA 游戏制作 GUI? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我的游戏有基本功能 可以通过命令行玩 但我想在它上面放一个 GUI 它是一款使用 C 和 XNA 框架编写的平台游戏 我用谷歌搜索并找
  • Tridion DTAP 澄清 - 我需要多少个内容交付设置?

    我询问了 Tridion DTAP 的开发人员设置 开发 测试 验收和生产 在另一个问题中 https stackoverflow com questions 11166754 proper dtap setup for content d
  • 使用 Python + Pylons 进行错误处理

    使用 Python Pylons 处理错误的正确方法是什么 假设用户通过表单设置密码 当通过控制器传递给模型类时 会抛出错误 因为密码太短 应如何处理该错误 以便在网页上显示错误消息 而不是整个脚本终止于错误页面 控制器本身是否应该有任何错
  • endl 和 cout 后的行距?

    我注意到在下面的代码中 cout lt lt Please enter your number cin gt gt Number cout lt lt Is this spaced C 命令窗口中的输出自动将 Is this spaced
  • 如何通过 Javascript 访问元素的 focus/hover/visited CSS 属性?

    我现在可能很累并且想法很奇怪 但我根本找不到如何检索元素的聚焦 悬停或访问状态中定义的 CSS 属性的值 目标是使用 Javascript 中的值 重要的 I 不需要获取聚焦 悬停 访问的元素 我想访问某些任意元素的值在 DOM 中为以下状
  • 如果省略 input_shape,Keras 模型的结构是什么?为什么它的性能更好?

    我省略了input shape错误地出现在我的 Keras 模型的第一层中 最终我注意到了这一点并修复了它 我的模型的性能急剧下降 查看有和没有的模型结构input shape 我发现性能更好的模型的输出形状为multiple 此外 将其绘