Mengapa model keras memprediksi lebih lambat setelah dikompilasi?

23

kecepatan prediksi keras

Secara teori, prediksi harus konstan karena bobot memiliki ukuran tetap. Bagaimana saya mendapatkan kembali kecepatan saya setelah dikompilasi (tanpa perlu menghapus optimizer)?

Lihat eksperimen terkait: https://nbviewer.jupyter.org/github/off99555/TensorFlowExperiments/blob/master/test-prediction-speed-after-compile.ipynb?flush_cache=true

off99555
sumber
Saya pikir Anda perlu menyesuaikan model setelah kompilasi kemudian gunakan model yang terlatih untuk memprediksi. Lihat di sini
naif
@naive Fitting tidak relevan dengan masalah ini. Jika Anda tahu bagaimana sebenarnya jaringan bekerja, Anda akan penasaran mengapa ramalannya lebih lambat. Saat memprediksi, hanya bobot yang digunakan untuk perkalian matriks, dan bobot harus diperbaiki sebelum dan sesudah dikompilasi, sehingga waktu prediksi harus tetap konstan.
off99555
Saya tahu itu tidak relevan dengan masalah ini . Dan, orang tidak perlu tahu bagaimana jaringan bekerja untuk menunjukkan bahwa tugas yang Anda buat dan membandingkan akurasi sebenarnya tidak ada artinya. Tanpa mencocokkan model pada beberapa data yang Anda prediksi dan Anda benar-benar membandingkan waktu yang dibutuhkan. Ini bukan kasus penggunaan biasa atau benar untuk jaringan saraf
naif
3
@ naif Masalahnya menyangkut memahami kinerja model yang dikompilasi vs yang tidak dikompilasi, tidak ada hubungannya dengan akurasi atau desain model. Ini adalah masalah sah yang dapat membebani pengguna TF - saya sendiri tidak memiliki petunjuk tentang hal itu sampai menemukan pertanyaan ini.
OverLordGoldDragon
1
@ naif Anda tidak bisa fittanpa compile; Pengoptimal bahkan tidak ada untuk memperbarui bobot. predict dapat digunakan tanpa fitatau compileseperti yang dijelaskan dalam jawaban saya, tetapi perbedaan kinerja seharusnya tidak sedramatis ini - karena itu masalahnya.
OverLordGoldDragon

Jawaban:

22

UPDATE - 1/15/2020 : praktik terbaik saat ini untuk ukuran batch kecil adalah untuk memberi makan input ke model secara langsung - yaitu preds = model(x), dan jika lapisan berperilaku berbeda di kereta / inferensi model(x, training=False),. Per komit terbaru, ini sekarang didokumentasikan .

Saya belum membuat tolok ukur ini, tetapi per diskusi Git , ini juga layak untuk dicoba predict_on_batch()- terutama dengan peningkatan pada TF 2.1.


ULTIMATE PENYEBAB : self._experimental_run_tf_function = True. Itu eksperimental . Tapi itu sebenarnya tidak buruk.

Untuk membaca pengembang TensorFlow: bersihkan kode Anda . Ini berantakan. Dan itu melanggar praktik pengkodean penting, seperti satu fungsi melakukan satu hal ; _process_inputsmelakukan banyak lebih dari "input proses", sama untuk _standardize_user_data. "Saya tidak dibayar cukup" - tetapi Anda lakukan membayar, di waktu tambahan menghabiskan memahami barang-barang Anda sendiri, dan pada pengguna mengisi halaman Masalah Anda dengan bug lebih mudah diselesaikan dengan kode yang lebih jelas.


RINGKASAN : Hanya sedikit lebih lambat compile().

compile()menetapkan bendera internal yang menetapkan fungsi prediksi berbeda predict. Fungsi ini membangun grafik baru pada setiap panggilan, memperlambatnya relatif terhadap yang tidak dikompilasi. Namun, perbedaannya hanya diucapkan ketika waktu kereta jauh lebih pendek dari waktu pemrosesan data . Jika kita meningkatkan ukuran model menjadi setidaknya menengah, keduanya menjadi sama. Lihat kode di bagian bawah.

Sedikit peningkatan dalam waktu pemrosesan data ini lebih dari dikompensasi oleh kemampuan grafik yang diperkuat. Karena lebih efisien jika hanya menyimpan satu grafik model, satu pra-kompilasi dibuang. Meskipun demikian : jika model Anda relatif kecil terhadap data, Anda lebih baik tanpa compile()inferensi model. Lihat jawaban saya yang lain untuk solusinya.


APA YANG HARUS SAYA LAKUKAN?

