कायर मॉडल संकलन के बाद धीमी होने की भविष्यवाणी क्यों करता है?


23

भविष्यवाणी की गति

सिद्धांत रूप में, भविष्यवाणी स्थिर होनी चाहिए क्योंकि भार का एक निश्चित आकार होता है। संकलन के बाद (आशावादी को हटाने की आवश्यकता के बिना) मैं अपनी गति कैसे प्राप्त करूं?

संबंधित प्रयोग देखें: https://nbviewer.jupyter.org/github/off99555/TensorFlowExperiments/blob/master/test-prediction-speed-after-compile.ipynb?flush_cache=true


मुझे लगता है कि आपको संकलन के बाद मॉडल को फिट करने की आवश्यकता है तो भविष्यवाणी करने के लिए प्रशिक्षित मॉडल का उपयोग करें। यहाँ
भोली

@ एंटिव फिटिंग मुद्दे के लिए अप्रासंगिक है। यदि आप जानते हैं कि नेटवर्क वास्तव में कैसे काम करता है तो आप उत्सुक होंगे कि भविष्यवाणी धीमी क्यों है। भविष्यवाणी करते समय, केवल वेट का उपयोग मैट्रिक्स गुणा के लिए किया जाता है, और वज़न को संकलन से पहले और बाद में तय किया जाना चाहिए, इसलिए भविष्यवाणी का समय स्थिर रहना चाहिए।
15:99 बजे ऑफस्पॉट 5

मुझे पता है कि यह मुद्दा अप्रासंगिक है । और, किसी को यह जानने की आवश्यकता नहीं है कि नेटवर्क यह इंगित करने के लिए कैसे काम करता है कि आप जिन कार्यों के साथ आए हैं और सटीकता के लिए तुलना करना वास्तव में अर्थहीन है। कुछ डेटा पर मॉडल को फिट किए बिना आप भविष्यवाणी कर रहे हैं और आप वास्तव में लिए गए समय की तुलना कर रहे हैं। यह एक तंत्रिका नेटवर्क के लिए सामान्य या सही उपयोग के मामले नहीं है
भोला

3
@naive मॉडल के प्रदर्शन को समझने में समस्या बनाम संकलित होने की चिंता, सटीकता या मॉडल डिजाइन के साथ कुछ भी नहीं करना। यह एक वैध मुद्दा है कि TF उपयोगकर्ताओं को खर्च कर सकते हैं - मैं एक के लिए इस सवाल पर ठोकर तक इसके बारे में कोई सुराग नहीं था।
OverLordGoldDragon

1
@naive आप के fitबिना नहीं कर सकते compile; ऑप्टिमाइज़र किसी भी वज़न को अपडेट करने के लिए मौजूद नहीं है। मेरे उत्तर में वर्णित किए बिना या के रूप में उपयोग किया predict जा सकता है , लेकिन प्रदर्शन अंतर इस नाटकीय नहीं होना चाहिए - इसलिए समस्या। fitcompile
ओवरलॉर्डगोल्डड्रैगन

जवाबों:


22

अद्यतन - 1/15/2020 : छोटे बैच आकारों के लिए मौजूदा सबसे अच्छा अभ्यास सीधे मॉडल को इनपुट खिलाने के लिए होना चाहिए - यानी preds = model(x), और अगर परतें ट्रेन / इंजेक्शन पर अलग तरह से व्यवहार करती हैं, तो model(x, training=False)। नवीनतम प्रतिबद्ध के अनुसार, यह अब प्रलेखित है

मैंने इन्हें बेंचमार्क नहीं किया है, लेकिन Git चर्चा के अनुसार , यह भी कोशिश करने लायक है predict_on_batch()- विशेष रूप से TF 2.1 में सुधार के साथ।


अंतिम अपराधी : self._experimental_run_tf_function = True। यह प्रायोगिक है । लेकिन यह वास्तव में बुरा नहीं है।

किसी भी TensorFlow देव पढ़ने के लिए: अपने कोड को साफ करें । यह एक गड़बड़ है। और यह महत्वपूर्ण कोडिंग प्रथाओं का उल्लंघन करता है, जैसे कि एक फ़ंक्शन एक काम करता है ; _process_inputsएक करता है बहुत से "प्रक्रिया आदानों", के लिए एक ही अधिक _standardize_user_data। "मैं पर्याप्त भुगतान नहीं कर रहा हूं" - लेकिन आप भुगतान करते हैं , अतिरिक्त समय में अपने सामान को समझने में खर्च करते हैं, और उपयोगकर्ताओं में आपके इश्यू पृष्ठ को बग के साथ आसानी से भरना एक स्पष्ट कोड के साथ हल किया जाता है।


