tf.data: إنشاء خطوط أنابيب إدخال TensorFlow

عرض على TensorFlow.org تشغيل في Google Colab عرض المصدر على جيثبتحميل دفتر

تمكّنك واجهة برمجة تطبيقات tf.data من إنشاء خطوط إدخال معقدة من قطع بسيطة قابلة لإعادة الاستخدام. على سبيل المثال ، قد يقوم خط الأنابيب الخاص بنموذج الصورة بتجميع البيانات من الملفات في نظام الملفات الموزعة ، وتطبيق اضطرابات عشوائية على كل صورة ، ودمج الصور المختارة عشوائيًا في دفعة للتدريب. قد يتضمن خط الأنابيب لنموذج نصي استخراج الرموز من بيانات النص الخام ، وتحويلها إلى تضمين معرفات في جدول بحث ، وتجميع التسلسلات ذات الأطوال المختلفة معًا. تتيح واجهة برمجة تطبيقات tf.data إمكانية معالجة كميات كبيرة من البيانات ، وقراءتها من تنسيقات بيانات مختلفة ، وإجراء تحويلات معقدة.

تقدم tf.data API تجريد tf.data.Dataset الذي يمثل سلسلة من العناصر ، حيث يتكون كل عنصر من مكون واحد أو أكثر. على سبيل المثال ، في خط أنابيب الصورة ، قد يكون العنصر مثالاً تدريبياً منفردًا ، مع زوج من مكونات الموتر التي تمثل الصورة والتسمية الخاصة بها.

توجد طريقتان متميزتان لإنشاء مجموعة بيانات:

  • ينشئ مصدر البيانات Dataset من البيانات المخزنة في الذاكرة أو في ملف واحد أو أكثر.

  • يُنشئ تحويل البيانات مجموعة بيانات من واحد أو أكثر من كائنات tf.data.Dataset .

import tensorflow as tf
import pathlib
import os
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

np.set_printoptions(precision=4)

ميكانيكا أساسية

لإنشاء مسار إدخال ، يجب أن تبدأ بمصدر بيانات. على سبيل المثال ، لإنشاء مجموعة بيانات من البيانات الموجودة في الذاكرة ، يمكنك استخدام Dataset tf.data.Dataset.from_tensors() أو tf.data.Dataset.from_tensor_slices() . بدلاً من ذلك ، إذا كانت بيانات الإدخال مخزنة في ملف بتنسيق TFRecord الموصى به ، فيمكنك استخدام tf.data.TFRecordDataset() .

بمجرد أن يكون لديك كائن Dataset ، يمكنك تحويله إلى Dataset جديد عن طريق تسلسل استدعاءات الأسلوب على كائن tf.data.Dataset . على سبيل المثال ، يمكنك تطبيق تحويلات لكل عنصر مثل Dataset.map() والتحويلات متعددة العناصر مثل Dataset.batch() . راجع وثائق tf.data.Dataset للحصول على قائمة كاملة بالتحولات.

كائن Dataset هو Python قابل للتكرار. هذا يجعل من الممكن استهلاك عناصرها باستخدام حلقة for:

dataset = tf.data.Dataset.from_tensor_slices([8, 3, 0, 8, 2, 1])
dataset
<TensorSliceDataset element_spec=TensorSpec(shape=(), dtype=tf.int32, name=None)>
for elem in dataset:
  print(elem.numpy())
8
3
0
8
2
1

أو عن طريق إنشاء مكرر Python بشكل صريح باستخدام iter واستهلاك عناصره باستخدام next :

it = iter(dataset)

print(next(it).numpy())
8

بدلاً من ذلك ، يمكن استهلاك عناصر مجموعة البيانات باستخدام reduce التحويل ، مما يقلل من جميع العناصر لإنتاج نتيجة واحدة. يوضح المثال التالي كيفية استخدام reduce التحويل لحساب مجموع مجموعة بيانات من الأعداد الصحيحة.

print(dataset.reduce(0, lambda state, value: state + value).numpy())
22

هيكل مجموعة البيانات

تنتج مجموعة البيانات سلسلة من العناصر ، حيث يكون كل عنصر هو نفس البنية (المتداخلة) للمكونات . يمكن أن تكون المكونات الفردية للهيكل من أي نوع يمكن تمثيله بواسطة tf.TypeSpec ، بما في ذلك tf.Tensor أو tf.sparse.SparseTensor أو tf.RaggedTensor أو tf.TensorArray أو tf.data.Dataset .

تتضمن تكوينات Python التي يمكن استخدامها للتعبير عن البنية (المتداخلة) للعناصر ، tuple و NamedTuple و dict و OrderedDict . على وجه الخصوص ، لا تعد list تكوينًا صالحًا للتعبير عن بنية عناصر مجموعة البيانات. هذا لأن مستخدمي tf.data الأوائل شعروا بقوة بمدخلات list (على سبيل المثال التي تم تمريرها إلى tf.data.Dataset.from_tensors ) التي يتم تعبئتها تلقائيًا كموترات ومخرجات list (على سبيل المثال ، قيم إرجاع وظائف محددة بواسطة المستخدم) يتم إجبارها في tuple . نتيجة لذلك ، إذا كنت ترغب في معاملة مدخلات list كبنية ، فأنت بحاجة إلى تحويلها إلى tuple وإذا كنت ترغب في أن يكون إخراج list مكونًا واحدًا ، فأنت بحاجة إلى حزمه بشكل صريح باستخدام tf.stack .

تتيح لك الخاصية Dataset.element_spec فحص نوع كل مكون عنصر. تقوم الخاصية بإرجاع بنية متداخلة لكائنات tf.TypeSpec ، تطابق بنية العنصر ، والتي قد تكون مكونًا واحدًا ، أو مجموعة مكونات ، أو مجموعة متداخلة من المكونات. فمثلا:

dataset1 = tf.data.Dataset.from_tensor_slices(tf.random.uniform([4, 10]))

dataset1.element_spec
TensorSpec(shape=(10,), dtype=tf.float32, name=None)
dataset2 = tf.data.Dataset.from_tensor_slices(
   (tf.random.uniform([4]),
    tf.random.uniform([4, 100], maxval=100, dtype=tf.int32)))

dataset2.element_spec
(TensorSpec(shape=(), dtype=tf.float32, name=None),
 TensorSpec(shape=(100,), dtype=tf.int32, name=None))
dataset3 = tf.data.Dataset.zip((dataset1, dataset2))

dataset3.element_spec
(TensorSpec(shape=(10,), dtype=tf.float32, name=None),
 (TensorSpec(shape=(), dtype=tf.float32, name=None),
  TensorSpec(shape=(100,), dtype=tf.int32, name=None)))
# Dataset containing a sparse tensor.
dataset4 = tf.data.Dataset.from_tensors(tf.SparseTensor(indices=[[0, 0], [1, 2]], values=[1, 2], dense_shape=[3, 4]))

dataset4.element_spec
SparseTensorSpec(TensorShape([3, 4]), tf.int32)
# Use value_type to see the type of value represented by the element spec
dataset4.element_spec.value_type
tensorflow.python.framework.sparse_tensor.SparseTensor

تدعم تحويلات Dataset البيانات مجموعات البيانات لأي بنية. عند استخدام Dataset.map() و Dataset.filter() ، التي تطبق دالة على كل عنصر ، تحدد بنية العنصر وسيطات الوظيفة:

dataset1 = tf.data.Dataset.from_tensor_slices(
    tf.random.uniform([4, 10], minval=1, maxval=10, dtype=tf.int32))

dataset1
<TensorSliceDataset element_spec=TensorSpec(shape=(10,), dtype=tf.int32, name=None)>
for z in dataset1:
  print(z.numpy())
[3 3 7 5 9 8 4 2 3 7]
[8 9 6 7 5 6 1 6 2 3]
[9 8 4 4 8 7 1 5 6 7]
[5 9 5 4 2 5 7 8 8 8]
dataset2 = tf.data.Dataset.from_tensor_slices(
   (tf.random.uniform([4]),
    tf.random.uniform([4, 100], maxval=100, dtype=tf.int32)))

dataset2
<TensorSliceDataset element_spec=(TensorSpec(shape=(), dtype=tf.float32, name=None), TensorSpec(shape=(100,), dtype=tf.int32, name=None))>
dataset3 = tf.data.Dataset.zip((dataset1, dataset2))

dataset3
<ZipDataset element_spec=(TensorSpec(shape=(10,), dtype=tf.int32, name=None), (TensorSpec(shape=(), dtype=tf.float32, name=None), TensorSpec(shape=(100,), dtype=tf.int32, name=None)))>
for a, (b,c) in dataset3:
  print('shapes: {a.shape}, {b.shape}, {c.shape}'.format(a=a, b=b, c=c))
