为什么TensorFlow 2比TensorFlow 1慢得多?

198
许多用户引用它作为切换到Pytorch的原因,但我仍然没有找到一个正当的理由/解释来牺牲最重要的实际质量--速度,来进行急切执行。

以下是代码基准测试性能,TF1 vs. TF2 - 在TF1上运行速度普遍比TF2快47%至276%。

我的问题是:在图形或硬件级别上,是什么导致如此显着的减速?


寻找详细答案- 已经熟悉广泛的概念。有关Git

规格: CUDA 10.0.130, cuDNN 7.4.2, Python 3.7.4, Windows 10, GTX 1070


基准测试结果:


更新:根据下面的代码禁用急切执行并不能帮助。然而,行为不一致:有时以图形模式运行会显着提高速度,而其他时候相对于Eager则更慢。


基准测试代码:

# use tensorflow.keras... to benchmark tf.keras; used GPU for all above benchmarks
from keras.layers import Input, Dense, LSTM, Bidirectional, Conv1D
from keras.layers import Flatten, Dropout
from keras.models import Model
from keras.optimizers import Adam
import keras.backend as K
import numpy as np
from time import time

batch_shape = (32, 400, 16)
X, y = make_data(batch_shape)

model_small = make_small_model(batch_shape)
model_small.train_on_batch(X, y)  # skip first iteration which builds graph
timeit(model_small.train_on_batch, 200, X, y)

K.clear_session()  # in my testing, kernel was restarted instead

model_medium = make_medium_model(batch_shape)
model_medium.train_on_batch(X, y)  # skip first iteration which builds graph
timeit(model_medium.train_on_batch, 10, X, y)

使用的函数:

def timeit(func, iterations, *args):
    t0 = time()
    for _ in range(iterations):
        func(*args)
    print("Time/iter: %.4f sec" % ((time() - t0) / iterations))

def make_small_model(batch_shape):
    ipt   = Input(batch_shape=batch_shape)
    x     = Conv1D(128, 400, strides=4, padding='same')(ipt)
    x     = Flatten()(x)
    x     = Dropout(0.5)(x)
    x     = Dense(64, activation='relu')(x)
    out   = Dense(1,  activation='sigmoid')(x)
    model = Model(ipt, out)
    model.compile(Adam(lr=1e-4), 'binary_crossentropy')
    return model

def make_medium_model(batch_shape):
    ipt   = Input(batch_shape=batch_shape)
    x     = Bidirectional(LSTM(512, activation='relu', return_sequences=True))(ipt)
    x     = LSTM(512, activation='relu', return_sequences=True)(x)
    x     = Conv1D(128, 400, strides=4, padding='same')(x)
    x     = Flatten()(x)
    x     = Dense(256, activation='relu')(x)
    x     = Dropout(0.5)(x)
    x     = Dense(128, activation='relu')(x)
    x     = Dense(64,  activation='relu')(x)
    out   = Dense(1,   activation='sigmoid')(x)
    model = Model(ipt, out)
    model.compile(Adam(lr=1e-4), 'binary_crossentropy')
    return model
    
def make_data(batch_shape):
    return np.random.randn(*batch_shape), np.random.randint(0, 2, (batch_shape[0], 1))

你是否曾经使用过类似cProfile这样的工具来分析哪一部分使它们如此不同? - zihaozhihao
@zihaozhihao 我已经阅读了这篇文章,虽然不是针对这个问题;根据之前的链接和编写自定义优化器,我已经熟悉了调用的差异,但是我不理解为什么一个比另一个慢 - 任何非 TensorFlow 专家都无法从源代码中理解它,而且源代码混乱不堪,也没有相对性能的文档记录。需要图形/硬件级别的信息,而分析工具无法提供(就我所知)。 - OverLordGoldDragon
在这两个测试中,NumPy版本是否相同? - chabir
哎呀……如果旧版Keras已经比PyTorch慢得多,那现在想象一下吧。 - Daniel Möller
问题随模型大小扩展吗?另外,您是否尝试在其他操作系统上运行相同的基准测试? - Oleg Vorobiov
在像CIFAR-10这样的简单数据集上训练分类器时,与速度一起,当在TF1和TF2上进行相同数量的epochs训练时,我也发现TF1的准确性更高。例如,当一个简单模型训练50个epochs时,在TF1上显示出更好的准确性,而在TF2上则表现出奇怪的结果。 - Mohbat Tharani
2个回答