Bandingkan kinerja model yang dikompilasi vs yang tidak dikompilasi seperti yang saya miliki dalam kode di bagian bawah.

  • Dikompilasi lebih cepat : dijalankan predictpada model yang dikompilasi.
  • Dikompilasi lebih lambat : dijalankan predictpada model yang tidak dikompilasi.

Ya, keduanya mungkin, dan itu akan tergantung pada (1) ukuran data; (2) ukuran model; (3) perangkat keras. Kode di bagian bawah sebenarnya menunjukkan model yang dikompilasi menjadi lebih cepat, tetapi 10 iterasi adalah sampel kecil. Lihat "pemecahan masalah" di jawaban saya yang lain untuk "bagaimana caranya".


RINCIAN :

Butuh beberapa saat untuk debug, tetapi menyenangkan. Di bawah ini saya menggambarkan penyebab utama yang saya temukan, mengutip beberapa dokumentasi yang relevan, dan menunjukkan hasil profiler yang mengarah pada hambatan utama.

( FLAG == self.experimental_run_tf_function, untuk singkatnya)

  1. Modelsecara instantiate default dengan FLAG=False. compile()set ke True.
  2. predict() melibatkan perolehan fungsi prediksi, func = self._select_training_loop(x)
  3. Tanpa kwarg khusus yang diteruskan ke predictdan compile, semua bendera lainnya sedemikian rupa sehingga:
    • (A) FLAG==True ->func = training_v2.Loop()
    • (B) FLAG==False ->func = training_arrays.ArrayLikeTrainingLoop()
  4. Dari dokumentasi kode sumber , (A) sangat bergantung pada grafik, menggunakan lebih banyak strategi distribusi, dan ops cenderung membuat & menghancurkan elemen grafik, yang "dapat" mempengaruhi kinerja.

Benar pelakunya : _process_inputs(), akuntansi untuk 81% dari runtime . Komponen utamanya? _create_graph_function(), 72% dari runtime . Metode ini bahkan tidak ada untuk (B) . Menggunakan model menengah, bagaimanapun, _process_inputsterdiri dari kurang dari 1% dari runtime . Kode di bawah, dan hasil profiling mengikuti.


PROSESOR DATA :

(A) :, <class 'tensorflow.python.keras.engine.data_adapter.TensorLikeDataAdapter'>digunakan dalam _process_inputs(). Kode sumber yang relevan

(B) : numpy.ndarray, dikembalikan oleh convert_eager_tensors_to_numpy. Kode sumber yang relevan , dan di sini


FUNGSI PELAKSANAAN MODEL (mis. Prediksi)

(A) : fungsi distribusi , dan di sini

(B) : fungsi distribusi (berbeda) , dan di sini


PROFILER : hasil untuk kode di jawaban saya yang lain, "model kecil", dan dalam jawaban ini, "model sedang":

Model kecil : 1000 iterasi,compile()

Model kecil : 1000 iterasi, no compile()

Model medium : 10 iterasi


DOKUMENTASI (tidak langsung) pada efek dari compile(): sumber

Tidak seperti operasi TensorFlow lainnya, kami tidak mengonversi input numerik python ke tensor. Selain itu, grafik baru dihasilkan untuk setiap nilai numerik python yang berbeda , misalnya panggilan g(2)dan g(3)akan menghasilkan dua grafik baru

function instantiate grafik terpisah untuk setiap set unik bentuk input dan tipe data . Misalnya, cuplikan kode berikut akan menghasilkan tiga grafik berbeda yang dilacak, karena setiap input memiliki bentuk yang berbeda

Objek fungsi tf. tunggal mungkin perlu memetakan ke beberapa grafik perhitungan di bawah tenda. Ini harus terlihat hanya karena kinerja (melacak grafik memiliki biaya komputasi dan memori bukan nol ) tetapi tidak boleh mempengaruhi kebenaran program


CONTOH COUNTER :

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)

Output :

