Saya mengalami masalah yang sama persis ini, dan man apakah itu lubang kelinci. Ingin memposting solusi saya di sini karena dapat menghemat satu hari kerja:
Struktur Data Spesifik Thread TensorFlow
Di TensorFlow, ada dua struktur data utama yang bekerja di balik layar saat Anda memanggil model.predict
(atau keras.models.load_model
, atau keras.backend.clear_session
, atau hampir semua fungsi lain yang berinteraksi dengan backend TensorFlow):
- Grafik TensorFlow, yang mewakili struktur model Keras Anda
- Sesi TensorFlow, yang merupakan koneksi antara grafik Anda saat ini dan waktu proses TensorFlow
Sesuatu yang tidak jelas secara eksplisit dalam dokumen tanpa menggali adalah bahwa sesi dan grafik adalah properti dari utas saat ini . Lihat dokumen API di sini dan di sini.
Menggunakan Model TensorFlow di Thread Berbeda
Wajar jika Anda ingin memuat model Anda sekali dan kemudian memanggil .predict()
di atasnya beberapa kali kemudian:
from keras.models import load_model
MY_MODEL = load_model('path/to/model/file')
def some_worker_function(inputs):
return MY_MODEL.predict(inputs)
Dalam konteks server web atau kumpulan pekerja seperti Celery, artinya Anda akan memuat model saat Anda mengimpor modul yang berisi load_model
baris, maka utas yang berbeda akan mengeksekusi some_worker_function
, menjalankan prediksi pada variabel global yang berisi model Keras. Namun, mencoba menjalankan prediksi pada model yang dimuat di utas berbeda menghasilkan kesalahan "tensor bukan elemen grafik ini". Berkat beberapa posting SO yang menyentuh topik ini, seperti ValueError:Tensor Tensor(...) bukan merupakan elemen dari grafik ini. Saat menggunakan model keras variabel global. Agar ini berfungsi, Anda harus berpegang pada grafik TensorFlow yang digunakan-- seperti yang kita lihat sebelumnya, grafik adalah properti dari utas saat ini. Kode yang diperbarui terlihat seperti ini:
from keras.models import load_model
import tensorflow as tf
MY_MODEL = load_model('path/to/model/file')
MY_GRAPH = tf.get_default_graph()
def some_worker_function(inputs):
with MY_GRAPH.as_default():
return MY_MODEL.predict(inputs)
Perubahan yang agak mengejutkan di sini adalah:kode di atas sudah cukup jika Anda menggunakan Thread
s, tetapi hang tanpa batas waktu jika Anda menggunakan Process
mis. Dan secara default, Celery menggunakan proses untuk mengelola semua kumpulan pekerjanya. Jadi pada titik ini, semuanya tetap tidak bekerja di Seledri.
Mengapa ini hanya berfungsi di Thread
s?
Dengan Python, Thread
s berbagi konteks eksekusi global yang sama dengan proses induk. Dari dokumen _thread Python:
Modul ini menyediakan primitif tingkat rendah untuk bekerja dengan banyak utas (juga disebut proses atau tugas ringan) — beberapa utas kontrol yang berbagi ruang data globalnya.
Karena utas bukanlah proses terpisah yang sebenarnya, mereka menggunakan penerjemah python yang sama dan dengan demikian tunduk pada Global Interpeter Lock (GIL) yang terkenal. Mungkin yang lebih penting untuk penyelidikan ini, mereka berbagi ruang data global dengan induknya.
Berbeda dengan ini, Process
es adalah sebenarnya proses baru yang dihasilkan oleh program. Artinya:
- Contoh juru bahasa Python baru (dan tanpa GIL)
- Ruang alamat global diduplikasi
Perhatikan perbedaannya di sini. Sementara Thread
s memiliki akses ke satu variabel Sesi global bersama (disimpan secara internal di tensorflow_backend
modul Keras), Process
es memiliki duplikat variabel Sesi.
Pemahaman terbaik saya tentang masalah ini adalah bahwa variabel Session seharusnya mewakili koneksi unik antara klien (proses) dan runtime TensorFlow, tetapi dengan diduplikasi dalam proses forking, informasi koneksi ini tidak disesuaikan dengan benar. Hal ini menyebabkan TensorFlow macet saat mencoba menggunakan Sesi yang dibuat dalam proses yang berbeda. Jika ada yang memiliki lebih banyak wawasan tentang bagaimana ini bekerja di bawah tenda di TensorFlow, saya akan senang mendengarnya!
Solusi / Solusi
Saya pergi dengan menyesuaikan Seledri sehingga menggunakan Thread
s bukannya Process
es untuk pengumpulan. Ada beberapa kelemahan dari pendekatan ini (lihat komentar GIL di atas), tetapi ini memungkinkan kita untuk memuat model hanya sekali. Lagi pula, kami tidak benar-benar terikat CPU karena runtime TensorFlow memaksimalkan semua inti CPU (ini dapat menghindari GIL karena tidak ditulis dengan Python). Anda harus menyediakan Seledri dengan perpustakaan terpisah untuk melakukan penggabungan berbasis utas; dokumen menyarankan dua opsi:gevent
atau eventlet
. Anda kemudian meneruskan perpustakaan yang Anda pilih ke pekerja melalui --pool
argumen baris perintah.
Atau, tampaknya (seperti yang telah Anda ketahui @pX0r) bahwa backend Keras lain seperti Theano tidak memiliki masalah ini. Itu masuk akal, karena masalah ini terkait erat dengan detail implementasi TensorFlow. Saya pribadi belum mencoba Theano, jadi jarak tempuh Anda mungkin berbeda.
Saya tahu pertanyaan ini telah diposting beberapa waktu yang lalu, tetapi masalahnya masih ada, jadi semoga ini bisa membantu seseorang!