为啥keras模型编译后预测速度变慢?

Posted

技术标签:

【中文标题】为啥keras模型编译后预测速度变慢?【英文标题】:Why does keras model predict slower after compile?为什么keras模型编译后预测速度变慢? 【发布时间】:2020-02-11 04:08:35 【问题描述】:

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

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

【问题讨论】:

我认为您需要在编译后拟合模型,然后使用训练好的模型进行预测。参考here @naive Fitting 与问题无关。如果您知道网络实际上是如何工作的,您会很好奇为什么预测会变慢。预测时,矩阵乘法只使用权重,编译前后权重必须固定,所以预测时间要保持不变。 我知道这与 问题 无关。而且,不需要知道网络是如何工作的,就可以指出你提出的任务和比较准确性实际上是没有意义的。如果没有在您预测的某些数据上拟合模型,您实际上是在比较所花费的时间。这不是神经网络的常见或正确用例 @naive 问题涉及理解模型性能编译与未编译,与准确性或模型设计无关。这是一个合法的问题,可能会让 TF 用户付出代价——我直到偶然发现这个问题才知道。 @naive 你不能没有fit 没有compile;优化器甚至不存在更新任何权重。 predict can 可以在没有 fitcompile 的情况下使用,如我的回答中所述,但性能差异不应该如此显着 - 因此是问题所在。 【参考方案1】:

更新 - 2020 年 1 月 15 日:当前小批量的最佳实践应该是直接将输入提供给模型 - 即 preds = model(x),如果层在训练/推理时表现不同,model(x, training=False)。根据最新的提交,现在是 documented。

我没有对这些进行基准测试,但根据 Git discussion,也值得尝试 predict_on_batch() - 尤其是在 TF 2.1 中有所改进。


终极罪魁祸首self._experimental_run_tf_function = True。这是experimental。但其实还不错。

致所有 TensorFlow 开发人员阅读:清理您的代码。一团糟。而且它违反了重要的编码习惯,例如一个函数做一件事_process_inputs 比“过程输入”做了很多很多_standardize_user_data 也是如此。 “我的薪水不够” - 但你确实付了钱,花在理解你自己的东西上的额外时间,以及让用户用更清晰的代码更容易解​​决的错误填写你的问题页面。


摘要:使用compile() 只会慢一点

compile() 设置一个内部标志,将不同的预测函数分配给predict。这个函数在每次调用时构造一个新图,相对于未编译的,它会减慢它的速度。但是,只有当训练时间远短于数据处理时间时,差异才会明显。如果我们增加模型大小到至少中等大小,两者变得相等。见底部代码。

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


我应该怎么做?

比较已编译和未编译的模型性能,正如我在底部的代码中所做的那样。

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

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


详情

这需要一段时间来调试,但很有趣。下面我描述了我发现的关键罪魁祸首,引用了一些相关文档,并展示了导致最终瓶颈的分析器结果。

FLAG == self.experimental_run_tf_function,为简洁起见)

    Model 默认使用FLAG=False 实例化。 compile() 将其设置为 Truepredict()涉及获取预测函数,func = self._select_training_loop(x) 没有任何特殊的 kwargs 传递给 predictcompile,所有其他标志都是这样的: (A) FLAG==True --> func = training_v2.Loop() (B) FLAG==False --> func = training_arrays.ArrayLikeTrainingLoop() 来自source code docstring,(A) 严重依赖图,使用更多分布策略,并且操作容易创建和破坏图元素,这“可能”(确实)影响性能。

真正的罪魁祸首_process_inputs(),占 81% 的运行时间。它的主要组成部分? _create_graph_function()72% 的运行时间。对于(B),这种方法甚至存在。但是,使用中型模型时,_process_inputs 包含不到 1% 的运行时间。代码在底部,分析结果如下。


数据处理器

(A)<class 'tensorflow.python.keras.engine.data_adapter.TensorLikeDataAdapter'>,用于_process_inputs()。 Relevant source code

(B)numpy.ndarray,由convert_eager_tensors_to_numpy 返回。 Relevant source code 和 here


模型执行函数(例如预测)

(A):distribution function 和 here

(B):distribution function (different) 和 here


PROFILER:我的另一个答案“微型模型”和这个答案“中型模型”中的代码结果:

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

微型模型:1000 次迭代,没有compile()

中型模型:10 次迭代


文档(间接地)关于compile()的影响:source

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

function 为每个唯一的输入集实例化一个单独的图 形状和数据类型。例如,下面的代码 sn -p 将导致 在三个不同的图中被追踪,因为每个输入都有不同的 形状

单个 tf.function 对象可能需要映射到多个计算图 在引擎盖下。这应该仅作为 performance 可见(跟踪图有 非零计算和内存成本)但不应影响正确性 程序的


反例

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

【讨论】:

对于任何模型大小我们应该做些什么来获得最快的预测速度的结论是什么?难道只是不做compile() @off99555 “适用于任何模型尺寸” - 没有这样的东西。阅读整个答案 - 如果我花了几个小时来调试它,那么从提问者那里花费几分钟应该是不合理的。 我阅读了整篇文章,但很难理解,因为我不是调试代码的人。所以你需要给出一个不涉及调试阶段发现的中间变量的结论。例如。 “如果你的模型很小,那么不要使用编译。如果你的模型是中等大小,你可以使用编译。`类似的东西。 @off99555 很公平;更新。新的部分是相当常识的,但我可以看到它并没有立即实现。 @off99555 不是我测试过的,但是非常大的模型(ResNet 等)可能会运行得更快,尤其是编译。如果在许多设备上分布 - 因为 (A) 更依赖于图形和分布。最可靠的测试是,嗯,一个测试 - 就像答案一样。不熟悉 TF lite,但这是一个单独的问题【参考方案2】:

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


.compile() 设置了大部分 TF/Keras 图,包括损失、指标、梯度,以及部分优化器及其权重 - 这保证了显着的减速。

出乎意料的是减速的程度——在我自己的实验中是 10 倍,对于 predict(),它不会更新任何权重。查看 TF2 的源代码,图形元素似乎紧密交织在一起,资源不一定被“公平”分配。

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

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


解决方法:训练模型,保存其权重,在不编译的情况下重新构建模型,加载权重。 不要保存整个模型(例如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

【讨论】:

这很有趣。有一段时间我想用静态图 model.fit() 测试训练与使用急切执行的动态循环,看看性能损失是否太大...... 过去我可以注意到 Keras 和 PyTorch 之间的显着速度差异(PyTorch 更快)。 我在这里打开了一个问题:github.com/tensorflow/tensorflow/issues/33340 是的。将与训练相关的代码放入预测中是一个糟糕的设计选择。因为用户会在生产中按顺序多次使用这个预测函数。它应该以最快的速度工作以引起最少的意外。与 numpy 实现相比,您只需要乘以一个矩阵,添加一个偏置,激活,这就是密集层。无需关注任何损失函数。 提示,你可以使用load_model(name, compile=False),它比保存/加载权重和重新创建模型更简单。

以上是关于为啥keras模型编译后预测速度变慢?的主要内容,如果未能解决你的问题,请参考以下文章

为啥使用相同的 Keras 模型和输入进行预测时会得到不同的结果?

keras sequential 预测值为啥输出后半部分为恒定值

Keras学习记录之模型编译-训练-评估-预测

为啥加载模型时需要加载优化器模型参数

为啥用于预测的 Keras LSTM 批量大小必须与拟合批量大小相同?

Keras强制使用CPU