सारांश : यह केवल थोड़ा धीमा है compile()

compile()एक आंतरिक झंडे को सेट करता है जो एक अलग भविष्यवाणी फ़ंक्शन को असाइन करता है predict। यह फ़ंक्शन प्रत्येक कॉल पर एक नया ग्राफ बनाता है, जो इसे अनलेडेड के सापेक्ष धीमा कर देता है। हालाँकि, अंतर केवल तब स्पष्ट होता है जब ट्रेन का समय डाटा प्रोसेसिंग समय से बहुत कम होता है । यदि हम मॉडल का आकार कम से कम मध्य आकार में बढ़ाते हैं , तो दोनों समान हो जाते हैं। सबसे नीचे कोड देखें।

डेटा प्रोसेसिंग समय में यह मामूली वृद्धि प्रवर्धित ग्राफ क्षमता द्वारा मुआवजा से अधिक है। चूँकि यह केवल एक मॉडल ग्राफ को चारों ओर रखने के लिए अधिक कुशल है, इसलिए पूर्व-संकलन को छोड़ दिया जाता है। फिर भी : यदि आपका मॉडल डेटा के सापेक्ष छोटा है, तो आप compile()मॉडल इंट्रेंस के बिना बेहतर हैं । वर्कअराउंड के लिए मेरा अन्य उत्तर देखें।


मुझे क्या करना चाहिए?

मॉडल प्रदर्शन की तुलना बनाम संकलित के रूप में मैं नीचे कोड में है।

  • संकलित तेजी से होता है : predictसंकलित मॉडल पर चलता है ।
  • संकलित धीमी है : predictएक असंचित मॉडल पर चलाएँ ।

हां, दोनों संभव हैं, और यह (1) डेटा आकार पर निर्भर करेगा; (2) मॉडल का आकार; (३) हार्डवेयर। नीचे स्थित कोड वास्तव में संकलित मॉडल को तेजी से दिखाता है , लेकिन 10 पुनरावृत्तियों एक छोटा नमूना है। "कैसे-करें" के लिए मेरे दूसरे उत्तर में "वर्कअराउंड" देखें।


विवरण :

यह डिबग करने में थोड़ा समय लगा, लेकिन मजेदार था। नीचे मैंने अपने द्वारा खोजे गए प्रमुख दोषियों का वर्णन किया है, कुछ प्रासंगिक दस्तावेज़ीकरण का हवाला देते हैं, और प्रोफाइलर परिणाम दिखाते हैं जो अंतिम अड़चन का कारण बनते हैं।

( FLAG == self.experimental_run_tf_function, संक्षिप्तता के लिए)

  1. Modelके साथ डिफ़ॉल्ट को दर्शाता है द्वारा FLAG=Falsecompile()इसे सेट करता है True
  2. predict() भविष्यवाणी समारोह प्राप्त करना शामिल है, func = self._select_training_loop(x)
  3. बिना किसी विशेष कावड़ के पास predictऔर compile, अन्य सभी झंडे ऐसे हैं:
    • (ए) FLAG==True ->func = training_v2.Loop()
    • (बी) FLAG==False ->func = training_arrays.ArrayLikeTrainingLoop()
  4. से स्रोत कोड docstring , (ए) से भारी मात्रा में ग्राफ निर्भर है और अधिक वितरण रणनीति का उपयोग करता है, और ऑप्स बनाने और ग्राफ तत्व है, जो (कर) "हो सकता है" प्रभाव प्रदर्शन को नष्ट करने से ग्रस्त हैं।

सच्चा अपराधी :, 81% रनटाइम के_process_inputs() लिए लेखांकन । इसका प्रमुख घटक? , 72% रनटाइम । यह विधि (B) के लिए भी मौजूद नहीं है । एक मध्यम आकार के मॉडल का उपयोग, तथापि, शामिल हैं क्रम के 1% से कम । नीचे कोड, और प्रोफाइलिंग परिणाम का पालन करें।_create_graph_function()_process_inputs


