نصائح متعلّقة بالأداء

يوفر هذا المستند نصائح أداء محددة لمجموعات بيانات TensorFlow (TFDS). لاحظ أن TFDS يوفر مجموعات البيانات ككائنات tf.data.Dataset ، لذا فإن النصائح الواردة من دليل tf.data لا تزال سارية.

مجموعات البيانات المرجعية

استخدم tfds.benchmark(ds) لقياس أي كائن tf.data.Dataset .

تأكد من الإشارة إلى batch_size= لتطبيع النتائج (على سبيل المثال 100 iter/sec -> 3200 ex/sec). يعمل هذا مع أي شيء قابل للتكرار (على سبيل المثال tfds.benchmark(tfds.as_numpy(ds)) ).

ds = tfds.load('mnist', split='train').batch(32).prefetch()
# Display some benchmark statistics
tfds.benchmark(ds, batch_size=32)
# Second iteration is much faster, due to auto-caching
tfds.benchmark(ds, batch_size=32)

مجموعات بيانات صغيرة (أقل من 1 جيجابايت)

تقوم جميع مجموعات بيانات TFDS بتخزين البيانات الموجودة على القرص بتنسيق TFRecord . بالنسبة لمجموعات البيانات الصغيرة (مثل MNIST وCIFAR-10/-100)، فإن القراءة من .tfrecord يمكن أن تضيف حملًا كبيرًا.

وبما أن مجموعات البيانات هذه تتناسب مع الذاكرة، فمن الممكن تحسين الأداء بشكل ملحوظ عن طريق التخزين المؤقت لمجموعة البيانات أو تحميلها مسبقًا. لاحظ أن TFDS يقوم تلقائيًا بتخزين مجموعات البيانات الصغيرة مؤقتًا (يحتوي القسم التالي على التفاصيل).

التخزين المؤقت لمجموعة البيانات

فيما يلي مثال على خط أنابيب البيانات الذي يقوم بتخزين مجموعة البيانات مؤقتًا بشكل صريح بعد تطبيع الصور.

def normalize_img(image, label):
  """Normalizes images: `uint8` -> `float32`."""
  return tf.cast(image, tf.float32) / 255., label


ds, ds_info = tfds.load(
    'mnist',
    split='train',
    as_supervised=True,  # returns `(img, label)` instead of dict(image=, ...)
    with_info=True,
)
# Applying normalization before `ds.cache()` to re-use it.
# Note: Random transformations (e.g. images augmentations) should be applied
# after both `ds.cache()` (to avoid caching randomness) and `ds.batch()` (for
# vectorization [1]).
ds = ds.map(normalize_img, num_parallel_calls=tf.data.AUTOTUNE)
ds = ds.cache()
# For true randomness, we set the shuffle buffer to the full dataset size.
ds = ds.shuffle(ds_info.splits['train'].num_examples)
# Batch after shuffling to get unique batches at each epoch.
ds = ds.batch(128)
ds = ds.prefetch(tf.data.experimental.AUTOTUNE)

عند التكرار على مجموعة البيانات هذه، سيكون التكرار الثاني أسرع بكثير من الأول بفضل التخزين المؤقت.

التخزين المؤقت التلقائي

افتراضيًا، تقوم TFDS بالتخزين المؤقت التلقائي (باستخدام ds.cache() ) مجموعات البيانات التي تلبي القيود التالية:

  • تم تحديد الحجم الإجمالي لمجموعة البيانات (جميع الانقسامات) و<250 ميجابايت
  • تم تعطيل shuffle_files ، أو تتم قراءة جزء واحد فقط

من الممكن إلغاء الاشتراك في التخزين المؤقت التلقائي عن طريق تمرير try_autocaching=False إلى tfds.ReadConfig في tfds.load . قم بإلقاء نظرة على وثائق كتالوج مجموعة البيانات لمعرفة ما إذا كانت مجموعة بيانات معينة ستستخدم ذاكرة التخزين المؤقت التلقائية.

تحميل البيانات الكاملة كموتر واحد

إذا كانت مجموعة البيانات الخاصة بك مناسبة للذاكرة، فيمكنك أيضًا تحميل مجموعة البيانات الكاملة كمصفوفة Tensor أو NumPy واحدة. من الممكن القيام بذلك عن طريق ضبط batch_size=-1 لتجميع كافة الأمثلة في ملف tf.Tensor واحد. ثم استخدم tfds.as_numpy للتحويل من tf.Tensor إلى np.array .

(img_train, label_train), (img_test, label_test) = tfds.as_numpy(tfds.load(
    'mnist',
    split=['train', 'test'],
    batch_size=-1,
    as_supervised=True,
))

مجموعات بيانات كبيرة

يتم تقسيم مجموعات البيانات الكبيرة (مقسمة إلى ملفات متعددة) وعادةً لا يتم احتواؤها في الذاكرة، لذا لا ينبغي تخزينها مؤقتًا.

خلط ورق اللعب والتدريب

أثناء التدريب، من المهم خلط البيانات جيدًا - فالبيانات التي تم خلطها بشكل سيئ يمكن أن تؤدي إلى انخفاض دقة التدريب.