34.8542 sec
34.7435 sec
OverLordGoldDragon
sumber
1
Apa kesimpulannya tentang apa yang harus kita lakukan untuk mendapatkan kecepatan prediksi tercepat untuk ukuran model apa pun? Apakah itu tidak dilakukan compile()?
off99555
3
@ off99555 "untuk ukuran model apa pun" - tidak ada yang seperti itu. Baca seluruh jawaban - jika saya membutuhkan waktu berjam-jam untuk men-debug itu, beberapa menit dari si penanya seharusnya tidak masuk akal.
OverLordGoldDragon
Saya membaca semuanya tetapi sulit untuk dipahami karena saya bukan orang yang men-debug kode. Jadi, Anda perlu memberikan kesimpulan yang tidak melibatkan variabel perantara yang Anda temukan selama fase debugging. Misalnya "Jika model Anda kecil, maka jangan gunakan kompilasi. Jika model Anda berukuran sedang, Anda dapat menggunakan kompilasi.` Sesuatu seperti itu.
off99555
1
@ off99555 Cukup adil; diperbarui. Bagian baru ini cukup masuk akal, tetapi saya bisa melihatnya tidak segera direalisasikan.
OverLordGoldDragon
1
@ off99555 Bukannya saya diuji, tetapi model yang sangat besar (ResNet, dll) dapat berjalan terutama dikompilasi lebih cepat, esp. jika didistribusikan pada banyak perangkat - karena (A) lebih berat grafik dan distribusi. Tes yang paling pasti adalah, seperti tes dalam jawaban. Tidak terbiasa dengan TF lite, tapi itu pertanyaan terpisah
OverLordGoldDragon
15

PEMBARUAN : lihat jawaban aktual yang diposting sebagai jawaban terpisah; posting ini mengandung info tambahan


.compile() mengatur sebagian besar grafik TF / Keras, termasuk kerugian, metrik, gradien, dan sebagian pengoptimal dan bobotnya - yang menjamin perlambatan yang mencolok.

Yang tidak terduga adalah tingkat perlambatan - 10 kali lipat pada percobaan saya sendiri, dan untuk predict(), yang tidak memperbarui bobot. Melihat ke kode sumber TF2, elemen grafik tampak saling terkait erat, dengan sumber daya tidak selalu dialokasikan "secara adil".

Kemungkinan diabaikan oleh pengembang tentang predictkinerja untuk model yang tidak dikompilasi, karena model biasanya digunakan dikompilasi - tetapi dalam praktiknya , ini adalah perbedaan yang tidak dapat diterima. Mungkin juga itu adalah "kejahatan yang perlu", karena ada solusi sederhana (lihat di bawah).

Ini bukan jawaban yang lengkap, dan saya harap seseorang dapat menyediakannya di sini - jika tidak, saya sarankan membuka masalah Github di TensorFlow. (OP memiliki; di sini )


Penanganan masalah : latih model, simpan bobotnya , bangun kembali model tanpa kompilasi, muat bobot. Jangan tidak menyimpan seluruh model (misalnya model.save()), karena akan beban yang disusun - bukan menggunakan model.save_weights()dan model.load_weights().

Solusi 2 : di atas, tetapi gunakan load_model(path, compile=False); kredit saran: D. Möller


UPDATE : untuk memperjelas, optimizer yang tidak sepenuhnya dipakai dengan compile, termasuk yang weightsdan updatestensor - ini dilakukan ketika panggilan pertama ke fungsi pas dibuat ( fit, train_on_batch, dll), melalui model._make_train_function().

Perilaku yang diamati dengan demikian bahkan lebih aneh. Lebih buruk lagi, membangun optimizer tidak menimbulkan perlambatan lebih lanjut (lihat di bawah) - menunjukkan "ukuran grafik" bukan penjelasan utama di sini.


EDIT : pada beberapa model, penurunan 30x . TensorFlow, apa yang sudah kamu lakukan. Contoh di bawah ini:

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)

Output :

0.9891 sec
29.785 sec
29.521 sec
OverLordGoldDragon
sumber
1
Itu menarik. Sudah lama saya ingin menguji pelatihan dengan grafik statis model.fit()versus loop dinamis dengan eksekusi bersemangat untuk melihat apakah kehilangan kinerja terlalu besar ...
Daniel Möller
1
Di masa lalu saya bisa melihat perbedaan kecepatan yang signifikan antara Keras dan PyTorch (menjadi cara PyTorch lebih cepat).
Daniel Möller
1
Saya telah membuka masalah di sini: github.com/tensorflow/tensorflow/issues/33340
off99555
2
Iya. Ini adalah pilihan desain yang buruk sehingga Anda memasukkan kode terkait pelatihan ke dalam prediksi. Karena pengguna akan menggunakan fungsi prediksi ini secara berurutan beberapa kali dalam produksi. Ini harus bekerja paling cepat untuk menyebabkan kejutan paling sedikit. Dibandingkan dengan implementasi numpy, Anda hanya perlu mengalikan matriks, menambahkan bias, mengaktifkan, dan hanya itu untuk lapisan padat. Tidak perlu mengkhawatirkan fungsi kerugian.
off99555
1
Petunjuk, Anda dapat menggunakan load_model(name, compile=False), ini lebih sederhana daripada menyimpan / memuat bobot dan membuat ulang model.
Daniel Möller