更新 - 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() 将其设置为 True。
-
predict()涉及获取预测函数,func = self._select_training_loop(x)
- 没有任何特殊的 kwargs 传递给
predict 和 compile,所有其他标志都是这样的:
-
(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