shapes: (10,), (), (100,)
shapes: (10,), (), (100,)
shapes: (10,), (), (100,)
shapes: (10,), (), (100,)

قراءة بيانات الإدخال

استهلاك مصفوفات NumPy

راجع تحميل مصفوفات NumPy لمزيد من الأمثلة.

إذا كانت جميع بيانات الإدخال الخاصة بك تتلاءم مع الذاكرة ، فإن أبسط طريقة لإنشاء مجموعة Dataset منها هي تحويلها إلى كائنات tf.Tensor واستخدام Dataset.from_tensor_slices() .

train, test = tf.keras.datasets.fashion_mnist.load_data()
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-labels-idx1-ubyte.gz
32768/29515 [=================================] - 0s 0us/step
40960/29515 [=========================================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-images-idx3-ubyte.gz
26427392/26421880 [==============================] - 0s 0us/step
26435584/26421880 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-labels-idx1-ubyte.gz
16384/5148 [===============================================================================================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-images-idx3-ubyte.gz
4423680/4422102 [==============================] - 0s 0us/step
4431872/4422102 [==============================] - 0s 0us/step
images, labels = train
images = images/255

dataset = tf.data.Dataset.from_tensor_slices((images, labels))
dataset
<TensorSliceDataset element_spec=(TensorSpec(shape=(28, 28), dtype=tf.float64, name=None), TensorSpec(shape=(), dtype=tf.uint8, name=None))>

استهلاك مولدات بايثون

مصدر بيانات شائع آخر يمكن استيعابه بسهولة كملف tf.data.Dataset هو مولد Python.

def count(stop):
  i = 0
  while i<stop:
    yield i
    i += 1
for n in count(5):
  print(n)
0
1
2
3
4

يحول مُنشئ Dataset.from_generator مُنشئ Python إلى مجموعة tf.data.Dataset كاملة الوظائف.

يأخذ المنشئ قابلاً للاستدعاء كمدخل وليس مكررًا. هذا يسمح له بإعادة تشغيل المولد عندما يصل إلى النهاية. يأخذ وسيطة args اختيارية ، والتي يتم تمريرها على أنها وسيطات قابلة للاستدعاء.

الوسيطة output_types مطلوبة لأن tf.data ينشئ tf.Graph داخليًا ، وتتطلب حواف الرسم البياني tf.dtype .

ds_counter = tf.data.Dataset.from_generator(count, args=[25], output_types=tf.int32, output_shapes = (), )
for count_batch in ds_counter.repeat().batch(10).take(10):
  print(count_batch.numpy())
[0 1 2 3 4 5 6 7 8 9]
[10 11 12 13 14 15 16 17 18 19]
[20 21 22 23 24  0  1  2  3  4]
[ 5  6  7  8  9 10 11 12 13 14]
[15 16 17 18 19 20 21 22 23 24]
[0 1 2 3 4 5 6 7 8 9]
[10 11 12 13 14 15 16 17 18 19]
[20 21 22 23 24  0  1  2  3  4]
[ 5  6  7  8  9 10 11 12 13 14]
[15 16 17 18 19 20 21 22 23 24]

وسيطة output_shapes ليست مطلوبة ولكن يوصى بها بشدة لأن العديد من عمليات TensorFlow لا تدعم موترات ذات رتبة غير معروفة. إذا كان طول محور معين غير معروف أو متغير ، فاضبطه على أنه None في output_shapes .

من المهم أيضًا ملاحظة أن output_shapes و output_types تتبع نفس قواعد التداخل مثل طرق مجموعة البيانات الأخرى.

هنا مثال مولد يوضح كلا الجانبين ، ويعيد مجموعات من المصفوفات ، حيث يكون المصفوفة الثانية متجهًا بطول غير معروف.

def gen_series():
  i = 0
  while True:
    size = np.random.randint(0, 10)
    yield i, np.random.normal(size=(size,))
    i += 1
for i, series in gen_series():
  print(i, ":", str(series))
  if i > 5:
    break
0 : [0.3939]
1 : [ 0.9282 -0.0158  1.0096  0.7155  0.0491  0.6697 -0.2565  0.487 ]
2 : [-0.4831  0.37   -1.3918 -0.4786  0.7425 -0.3299]
3 : [ 0.1427 -1.0438  0.821  -0.8766 -0.8369  0.4168]
4 : [-1.4984 -1.8424  0.0337  0.0941  1.3286 -1.4938]
5 : [-1.3158 -1.2102  2.6887 -1.2809]
6 : []

الناتج الأول هو int32 والثاني هو float32 .

العنصر الأول هو مقياس ، شكل () ، والثاني متجه غير معروف الطول والشكل (None,)

ds_series = tf.data.Dataset.from_generator(
    gen_series, 
    output_types=(tf.int32, tf.float32), 
    output_shapes=((), (None,)))

ds_series
<FlatMapDataset element_spec=(TensorSpec(shape=(), dtype=tf.int32, name=None), TensorSpec(shape=(None,), dtype=tf.float32, name=None))>

الآن يمكن استخدامه مثل tf.data.Dataset عادي. لاحظ أنه عند تجميع مجموعة بيانات ذات شكل متغير ، تحتاج إلى استخدام Dataset.padded_batch .

ds_series_batch = ds_series.shuffle(20).padded_batch(10)

ids, sequence_batch = next(iter(ds_series_batch))
print(ids.numpy())
print()
print(sequence_batch.numpy())
[ 8 10 18  1  5 19 22 17 21 25]

[[-0.6098  0.1366 -2.15   -0.9329  0.      0.    ]
 [ 1.0295 -0.033  -0.0388  0.      0.      0.    ]
 [-0.1137  0.3552  0.4363 -0.2487 -1.1329  0.    ]
 [ 0.      0.      0.      0.      0.      0.    ]
 [-1.0466  0.624  -1.7705  1.4214  0.9143 -0.62  ]
 [-0.9502  1.7256  0.5895  0.7237  1.5397  0.    ]
 [ 0.3747  1.2967  0.      0.      0.      0.    ]
 [-0.4839  0.292  -0.7909 -0.7535  0.4591 -1.3952]
 [-0.0468  0.0039 -1.1185 -1.294   0.      0.    ]
 [-0.1679 -0.3375  0.      0.      0.      0.    ]]

للحصول على مثال أكثر واقعية ، جرب تغليف preprocessing.image.ImageDataGenerator كملف tf.data.Dataset .

قم أولاً بتنزيل البيانات:

flowers = tf.keras.utils.get_file(
    'flower_photos',
    'https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz',
    untar=True)
Downloading data from https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz
228818944/228813984 [==============================] - 10s 0us/step
228827136/228813984 [==============================] - 10s 0us/step

قم image.ImageDataGenerator

img_gen = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1./255, rotation_range=20)
images, labels = next(img_gen.flow_from_directory(flowers))
Found 3670 images belonging to 5 classes.
print(images.dtype, images.shape)
print(labels.dtype, labels.shape)
float32 (32, 256, 256, 3)
float32 (32, 5)
ds = tf.data.Dataset.from_generator(
    lambda: img_gen.flow_from_directory(flowers), 
    output_types=(tf.float32, tf.float32), 
    output_shapes=([32,256,256,3], [32,5])
)

ds.element_spec
(TensorSpec(shape=(32, 256, 256, 3), dtype=tf.float32, name=None),
 TensorSpec(shape=(32, 5), dtype=tf.float32, name=None))
for images, label in ds.take(1):
  print('images.shape: ', images.shape)
  print('labels.shape: ', labels.shape)
Found 3670 images belonging to 5 classes.
images.shape:  (32, 256, 256, 3)
labels.shape:  (32, 5)

استهلاك بيانات TFRecord

راجع تحميل سجلات TF للحصول على مثال شامل.

تدعم tf.data API مجموعة متنوعة من تنسيقات الملفات بحيث يمكنك معالجة مجموعات البيانات الكبيرة التي لا تتناسب مع الذاكرة. على سبيل المثال ، تنسيق ملف TFRecord هو تنسيق ثنائي بسيط موجه نحو التسجيل تستخدمه العديد من تطبيقات TensorFlow لبيانات التدريب. تمكّنك فئة tf.data.TFRecordDataset من التدفق عبر محتويات واحد أو أكثر من ملفات TFRecord كجزء من مسار الإدخال.

فيما يلي مثال باستخدام ملف الاختبار من إشارات اسم الشارع الفرنسية (FSNS).

# Creates a dataset that reads all of the examples from two files.
fsns_test_file = tf.keras.utils.get_file("fsns.tfrec", "https://storage.googleapis.com/download.tensorflow.org/data/fsns-20160927/testdata/fsns-00000-of-00001")
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/fsns-20160927/testdata/fsns-00000-of-00001
7905280/7904079 [==============================] - 1s 0us/step
7913472/7904079 [==============================] - 1s 0us/step

يمكن أن تكون وسيطة filenames TFRecordDataset إما سلسلة أو قائمة سلاسل أو tf.Tensor من السلاسل. لذلك ، إذا كانت لديك مجموعتان من الملفات لأغراض التدريب والتحقق من الصحة ، فيمكنك إنشاء طريقة مصنع تُنتج مجموعة البيانات ، مع أخذ أسماء الملفات كوسيطة إدخال:

dataset = tf.data.TFRecordDataset(filenames = [fsns_test_file])
dataset
<TFRecordDatasetV2 element_spec=TensorSpec(shape=(), dtype=tf.string, name=None)>

تستخدم العديد من مشروعات TensorFlow سجلات tf.train.Example المتسلسلة في ملفات TFRecord الخاصة بهم. يجب فك تشفيرها قبل أن يتم فحصها:

raw_example = next(iter(dataset))
parsed = tf.train.Example.FromString(raw_example.numpy())

parsed.features.feature['image/text']
bytes_list {
  value: "Rue Perreyon"
}

استهلاك البيانات النصية

انظر تحميل النص للحصول على مثال نهاية إلى نهاية.

يتم توزيع العديد من مجموعات البيانات كملف نصي واحد أو أكثر. توفر tf.data.TextLineDataset طريقة سهلة لاستخراج الأسطر من ملف نصي واحد أو أكثر. بالنظر إلى اسم ملف واحد أو أكثر ، TextLineDataset واحدًا ذا قيمة سلسلة لكل سطر من هذه الملفات.

directory_url = 'https://storage.googleapis.com/download.tensorflow.org/data/illiad/'
file_names = ['cowper.txt', 'derby.txt', 'butler.txt']

file_paths = [
    tf.keras.utils.get_file(file_name, directory_url + file_name)
    for file_name in file_names
]
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/cowper.txt
819200/815980 [==============================] - 0s 0us/step
827392/815980 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/derby.txt
811008/809730 [==============================] - 0s 0us/step
819200/809730 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/butler.txt
811008/807992 [==============================] - 0s 0us/step
819200/807992 [==============================] - 0s 0us/step
dataset = tf.data.TextLineDataset(file_paths)

فيما يلي الأسطر القليلة الأولى من الملف الأول:

for line in dataset.take(5):
  print(line.numpy())
b"\xef\xbb\xbfAchilles sing, O Goddess! Peleus' son;"
b'His wrath pernicious, who ten thousand woes'
b"Caused to Achaia's host, sent many a soul"
b'Illustrious into Ades premature,'
b'And Heroes gave (so stood the will of Jove)'

لتبديل الأسطر بين الملفات ، استخدم Dataset.interleave . هذا يجعل من السهل خلط الملفات معًا. فيما يلي الأسطر الأولى والثانية والثالثة من كل ترجمة:

files_ds = tf.data.Dataset.from_tensor_slices(file_paths)
lines_ds = files_ds.interleave(tf.data.TextLineDataset, cycle_length=3)

for i, line in enumerate(lines_ds.take(9)):
  if i % 3 == 0:
    print()
  print(line.numpy())
b"\xef\xbb\xbfAchilles sing, O Goddess! Peleus' son;"
b"\xef\xbb\xbfOf Peleus' son, Achilles, sing, O Muse,"
b'\xef\xbb\xbfSing, O goddess, the anger of Achilles son of Peleus, that brought'

b'His wrath pernicious, who ten thousand woes'
b'The vengeance, deep and deadly; whence to Greece'
b'countless ills upon the Achaeans. Many a brave soul did it send'

b"Caused to Achaia's host, sent many a soul"
b'Unnumbered ills arose; which many a soul'
b'hurrying down to Hades, and many a hero did it yield a prey to dogs and'

بشكل افتراضي ، ينتج TextLineDataset كل سطر من كل ملف ، وهو ما قد لا يكون مرغوبًا فيه ، على سبيل المثال ، إذا كان الملف يبدأ بسطر رأس ، أو يحتوي على تعليقات. يمكن إزالة هذه الخطوط باستخدام Dataset.skip() أو Dataset.filter() . هنا ، يمكنك تخطي السطر الأول ، ثم التصفية للعثور على الناجين فقط.

titanic_file = tf.keras.utils.get_file("train.csv", "https://storage.googleapis.com/tf-datasets/titanic/train.csv")
titanic_lines = tf.data.TextLineDataset(titanic_file)
Downloading data from https://storage.googleapis.com/tf-datasets/titanic/train.csv
32768/30874 [===============================] - 0s 0us/step
40960/30874 [=======================================] - 0s 0us/step
for line in titanic_lines.take(10):
  print(line.numpy())
b'survived,sex,age,n_siblings_spouses,parch,fare,class,deck,embark_town,alone'
b'0,male,22.0,1,0,7.25,Third,unknown,Southampton,n'
b'1,female,38.0,1,0,71.2833,First,C,Cherbourg,n'
b'1,female,26.0,0,0,7.925,Third,unknown,Southampton,y'
b'1,female,35.0,1,0,53.1,First,C,Southampton,n'
b'0,male,28.0,0,0,8.4583,Third,unknown,Queenstown,y'
b'0,male,2.0,3,1,21.075,Third,unknown,Southampton,n'
b'1,female,27.0,0,2,11.1333,Third,unknown,Southampton,n'
b'1,female,14.0,1,0,30.0708,Second,unknown,Cherbourg,n'
b'1,female,4.0,1,1,16.7,Third,G,Southampton,n'
def survived(line):
  return tf.not_equal(tf.strings.substr(line, 0, 1), "0")

survivors = titanic_lines.skip(1).filter(survived)
for line in survivors.take(10):
  print(line.numpy())
b'1,female,38.0,1,0,71.2833,First,C,Cherbourg,n'
b'1,female,26.0,0,0,7.925,Third,unknown,Southampton,y'
b'1,female,35.0,1,0,53.1,First,C,Southampton,n'
b'1,female,27.0,0,2,11.1333,Third,unknown,Southampton,n'
b'1,female,14.0,1,0,30.0708,Second,unknown,Cherbourg,n'
b'1,female,4.0,1,1,16.7,Third,G,Southampton,n'
b'1,male,28.0,0,0,13.0,Second,unknown,Southampton,y'
b'1,female,28.0,0,0,7.225,Third,unknown,Cherbourg,y'
b'1,male,28.0,0,0,35.5,First,A,Southampton,y'
b'1,female,38.0,1,5,31.3875,Third,unknown,Southampton,n'

استهلاك بيانات CSV

انظر تحميل ملفات CSV وتحميل Pandas DataFrames لمزيد من الأمثلة.

تنسيق ملف CSV هو تنسيق شائع لتخزين البيانات الجدولية في نص عادي.

فمثلا:

titanic_file = tf.keras.utils.get_file("train.csv", "https://storage.googleapis.com/tf-datasets/titanic/train.csv")
df = pd.read_csv(titanic_file)
df.head()

إذا كانت بياناتك تتلاءم مع الذاكرة ، فإن نفس طريقة Dataset.from_tensor_slices تعمل على القواميس ، مما يسمح باستيراد هذه البيانات بسهولة:

titanic_slices = tf.data.Dataset.from_tensor_slices(dict(df))

for feature_batch in titanic_slices.take(1):
  for key, value in feature_batch.items():
    print("  {!r:20s}: {}".format(key, value))
'survived'          : 0
  'sex'               : b'male'
  'age'               : 22.0
  'n_siblings_spouses': 1
  'parch'             : 0
  'fare'              : 7.25
  'class'             : b'Third'
  'deck'              : b'unknown'
  'embark_town'       : b'Southampton'
  'alone'             : b'n'

هناك نهج أكثر قابلية للتوسع وهو التحميل من القرص حسب الضرورة.

توفر الوحدة النمطية tf.data طرقًا لاستخراج السجلات من واحد أو أكثر من ملفات CSV التي تتوافق مع RFC 4180 .

تعتبر الدالة experimental.make_csv_dataset .make_csv_dataset هي واجهة عالية المستوى لقراءة مجموعات من ملفات csv. وهو يدعم الاستدلال على نوع العمود والعديد من الميزات الأخرى ، مثل التجميع والخلط ، لجعل الاستخدام بسيطًا.

titanic_batches = tf.data.experimental.make_csv_dataset(
    titanic_file, batch_size=4,
    label_name="survived")
for feature_batch, label_batch in titanic_batches.take(1):
  print("'survived': {}".format(label_batch))
  print("features:")
  for key, value in feature_batch.items():
    print("  {!r:20s}: {}".format(key, value))
'survived': [1 0 0 0]
features:
  'sex'               : [b'female' b'female' b'male' b'male']
  'age'               : [32. 28. 37. 50.]
  'n_siblings_spouses': [0 3 0 0]
  'parch'             : [0 1 1 0]
  'fare'              : [13.     25.4667 29.7    13.    ]
  'class'             : [b'Second' b'Third' b'First' b'Second']
  'deck'              : [b'unknown' b'unknown' b'C' b'unknown']
  'embark_town'       : [b'Southampton' b'Southampton' b'Cherbourg' b'Southampton']
  'alone'             : [b'y' b'n' b'n' b'y']

يمكنك استخدام وسيطة select_columns إذا كنت تحتاج فقط إلى مجموعة فرعية من الأعمدة.

titanic_batches = tf.data.experimental.make_csv_dataset(
    titanic_file, batch_size=4,
    label_name="survived", select_columns=['class', 'fare', 'survived'])
for feature_batch, label_batch in titanic_batches.take(1):
  print("'survived': {}".format(label_batch))
  for key, value in feature_batch.items():
    print("  {!r:20s}: {}".format(key, value))
'survived': [0 1 1 0]
  'fare'              : [ 7.05 15.5  26.25  8.05]
  'class'             : [b'Third' b'Third' b'Second' b'Third']

هناك أيضًا فئة experimental.CsvDataset منخفضة المستوى .CsvDataset توفر تحكمًا أفضل في الحبيبات. لا يدعم الاستدلال بنوع العمود. بدلاً من ذلك ، يجب تحديد نوع كل عمود.

titanic_types  = [tf.int32, tf.string, tf.float32, tf.int32, tf.int32, tf.float32, tf.string, tf.string, tf.string, tf.string] 
dataset = tf.data.experimental.CsvDataset(titanic_file, titanic_types , header=True)

for line in dataset.take(10):
  print([item.numpy() for item in line])
[0, b'male', 22.0, 1, 0, 7.25, b'Third', b'unknown', b'Southampton', b'n']
[1, b'female', 38.0, 1, 0, 71.2833, b'First', b'C', b'Cherbourg', b'n']
[1, b'female', 26.0, 0, 0, 7.925, b'Third', b'unknown', b'Southampton', b'y']
[1, b'female', 35.0, 1, 0, 53.1, b'First', b'C', b'Southampton', b'n']
[0, b'male', 28.0, 0, 0, 8.4583, b'Third', b'unknown', b'Queenstown', b'y']
[0, b'male', 2.0, 3, 1, 21.075, b'Third', b'unknown', b'Southampton', b'n']
[1, b'female', 27.0, 0, 2, 11.1333, b'Third', b'unknown', b'Southampton', b'n']
[1, b'female', 14.0, 1, 0, 30.0708, b'Second', b'unknown', b'Cherbourg', b'n']
[1, b'female', 4.0, 1, 1, 16.7, b'Third', b'G', b'Southampton', b'n']
[0, b'male', 20.0, 0, 0, 8.05, b'Third', b'unknown', b'Southampton', b'y']

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

%%writefile missing.csv
1,2,3,4
,2,3,4
1,,3,4
1,2,,4
1,2,3,
,,,
Writing missing.csv
# Creates a dataset that reads all of the records from two CSV files, each with
# four float columns which may have missing values.

record_defaults = [999,999,999,999]
dataset = tf.data.experimental.CsvDataset("missing.csv", record_defaults)
dataset = dataset.map(lambda *items: tf.stack(items))
dataset
<MapDataset element_spec=TensorSpec(shape=(4,), dtype=tf.int32, name=None)>
for line in dataset:
  print(line.numpy())
[1 2 3 4]
[999   2   3   4]
[  1 999   3   4]
[  1   2 999   4]
[  1   2   3 999]
[999 999 999 999]

بشكل افتراضي ، ينتج عن CsvDataset كل عمود من كل سطر من الملف ، وهو ما قد لا يكون مرغوبًا فيه ، على سبيل المثال إذا كان الملف يبدأ بسطر رأس يجب تجاهله ، أو إذا كانت بعض الأعمدة غير مطلوبة في الإدخال. يمكن إزالة هذه الأسطر والحقول باستخدام وسيطتي header و select_cols على التوالي.

# Creates a dataset that reads all of the records from two CSV files with
# headers, extracting float data from columns 2 and 4.
record_defaults = [999, 999] # Only provide defaults for the selected columns
dataset = tf.data.experimental.CsvDataset("missing.csv", record_defaults, select_cols=[1, 3])
dataset = dataset.map(lambda *items: tf.stack(items))
dataset
<MapDataset element_spec=TensorSpec(shape=(2,), dtype=tf.int32, name=None)>
for line in dataset:
  print(line.numpy())
[2 4]
[2 4]
[999   4]
[2 4]
[  2 999]
[999 999]

تستهلك مجموعات من الملفات

هناك العديد من مجموعات البيانات الموزعة كمجموعة من الملفات ، حيث يمثل كل ملف مثالاً.

flowers_root = tf.keras.utils.get_file(
    'flower_photos',
    'https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz',
    untar=True)
flowers_root = pathlib.Path(flowers_root)

يحتوي الدليل الجذر على دليل لكل فئة:

for item in flowers_root.glob("*"):
  print(item.name)
sunflowers
daisy
LICENSE.txt
roses
tulips
dandelion

الملفات الموجودة في كل دليل فئة هي أمثلة:

list_ds = tf.data.Dataset.list_files(str(flowers_root/'*/*'))

for f in list_ds.take(5):
  print(f.numpy())
b'/home/kbuilder/.keras/datasets/flower_photos/sunflowers/5018120483_cc0421b176_m.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/dandelion/8642679391_0805b147cb_m.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/sunflowers/8266310743_02095e782d_m.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/tulips/13176521023_4d7cc74856_m.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/dandelion/19437578578_6ab1b3c984.jpg'

اقرأ البيانات باستخدام الدالة tf.io.read_file الملصق من المسار ، مع إرجاع أزواج (image, label) :

def process_path(file_path):
  label = tf.strings.split(file_path, os.sep)[-2]
  return tf.io.read_file(file_path), label

labeled_ds = list_ds.map(process_path)
for image_raw, label_text in labeled_ds.take(1):
  print(repr(image_raw.numpy()[:100]))
  print()
  print(label_text.numpy())
b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00\xff\xe2\x0cXICC_PROFILE\x00\x01\x01\x00\x00\x0cHLino\x02\x10\x00\x00mntrRGB XYZ \x07\xce\x00\x02\x00\t\x00\x06\x001\x00\x00acspMSFT\x00\x00\x00\x00IEC sRGB\x00\x00\x00\x00\x00\x00'

b'daisy'

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

تجميع بسيط

أبسط شكل من أشكال تجميع n العناصر المتتالية لمجموعة البيانات في عنصر واحد. يقوم تحويل Dataset.batch() بهذا بالضبط ، مع نفس قيود عامل التشغيل tf.stack() ، المطبق على كل مكون من العناصر: على سبيل المثال ، لكل مكون i ، يجب أن يكون لجميع العناصر موتر من نفس الشكل بالضبط.

inc_dataset = tf.data.Dataset.range(100)
dec_dataset = tf.data.Dataset.range(0, -100, -1)
dataset = tf.data.Dataset.zip((inc_dataset, dec_dataset))
batched_dataset = dataset.batch(4)

for batch in batched_dataset.take(4):
  print([arr.numpy() for arr in batch])
[array([0, 1, 2, 3]), array([ 0, -1, -2, -3])]
[array([4, 5, 6, 7]), array([-4, -5, -6, -7])]
[array([ 8,  9, 10, 11]), array([ -8,  -9, -10, -11])]
[array([12, 13, 14, 15]), array([-12, -13, -14, -15])]

بينما يحاول tf.data نشر معلومات الشكل ، ينتج عن الإعدادات الافتراضية لـ Dataset.batch حجم دُفعة غير معروف لأن الدُفعة الأخيرة قد لا تكون ممتلئة. لاحظ عدم None في الشكل:

batched_dataset
<BatchDataset element_spec=(TensorSpec(shape=(None,), dtype=tf.int64, name=None), TensorSpec(shape=(None,), dtype=tf.int64, name=None))>

استخدم وسيطة drop_remainder لتجاهل تلك الدفعة الأخيرة ، والحصول على نشر الشكل الكامل:

batched_dataset = dataset.batch(7, drop_remainder=True)
batched_dataset
<BatchDataset element_spec=(TensorSpec(shape=(7,), dtype=tf.int64, name=None), TensorSpec(shape=(7,), dtype=tf.int64, name=None))>

موترات الخلط مع الحشو

تعمل الوصفة أعلاه مع الموترات التي لها نفس الحجم. ومع ذلك ، فإن العديد من النماذج (مثل نماذج التسلسل) تعمل مع بيانات الإدخال التي يمكن أن يكون لها أحجام مختلفة (مثل تسلسلات ذات أطوال مختلفة). للتعامل مع هذه الحالة ، يمكّنك تحويل Dataset.padded_batch من تجميع الموترات ذات الشكل المختلف عن طريق تحديد واحد أو أكثر من الأبعاد التي قد تكون مبطنة فيها.

dataset = tf.data.Dataset.range(100)
dataset = dataset.map(lambda x: tf.fill([tf.cast(x, tf.int32)], x))
dataset = dataset.padded_batch(4, padded_shapes=(None,))

for batch in dataset.take(2):
  print(batch.numpy())
  print()
[[0 0 0]
 [1 0 0]
 [2 2 0]
 [3 3 3]]

[[4 4 4 4 0 0 0]
 [5 5 5 5 5 0 0]
 [6 6 6 6 6 6 0]
 [7 7 7 7 7 7 7]]

يسمح لك تحويل Dataset.padded_batch بتعيين حشوة مختلفة لكل بُعد من كل مكون ، وقد يكون متغير الطول (يُشار إليه None في المثال أعلاه) أو بطول ثابت. من الممكن أيضًا تجاوز قيمة المساحة المتروكة ، والتي يتم تعيينها افتراضيًا على 0.

تدريبات سير العمل

معالجة عهود متعددة

تقدم tf.data API طريقتين رئيسيتين لمعالجة عدة فترات من نفس البيانات.

إن أبسط طريقة للتكرار عبر مجموعة بيانات في فترات متعددة هي استخدام تحويل Dataset.repeat() . أولاً ، أنشئ مجموعة بيانات من البيانات العملاقة:

titanic_file = tf.keras.utils.get_file("train.csv", "https://storage.googleapis.com/tf-datasets/titanic/train.csv")
titanic_lines = tf.data.TextLineDataset(titanic_file)
def plot_batch_sizes(ds):
  batch_sizes = [batch.shape[0] for batch in ds]
  plt.bar(range(len(batch_sizes)), batch_sizes)
  plt.xlabel('Batch number')
  plt.ylabel('Batch size')

سيؤدي تطبيق تحويل Dataset.repeat() بدون وسيطات إلى تكرار الإدخال إلى أجل غير مسمى.

Dataset.repeat التحويل المتكرر لمجموعة البيانات حججها دون الإشارة إلى نهاية حقبة واحدة وبداية الحقبة التالية. لهذا السبب ، فإن تطبيق Dataset.batch تطبيقه بعد Dataset.repeat سوف ينتج عنه دفعات تمتد عبر حدود الحقبة:

titanic_batches = titanic_lines.repeat(3).batch(128)
plot_batch_sizes(titanic_batches)

بي إن جي

إذا كنت بحاجة إلى فصل واضح للحقبة ، فضع Dataset.batch قبل التكرار:

titanic_batches = titanic_lines.batch(128).repeat(3)

plot_batch_sizes(titanic_batches)

بي إن جي

إذا كنت ترغب في إجراء عملية حسابية مخصصة (على سبيل المثال لجمع الإحصائيات) في نهاية كل حقبة ، فمن الأسهل إعادة تشغيل تكرار مجموعة البيانات في كل فترة:

epochs = 3
dataset = titanic_lines.batch(128)

for epoch in range(epochs):
  for batch in dataset:
    print(batch.shape)
  print("End of epoch: ", epoch)
(128,)
(128,)
(128,)
(128,)
(116,)
End of epoch:  0
(128,)
(128,)
(128,)
(128,)
(116,)
End of epoch:  1
(128,)
(128,)
(128,)
(128,)
(116,)
End of epoch:  2

خلط بيانات الإدخال عشوائيًا

يحافظ تحويل Dataset.shuffle() على مخزن مؤقت بحجم ثابت ويختار العنصر التالي بشكل موحد عشوائيًا من هذا المخزن المؤقت.

أضف فهرسًا إلى مجموعة البيانات حتى تتمكن من رؤية التأثير:

lines = tf.data.TextLineDataset(titanic_file)
counter = tf.data.experimental.Counter()

dataset = tf.data.Dataset.zip((counter, lines))
dataset = dataset.shuffle(buffer_size=100)
dataset = dataset.batch(20)
dataset
<BatchDataset element_spec=(TensorSpec(shape=(None,), dtype=tf.int64, name=None), TensorSpec(shape=(None,), dtype=tf.string, name=None))>

نظرًا لأن buffer_size هو 100 ، وحجم الدُفعة هو 20 ، فإن الدفعة الأولى لا تحتوي على عناصر بمؤشر يزيد عن 120.

n,line_batch = next(iter(dataset))
print(n.numpy())
[ 52  94  22  70  63  96  56 102  38  16  27 104  89  43  41  68  42  61
 112   8]

كما هو الحال مع Dataset.batch ، فإن الترتيب المتعلق بـ Dataset.repeat مهم.

لا يشير Dataset.shuffle إلى نهاية حقبة حتى يصبح المخزن المؤقت عشوائيًا فارغًا. لذا فإن التبديل العشوائي الذي يتم وضعه قبل التكرار سيُظهر كل عنصر في حقبة واحدة قبل الانتقال إلى المرحلة التالية:

dataset = tf.data.Dataset.zip((counter, lines))
shuffled = dataset.shuffle(buffer_size=100).batch(10).repeat(2)

print("Here are the item ID's near the epoch boundary:\n")
for n, line_batch in shuffled.skip(60).take(5):
  print(n.numpy())
Here are the item ID's near the epoch boundary:

[509 595 537 550 555 591 480 627 482 519]
[522 619 538 581 569 608 531 558 461 496]
[548 489 379 607 611 622 234 525]
[ 59  38   4  90  73  84  27  51 107  12]
[77 72 91 60  7 62 92 47 70 67]
shuffle_repeat = [n.numpy().mean() for n, line_batch in shuffled]
plt.plot(shuffle_repeat, label="shuffle().repeat()")
plt.ylabel("Mean item ID")
plt.legend()
<matplotlib.legend.Legend at 0x7f7e7061c650>

بي إن جي

لكن التكرار قبل خلط ورق اللعب يمزج حدود العصر معًا:

dataset = tf.data.Dataset.zip((counter, lines))
shuffled = dataset.repeat(2).shuffle(buffer_size=100).batch(10)

print("Here are the item ID's near the epoch boundary:\n")
for n, line_batch in shuffled.skip(55).take(15):
  print(n.numpy())
Here are the item ID's near the epoch boundary:

[  6   8 528 604  13 492 308 441 569 475]
[  5 626 615 568  20 554 520 454  10 607]
[510 542   0 363  32 446 395 588  35   4]
[  7  15  28  23  39 559 585  49 252 556]
[581 617  25  43  26 548  29 460  48  41]
[ 19  64  24 300 612 611  36  63  69  57]
[287 605  21 512 442  33  50  68 608  47]
[625  90  91 613  67  53 606 344  16  44]
[453 448  89  45 465   2  31 618 368 105]
[565   3 586 114  37 464  12 627  30 621]
[ 82 117  72  75  84  17 571 610  18 600]
[107 597 575  88 623  86 101  81 456 102]
[122  79  51  58  80  61 367  38 537 113]
[ 71  78 598 152 143 620 100 158 133 130]
[155 151 144 135 146 121  83  27 103 134]
repeat_shuffle = [n.numpy().mean() for n, line_batch in shuffled]

plt.plot(shuffle_repeat, label="shuffle().repeat()")
plt.plot(repeat_shuffle, label="repeat().shuffle()")
plt.ylabel("Mean item ID")
plt.legend()
<matplotlib.legend.Legend at 0x7f7e706013d0>

بي إن جي

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

ينتج عن تحويل Dataset.map(f) مجموعة بيانات جديدة من خلال تطبيق وظيفة معينة f على كل عنصر من عناصر مجموعة بيانات الإدخال. يعتمد على وظيفة map() التي يتم تطبيقها بشكل شائع على القوائم (وغيرها من الهياكل) في لغات البرمجة الوظيفية. تأخذ الوظيفة f كائنات tf.Tensor التي تمثل عنصرًا واحدًا في الإدخال ، وتعيد كائنات tf.Tensor التي ستمثل عنصرًا واحدًا في مجموعة البيانات الجديدة. يستخدم تنفيذه عمليات TensorFlow القياسية لتحويل عنصر إلى آخر.

يغطي هذا القسم أمثلة شائعة حول كيفية استخدام Dataset.map() .

فك بيانات الصورة وتغيير حجمها

عند تدريب شبكة عصبية على بيانات الصورة الواقعية ، غالبًا ما يكون من الضروري تحويل الصور ذات الأحجام المختلفة إلى حجم مشترك ، بحيث يمكن تجميعها في حجم ثابت.

إعادة إنشاء مجموعة بيانات أسماء ملفات الزهرة:

list_ds = tf.data.Dataset.list_files(str(flowers_root/'*/*'))

اكتب دالة تعالج عناصر مجموعة البيانات.

# Reads an image from a file, decodes it into a dense tensor, and resizes it
# to a fixed shape.
def parse_image(filename):
  parts = tf.strings.split(filename, os.sep)
  label = parts[-2]

  image = tf.io.read_file(filename)
  image = tf.io.decode_jpeg(image)
  image = tf.image.convert_image_dtype(image, tf.float32)
  image = tf.image.resize(image, [128, 128])
  return image, label

اختبار أنها تعمل.

file_path = next(iter(list_ds))
image, label = parse_image(file_path)

def show(image, label):
  plt.figure()
  plt.imshow(image)
  plt.title(label.numpy().decode('utf-8'))
  plt.axis('off')

show(image, label)

بي إن جي

ضعها على مجموعة البيانات.

images_ds = list_ds.map(parse_image)

for image, label in images_ds.take(2):
  show(image, label)

بي إن جي

بي إن جي

تطبيق منطق بايثون التعسفي

لأسباب تتعلق بالأداء ، استخدم عمليات TensorFlow للمعالجة المسبقة لبياناتك كلما أمكن ذلك. ومع ذلك ، من المفيد أحيانًا استدعاء مكتبات Python الخارجية عند تحليل بيانات الإدخال. يمكنك استخدام العملية tf.py_function() Dataset.map() .

على سبيل المثال ، إذا كنت تريد تطبيق تدوير عشوائي ، فإن وحدة tf.image لها فقط tf.image.rot90 ، وهو أمر غير مفيد جدًا لتكبير الصورة.

لإثبات tf.py_function ، حاول استخدام الدالة scipy.ndimage.rotate بدلاً من ذلك:

import scipy.ndimage as ndimage

def random_rotate_image(image):
  image = ndimage.rotate(image, np.random.uniform(-30, 30), reshape=False)
  return image
image, label = next(iter(images_ds))
image = random_rotate_image(image)
show(image, label)
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).

