为什么keras模型预测编译后会变慢?


23

预测速度keras

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

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


我认为您需要在编译后拟合模型,然后使用经过训练的模型进行预测。请参阅此处
天真的

@naive拟合与该问题无关。如果您知道网络实际运行的方式,您会很好奇为什么预测速度较慢。预测时,仅将权重用于矩阵乘法,并且权重必须在编译之前和之后固定,因此预测时间应保持恒定。
9555

我知道这与问题无关。而且,不需要知道网络是如何工作的,就可以指出您提出的任务并比较准确性实际上是没有意义的。如果没有将模型拟合到您正在预测的某些数据上,则实际上是在比较所花费的时间。这不是神经网络的常见或正确用例
天真的

3
@naive问题涉及理解模型性能的编译与未编译,与准确性或模型设计无关。这是一个可能使TF用户付出代价的合理问题-我一直不知道这个问题,直到遇到这个问题。
OverLordGoldDragon

1
@天真你不能fit没有compile; 优化器甚至不存在以更新任何权重。predict 可以不使用fitcompile如我的答案所述使用,但是性能差异不应该这么大-因此存在问题。
OverLordGoldDragon

Answers:


22

UPDATE-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=Falsecompile()将其设置为True
  2. 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.ndarrayconvert_eager_tensors_to_numpy相关的源代码在这里


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

(A)分布函数这里

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


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

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

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

中型:10次迭代


文档(间接)对以下方面的影响compile()来源

与其他TensorFlow操作不同,我们不会将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

1
对于任何尺寸的模型,要获得最快的预测速度我们应该怎么做?只是不做compile()
9555

3
@ off99555“适用于任何型号”-没有这样的东西。阅读完整的答案-如果我花了几个小时来调试它,那么距离发问者的几分钟应该不是没有道理的。
OverLordGoldDragon

我读了整本书,但是很难理解,因为我不是调试代码的人。因此,您需要给出一个不包含在调试阶段发现的中间变量的结论。例如:“如果您的模型很小,则不要使用编译。如果您的模型是中等大小,则可以使用编译。`类似的东西
。– off99555

1
@ off99555足够公平;更新。新的部分是常识,但是我可以看到它并没有立即实现。
OverLordGoldDragon

1
@ off99555并不是我测试过的,但是非常大的模型(ResNet等)可以以明显更快的速度运行,尤其是。如果分布在许多设备上- (A)的图形和分布比较繁重。最可靠的测试就是测试-就像答案中一样。不熟悉TF Lite,但这是一个单独的问题
OverLordGoldDragon

15

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


.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


UPDATE:澄清,优化器完全实例化compile,包括其weightsupdates张量-这样做时,拟合函数的第一个电话由(fittrain_on_batch,等),通过model._make_train_function()

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


编辑:在某些型号上,速度降低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
那很有意思。一段时间以来,我想用静态图model.fit()与动态循环测试训练,并急于执行,以查看性能损失是否太大...
DanielMöller19年

1
在过去,我可以注意到Keras和PyTorch之间存在明显的速度差异(PyTorch的运行速度更快)。
DanielMöller19年

1
我在这里打开了一个问题:github.com/tensorflow/tensorflow/issues/33340
off99555

2
是。将培训相关的代码放在预测中是一个糟糕的设计选择。因为用户将在生产中多次顺序使用此预测功能。它应该工作最快,以引起最少的惊喜。与numpy实现相比,您只需要乘一个矩阵,添加一个偏差,激活即可,仅此而已。无需担心任何损失函数。
off99555”,

1
提示,您可以使用load_model(name, compile=False),它比保存/加载权重和重新创建模型更简单。
DanielMöller19年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.