なぜkerasモデルはコンパイル後に予測が遅くなるのですか?


23

予測速度ケラス

理論的には、重みは固定サイズであるため、予測は一定でなければなりません。コンパイル後に速度を戻すにはどうすればよいですか(オプティマイザを削除する必要はありません)。

関連する実験を参照してください:https : //nbviewer.jupyter.org/github/off99555/TensorFlowExperiments/blob/master/test-prediction-speed-after-compile.ipynb?flush_cache=true


コンパイル後にモデルを適合させ、トレーニング済みモデルを使用して予測する必要があると思います。ここを
ナイーブな

@naive Fittingはこの問題には関係ありません。ネットワークが実際にどのように機能するかを知っているなら、なぜ予測が遅いのか気になるでしょう。予測時には、重みのみが行列の乗算に使用され、重みはコンパイルの前後で固定する必要があるため、予測時間は一定に保つ必要があります。
off99555

それは問題とは無関係です。また、ネットワークがどのように機能しているかを知る必要はなく、思いついたタスクと正確さを比較するタスクが実際には無意味であることを指摘する必要はありません。一部のデータにモデルを当てはめない場合は、予測を行い、実際にかかった時間を比較します。これはニューラルネットワークの通常または適切な使用例ではありません
ナイーブ

3
@naive問題は、コンパイルされたモデルとコンパイルされていないモデルのパフォーマンスの理解に関係し、精度やモデル設計とは何の関係もありません。これはTFユーザーにコストがかかる可能性のある正当な問題です。私にとって、この質問に出くわすまで、それについての手がかりはありませんでした。
OverLordGoldDragon

1
@naive fitなしではできませんcompile。重みを更新するオプティマイザさえ存在しません。私の回答なしで、または私の説明で説明されているように使用predict できますが、パフォーマンスの違いはそれほど劇的ではないはずです。fitcompile
OverLordGoldDragon

回答:


22

更新-2020年1月15日:小さいバッチサイズの現在のベストプラクティスは、入力をモデルに直接フィードすることです。つまりpreds = model(x)、層がトレイン/推論で異なる動作をする場合model(x, training=False)。最新のコミットごとに、これは文書化されています

私はこれらをベンチマークしていませんが、Gitディスカッションによれば、predict_on_batch()特にTF 2.1の改善により、試す価値もあります。


究極のカルプリットself._experimental_run_tf_function = True。それはだ実験。しかし、それは実際に悪いことではありません。

TensorFlow開発者が読んでいるすべての人へ:コードをクリーンアップします。それは混乱です。また、1つの関数が1つのことを行うなど、重要なコーディング方法に違反しています。_process_inputsと同様に、「入力の処理」以外にも多くのことを行い_standardize_user_dataます。「私は十分に支払われていません」-しかし、あなたあなた自身のものを理解するのに費やされた余分な時間で、そしてより明確なコードでより簡単に解決されるバグであなたの問題ページを埋めるユーザーで支払います。


概要:で少し遅くなりますcompile()

compile()に異なる予測関数を割り当てる内部フラグを設定しpredictます。この関数は、呼び出しごとに新しいグラフ作成し、コンパイルされていない場合に比べて速度を低下させます。ただし、違いが顕著になるのはトレーニング時間がデータ処理時間よりもはるかに短い場合のみです。我々は場合増加、少なくとも中規模のモデルのサイズを、2は等しくなります。下部のコードを参照してください。

データ処理時間のこのわずかな増加は、増幅されたグラフ機能によって十分に補われます。モデルグラフを1つだけ保持する方が効率的であるため、1つのプリコンパイルは破棄されます。それでも、モデルがデータに比べて小さい場合は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. predictcompileに渡される特別なクワーグがなければ、他のすべてのフラグは次のようになります。
    • (A) FLAG==True ->func = training_v2.Loop()
    • (B) FLAG==False ->func = training_arrays.ArrayLikeTrainingLoop()
  4. ソースコードdocstringから、(A)はグラフに大きく依存しており、より多くの分散戦略を使用しており、opはグラフ要素を作成および破棄する傾向があり、パフォーマンスに「影響する」可能性があります(します)。

真の原因_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)分布関数(異なる)ここ


プロファイラー:私の他の回答「小さなモデル」、およびこの回答「中規模モデル」のコードの結果:

小さなモデル:1000反復、compile()

小さなモデル:1000回の反復、いいえ compile()

中規模モデル:10反復


影響に関する文書化(間接的)compile()ソース

他のTensorFlow演算とは異なり、Pythonの数値入力をテンソルに変換しません。さらに、新しいグラフはPythonの数値ごとに生成されます。たとえば、呼び出してg(2)g(3)2つの新しいグラフを生成します

function 入力形状とデータ型の一意のセットごとに個別のグラフをインスタンス化します。たとえば、次のコードスニペットでは、各入力の形状が異なるため、3つの異なるグラフがトレースされます。

単一のtf.functionオブジェクトは、内部で複数の計算グラフにマップする必要がある場合があります。これはパフォーマンスとしてのみ表示されます(トレースグラフの計算コストとメモリコストゼロではありません)が、プログラムの正確さに影響を与えることはありません。


対抗法

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()ですか?
off99555

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()とロードされるため、代わりにand を使用してくださいmodel.load_weights()

回避策2:上記、ただし以下を使用load_model(path, compile=False)。提案クレジット:D.メラー


UPDATE:明確には、オプティマイザはされない十分でインスタンスcompileそのなど、weights及びupdatesテンソル-これが行われるフィッティング関数の最初の呼び出しは、(行われたとき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()パフォーマンスの損失が大きすぎるかどうかを確認するために熱望して実行した動的ループ対...
ダニエル・メーラー

1
過去には、KerasとPyTorchの速度に大きな違いがあることに気づきました(PyTorchの方がはるかに高速です)。
ダニエル・メーラー

1
私はここで問題を開いています:github.com/tensorflow/tensorflow/issues/33340
off99555

2
はい。トレーニング関連のコードを予測の内部に配置するのは悪い設計選択です。ユーザーはこの予測関数を実稼働環境で連続して複数回使用するためです。驚きを最小限に抑えるために、最も速く動作するはずです。numpy実装と比較すると、マトリックスを乗算し、バイアスを追加し、アクティブ化するだけで済みます。これが、高密度レイヤーの場合の説明です。損失関数を考慮する必要はありません。
off99555

1
ヒント:を使用できますload_model(name, compile=False)。これは、重みの保存/読み込みやモデルの再作成よりも簡単です。
DanielMöller19年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.