بي إن جي

لاستخدام هذه الوظيفة مع Dataset.map ، تنطبق نفس التحذيرات كما هو الحال مع Dataset.from_generator ، تحتاج إلى وصف أشكال وأنواع الإرجاع عند تطبيق الوظيفة:

def tf_random_rotate_image(image, label):
  im_shape = image.shape
  [image,] = tf.py_function(random_rotate_image, [image], [tf.float32])
  image.set_shape(im_shape)
  return image, label
rot_ds = images_ds.map(tf_random_rotate_image)

for image, label in rot_ds.take(2):
  show(image, label)
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).

بي إن جي

بي إن جي

تحليل رسائل المخزن المؤقت لبروتوكول tf.Example

العديد من خطوط أنابيب الإدخال تستخرج tf.train.Example بروتوكول رسائل المخزن المؤقت من تنسيق TFRecord. يحتوي كل سجل tf.train.Example على واحدة أو أكثر من "الميزات" ، وعادةً ما يحول مسار الإدخال هذه الميزات إلى موترات.

fsns_test_file = tf.keras.utils.get_file("fsns.tfrec", "https://storage.googleapis.com/download.tensorflow.org/data/fsns-20160927/testdata/fsns-00000-of-00001")
dataset = tf.data.TFRecordDataset(filenames = [fsns_test_file])
dataset
<TFRecordDatasetV2 element_spec=TensorSpec(shape=(), dtype=tf.string, name=None)>