بالإضافة إلى استخدام ds.shuffle لخلط السجلات عشوائيًا، يجب عليك أيضًا تعيين shuffle_files=True للحصول على سلوك خلط جيد لمجموعات البيانات الأكبر حجمًا والمقسمة إلى ملفات متعددة. وبخلاف ذلك، ستقرأ العصور الأجزاء بنفس الترتيب، وبالتالي لن تكون البيانات عشوائية حقًا.

ds = tfds.load('imagenet2012', split='train', shuffle_files=True)

بالإضافة إلى ذلك، عندما shuffle_files=True ، يقوم TFDS بتعطيل options.deterministic ، مما قد يؤدي إلى زيادة طفيفة في الأداء. للحصول على خلط حتمي، من الممكن إلغاء الاشتراك في هذه الميزة باستخدام tfds.ReadConfig : إما عن طريق تعيين read_config.shuffle_seed أو الكتابة فوق read_config.options.deterministic .

تقسيم بياناتك تلقائيًا بين العاملين (TF)

عند التدريب على عدة عاملين، يمكنك استخدام الوسيطة input_context الخاصة بـ tfds.ReadConfig ، بحيث يقرأ كل عامل مجموعة فرعية من البيانات.

input_context = tf.distribute.InputContext(
    input_pipeline_id=1,  # Worker id
    num_input_pipelines=4,  # Total number of workers
)
read_config = tfds.ReadConfig(
    input_context=input_context,
)
ds = tfds.load('dataset', split='train', read_config=read_config)

وهذا مكمل لواجهة برمجة التطبيقات المقسمة فرعيًا. أولاً، يتم تطبيق واجهة برمجة التطبيقات المقسمة إلى فرعية: يتم تحويل train[:50%] إلى قائمة الملفات المطلوب قراءتها. بعد ذلك، يتم تطبيق عملية ds.shard() على تلك الملفات. على سبيل المثال، عند استخدام train[:50%] مع num_input_pipelines=2 ، سيقرأ كل من العاملين 1/4 من البيانات.

عندما يكون shuffle_files=True ، يتم تبديل الملفات عشوائيًا داخل عامل واحد، ولكن ليس بين العمال. سيقرأ كل عامل نفس المجموعة الفرعية من الملفات بين العصور.

تقسيم بياناتك تلقائيًا بين العاملين (Jax)

مع Jax، يمكنك استخدام tfds.split_for_jax_process أو tfds.even_splits API لتوزيع بياناتك عبر العمال. راجع دليل API المقسم .

split = tfds.split_for_jax_process('train', drop_remainder=True)
ds = tfds.load('my_dataset', split=split)

tfds.split_for_jax_process هو اسم مستعار بسيط لـ:

# The current `process_index` loads only `1 / process_count` of the data.
splits = tfds.even_splits('train', n=jax.process_count(), drop_remainder=True)
split = splits[jax.process_index()]

فك تشفير الصور بشكل أسرع

بشكل افتراضي، يقوم TFDS بفك تشفير الصور تلقائيًا. ومع ذلك، هناك حالات يمكن أن يكون من الأفضل فيها تخطي فك تشفير الصورة باستخدام tfds.decode.SkipDecoding وتطبيق العملية tf.io.decode_image يدويًا:

الكود الخاص بكلا المثالين متاح في دليل فك التشفير .

تخطي الميزات غير المستخدمة

إذا كنت تستخدم مجموعة فرعية من الميزات فقط، فمن الممكن تخطي بعض الميزات تمامًا. إذا كانت مجموعة البيانات الخاصة بك تحتوي على العديد من الميزات غير المستخدمة، فإن عدم فك تشفيرها يمكن أن يؤدي إلى تحسين الأداء بشكل كبير. راجع https://www.tensorflow.org/datasets/decode#only_decode_a_sub-set_of_the_features

يستخدم tf.data كل ذاكرة الوصول العشوائي الخاصة بي!

إذا كانت ذاكرة الوصول العشوائي (RAM) لديك محدودة، أو إذا كنت تقوم بتحميل العديد من مجموعات البيانات بالتوازي أثناء استخدام tf.data ، فإليك بعض الخيارات التي يمكن أن تساعدك:

تجاوز حجم المخزن المؤقت

builder.as_dataset(
  read_config=tfds.ReadConfig(
    ...
    override_buffer_size=1024,  # Save quite a bit of RAM.
  ),
  ...
)

يؤدي هذا إلى تجاوز buffer_size الذي تم تمريره إلى TFRecordDataset (أو ما يعادله): https://www.tensorflow.org/api_docs/python/tf/data/TFRecordDataset#args

استخدم tf.data.Dataset.with_options لإيقاف السلوكيات السحرية

https://www.tensorflow.org/api_docs/python/tf/data/Dataset#with_options

options = tf.data.Options()

# Stop magic stuff that eats up RAM:
options.autotune.enabled = False
options.experimental_distribute.auto_shard_policy = (
  tf.data.experimental.AutoShardPolicy.OFF)
options.experimental_optimization.inject_prefetch = False

data = data.with_options(options)