यह उत्तर : TF2 बनाम TF1 ट्रेन लूप, इनपुट डेटा प्रोसेसर, और ईगर बनाम ग्राफ मोड निष्पादन सहित - इस मुद्दे का एक विस्तृत, ग्राफ / हार्डवेयर-स्तरीय विवरण प्रदान करना है। एक समस्या सारांश और संकल्प दिशानिर्देशों के लिए, मेरे अन्य उत्तर को देखें।
प्रदर्शन वर्धमान : कभी-कभी कॉन्फ़िगरेशन के आधार पर एक तेज़, कभी-कभी दूसरा होता है। जहाँ तक TF2 बनाम TF1 चला जाता है, वे औसत पर बराबर के बारे में हैं, लेकिन महत्वपूर्ण कॉन्फ़िगरेशन-आधारित अंतर मौजूद हैं, और TF1 TF2 को अक्सर इसके विपरीत से अधिक ट्रम्प करता है। नीचे "बेंचमार्किंग" देखें।
EAGER वी.एस. GRAPH : कुछ के लिए इस पूरे उत्तर का मांस: TF2 की उत्सुकता मेरे परीक्षण के अनुसार, TF1 की तुलना में धीमी है। विवरण नीचे दिया गया है।
दोनों के बीच मूलभूत अंतर है: ग्राफ एक कम्प्यूटेशनल नेटवर्क को लगातार सेट करता है, और निष्पादित करता है जब 'को बताया' - जबकि एगर निर्माण पर सब कुछ निष्पादित करता है। लेकिन कहानी केवल यहाँ से शुरू होती है:
उत्सुक ग्राफ से रहित नहीं है , और वास्तव में ज्यादातर ग्राफ हो सकता है , अपेक्षा के विपरीत। यह काफी हद तक है, ग्राफ़ निष्पादित किया गया है - इसमें मॉडल और ऑप्टिमाइज़र वेट शामिल हैं, जिसमें ग्राफ़ का एक बड़ा हिस्सा शामिल है।
एगर निष्पादन में खुद के ग्राफ के हिस्से का पुनर्निर्माण करता है ; ग्राफ़ का प्रत्यक्ष परिणाम पूरी तरह से निर्मित नहीं हो रहा है - प्रोफाइलर परिणाम देखें। यह एक कम्प्यूटेशनल उपरि है।
उत्सुक धीमी w / Numpy आदानों है ; इस Git टिप्पणी के अनुसार और कोड, उत्सुक में Numpy आदानों सीपीयू से GPU को tensors को कॉपी करने की अपनी अतिरिक्त लागत शामिल हैं। स्रोत कोड के माध्यम से कदम, डेटा हैंडलिंग अंतर स्पष्ट हैं; ईगर सीधे नेम्पी से गुजरता है, जबकि ग्राफ टेंसर्स से गुजरता है जो फिर नेम्पी का मूल्यांकन करता है; सटीक प्रक्रिया की अनिश्चितता, लेकिन बाद में GPU- स्तर के अनुकूलन शामिल होने चाहिए
TF2 Eager, TF1 Eager की तुलना में धीमा है - यह अप्रत्याशित है। नीचे दिए गए बेंचमार्किंग परिणाम देखें। मतभेद नगण्य से महत्वपूर्ण तक फैले हैं, लेकिन सुसंगत हैं। यह सुनिश्चित करें कि यह मामला क्यों है - यदि कोई टीएफ देव स्पष्ट करता है, तो उत्तर को अपडेट करेगा।
TF2 बनाम TF1 : TF देव, Q. स्कॉट झू की प्रतिक्रिया के प्रासंगिक अंशों को उद्धृत करते हुए - w / बिट ऑफ माई जोर एंड रीवॉर्डिंग:
उत्सुकता में, रनटाइम को ऑप्स को निष्पादित करने और अजगर कोड की प्रत्येक पंक्ति के लिए संख्यात्मक मान वापस करने की आवश्यकता होती है। एकल चरण निष्पादन की प्रकृति के कारण यह धीमा हो जाता है ।
TF2 में, Keras प्रशिक्षण, eval और भविष्यवाणी के लिए अपना ग्राफ बनाने के लिए tf.function का उपयोग करता है। हम उन्हें मॉडल के लिए "निष्पादन फ़ंक्शन" कहते हैं। TF1 में, "निष्पादन फ़ंक्शन" एक फ़ंक्ग्राफ़ था, जिसने कुछ सामान्य घटक को TF फ़ंक्शन के रूप में साझा किया था, लेकिन इसका एक अलग कार्यान्वयन है।
इस प्रक्रिया के दौरान, हमने किसी तरह से train_on_batch (), test_on_batch () और predict_on_batch () के लिए गलत कार्यान्वयन छोड़ दिया । वे अभी भी संख्यात्मक रूप से सही हैं , लेकिन x_on_batch के लिए निष्पादन फ़ंक्शन tf.function लिपटे अजगर फ़ंक्शन के बजाय एक शुद्ध पायथन फ़ंक्शन है। यह करेगा सुस्ती कारण
TF2 में, हम सभी इनपुट डेटा को एक tf.data.Dataset में परिवर्तित करते हैं, जिसके द्वारा हम एकल प्रकार के इनपुट को संभालने के लिए अपने निष्पादन फ़ंक्शन को एकजुट कर सकते हैं। डेटासेट रूपांतरण में कुछ ओवरहेड हो सकता है , और मुझे लगता है कि यह एक बार का ओवरहेड है, बजाय एक प्रति बैच लागत के
ऊपर अंतिम पैराग्राफ के अंतिम वाक्य के साथ, और नीचे पैराग्राफ के अंतिम खंड:
उत्सुक मोड में सुस्ती को दूर करने के लिए, हमारे पास @ tf.function है, जो एक अजगर फ़ंक्शन को एक ग्राफ में बदल देगा। जब एनपी सरणी की तरह संख्यात्मक मूल्य फ़ीड करते हैं, तो tf.function का शरीर स्थिर ग्राफ़ में परिवर्तित हो जाता है, अनुकूलित किया जाता है, और अंतिम मान लौटाता है, जो तेज़ है और TF1 ग्राफ़ मोड के समान प्रदर्शन होना चाहिए।
मैं असहमत हूं - मेरे प्रोफाइलिंग परिणामों के अनुसार, जो एगर के इनपुट डेटा प्रोसेसिंग को ग्राफ के मुकाबले काफी धीमा दिखाते हैं। इसके अलावा, tf.data.Dataset
विशेष रूप से अनिश्चित , लेकिन ईगर बार-बार एक ही डेटा रूपांतरण विधियों के कई बार कॉल करता है - प्रोफाइलर देखें।
अंत में, देव की लिंक्ड कमिट: केरस v2 लूप्स का समर्थन करने के लिए परिवर्तनों की महत्वपूर्ण संख्या ।
ट्रेन लूप : (1) ईगर बनाम ग्राफ पर निर्भर करता है; (2) इनपुट डेटा फॉर्मेट, प्रशिक्षण एक अलग ट्रेन लूप के साथ आगे बढ़ेगा - TF2 _select_training_loop()
,, training.py , में से एक:
training_v2.Loop()
training_distributed.DistributionMultiWorkerTrainingLoop(
training_v2.Loop()) # multi-worker mode
# Case 1: distribution strategy
training_distributed.DistributionMultiWorkerTrainingLoop(
training_distributed.DistributionSingleWorkerTrainingLoop())
# Case 2: generator-like. Input is Python generator, or Sequence object,
# or a non-distributed Dataset or iterator in eager execution.
training_generator.GeneratorOrSequenceTrainingLoop()
training_generator.EagerDatasetOrIteratorTrainingLoop()
# Case 3: Symbolic tensors or Numpy array-like. This includes Datasets and iterators
# in graph mode (since they generate symbolic tensors).
training_generator.GeneratorLikeTrainingLoop() # Eager
training_arrays.ArrayLikeTrainingLoop() # Graph
प्रत्येक संसाधन आवंटन को अलग-अलग तरीके से संभालता है, और प्रदर्शन और क्षमता पर परिणाम को सहन करता है।
ट्रेन लूप: fit
बनाम train_on_batch
, keras
बनामtf.keras
: चार में से प्रत्येक अलग रेल लूप का उपयोग करता है, हालांकि हर संभव संयोजन में नहीं। keras
' fit
उदाहरण के लिए, का एक रूप का उपयोग करता है fit_loop
, उदाहरण के लिए training_arrays.fit_loop()
, और इसके train_on_batch
उपयोग कर सकते हैं K.function()
। tf.keras
पिछले अनुभाग में भाग में वर्णित एक अधिक परिष्कृत पदानुक्रम है।
ट्रेन लूप्स: प्रलेखन - विभिन्न निष्पादन विधियों में से कुछ पर प्रासंगिक स्रोत :
अन्य TensorFlow संचालन के विपरीत, हम अजगर संख्यात्मक इनपुट को दहाई में नहीं बदलते हैं। इसके अलावा, प्रत्येक अलग अजगर संख्यात्मक मूल्य के लिए एक नया ग्राफ उत्पन्न होता है
function
इनपुट आकृतियों और डेटाटिप्स के हर अनूठे सेट के लिए एक अलग ग्राफ को इंस्टेंट करता है ।
एक एकल tf.function ऑब्जेक्ट को हुड के तहत कई कम्प्यूटेशन ग्राफ़ पर मैप करने की आवश्यकता हो सकती है। इसे केवल प्रदर्शन के रूप में देखा जाना चाहिए (अनुरेखण रेखांकन में एक गैर-कम्प्यूटेशनल और मेमोरी लागत है )
इनपुट डेटा प्रोसेसर : ऊपर के समान, रनटाइम कॉन्फ़िगरेशन (निष्पादन मोड, डेटा प्रारूप, वितरण रणनीति) के अनुसार आंतरिक झंडे के आधार पर प्रोसेसर को केस-बाय-केस चुना जाता है। ईगर के साथ सबसे सरल मामला, जो सीधे w / Numpy सरणियों का काम करता है। कुछ विशिष्ट उदाहरणों के लिए, इस उत्तर को देखें ।
मॉडल आकार, डेटा आकार:
- निर्णायक है; किसी भी एकल कॉन्फ़िगरेशन ने सभी मॉडल और डेटा आकार के साथ ताज पहनाया।
- मॉडल आकार के सापेक्ष डेटा आकार महत्वपूर्ण है; छोटे डेटा और मॉडल के लिए, डेटा ट्रांसफर (जैसे CPU से GPU) ओवरहेड हावी हो सकता है। इसी तरह, छोटे ओवरहेड प्रोसेसर बड़े डेटा पर प्रति डेटा रूपांतरण समय पर हावी हो सकते हैं (देखें)
convert_to_tensor
"PROFILER" में )
- स्पीड प्रति ट्रेन लूप में भिन्न होती है और इनपुट डेटा प्रोसेसर 'संसाधनों से निपटने के साधन अलग होते हैं।
बेंचमार्क : पीस मांस। - शब्द दस्तावेज़ - एक्सेल स्प्रेडशीट
शब्दावली :
- %-रहित संख्याएँ सभी सेकंड हैं
- % गणना के रूप में
(1 - longer_time / shorter_time)*100
; औचित्य: हम इस बात से दिलचस्पी रखते हैं कि कौन सा कारक दूसरे की तुलना में तेज़ है; shorter / longer
वास्तव में एक गैर-रैखिक संबंध है, प्रत्यक्ष तुलना के लिए उपयोगी नहीं है
- % संकेत निर्धारण:
- TF2 बनाम TF1:
+
यदि TF2 तेज है
- जीवीई (ग्राफ बनाम ईगर):
+
यदि ग्राफ तेज है
- TF2 = TensorFlow 2.0.0 + केरस 2.3.1; टीएफ 1 = टेन्सरफ्लो 1.14.0 + केरस 2.2.5
PROFILER :
PROFILER - स्पष्टीकरण : स्पाइडर 3.3.6 आईडीई प्रोफाइलर।
कुछ कार्यों को दूसरों के घोंसले में दोहराया जाता है; इसलिए, "डेटा प्रोसेसिंग" और "प्रशिक्षण" कार्यों के बीच सटीक पृथक्करण को ट्रैक करना मुश्किल है, इसलिए कुछ ओवरलैप होंगे - जैसा कि बहुत अंतिम परिणाम में स्पष्ट किया गया है।
% आंकड़े ने wrt रनटाइम की गणना की माइनस समय का निर्माण करते हैं
- सभी (अद्वितीय) रनटाइम को जोड़कर समय की गणना करें, जिन्हें 1 या 2 बार कहा गया था
- ट्रेन के समय को सभी (अद्वितीय) रनटाइम से जोड़कर गणना की जाती है, जिन्हें एक ही समय में # पुनरावृत्तियों के # समान कहा जाता है, और उनके कुछ घोंसलों के रनटाइम
- फ़ंक्शंस उनके मूल नामों के अनुसार प्रोफाइल किए जाते हैं , दुर्भाग्यवश (यानी
_func = func
प्रोफ़ाइल के रूप में func
), जो निर्माण समय में मिश्रण करता है - इसलिए इसे बाहर करने की आवश्यकता है
परीक्षण पर्यावरण :
- नीचे w / न्यूनतम पृष्ठभूमि कार्यों में निष्पादित कोड चल रहा है
- GPU इस समय में पुनरावृत्तियों से पहले w / कुछ पुनरावृत्तियों को "वार्म अप" कर रहा था, जैसा कि इस पोस्ट में सुझाया गया है
- CUDA 10.0.130, cuDNN 7.6.0, TensorFlow 1.14.0, और TensorFlow 2.0.0 स्रोत से निर्मित, प्लस एनाकोंडा
- पायथन 3.7.4, स्पाइडर 3.3.6 आईडीई
- GTX 1070, विंडोज 10, 24GB DDR4 2.4-MHz RAM, i7-7700HQ 2.8-GHz CPU
विधि :
- बेंचमार्क 'छोटा', 'मध्यम', और 'बड़े' मॉडल और डेटा आकार
- प्रत्येक मॉडल आकार के लिए मापदंडों के # फिक्स, इनपुट डेटा आकार से स्वतंत्र
- "बड़े" मॉडल में अधिक पैरामीटर और परतें हैं
- "बड़े" डेटा का एक लंबा अनुक्रम है, लेकिन वही
batch_size
औरnum_channels
- मॉडल केवल उपयोग करते हैं
Conv1D
,Dense
'learnable' परतों; आरएनएन प्रति टीएफ-संस्करण के प्रतिरूप से बचते हैं। मतभेद
- मॉडल और ऑप्टिमाइज़र ग्राफ़ बिल्डिंग को छोड़ने के लिए हमेशा बेंचमार्किंग लूप के बाहर एक ट्रेन फिट की गई
- विरल डेटा (जैसे
layers.Embedding()
) या विरल लक्ष्य (जैसे) का उपयोग नहीं करनाSparseCategoricalCrossEntropy()
सीमाएं : एक "पूर्ण" उत्तर हर संभव ट्रेन लूप और पुनरावृत्त को समझाएगा, लेकिन यह निश्चित रूप से मेरे समय की क्षमता, कोई नहीं, पेचेक या सामान्य आवश्यकता से परे है। परिणाम केवल पद्धति के रूप में अच्छे हैं - एक खुले दिमाग के साथ व्याख्या।
कोड :
import numpy as np
import tensorflow as tf
import random
from termcolor import cprint
from time import time
from tensorflow.keras.layers import Input, Dense, Conv1D
from tensorflow.keras.layers import Dropout, GlobalAveragePooling1D
from tensorflow.keras.models import Model
from tensorflow.keras.optimizers import Adam
import tensorflow.keras.backend as K
#from keras.layers import Input, Dense, Conv1D
#from keras.layers import Dropout, GlobalAveragePooling1D
#from keras.models import Model
#from keras.optimizers import Adam
#import keras.backend as K
#tf.compat.v1.disable_eager_execution()
#tf.enable_eager_execution()
def reset_seeds(reset_graph_with_backend=None, verbose=1):
if reset_graph_with_backend is not None:
K = reset_graph_with_backend
K.clear_session()
tf.compat.v1.reset_default_graph()
if verbose:
print("KERAS AND TENSORFLOW GRAPHS RESET")
np.random.seed(1)
random.seed(2)
if tf.__version__[0] == '2':
tf.random.set_seed(3)
else:
tf.set_random_seed(3)
if verbose:
print("RANDOM SEEDS RESET")
print("TF version: {}".format(tf.__version__))
reset_seeds()
def timeit(func, iterations, *args, _verbose=0, **kwargs):
t0 = time()
for _ in range(iterations):
func(*args, **kwargs)
print(end='.'*int(_verbose))
print("Time/iter: %.4f sec" % ((time() - t0) / iterations))
def make_model_small(batch_shape):
ipt = Input(batch_shape=batch_shape)
x = Conv1D(128, 40, strides=4, padding='same')(ipt)
x = GlobalAveragePooling1D()(x)
x = Dropout(0.5)(x)
x = Dense(64, activation='relu')(x)
out = Dense(1, activation='sigmoid')(x)
model = Model(ipt, out)
model.compile(Adam(lr=1e-4), 'binary_crossentropy')
return model
def make_model_medium(batch_shape):
ipt = Input(batch_shape=batch_shape)
x = ipt
for filters in [64, 128, 256, 256, 128, 64]:
x = Conv1D(filters, 20, strides=1, padding='valid')(x)
x = GlobalAveragePooling1D()(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)
model.compile(Adam(lr=1e-4), 'binary_crossentropy')
return model
def make_model_large(batch_shape):
ipt = Input(batch_shape=batch_shape)
x = Conv1D(64, 400, strides=4, padding='valid')(ipt)
x = Conv1D(128, 200, strides=1, padding='valid')(x)
for _ in range(40):
x = Conv1D(256, 12, strides=1, padding='same')(x)
x = Conv1D(512, 20, strides=2, padding='valid')(x)
x = Conv1D(1028, 10, strides=2, padding='valid')(x)
x = Conv1D(256, 1, strides=1, padding='valid')(x)
x = GlobalAveragePooling1D()(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)
model.compile(Adam(lr=1e-4), 'binary_crossentropy')
return model
def make_data(batch_shape):
return np.random.randn(*batch_shape), \
np.random.randint(0, 2, (batch_shape[0], 1))
def make_data_tf(batch_shape, n_batches, iters):
data = np.random.randn(n_batches, *batch_shape),
trgt = np.random.randint(0, 2, (n_batches, batch_shape[0], 1))
return tf.data.Dataset.from_tensor_slices((data, trgt))#.repeat(iters)
batch_shape_small = (32, 140, 30)
batch_shape_medium = (32, 1400, 30)
batch_shape_large = (32, 14000, 30)
batch_shapes = batch_shape_small, batch_shape_medium, batch_shape_large
make_model_fns = make_model_small, make_model_medium, make_model_large
iterations = [200, 100, 50]
shape_names = ["Small data", "Medium data", "Large data"]
model_names = ["Small model", "Medium model", "Large model"]
def test_all(fit=False, tf_dataset=False):
for model_fn, model_name, iters in zip(make_model_fns, model_names, iterations):
for batch_shape, shape_name in zip(batch_shapes, shape_names):
if (model_fn is make_model_large) and (batch_shape is batch_shape_small):
continue
reset_seeds(reset_graph_with_backend=K)
if tf_dataset:
data = make_data_tf(batch_shape, iters, iters)
else:
data = make_data(batch_shape)
model = model_fn(batch_shape)
if fit:
if tf_dataset:
model.train_on_batch(data.take(1))
t0 = time()
model.fit(data, steps_per_epoch=iters)
print("Time/iter: %.4f sec" % ((time() - t0) / iters))
else:
model.train_on_batch(*data)
timeit(model.fit, iters, *data, _verbose=1, verbose=0)
else:
model.train_on_batch(*data)
timeit(model.train_on_batch, iters, *data, _verbose=1)
cprint(">> {}, {} done <<\n".format(model_name, shape_name), 'blue')
del model
test_all(fit=True, tf_dataset=False)