يمكنك العمل مع tf.train.Example protos خارج tf.data.Dataset لفهم البيانات:

raw_example = next(iter(dataset))
parsed = tf.train.Example.FromString(raw_example.numpy())

feature = parsed.features.feature
raw_img = feature['image/encoded'].bytes_list.value[0]
img = tf.image.decode_png(raw_img)
plt.imshow(img)
plt.axis('off')
_ = plt.title(feature["image/text"].bytes_list.value[0])

بي إن جي

raw_example = next(iter(dataset))
def tf_parse(eg):
  example = tf.io.parse_example(
      eg[tf.newaxis], {
          'image/encoded': tf.io.FixedLenFeature(shape=(), dtype=tf.string),
          'image/text': tf.io.FixedLenFeature(shape=(), dtype=tf.string)
      })
  return example['image/encoded'][0], example['image/text'][0]
img, txt = tf_parse(raw_example)
print(txt.numpy())
print(repr(img.numpy()[:20]), "...")
b'Rue Perreyon'
b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x02X' ...
decoded = dataset.map(tf_parse)
decoded
<MapDataset element_spec=(TensorSpec(shape=(), dtype=tf.string, name=None), TensorSpec(shape=(), dtype=tf.string, name=None))>
image_batch, text_batch = next(iter(decoded.batch(10)))
image_batch.shape
TensorShape([10])