127
更新于 8/30/2020: TensorFlow 2.3 终于实现了:所有情况的运行速度都比之前的版本快,甚至更快。

此外,我之前的更新对 TensorFlow 不公平;我的 GPU 有问题,最近一直在过热。如果您看到迭代时间的上升茎形图,则这是一个可靠的症状。最后,请参阅开发人员关于 Eager vs Graph 的说明。

这可能是我在这个答案中的最后一次更新。关于模型速度的真实统计数据只能由您自己在您的设备上找到。


更新于2020年5月19日:TF 2.2,使用相同的测试:Eager模式速度仅有轻微提高。下面是针对大型Numpy train_on_batch情况的绘图,x轴是连续的拟合迭代次数;我的GPU并没有达到其最大容量,所以怀疑它不会被限制,但是随着时间的推移,迭代确实变慢了。

enter image description here

根据上述内容,Graph和Eager分别比它们的TF1版本慢1.56x1.97x。不确定是否需要进一步调试,因为考虑到TensorFlow对自定义/低级功能的支持较差,我正在考虑切换到Pytorch。但是,我已经打开了一个问题以获取开发人员的反馈。
更新于2020年2月18日:我已经测试了2.1和2.1-nightly版本,结果是千差万别的。除了一个配置(模型和数据大小)之外,其他都比TF2和TF1的最佳性能快得多或者一样快。那唯一较慢的一个配置,尤其是在Graph execution(执行图)中,速度显著地变慢了(1.6倍到2.5倍)。

此外,对于我测试过的一个大型模型来说,Graph和Eager之间的重现性差异非常极端 - 这不可通过随机性/计算并行性来解释。由于时间限制,我目前无法提供这些声明的可重现代码,因此我强烈建议您为自己的模型进行测试。

我还没有在Git上开启有关这些问题的问题,但我评论了原始网页- 还没有得到回应。如果有进展,我会及时更新答案。


结论:如果你知道该怎么做,那么它不是问题。但是如果你不知道该怎么做,那么它可能会让你付出很高的代价 - 平均需要多次 GPU 升级,最坏情况下甚至需要多个 GPU。


这个答案旨在提供问题的高级描述,以及如何根据您的需求决定培训配置的指南。有关详细的低级描述,包括所有基准测试结果+使用的代码,请参见我的其他答案。

如果我了解到更多信息,我会更新我的答案 - 您可以将此问题加为书签/“标记”以供参考。


问题概述:作为 TensorFlow 开发者 Q. Scott Zhu 所确认的,TF2 的重点开发在 Eager 执行和与 Keras 的紧密集成上,这涉及对 TF 源代码进行了大量更改,包括在图形级别上。优点是:大大扩展了处理、分发、调试和部署能力。然而,其中一些成本是速度较慢。
然而,问题相当复杂。不仅仅是 TF1 vs. TF2 - 影响训练速度的显著差异因素包括:
  1. TF2 vs. TF1
  2. Eager vs. Graph mode
  3. keras vs. tf.keras
  4. numpy vs. tf.data.Dataset vs. ...
  5. train_on_batch() vs. fit()
  6. GPU vs. CPU
  7. model(x) vs. model.predict(x) vs. ...

不幸的是,以上几乎都与其他内容相关,每个都可能相对于另一个至少使执行时间加倍。幸运的是,您可以系统地确定哪种方法最有效,并使用一些快捷方式 - 正如我将展示的那样。


我该怎么做? 目前唯一的方法是 - 根据您的特定模型、数据和硬件进行实验。没有单一的配置始终表现最佳 - 但有一些要点和禁忌可以简化您的搜索:

>> 要做:

  • train_on_batch() + numpy + tf.keras + TF1 + Eager/Graph
  • train_on_batch() + numpy + tf.keras + TF2 + Graph
  • fit() + numpy + tf.keras + TF1/TF2 + Graph + 大型模型和数据