डेटा प्रोसेसर :

(ए) :, <class 'tensorflow.python.keras.engine.data_adapter.TensorLikeDataAdapter'>में इस्तेमाल किया _process_inputs()प्रासंगिक स्रोत कोड

(बी) :, numpy.ndarrayद्वारा लौटाया गया convert_eager_tensors_to_numpyप्रासंगिक स्रोत कोड , और यहां


मॉडल एक्सेल्यूशन फंक्शन (उदाहरण के लिए)

(ए) : वितरण समारोह , और यहां

(बी) : वितरण समारोह (अलग) , और यहां


PROFILER : मेरे अन्य उत्तर में कोड के लिए परिणाम, "छोटे मॉडल", और इस उत्तर में, "मध्यम मॉडल":

छोटे मॉडल : 1000 पुनरावृत्तियों,compile()

छोटे मॉडल : 1000 पुनरावृत्तियों, नहीं compile()

मध्यम मॉडल : 10 पुनरावृत्तियों


प्रलेखन (परोक्ष रूप से) के प्रभाव पर compile(): स्रोत

अन्य TensorFlow संचालन के विपरीत, हम अजगर संख्यात्मक इनपुट को दहाई में नहीं बदलते हैं। इसके अलावा, उदाहरण के लिए, प्रत्येक विशिष्ट अजगर संख्यात्मक मूल्य के लिए एक नया ग्राफ़ उत्पन्न होता है g(2)और g(3)दो नए ग्राफ़ उत्पन्न करेगा

function इनपुट आकृतियों और डेटाटिप्स के हर अनूठे सेट के लिए एक अलग ग्राफ को इंस्टेंट करता है । उदाहरण के लिए, निम्न कोड स्निपेट का परिणाम तीन अलग-अलग ग्राफ़ों में पता लगाया जाएगा, क्योंकि प्रत्येक इनपुट का एक अलग आकार होता है

एक एकल tf.function ऑब्जेक्ट को हुड के तहत कई कम्प्यूटेशन ग्राफ़ पर मैप करने की आवश्यकता हो सकती है। यह केवल प्रदर्शन के रूप में दिखाई देना चाहिए (अनुरेखण रेखांकन में एक गैर-कम्प्यूटेशनल और मेमोरी लागत है ) लेकिन कार्यक्रम की शुद्धता को प्रभावित नहीं करना चाहिए


COUNTEREXAMPLE :

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()?
23:99 पर ऑफस्पिन 5

3
@ off99555 "किसी भी मॉडल के आकार के लिए" - ऐसी कोई बात नहीं है। पूरा उत्तर पढ़ें - यदि मुझे इसे डीबग करने में घंटों लग जाते हैं, तो पूछने वाले से कुछ मिनट अनुचित नहीं होने चाहिए।
ओवरलॉर्डगोल्डड्रैगन

मैंने पूरी बात पढ़ी लेकिन यह समझना मुश्किल है क्योंकि मैं ऐसा नहीं हूं जिसने कोड को डिबग किया हो। इसलिए आपको एक निष्कर्ष देने की आवश्यकता है जो कि डिबगिंग चरण के दौरान आपको मिलने वाले मध्यवर्ती चर को शामिल नहीं करता है। उदा "यदि आपका मॉडल छोटा है, तो संकलन का उपयोग न करें। यदि आपका मॉडल मध्यम आकार का है, तो आप संकलन का उपयोग कर सकते हैं। कुछ ऐसा ही है।
off99555

1
@ off99555 मेला काफी; अपडेट किया गया। नया खंड काफी सामान्य ज्ञान है, लेकिन मैं देख सकता हूं कि इसे तुरंत महसूस नहीं किया जा सकता है।
ओवरलॉर्डगोल्डड्रैगन

1
@ off99555 ऐसा नहीं है कि मैंने परीक्षण किया है, लेकिन बहुत बड़े मॉडल (ResNet, आदि) विशेष रूप से तेजी से संकलित, esp चला सकते हैं। यदि कई उपकरणों पर वितरित किया जाता है - (ए) अधिक ग्राफ है और वितरण-भारी है। सबसे अच्छा परीक्षण है, ठीक है, एक परीक्षा - जैसे उत्तर में। TF लाइट से अपरिचित, लेकिन यह एक अलग सवाल है
OverLordGoldDragon

15