نافذة السلسلة الزمنية

للحصول على مثال عن السلاسل الزمنية من نهاية إلى نهاية ، انظر: توقع السلاسل الزمنية .

غالبًا ما يتم تنظيم بيانات السلاسل الزمنية بحيث يكون المحور الزمني سليمًا.

استخدم نطاق مجموعة Dataset.range لتوضيح:

range_ds = tf.data.Dataset.range(100000)

عادةً ما تريد النماذج التي تستند إلى هذا النوع من البيانات شريحة زمنية متجاورة.

سيكون أبسط طريقة هي تجميع البيانات:

باستخدام batch

batches = range_ds.batch(10, drop_remainder=True)

for batch in batches.take(5):
  print(batch.numpy())
[0 1 2 3 4 5 6 7 8 9]
[10 11 12 13 14 15 16 17 18 19]
[20 21 22 23 24 25 26 27 28 29]
[30 31 32 33 34 35 36 37 38 39]
[40 41 42 43 44 45 46 47 48 49]

أو لعمل تنبؤات كثيفة خطوة واحدة إلى المستقبل ، يمكنك تغيير الميزات والتسميات بخطوة واحدة بالنسبة لبعضها البعض:

def dense_1_step(batch):
  # Shift features and labels one step relative to each other.
  return batch[:-1], batch[1:]