>> 不要做:

  • fit() + numpy + keras 适用于小到中型模型和数据

  • fit() + numpy + tf.keras + TF1/TF2 + Eager

  • train_on_batch() + numpy + keras + TF1 + Eager

  • [重要] tf.python.keras; 它可能会运行10-100倍慢,并且存在许多错误; 更多信息

    • 这包括layersmodelsoptimizers和相关的“开箱即用”使用导入;操作、工具和相关的“私有”导入是可以的 — 但为了确保,请检查备选项,以及它们是否在tf.keras中使用。
请参考我其他回答底部的代码,以获取一个示例基准测试设置。上面的列表主要基于其他答案中的“BENCHMARKS”表格。

上述 DO's 和 DON'T's 的限制:

  • 这个问题的标题是“为什么TF2比TF1慢得多?”,尽管它的正文明确关注训练,但问题并不仅限于此;推断也受到主要速度差异的影响,甚至在相同的TF版本、导入、数据格式等情况下也会受到影响 - 参见this answer
  • RNNs 很可能会显着改变另一个答案中的数据表格,因为它们在TF2中得到了改进。
  • 模型主要使用 Conv1DDense - 没有 RNN,稀疏数据/目标,4/5D 输入和其他配置
  • 输入数据仅限于 numpytf.data.Dataset,而许多其他格式也存在;请参见另一个答案
  • GPU 被使用;结果在 CPU 上将会有所不同。实际上,在我提出这个问题时,我的 CUDA 没有正确配置,一些结果基于 CPU。

为什么TF2会为了迅速执行而牺牲最实用的质量——速度呢?事实上并没有,图形仍然可用。但如果问题是“为什么要急切执行”:
- 更好的调试:你可能遇到过许多问题,比如“如何获得中间层输出”或“如何检查权重”,使用急切执行,这几乎就像.__dict__一样简单。相比之下,图形需要熟悉特殊的后端函数,大大复杂化了整个调试和内省过程。 - 更快的原型制作:与上述类似的想法;更快的理解=留给实际DL的时间更多。
如何启用/禁用 Eager?
tf.enable_eager_execution()  # TF1; must be done before any model/tensor creation
tf.compat.v1.disable_eager_execution() # TF2; above holds

在TF2中存在误导;请参见此处

额外信息:

  • 在TF2中要小心使用_on_batch()方法;根据TF开发人员的说法,它们仍然使用较慢的实现,但这并非有意为之-即将修复。有关详细信息,请参见其他答案。

请求TensorFlow开发人员:

  1. 请修复train_on_batch()以及多次调用fit()的性能问题;对于许多人,特别是我来说,自定义训练循环非常重要。
  2. 为了让用户了解这些性能差异,请添加文档/ docstring提及。
  3. 提高整体执行速度,避免用户转向Pytorch。

致谢:感谢:


更新:

  • 2019年11月14日 - 在我的实际应用中找到了一个模型,使用Numpy输入数据在所有配置下运行速度较慢。差异范围在13-19%之间,平均为17%。然而,kerastf.keras之间的差异更加明显: 18-40%,平均32%(TF1和2都是如此)。 (* - 除Eager外,因为TF2 OOM'd)

  • 2019年11月17日 - 开发人员在最近的提交中更新了on_batch()方法,并声称已经改进了速度 - 将在TF 2.1中发布,或者现在可以使用tf-nightly。由于无法运行后者,将延迟测试直至2.1。

  • 2020年2月20日 - 预测性能也值得测试; 例如,在TF2中,CPU预测时间可能涉及周期性峰值


5
fit_generator怎么样?......我实际上从来不想用train_on_batch,管理自己的训练循环跨批次是一个极其糟糕的反模式,即使代价很大也要避免。 - ely
@ely 如我在另一个回答中所述,它仍需经过测试 - 但如果有什么问题,我预测它将与小的额外数据处理开销相适应。至于训练循环,我编写了自己的定制循环,最终变成了一种API;fit_generator缺乏内省、可定制性和保存/加载功能,因此对我来说绝对不行。我最终会在Github上发布我的训练循环。 - OverLordGoldDragon
缺乏内省和可定制性对我来说是一种特性,而不是错误。我不知道保存/加载评论是指什么?在数据生成器无法控制的循环过程中进行中间保存/加载?(我个人也很高兴只依赖回调函数完成这个任务,并且认为任何进一步的可定制性都是我的训练循环设计有问题的代码气味)。 - ely
1
@ely 这并不简单,但对于训练具有复杂输入数据管道、目标函数和非 API 模型配置(例如集成)是必要的。内省对于许多调试和特征工程目的来说是必须的。缺乏外部保存/加载以及计算密集型模型的训练循环暂停和恢复功能 - 是一场噩梦。无论如何,最终取决于您的具体需求,而偏离主题;使用 fit_generator 测试应用程序性能的最可靠方法就是测试它。 - OverLordGoldDragon

71

THIS ANSWER: 旨在提供详细的问题描述,包括TF2与TF1的训练循环、输入数据处理和Eager与Graph模式执行。有关问题摘要和解决方案,请参见我的其他答案。


PERFORMANCE VERDICT: 有时一个更快,有时另一个更快,取决于配置。就 TF2 与 TF1 而言,它们平均水平相当,但存在重要的基于配置的差异,TF1 比 TF2 更常占优势。请参见下面的“基准测试”。


EAGER VS. GRAPH: 对于某些人来说,这是整个答案的精髓:根据我的测试,TF2 的 Eager 比 TF1 的 Eager 更。更多细节请参见下文。

两者之间的根本区别是:Graph会主动设置一个计算网络,并在“被告知”时执行——而 Eager 在创建时即执行所有内容。但故事从这里开始:

  • Eager 并不缺乏 Graph,实际上可能大部分都是 Graph,与预期相反。它主要是执行的 Graph——这包括模型和优化器权重,构成了图的很大一部分。

  • Eager 在执行时会重建自己的一部分图形;这是 Graph 不完全构建的直接后果——请参见分析器结果。这有计算开销。

  • 使用 Numpy 输入时,Eager 更慢;根据此 Git 评论和代码,在 Eager 中,Numpy 输入包括将张量从 CPU 复制到 GPU 的开销成本。通过源代码步进,数据处理差异很明显;Eager 直接传递 Numpy,而 Graph 传递张量,然后计算为 Numpy;不确定确切过程,但后者应该涉及 GPU 级别的优化。

  • TF2 Eager 比 TF1 Eager 更——这是……出乎意料的。请参见下面的基准测试结果。差异从微不足道到显著,但是始终如一。不确定原因——如果 TF 开发人员澄清,将更新答案。


TF2 vs. TF1:引用 TF 开发人员 Q. Scott Zhu 的相关部分回复——带有一些我的强调和措辞:

在 Eager 中,运行时需要执行操作并返回每行 Python 代码的数值。单步执行的性质导致其速度变慢。

在 TF2 中,Keras 利用 tf.function 为模型构建其训练、评估和预测的图形。我们将它们称为“执行函数”。在 TF1 中,“执行函数”是 FuncGraph,它与 TF 函数共享一些公共组件,但具有不同的实现。

在过程中,我们不知怎么地留下了 train_on_batch()、test_on_batch() 和 predict_on_batch() 的不正确实现。它们在数值上仍然是正确的,但 x_on_batch 的执行函数是一个纯 Python 函数,而不是一个被 tf.function 包装的 Python 函数。这将导致速度变慢。
在 TF2 中,我们将所有输入数据转换为 tf.data.Dataset,通过这种方式,我们可以统一执行函数以处理单一类型的输入。可能会有一些开销在数据集转换时出现,我认为这是一次性的开销,而不是每个批次的成本。
针对 Eager 模式的速度问题,我们使用了 @tf.function,它会将一个 Python 函数转换为图形。当提供数值值(例如 np 数组)时,tf.function 的主体被转换为静态图形,并进行优化,返回最终值,这是快速且应该具有类似 TF1 图形模式的性能。
我不同意上段的最后一句话,并根据我的分析结果指出,Eager 的输入数据处理速度比 Graph 快得多。此外,我不确定特别是 tf.data.Dataset,但 Eager 反复调用多个相同的数据转换方法 - 请参见分析器。
最后,开发人员关联的提交:大量更改以支持 Keras v2 循环Train Loops: 根据(1)Eager vs. Graph;(2)输入数据格式,训练将使用不同的 train loop 进行 - 在 TF2 中,_select_training_loop()training.py,其中之一:
training_v2.Loop()
training_distributed.DistributionMultiWorkerTrainingLoop(
              training_v2.Loop()) # multi-worker mode
# Case 1: distribution strategy
training_distributed.DistributionMultiWorkerTrainingLoop(
            training_distributed.DistributionSingleWorkerTrainingLoop())
# Case 2: generator-like. Input is Python generator, or Sequence object,
# or a non-distributed Dataset or iterator in eager execution.
training_generator.GeneratorOrSequenceTrainingLoop()
training_generator.EagerDatasetOrIteratorTrainingLoop()
# Case 3: Symbolic tensors or Numpy array-like. This includes Datasets and iterators 
# in graph mode (since they generate symbolic tensors).
training_generator.GeneratorLikeTrainingLoop() # Eager
training_arrays.ArrayLikeTrainingLoop() # Graph

每个处理资源分配方式不同,对性能和功能有不同的影响。


训练循环:fit vs train_on_batch,keras vs tf.keras:这四个中的每一个使用不同的训练循环,尽管可能没有每种可能的组合都有。例如,kerasfit使用一种fit_loop形式,例如training_arrays.fit_loop(),其train_on_batch可能使用K.function()tf.keras具有更复杂的层次结构,在前面的部分中部分描述。


训练循环:文档 - 有关某些不同执行方法的相关源docstring

与其他TensorFlow操作不同,我们不会将python数值输入转换为张量。此外,为每个不同的python数值生成一个新图形

function 为每组唯一的输入形状和数据类型实例化单独的图形

单个 tf.function 对象可能需要映射到多个计算图形,这应该仅作为性能可见(跟踪图形具有非零的计算和内存成本


输入数据处理器:与上面类似,根据运行时配置(执行模式,数据格式,分发策略)设置内部标志来逐个选择处理器。最简单的情况是使用Eager,它直接使用Numpy数组。有关一些特定示例,请参见此答案


模型大小,数据大小:

  • 是决定性的;没有一个单一的配置位于所有模型和数据大小之上。
  • 相对于模型大小的数据大小很重要;对于小数据和模型,数据传输(例如CPU到GPU)开销可能占主导地位。同样,小的开销处理器在大的数据上运行可能会更慢,每个数据转换时间支配(请参见“ PROFILER”中的convert_to_tensor
  • 由于训练循环和输入数据处理器处理资源的方式不同,因此速度也有所不同。

基准测试:磨碎的肉。 - Word文档 - Excel电子表格


术语:

  • %-less数字均为秒数
  • %计算公式为(1-longer_time/shorter_time)*100;原理:我们感兴趣的是一个比另一个快了多少倍;shorter / longer实际上是一种非线性关系,不适用于直接比较
  • %符号的确定:
    • TF2 vs TF1:+如果TF2更快
    • GvE(图形vs. Eager):+如果Graph更快
  • TF2 = TensorFlow 2.0.0 + Keras 2.3.1;TF1 = TensorFlow 1.14.0 + Keras 2.2.5

PROFILER:


PROFILER - 说明: Spyder 3.3.6 IDE profiler.

  • 某些函数在其他函数的嵌套中重复出现;因此,很难跟踪“数据处理”和“训练”函数之间的确切分离,因此会有一些重叠 - 正如最后一个结果所表明的那样。

  • %数字是相对于减去构建时间的运行时间计算的。

  • 构建时间通过将所有(唯一)调用了1或2次的运行时间相加来计算

  • 训练时间通过将所有(唯一)调用次数与迭代次数相同的运行时间以及它们的一些嵌套运行时间相加来计算

  • 函数根据其原始名称进行剖析,不幸的是(即_func = func将作为func进行剖析),这混合了构建时间-因此需要排除它


测试环境:

  • 以最少的后台任务运行代码
  • 按照这篇文章中建议,进行了几次迭代以预热GPU
  • CUDA 10.0.130、cuDNN 7.6.0、TensorFlow 1.14.0 和 TensorFlow 2.0.0都是通过源代码编译构建的,还使用了Anaconda
  • Python版本为3.7.4,使用Spyder 3.3.6 IDE
  • GTX 1070显卡,安装的操作系统是Windows 10,具有24GB DDR4 2.4-MHz RAM和i7-7700HQ 2.8-GHz CPU

方法:

  • 基准测试'小型'、'中型'和'大型'模型数据集
  • 为每个模型大小固定参数数量,与输入数据大小无关
  • '更大'的模型具有更多的参数和层数
  • '更大'的数据具有更长的序列,但相同batch_sizenum_channels
  • 模型仅使用Conv1DDense '可学习'层;避免使用RNN以防止TF版本实现差异
  • 总是在基准测试循环之外运行一个train fit,以省略模型和优化器图形构建
  • 不使用稀疏数据(例如layers.Embedding())或稀疏目标(例如SparseCategoricalCrossEntropy()

限制: "完整"的答案将解释每个可能的训练循环和迭代器,但这显然超出了我的时间能力、不存在的工资或一般必要性。结果仅与方法一样好 - 开放心态地解释。


代码:

import numpy as np
import tensorflow as tf
import random
from termcolor import cprint
from time import time

from tensorflow.keras.layers import Input, Dense, Conv1D
from tensorflow.keras.layers import Dropout, GlobalAveragePooling1D
from tensorflow.keras.models import Model
from tensorflow.keras.optimizers import Adam
import tensorflow.keras.backend as K
#from keras.layers import Input, Dense, Conv1D
#from keras.layers import Dropout, GlobalAveragePooling1D
#from keras.models import Model 
#from keras.optimizers import Adam
#import keras.backend as K

#tf.compat.v1.disable_eager_execution()
#tf.enable_eager_execution()

def reset_seeds(reset_graph_with_backend=None, verbose=1):
    if reset_graph_with_backend is not None:
        K = reset_graph_with_backend
        K.clear_session()
        tf.compat.v1.reset_default_graph()
        if verbose:
            print("KERAS AND TENSORFLOW GRAPHS RESET")

    np.random.seed(1)
    random.seed(2)
    if tf.__version__[0] == '2':
        tf.random.set_seed(3)
    else:
        tf.set_random_seed(3)
    if verbose:
        print("RANDOM SEEDS RESET")

print("TF version: {}".format(tf.__version__))
reset_seeds()

def timeit(func, iterations, *args, _verbose=0, **kwargs):
    t0 = time()
    for _ in range(iterations):
        func(*args, **kwargs)
        print(end='.'*int(_verbose))
    print("Time/iter: %.4f sec" % ((time() - t0) / iterations))

def make_model_small(batch_shape):
    ipt   = Input(batch_shape=batch_shape)
    x     = Conv1D(128, 40, strides=4, padding='same')(ipt)
    x     = GlobalAveragePooling1D()(x)
    x     = Dropout(0.5)(x)
    x     = Dense(64, activation='relu')(x)
    out   = Dense(1,  activation='sigmoid')(x)
    model = Model(ipt, out)
    model.compile(Adam(lr=1e-4), 'binary_crossentropy')
    return model

def make_model_medium(batch_shape):
    ipt = Input(batch_shape=batch_shape)
    x = ipt
    for filters in [64, 128, 256, 256, 128, 64]:
        x  = Conv1D(filters, 20, strides=1, padding='valid')(x)
    x     = GlobalAveragePooling1D()(x)
    x     = Dense(256, activation='relu')(x)
    x     = Dropout(0.5)(x)
    x     = Dense(128, activation='relu')(x)
    x     = Dense(64,  activation='relu')(x)
    out   = Dense(1,   activation='sigmoid')(x)
    model = Model(ipt, out)
    model.compile(Adam(lr=1e-4), 'binary_crossentropy')
    return model

def make_model_large(batch_shape):
    ipt   = Input(batch_shape=batch_shape)
    x     = Conv1D(64,  400, strides=4, padding='valid')(ipt)
    x     = Conv1D(128, 200, strides=1, padding='valid')(x)
    for _ in range(40):
        x = Conv1D(256,  12, strides=1, padding='same')(x)
    x     = Conv1D(512,  20, strides=2, padding='valid')(x)
    x     = Conv1D(1028, 10, strides=2, padding='valid')(x)
    x     = Conv1D(256,   1, strides=1, padding='valid')(x)
    x     = GlobalAveragePooling1D()(x)
    x     = Dense(256, activation='relu')(x)
    x     = Dropout(0.5)(x)
    x     = Dense(128, activation='relu')(x)
    x     = Dense(64,  activation='relu')(x)    
    out   = Dense(1,   activation='sigmoid')(x)
    model = Model(ipt, out)
    model.compile(Adam(lr=1e-4), 'binary_crossentropy')
    return model

def make_data(batch_shape):
    return np.random.randn(*batch_shape), \
           np.random.randint(0, 2, (batch_shape[0], 1))
           
def make_data_tf(batch_shape, n_batches, iters):
    data = np.random.randn(n_batches, *batch_shape),
    trgt = np.random.randint(0, 2, (n_batches, batch_shape[0], 1))
    return tf.data.Dataset.from_tensor_slices((data, trgt))#.repeat(iters)

batch_shape_small  = (32, 140,   30)
batch_shape_medium = (32, 1400,  30)
batch_shape_large  = (32, 14000, 30)

batch_shapes = batch_shape_small, batch_shape_medium, batch_shape_large
make_model_fns = make_model_small, make_model_medium, make_model_large
iterations = [200, 100, 50]
shape_names = ["Small data",  "Medium data",  "Large data"]
model_names = ["Small model", "Medium model", "Large model"]

def test_all(fit=False, tf_dataset=False):
    for model_fn, model_name, iters in zip(make_model_fns, model_names, iterations):
        for batch_shape, shape_name in zip(batch_shapes, shape_names):
            if (model_fn is make_model_large) and (batch_shape == batch_shape_small):
                continue
            reset_seeds(reset_graph_with_backend=K)
            if tf_dataset:
                data = make_data_tf(batch_shape, iters, iters)
            else:
                data = make_data(batch_shape)
            model = model_fn(batch_shape)

            if fit:
                if tf_dataset:
                    model.train_on_batch(data.take(1))
                    t0 = time()
                    model.fit(data, steps_per_epoch=iters)
                    print("Time/iter: %.4f sec" % ((time() - t0) / iters))
                else:
                    model.train_on_batch(*data)
                    timeit(model.fit, iters, *data, _verbose=1, verbose=0)
            else:
                model.train_on_batch(*data)
                timeit(model.train_on_batch, iters, *data, _verbose=1)
            cprint(">> {}, {} done <<\n".format(model_name, shape_name), 'blue')
            del model

test_all(fit=True, tf_dataset=False)

2
我不确定你的代码是否正确。我认为你的模型总是在图模式下运行,因为你调用model.compile而没有使用run_eagerly=True参数。如果在急切模式下,您可以使用tf.function在图模式下运行部分代码。因此,我认为compile的默认实现是创建计算图,而不是为了性能而急切地运行它。还要注意,如果您的模型是卷积的,则在图模式下看不到加速,因为Python交互很少。如果您执行许多数学运算,则可能会产生很大的差异(也会影响内存利用率)。 - user2781994
1
@OverLordGoldDragon 但是在TF 2中,急切模式默认开启,但是如果没有使用run_eagerly=True参数来编译模型,则会保证图模式,对吗? - user2781994
@OverLordGoldDragon 我同意并不是所有的导入方法都在图模式下运行,但我认为model.compile或者model.fit必须确保训练在内部以图模式运行。 - user2781994
@OverLordGoldDragon TRUE - "tf.keras.Model.compile接受三个重要参数:...此外,为了确保模型能够及时训练和评估,您可以确保将run_eagerly=True作为参数传递给compile。"(来源https://www.tensorflow.org/guide/keras/overview)因此,如果您不传递`run_eagerly=True`,模型可能会在图形模式下运行。我不确定决定因素是什么,但如果图形模式比急切模式更有效,为什么不运行呢? - user2781994
有道理,因为在急切模式下,具有计算重型卷积/密集层的模型的性能损失是最小的,所以你看不到差异是有道理的。但我的观点是,你正在测量的东西在许多情况下可能只是输入管道,而不是实际的模型执行。 - user2781994
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接