为什么keras模型预测编译后会变慢?
keras
python
tensorflow
36
0

预测速度keras

理论上,由于权重具有固定大小,因此预测应该是恒定的。如何在编译后恢复速度(无需删除优化器)?

查看相关实验: https : //nbviewer.jupyter.org/github/off99555/TensorFlowExperiments/blob/master/test-prediction-speed-after-compile.ipynb?flush_cache=true

参考资料:
Stack Overflow
收藏
评论
共 2 个回答
高赞 时间 活跃

更新 :将实际答案发布为单独的答案;这篇文章包含补充信息


.compile()设置了大多数TF / Keras图,包括损耗,度量,梯度,以及部分优化器及其权重-这可以确保显着的速度降低。

出人意料的经济放缓的程度-我自己的实验10倍,并predict()它不更新任何权重。查看TF2的源代码,图形元素似乎紧密地交织在一起,资源不一定“公平地”分配。

开发人员可能会忽略未编译模型的predict性能,因为通常使用已编译的模型-但实际上 ,这是无法接受的差异。因为有一个简单的解决方法,这也有可能是“必要的邪恶”(见下文)。

这不是一个完整的答案,我希望有人可以在这里提供它-如果不能,我建议在TensorFlow上打开Github问题。 (OP有; 这里


解决方法 :训练模型,保存其权重 ,无需编译即可重建模型,加载权重。 不要保存整个模型(如model.save()因为它会加载有编译-而不是使用model.save_weights()model.load_weights()

解决方法2 :上面,但使用load_model(path, compile=False) ;建议信用: D。Möller


更新 :澄清一下,优化器完全使用compile实例化,包括其weightsupdates张量-当通过model._make_train_function()首次调用拟合函数时( fittrain_on_batch等)完成此操作。

因此,观察到的行为更加奇怪。更糟糕的是,构建优化器不会引起进一步的减速(请参阅下文)-暗示“图形大小”不是此处的主要解释。


编辑 :在某些型号上, 速度降低30倍 。 TensorFlow,您做了什么。下面的例子:

from tensorflow.keras.layers import Input, Dense
from tensorflow.keras.models import Model
import numpy as np
from time import time

def timeit(func, arg, iterations):
    t0 = time()
    for _ in range(iterations):
        func(arg)
    print("%.4f sec" % (time() - t0))

ipt   = Input(shape=(4,))
x     = Dense(2, activation='relu')(ipt)
out   = Dense(1, activation='sigmoid')(x)
model = Model(ipt, out)

X = np.random.randn(32,4)

timeit(model.predict, X, 1000)
model.compile('adam', loss='binary_crossentropy')
timeit(model.predict, X, 1000)
model._make_train_function()  # build optimizer
timeit(model.predict, X, 1000)

输出

0.9891 sec
29.785 sec
29.521 sec
收藏
评论

更新-1/15/2020 :小批量的当前最佳实践应该是直接将输入输入模型-即preds = model(x) ,如果各层在训练/推理时表现不同,则model(x, training=False) 。每次最新提交,现在都记录在案

我还没有对这些基准进行基准测试,但是根据Git的讨论 ,还值得尝试使用predict_on_batch() -尤其是在TF 2.1中进行了改进。


最终文化self._experimental_run_tf_function = True 。这是实验性的 。但这实际上并不坏。

对于任何TensorFlow开发人员而言: 清理代码 。一团糟。而且它违反了重要的编码惯例,例如, 一项功能只能做一件事情_process_inputs做了很多比“过程输入”,同为_standardize_user_data 。 “我付的钱不够”-但是,您确实要付出,要花更多的时间来理解自己的东西,并且用户要在您的“问题”页面中填充错误,而这些错误更容易用更清晰的代码解决。


简介 :使用compile()只会稍微一点

compile()设置一个内部标志,该标志分配一个不同的预测函数来进行predict 。此函数在每次调用时构造一个新图 ,相对于未编译而言,它的速度变慢。但是,仅当训练时间比数据处理时间短得多时,差异才明显。如果我们增加模型大小至少中型,两人成为相等。请参阅底部的代码。

数据处理时间的这种轻微增加远远超过了放大图形功能所能弥补的。由于仅保留一个模型图更为有效,因此放弃了一个预编译。 尽管如此 :如果您的模型相对于数据而言较小,那么最好不用compile()进行模型推断。请参阅我的其他答案以找到解决方法。


我该怎么办?

比较我在底部的代码中已编译与未编译的模型性能。

  • 编译速度更快 :在已编译的模型上运行predict
  • 编译较慢 :在未编译的模型上运行predict

是的, 两者都是可能的,这取决于(1)数据大小; (2)型号尺寸; (3)硬件。底部的代码实际上表明编译的模型更快,但是10次迭代只是一个小样本。请参阅我的其他答案中的“解决方法”,以获取“操作方法”。


细节

调试花费了一段时间,但很有趣。下面,我描述了发现的关键元凶,引用了一些相关文档,并显示了导致最终瓶颈的探查器结果。

(为简便起见, FLAG == self.experimental_run_tf_function

  1. 默认情况下, Model使用FLAG=False实例化。 compile()将其设置为True
  2. func = self._select_training_loop(x) predict()涉及获取预测函数func = self._select_training_loop(x)
  3. 没有传递任何特殊的kwarg来进行predictcompile ,所有其他标志都是这样的:
    • (A) FLAG==True -> func = training_v2.Loop()
    • (B) FLAG==False > func = training_arrays.ArrayLikeTrainingLoop()
  4. 源代码docstring来看, (A)非常依赖图,使用更多的分发策略,并且ops易于创建和销毁图元素,“可能”(确实)影响性能。

真正的罪魁祸首_process_inputs() ,占运行时的81% 。它的主要成分? _create_graph_function()占运行时间的72%(B)甚至不存在此方法。但是,使用中型模型, _process_inputs 占运行时的不到1% 。代码位于底部,并提供概要分析结果。


数据处理器

(A)<class 'tensorflow.python.keras.engine.data_adapter.TensorLikeDataAdapter'> ,在_process_inputs()相关源代码

(B)numpy.ndarray ,由convert_eager_tensors_to_numpy返回。 相关的源代码在这里


模型执行功能 (例如预测)

(A)分布函数这里

(B)分布函数(不同)这里


PROFILER :我的另一个答案“小模型”和此答案“中等模型”中代码的结果:

小模型 :1000次迭代, compile()

小模型 :1000次迭代, 无需 compile()

中型 :10次迭代


文档 (间接)对compile()

与其他TensorFlow操作不同,我们不会将python数值输入转换为张量。此外, 将为每个不同的python数值生成一个新图 ,例如调用g(2)g(3)将生成两个新图

function 为每个唯一的输入形状和数据类型集实例化一个单独的图 。例如,以下代码片段将导致跟踪三个不同的图形,因为每个输入的形状都不相同

一个tf.function对象可能需要映射到后台的多个计算图。这应该仅在性能方面可见(跟踪图的计算和内存成本非零 ),但不应影响程序的正确性


COUNTEREXAMPLE

from tensorflow.keras.layers import Input, Dense, LSTM, Bidirectional, Conv1D
from tensorflow.keras.layers import Flatten, Dropout
from tensorflow.keras.models import Model
import numpy as np
from time import time

def timeit(func, arg, iterations):
    t0 = time()
    for _ in range(iterations):
        func(arg)
    print("%.4f sec" % (time() - t0))

batch_size = 32
batch_shape = (batch_size, 400, 16)
ipt   = Input(batch_shape=batch_shape)
x     = Bidirectional(LSTM(512, activation='relu', return_sequences=True))(ipt)
x     = LSTM(512, activation='relu', return_sequences=True)(ipt)
x     = Conv1D(128, 400, 1, 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)

X = np.random.randn(*batch_shape)
timeit(model.predict, X, 10)
model.compile('adam', loss='binary_crossentropy')
timeit(model.predict, X, 10)

输出

34.8542 sec
34.7435 sec
收藏
评论
新手导航
  • 社区规范
  • 提出问题
  • 进行投票
  • 个人资料
  • 优化问题
  • 回答问题

关于我们

常见问题

内容许可

联系我们

@2020 AskGo
京ICP备20001863号