predict_dense_1_step = batches.map(dense_1_step)

for features, label in predict_dense_1_step.take(3):
  print(features.numpy(), " => ", label.numpy())
[0 1 2 3 4 5 6 7 8]  =>  [1 2 3 4 5 6 7 8 9]
[10 11 12 13 14 15 16 17 18]  =>  [11 12 13 14 15 16 17 18 19]
[20 21 22 23 24 25 26 27 28]  =>  [21 22 23 24 25 26 27 28 29]

للتنبؤ بنافذة كاملة بدلاً من الإزاحة الثابتة ، يمكنك تقسيم الدُفعات إلى جزأين:

batches = range_ds.batch(15, drop_remainder=True)

def label_next_5_steps(batch):
  return (batch[:-5],   # Inputs: All except the last 5 steps
          batch[-5:])   # Labels: The last 5 steps

predict_5_steps = batches.map(label_next_5_steps)

for features, label in predict_5_steps.take(3):
  print(features.numpy(), " => ", label.numpy())
[0 1 2 3 4 5 6 7 8 9]  =>  [10 11 12 13 14]
[15 16 17 18 19 20 21 22 23 24]  =>  [25 26 27 28 29]
[30 31 32 33 34 35 36 37 38 39]  =>  [40 41 42 43 44]

للسماح ببعض التداخل بين ميزات دفعة واحدة وتسميات أخرى ، استخدم Dataset.zip :