अद्यतन : वास्तविक उत्तर को एक अलग उत्तर के रूप में पोस्ट किया गया देखें; इस पोस्ट में पूरक जानकारी है


.compile() घाटे, मैट्रिक्स, ग्रेडिएंट्स और आंशिक रूप से ऑप्टिमाइज़र और इसके वज़न सहित TF / Keras ग्राफ के अधिकांश भाग को सेट करता है - जो एक उल्लेखनीय मंदी की गारंटी देता है।

क्या है अप्रत्याशित मंदी की हद तक है - अपने खुद के प्रयोग पर 10 गुना, और के लिए predict()है, जो किसी भी वजन अपडेट नहीं होता। TF2 के स्रोत कोड को देखते हुए, ग्राफ एलिमेंट्स कसकर इंटरकेटेड दिखाई देते हैं, जरूरी नहीं कि संसाधनों को "निष्पक्ष" आवंटित किया जाए।

predictएक मॉडल के लिए डेवलपर्स के प्रदर्शन पर संभावित अनदेखी , क्योंकि मॉडल आमतौर पर संकलित किए जाते हैं - लेकिन व्यवहार में , यह एक अस्वीकार्य अंतर है। यह भी संभव है कि यह एक "आवश्यक बुराई" है, क्योंकि एक साधारण वर्कअराउंड है (नीचे देखें)।

यह एक पूर्ण उत्तर नहीं है, और मुझे आशा है कि कोई व्यक्ति इसे यहां प्रदान कर सकता है - यदि नहीं, तो मैं TensorFlow पर एक Github मुद्दा खोलने का सुझाव दूंगा। (ओपी ने यहां ; )


वर्कअराउंड : एक मॉडल को प्रशिक्षित करें, उसके वज़न को बचाएं , मॉडल को बिना संकलन के फिर से बनाएं, वज़न को लोड करें। करो नहीं पूरे मॉडल (जैसे बचाने model.save(),) के रूप में यह संकलित लोड हो जाएगा - के बजाय का उपयोग करें model.save_weights()और model.load_weights()

समाधान 2 : ऊपर, लेकिन उपयोग करें load_model(path, compile=False); सुझाव क्रेडिट: डी। मोलर


अद्यतन : स्पष्ट करने के लिए, अनुकूलक है नहीं पूरी तरह से साथ instantiated compile, इसके सहित weightsऔर updatestensors - यह किया जाता है जब एक फिटिंग कार्य करने के लिए पहली कॉल किया जाता है ( fit, train_on_batch, आदि), के माध्यम से model._make_train_function()

देखा गया व्यवहार इस प्रकार और भी विचित्र है। इससे भी बदतर, ऑप्टिमाइज़र का निर्माण किसी भी आगे की मंदी को कम नहीं करता है (नीचे देखें) - "ग्राफ आकार" का सुझाव देना यहां का मुख्य विवरण नहीं है।


EDIT : कुछ मॉडलों पर, एक 30x मंदी । 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
अतीत में मैं केरस और पियरटेक के बीच एक महत्वपूर्ण गति अंतर देख सकता था (तेजी से पाइरॉच होने के कारण)।
डेनियल मोलर

1
मैंने यहाँ एक मुद्दा खोला है: github.com/tensorflow/tensorflow/issues/33340
15:99 बजे

2
हाँ। यह एक बुरा डिज़ाइन विकल्प है कि आप भविष्यवाणी से संबंधित प्रशिक्षण कोड डालते हैं। क्योंकि उपयोगकर्ता इस भविष्यवाणी फ़ंक्शन का उत्पादन में क्रमिक रूप से कई बार उपयोग करेंगे। कम से कम आश्चर्य पैदा करने के लिए सबसे तेज़ काम करना चाहिए। सुन्न कार्यान्वयन की तुलना में, आपको केवल एक मैट्रिक्स को गुणा करना, एक पूर्वाग्रह जोड़ना, सक्रिय करना होगा, और यह एक घनी परत के लिए है। किसी भी नुकसान फ़ंक्शन को चिंता करने की कोई आवश्यकता नहीं है।
ऑफ99555

1
संकेत, आप उपयोग कर सकते हैं load_model(name, compile=False), यह वजन को बचाने / लोड करने और मॉडल को फिर से बनाने की तुलना में सरल है।
डैनियल मोलर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.