feature_length = 10
label_length = 3

features = range_ds.batch(feature_length, drop_remainder=True)
labels = range_ds.batch(feature_length).skip(1).map(lambda labels: labels[:label_length])

predicted_steps = tf.data.Dataset.zip((features, labels))

for features, label in predicted_steps.take(5):
  print(features.numpy(), " => ", label.numpy())
[0 1 2 3 4 5 6 7 8 9]  =>  [10 11 12]
[10 11 12 13 14 15 16 17 18 19]  =>  [20 21 22]
[20 21 22 23 24 25 26 27 28 29]  =>  [30 31 32]
[30 31 32 33 34 35 36 37 38 39]  =>  [40 41 42]
[40 41 42 43 44 45 46 47 48 49]  =>  [50 51 52]

باستخدام window

أثناء استخدام أعمال Dataset.batch ، هناك مواقف قد تحتاج فيها إلى تحكم أكثر دقة. يمنحك أسلوب Dataset.window كاملاً ، ولكنه يتطلب بعض العناية: فهو يقوم بإرجاع مجموعة Dataset من Datasets . انظر هيكل مجموعة البيانات للحصول على التفاصيل.

window_size = 5

windows = range_ds.window(window_size, shift=1)
for sub_ds in windows.take(5):
  print(sub_ds)
<_VariantDataset element_spec=TensorSpec(shape=(), dtype=tf.int64, name=None)>
<_VariantDataset element_spec=TensorSpec(shape=(), dtype=tf.int64, name=None)>
<_VariantDataset element_spec=TensorSpec(shape=(), dtype=tf.int64, name=None)>
<_VariantDataset element_spec=TensorSpec(shape=(), dtype=tf.int64, name=None)>
<_VariantDataset element_spec=TensorSpec(shape=(), dtype=tf.int64, name=None)>

يمكن أن تأخذ طريقة Dataset.flat_map مجموعة بيانات من مجموعات البيانات وتسويتها في مجموعة بيانات واحدة:

for x in windows.flat_map(lambda x: x).take(30):
   print(x.numpy(), end=' ')
0 1 2 3 4 1 2 3 4 5 2 3 4 5 6 3 4 5 6 7 4 5 6 7 8 5 6 7 8 9

في جميع الحالات تقريبًا ، ستحتاج إلى .batch مجموعة البيانات أولاً:

def sub_to_batch(sub):
  return sub.batch(window_size, drop_remainder=True)

for example in windows.flat_map(sub_to_batch).take(5):
  print(example.numpy())
[0 1 2 3 4]
[1 2 3 4 5]
[2 3 4 5 6]
[3 4 5 6 7]
[4 5 6 7 8]

الآن ، يمكنك أن ترى أن وسيطة shift تتحكم في مقدار تحرك كل نافذة.

بتجميع هذا معًا ، يمكنك كتابة هذه الوظيفة:

def make_window_dataset(ds, window_size=5, shift=1, stride=1):
  windows = ds.window(window_size, shift=shift, stride=stride)

  def sub_to_batch(sub):
    return sub.batch(window_size, drop_remainder=True)

  windows = windows.flat_map(sub_to_batch)
  return windows
ds = make_window_dataset(range_ds, window_size=10, shift = 5, stride=3)

for example in ds.take(10):
  print(example.numpy())
[ 0  3  6  9 12 15 18 21 24 27]
[ 5  8 11 14 17 20 23 26 29 32]
[10 13 16 19 22 25 28 31 34 37]
[15 18 21 24 27 30 33 36 39 42]
[20 23 26 29 32 35 38 41 44 47]
[25 28 31 34 37 40 43 46 49 52]
[30 33 36 39 42 45 48 51 54 57]
[35 38 41 44 47 50 53 56 59 62]
[40 43 46 49 52 55 58 61 64 67]
[45 48 51 54 57 60 63 66 69 72]

بعد ذلك يكون من السهل استخراج الملصقات ، كما كان من قبل:

dense_labels_ds = ds.map(dense_1_step)

for inputs,labels in dense_labels_ds.take(3):
  print(inputs.numpy(), "=>", labels.numpy())
[ 0  3  6  9 12 15 18 21 24] => [ 3  6  9 12 15 18 21 24 27]
[ 5  8 11 14 17 20 23 26 29] => [ 8 11 14 17 20 23 26 29 32]
[10 13 16 19 22 25 28 31 34] => [13 16 19 22 25 28 31 34 37]

جارى الاختزال

عند العمل باستخدام مجموعة بيانات غير متوازنة للغاية في الفئة ، قد ترغب في إعادة تشكيل مجموعة البيانات. يوفر tf.data طريقتين للقيام بذلك. تعد مجموعة بيانات الاحتيال في بطاقة الائتمان مثالًا جيدًا على هذا النوع من المشاكل.

zip_path = tf.keras.utils.get_file(
    origin='https://storage.googleapis.com/download.tensorflow.org/data/creditcard.zip',
    fname='creditcard.zip',
    extract=True)

csv_path = zip_path.replace('.zip', '.csv')
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/creditcard.zip
69156864/69155632 [==============================] - 2s 0us/step
69165056/69155632 [==============================] - 2s 0us/step
creditcard_ds = tf.data.experimental.make_csv_dataset(
    csv_path, batch_size=1024, label_name="Class",
    # Set the column types: 30 floats and an int.
    column_defaults=[float()]*30+[int()])

الآن ، تحقق من توزيع الفئات ، فهو منحرف للغاية:

def count(counts, batch):
  features, labels = batch
  class_1 = labels == 1
  class_1 = tf.cast(class_1, tf.int32)

  class_0 = labels == 0
  class_0 = tf.cast(class_0, tf.int32)

  counts['class_0'] += tf.reduce_sum(class_0)
  counts['class_1'] += tf.reduce_sum(class_1)

  return counts
counts = creditcard_ds.take(10).reduce(
    initial_state={'class_0': 0, 'class_1': 0},
    reduce_func = count)

counts = np.array([counts['class_0'].numpy(),
                   counts['class_1'].numpy()]).astype(np.float32)

fractions = counts/counts.sum()
print(fractions)
[0.9956 0.0044]

النهج الشائع للتدريب باستخدام مجموعة بيانات غير متوازنة هو موازنة ذلك. يتضمن tf.data عدة طرق تمكّن سير العمل هذا:

أخذ عينات مجموعات البيانات

تتمثل إحدى طرق إعادة تشكيل مجموعة بيانات في استخدام sample_from_datasets . يكون هذا أكثر قابلية للتطبيق عندما يكون لديك مجموعة data.Dataset لكل فئة.

هنا ، ما عليك سوى استخدام عامل التصفية لإنشاءها من بيانات الاحتيال على بطاقة الائتمان:

negative_ds = (
  creditcard_ds
    .unbatch()
    .filter(lambda features, label: label==0)
    .repeat())
positive_ds = (
  creditcard_ds
    .unbatch()
    .filter(lambda features, label: label==1)
    .repeat())
for features, label in positive_ds.batch(10).take(1):
  print(label.numpy())
[1 1 1 1 1 1 1 1 1 1]

لاستخدام tf.data.Dataset.sample_from_datasets ، قم بتمرير مجموعات البيانات ووزن كل منها:

balanced_ds = tf.data.Dataset.sample_from_datasets(
    [negative_ds, positive_ds], [0.5, 0.5]).batch(10)

تنتج مجموعة البيانات الآن أمثلة لكل فئة باحتمال 50/50:

for features, labels in balanced_ds.take(10):
  print(labels.numpy())
[1 0 1 0 1 0 1 1 1 1]
[0 0 1 1 0 1 1 1 1 1]
[1 1 1 1 0 0 1 0 1 0]
[1 1 1 0 1 0 0 1 1 1]
[0 1 0 1 1 1 0 1 1 0]
[0 1 0 0 0 1 0 0 0 0]
[1 1 1 1 1 0 0 1 1 0]
[0 0 0 1 0 1 1 1 0 0]
[0 0 1 1 1 1 0 1 1 1]
[1 0 0 1 1 1 1 0 1 1]

إعادة تشكيل الرفض

تتمثل إحدى مشكلات نهج Dataset.sample_from_datasets أعلاه في أنه يحتاج إلى tf.data.Dataset منفصل لكل فئة. يمكنك استخدام Dataset.filter لإنشاء مجموعتي البيانات هاتين ، ولكن ينتج عن ذلك تحميل جميع البيانات مرتين.

يمكن تطبيق طريقة data.Dataset.rejection_resample على مجموعة بيانات لإعادة توازنها ، مع تحميلها مرة واحدة فقط. سيتم إسقاط العناصر من مجموعة البيانات لتحقيق التوازن.

تأخذ data.Dataset.rejection_resample الوسيطة class_func . يتم تطبيق class_func هذا على كل عنصر من عناصر مجموعة البيانات ، ويتم استخدامه لتحديد الفئة التي ينتمي إليها المثال لأغراض الموازنة.

الهدف هنا هو موازنة توزيع lable ، وعناصر creditcard_ds هي بالفعل أزواج (features, label) . لذلك يحتاج class_func فقط إلى إرجاع تلك التسميات:

def class_func(features, label):
  return label

تتعامل طريقة إعادة التشكيل مع أمثلة فردية ، لذلك في هذه الحالة يجب عليك unbatch مجموعة البيانات قبل تطبيق هذه الطريقة.

تحتاج الطريقة إلى توزيع مستهدف ، واختيارياً تقدير توزيع أولي كمدخلات.

resample_ds = (
    creditcard_ds
    .unbatch()
    .rejection_resample(class_func, target_dist=[0.5,0.5],
                        initial_dist=fractions)
    .batch(10))
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/data/ops/dataset_ops.py:5797: Print (from tensorflow.python.ops.logging_ops) is deprecated and will be removed after 2018-08-20.
Instructions for updating:
Use tf.print instead of tf.Print. Note that tf.print returns a no-output operator that directly prints the output. Outside of defuns or eager mode, this operator will not be executed unless it is directly specified in session.run or used as a control dependency for other operators. This is only a concern in graph mode. Below is an example of how to ensure tf.print executes in graph mode:

تقوم طريقة rejection_resample بإرجاع (class, example) أزواج حيث تكون class هي ناتج class_func . في هذه الحالة ، كان example بالفعل زوجًا (feature, label) ، لذا استخدم map لإسقاط النسخة الإضافية من الملصقات:

balanced_ds = resample_ds.map(lambda extra_label, features_and_label: features_and_label)

تنتج مجموعة البيانات الآن أمثلة لكل فئة باحتمال 50/50:

for features, labels in balanced_ds.take(10):
  print(labels.numpy())
Proportion of examples rejected by sampler is high: [0.995605469][0.995605469 0.00439453125][0 1]
Proportion of examples rejected by sampler is high: [0.995605469][0.995605469 0.00439453125][0 1]
Proportion of examples rejected by sampler is high: [0.995605469][0.995605469 0.00439453125][0 1]
Proportion of examples rejected by sampler is high: [0.995605469][0.995605469 0.00439453125][0 1]
Proportion of examples rejected by sampler is high: [0.995605469][0.995605469 0.00439453125][0 1]
Proportion of examples rejected by sampler is high: [0.995605469][0.995605469 0.00439453125][0 1]
Proportion of examples rejected by sampler is high: [0.995605469][0.995605469 0.00439453125][0 1]
Proportion of examples rejected by sampler is high: [0.995605469][0.995605469 0.00439453125][0 1]
Proportion of examples rejected by sampler is high: [0.995605469][0.995605469 0.00439453125][0 1]
Proportion of examples rejected by sampler is high: [0.995605469][0.995605469 0.00439453125][0 1]
[0 1 1 1 0 1 1 0 1 1]
[1 1 0 1 0 0 0 0 1 1]
[1 1 1 1 0 0 0 0 1 1]
[1 0 0 1 0 0 1 0 1 1]
[1 0 0 0 0 1 0 0 0 0]
[1 0 0 1 1 0 1 1 1 0]
[1 1 0 0 0 0 0 0 0 1]
[0 0 1 0 0 0 1 0 1 1]
[0 1 0 1 0 1 0 0 0 1]
[0 0 0 0 0 0 0 0 1 1]

تكرارات التفتيش

يدعم Tensorflow أخذ نقاط التفتيش حتى يتمكن عند إعادة تشغيل عملية التدريب الخاصة بك من استعادة أحدث نقطة تفتيش لاستعادة معظم تقدمها. بالإضافة إلى تحديد متغيرات النموذج ، يمكنك أيضًا التحقق من تقدم مكرر مجموعة البيانات. قد يكون هذا مفيدًا إذا كان لديك مجموعة بيانات كبيرة ولا تريد بدء مجموعة البيانات من البداية في كل إعادة تشغيل. لاحظ مع ذلك أن نقاط فحص المكرر قد تكون كبيرة ، نظرًا لأن عمليات التحويل مثل shuffle prefetch تتطلب عناصر تخزين مؤقت داخل المكرر.

لتضمين المكرر في نقطة فحص ، قم بتمرير المكرر إلى tf.train.Checkpoint constructor.

range_ds = tf.data.Dataset.range(20)

iterator = iter(range_ds)
ckpt = tf.train.Checkpoint(step=tf.Variable(0), iterator=iterator)
manager = tf.train.CheckpointManager(ckpt, '/tmp/my_ckpt', max_to_keep=3)

print([next(iterator).numpy() for _ in range(5)])

save_path = manager.save()

print([next(iterator).numpy() for _ in range(5)])

ckpt.restore(manager.latest_checkpoint)

print([next(iterator).numpy() for _ in range(5)])
[0, 1, 2, 3, 4]
[5, 6, 7, 8, 9]
[5, 6, 7, 8, 9]

استخدام tf.data مع tf.keras

تعمل واجهة برمجة تطبيقات tf.keras تبسيط العديد من جوانب إنشاء نماذج التعلم الآلي وتنفيذها. تدعم واجهات برمجة التطبيقات الخاصة بها .fit() () و .evaluate ( .evaluate() و .predict() مجموعات البيانات كمدخلات. فيما يلي مجموعة بيانات سريعة وإعداد نموذج:

train, test = tf.keras.datasets.fashion_mnist.load_data()

images, labels = train
images = images/255.0
labels = labels.astype(np.int32)
fmnist_train_ds = tf.data.Dataset.from_tensor_slices((images, labels))
fmnist_train_ds = fmnist_train_ds.shuffle(5000).batch(32)

model = tf.keras.Sequential([
  tf.keras.layers.Flatten(),
  tf.keras.layers.Dense(10)
])

model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), 
              metrics=['accuracy'])

كل ما تحتاجه من أجل Model.fit و Model.evaluate هو تمرير مجموعة بيانات من أزواج (feature, label) :

model.fit(fmnist_train_ds, epochs=2)
Epoch 1/2
1875/1875 [==============================] - 4s 2ms/step - loss: 0.5984 - accuracy: 0.7973
Epoch 2/2
1875/1875 [==============================] - 4s 2ms/step - loss: 0.4607 - accuracy: 0.8430
<keras.callbacks.History at 0x7f7e70283110>

إذا قمت بتمرير مجموعة بيانات لا نهائية ، على سبيل المثال من خلال استدعاء Dataset.repeat() ، فأنت تحتاج فقط إلى تمرير وسيطة steps_per_epoch :

model.fit(fmnist_train_ds.repeat(), epochs=2, steps_per_epoch=20)
Epoch 1/2
20/20 [==============================] - 0s 2ms/step - loss: 0.4574 - accuracy: 0.8672
Epoch 2/2
20/20 [==============================] - 0s 2ms/step - loss: 0.4216 - accuracy: 0.8562
<keras.callbacks.History at 0x7f7e144948d0>

للتقييم يمكنك اجتياز عدد خطوات التقييم:

loss, accuracy = model.evaluate(fmnist_train_ds)
print("Loss :", loss)
print("Accuracy :", accuracy)
1875/1875 [==============================] - 4s 2ms/step - loss: 0.4350 - accuracy: 0.8524
Loss : 0.4350026249885559
Accuracy : 0.8524333238601685

لمجموعات البيانات الطويلة ، عيّن عدد الخطوات المراد تقييمها:

loss, accuracy = model.evaluate(fmnist_train_ds.repeat(), steps=10)
print("Loss :", loss)
print("Accuracy :", accuracy)
10/10 [==============================] - 0s 2ms/step - loss: 0.4345 - accuracy: 0.8687
Loss : 0.43447819352149963
Accuracy : 0.8687499761581421

الملصقات غير مطلوبة عند استدعاء Model.predict .

predict_ds = tf.data.Dataset.from_tensor_slices(images).batch(32)
result = model.predict(predict_ds, steps = 10)
print(result.shape)
(320, 10)

ولكن يتم تجاهل التسميات إذا قمت بتمرير مجموعة بيانات تحتوي عليها:

result = model.predict(fmnist_train_ds, steps = 10)
print(result.shape)
(320, 10)