احفظ التاريخ! يعود مؤتمر Google I / O من 18 إلى 20 مايو. سجل الآن
ترجمت واجهة Cloud Translation API‏ هذه الصفحة.
Switch to English

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

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

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

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

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

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

  • tf.data.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 shapes: (), types: tf.int32>
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 .

وتشمل البنى بيثون التي يمكن استخدامها للتعبير عن بنية (المتداخلة) من عناصر tuple ، dict ، NamedTuple ، و OrderedDict . على وجه الخصوص ، لا تعد list بنية صالحة للتعبير عن بنية عناصر مجموعة البيانات. هذا لأن مستخدمي tf.data الأوائل شعروا بقوة بمدخلات list (على سبيل المثال التي تم تمريرها إلى tf.data.Dataset.from_tensors ) التي يتم تعبئتها تلقائيًا tf.data.Dataset.from_tensors ومخرجات list (على سبيل المثال ، قيم إرجاع وظائف محددة بواسطة المستخدم) يتم فرضها في tuple . نتيجة لذلك ، إذا كنت ترغب في معاملة مدخلات list tf.stack ، فأنت بحاجة إلى تحويلها إلى 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 shapes: (10,), types: tf.int32>
for z in dataset1:
  print(z.numpy())
[8 6 2 1 5 3 8 8 3 6]
[6 8 8 8 5 4 5 1 3 3]
[2 9 9 1 9 2 8 9 4 6]
[1 1 4 1 5 7 1 9 6 7]
dataset2 = tf.data.Dataset.from_tensor_slices(
   (tf.random.uniform([4]),
    tf.random.uniform([4, 100], maxval=100, dtype=tf.int32)))

dataset2
<TensorSliceDataset shapes: ((), (100,)), types: (tf.float32, tf.int32)>
dataset3 = tf.data.Dataset.zip((dataset1, dataset2))

dataset3
<ZipDataset shapes: ((10,), ((), (100,))), types: (tf.int32, (tf.float32, tf.int32))>
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
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-images-idx3-ubyte.gz
26427392/26421880 [==============================] - 1s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-labels-idx1-ubyte.gz
8192/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
images, labels = train
images = images/255

dataset = tf.data.Dataset.from_tensor_slices((images, labels))
dataset
<TensorSliceDataset shapes: ((28, 28), ()), types: (tf.float64, tf.uint8)>

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

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

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 مُنشئ Dataset.from_generator إلى مجموعة بيانات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 لا تدعم الموترات ذات الترتيب غير المعروف. إذا كان طول محور معين غير معروف أو متغير ، output_shapes على أنه 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.7193  0.1489  0.3015]
1 : [ 0.9257 -0.8073  1.1848 -0.4203 -0.5188 -1.7014 -1.0934  2.6323]
2 : [-0.2845 -0.3942  0.8429 -0.2441  0.8038]
3 : [ 0.0553 -0.2246]
4 : [ 0.5449 -0.0552 -0.4308 -0.5846  0.2252  0.475  -0.6507  1.3336 -0.6148]
5 : []
6 : [-0.0788 -0.2671 -0.5868 -1.038  -0.145 ]

إخراج الأول هو int32 والثاني هو float32 .

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

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

ds_series
<FlatMapDataset shapes: ((), (None,)), types: (tf.int32, tf.float32)>

الآن يمكن استخدامه مثل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())
[14  2 16 18  5 13  3  4  0 25]

[[ 2.2655e-03 -1.1356e+00 -6.3269e-01  0.0000e+00  0.0000e+00  0.0000e+00
   0.0000e+00  0.0000e+00  0.0000e+00]
 [ 5.9163e-01  1.1532e-01 -9.4578e-01 -5.7305e-01  5.8756e-01 -8.3086e-01
   1.0705e+00  4.6149e-01 -7.8259e-01]
 [ 8.7261e-01 -1.4031e+00  2.1707e-01  0.0000e+00  0.0000e+00  0.0000e+00
   0.0000e+00  0.0000e+00  0.0000e+00]
 [ 0.0000e+00  0.0000e+00  0.0000e+00  0.0000e+00  0.0000e+00  0.0000e+00
   0.0000e+00  0.0000e+00  0.0000e+00]
 [ 2.2473e-01 -4.9448e-02 -5.0318e-01 -7.6812e-01 -5.9628e-01  0.0000e+00
   0.0000e+00  0.0000e+00  0.0000e+00]
 [-5.1477e-01  0.0000e+00  0.0000e+00  0.0000e+00  0.0000e+00  0.0000e+00
   0.0000e+00  0.0000e+00  0.0000e+00]
 [-1.3577e+00  7.6869e-02  1.3755e+00  1.1951e-01  0.0000e+00  0.0000e+00
   0.0000e+00  0.0000e+00  0.0000e+00]
 [ 1.4727e+00  5.2939e-01  1.1665e-01  6.6326e-01  0.0000e+00  0.0000e+00
   0.0000e+00  0.0000e+00  0.0000e+00]
 [-1.5769e+00  7.4631e-01  8.4916e-01  5.3781e-01 -2.3621e+00  1.0438e-01
   9.3234e-01  0.0000e+00  0.0000e+00]
 [-1.7867e+00 -1.5707e+00  1.7925e-02  0.0000e+00  0.0000e+00  0.0000e+00
   0.0000e+00  0.0000e+00  0.0000e+00]]

للحصول على مثال أكثر واقعية ، جرب تغليف 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 [==============================] - 6s 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 مجموعة متنوعة من تنسيقات الملفات بحيث يمكنك معالجة مجموعات البيانات الكبيرة التي لا تتناسب مع الذاكرة. على سبيل المثال ، تنسيق ملف 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 [==============================] - 0s 0us/step

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

dataset = tf.data.TFRecordDataset(filenames = [fsns_test_file])
dataset
<TFRecordDatasetV2 shapes: (), types: tf.string>

تستخدم العديد من مشروعات tf.train.Example سجلات 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
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/derby.txt
811008/809730 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/butler.txt
811008/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 الأسطر بين الملفات ، استخدم 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
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 1 0 1]
features:
  'sex'               : [b'female' b'male' b'male' b'female']
  'age'               : [27. 48. 28. 50.]
  'n_siblings_spouses': [0 1 0 0]
  'parch'             : [2 0 0 0]
  'fare'              : [11.1333 76.7292  7.8958 10.5   ]
  'class'             : [b'Third' b'First' b'Third' b'Second']
  'deck'              : [b'unknown' b'D' b'unknown' b'unknown']
  'embark_town'       : [b'Southampton' b'Cherbourg' b'Southampton' b'Southampton']
  'alone'             : [b'n' b'n' b'y' 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': [1 0 0 0]
  'fare'              : [29.7     7.8958 15.0458  7.75  ]
  'class'             : [b'First' b'Third' b'Second' b'Third']

هناك أيضًا فئة experimental.CsvDataset منخفضة المستوى experimental.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 shapes: (4,), types: tf.int32>
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 كل عمود من كل سطر من الملف ، وهو ما قد لا يكون مرغوبًا فيه ، على سبيل المثال إذا بدأ الملف بسطر رأس يجب تجاهله ، أو إذا كانت بعض الأعمدة غير مطلوبة في الإدخال. يمكن إزالة هذه الأسطر والحقول باستخدام select_cols 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 shapes: (2,), types: tf.int32>
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/7652532108_01ef94c476.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/roses/3276552939_8c31b22d3e.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/roses/4363734507_5cc4ed6e01.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/dandelion/20754920332_53b995fc63_n.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/dandelion/15219268336_f2460fca88_m.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\xed\x00\x1cPhotoshop 3.0\x008BIM\x04\x04\x00\x00\x00\x00\x00\x00\xff\xdb\x00C\x00\x03\x02\x02\x03\x02\x02\x03\x03\x03\x03\x04\x03\x03\x04\x05\x08\x05\x05\x04\x04\x05\n\x07\x07\x06\x08\x0c\n\x0c\x0c\x0b\n\x0b\x0b\r\x0e\x12\x10\r\x0e\x11\x0e\x0b\x0b\x10'

b'daisy'

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

تجميع بسيط

أبسط شكل من أشكال الجرعات مداخن n عناصر متتالية من مجموعة البيانات في عنصر واحد. يقوم تحويل Dataset.batch() بهذا بالضبط ، مع نفس قيود عامل التشغيل tf.stack() ، المطبق على كل مكون من العناصر: على 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 shapes: ((None,), (None,)), types: (tf.int64, tf.int64)>

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

batched_dataset = dataset.batch(7, drop_remainder=True)
batched_dataset
<BatchDataset shapes: ((7,), (7,)), types: (tf.int64, tf.int64)>

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

تعمل الوصفة أعلاه مع الموترات التي لها نفس الحجم. ومع ذلك ، فإن العديد من النماذج (مثل نماذج التسلسل) تعمل مع بيانات الإدخال التي يمكن أن يكون لها أحجام مختلفة (مثل تسلسلات ذات أطوال مختلفة). للتعامل مع هذه الحالة ، Dataset.padded_batch تحويل Dataset.padded_batch من 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.repeat التحويل Dataset.repeat حججه دون الإشارة إلى نهاية حقبة واحدة وبداية الحقبة التالية. وبسبب هذا ، Dataset.batch تطبيق Dataset.batch تطبيقه بعد Dataset.repeat سينتج عنه دفعات تمتد عبر حدود الحقبة:

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

بي إن جي

إذا كنت بحاجة إلى فصل واضح Dataset.batch ، فضع 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() تحويل 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 shapes: ((None,), (None,)), types: (tf.int64, tf.string)>

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

n,line_batch = next(iter(dataset))
print(n.numpy())
[ 36  72  81  39  19  32  70  11  64  18  56  86  30  40 111  80  87  13
 110 116]

كما هو الحال مع Dataset.batch فإن الترتيب Dataset.repeat بـ 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:

[622 295 617 552 465 394 592 593 442 488]
[469 543 329 600 359 471 554 627 620 456]
[565 550 563 464 556 618 587 449]
[20  0 71  8 25 86 10 57 72  2]
[ 78  99  85  36  62  13 108  46  56  97]
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 0x7fcccbcea860>

بي إن جي

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

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:

[607 519 555 627  23 469   9 501   2 500]
[546 470  14  21 583 552  15 539 340  30]
[620  29 586 570 250   6 599 498 618 610]
[ 16  25 518 505 465 418  12  22  59  51]
[ 10 558  47 553  52   7 616 454 561 597]
[363  68 441 530  75 603  57 339  64  37]
[ 46 388 626  39 600 577 609 467 451  44]
[601  78 537  69 514  54  11 613 502  32]
[ 62  76  85 391  34  65  36 105 541  96]
[548 576 107  61 543  19 527  72 111  38]
[ 98  26  43  70 463 581 115 559 590  86]
[ 50 594  77 490  55 108 370  63  87  94]
[122  27 136  73 617 535  40 144 112  24]
[ 67  99 151 128   5 154 120   4 343 102]
[625 132  83  35 356  80 130  60 156 100]
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 0x7fccb44c5f28>

بي إن جي

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

ينتج عن تحويل 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.image.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 shapes: (), types: tf.string>

يمكنك العمل مع 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 shapes: ((), ()), types: (tf.string, tf.string)>
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],   # Take the first 5 steps
          batch[-5:])   # take the remainder

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.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 shapes: (), types: tf.int64>
<_VariantDataset shapes: (), types: tf.int64>
<_VariantDataset shapes: (), types: tf.int64>
<_VariantDataset shapes: (), types: tf.int64>
<_VariantDataset shapes: (), types: tf.int64>

يمكن أن تأخذ طريقة Dataset.flat_map مجموعة بيانات من مجموعات البيانات 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 [==============================] - 3s 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.9952 0.0048]

النهج الشائع للتدريب باستخدام مجموعة بيانات غير متوازنة هو موازنة ذلك. يتضمن 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.experimental.sample_from_datasets بتمرير مجموعات البيانات ووزن كل منها:

balanced_ds = tf.data.experimental.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())
[0 0 1 1 0 0 1 1 0 1]
[0 1 1 0 0 1 0 0 1 1]
[0 0 0 1 0 1 1 0 0 1]
[1 0 0 1 1 0 1 1 1 1]
[1 0 0 1 1 1 0 1 1 1]
[0 0 1 0 1 1 0 1 0 0]
[1 0 1 0 0 1 0 1 1 0]
[0 0 0 0 1 0 0 1 0 1]
[0 0 0 0 0 0 1 1 0 1]
[1 0 1 0 0 0 1 0 0 1]

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

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

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

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

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

def class_func(features, label):
  return label

يحتاج جهاز إعادة العينات أيضًا إلى توزيع مستهدف ، وتقدير توزيع أولي اختياريًا:

resampler = tf.data.experimental.rejection_resample(
    class_func, target_dist=[0.5, 0.5], initial_dist=fractions)

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

resample_ds = creditcard_ds.unbatch().apply(resampler).batch(10)
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/data/experimental/ops/resampling.py:156: 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:

يُنشئ resampler (class, example) أزواجًا من إخراج 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())
[1 0 0 0 1 1 1 0 0 1]
[0 1 1 0 1 1 1 0 0 0]
[0 0 0 0 1 0 0 1 1 0]
[0 0 1 1 0 1 1 1 0 0]
[0 1 0 1 0 0 0 0 0 0]
[0 0 0 1 0 1 1 0 1 0]
[1 0 0 1 0 1 1 1 0 1]
[0 0 0 0 0 1 0 1 1 1]
[1 0 1 0 1 0 0 0 1 1]
[0 1 0 0 0 1 0 0 0 0]

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

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

لتضمين tf.train.Checkpoint في tf.train.Checkpoint ، قم بتمرير tf.train.Checkpoint إلى 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 واجهة برمجة تطبيقات tf.keras تبسيط العديد من جوانب إنشاء نماذج التعلم الآلي وتنفيذها. في .fit() و .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'])

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

model.fit(fmnist_train_ds, epochs=2)
Epoch 1/2
1875/1875 [==============================] - 4s 2ms/step - loss: 0.7767 - accuracy: 0.7407
Epoch 2/2
1875/1875 [==============================] - 3s 2ms/step - loss: 0.4709 - accuracy: 0.8387
<tensorflow.python.keras.callbacks.History at 0x7fcca40a2080>

إذا قمت بتمرير مجموعة بيانات لا نهائية ، على سبيل المثال من خلال استدعاء 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.4301 - accuracy: 0.8609
Epoch 2/2
20/20 [==============================] - 0s 2ms/step - loss: 0.4454 - accuracy: 0.8281
<tensorflow.python.keras.callbacks.History at 0x7fccb416bd30>

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

loss, accuracy = model.evaluate(fmnist_train_ds)
print("Loss :", loss)
print("Accuracy :", accuracy)
1875/1875 [==============================] - 4s 2ms/step - loss: 0.4484 - accuracy: 0.8435
Loss : 0.4483910799026489
Accuracy : 0.843500018119812

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

loss, accuracy = model.evaluate(fmnist_train_ds.repeat(), steps=10)
print("Loss :", loss)
print("Accuracy :", accuracy)
10/10 [==============================] - 0s 2ms/step - loss: 0.4643 - accuracy: 0.8531
Loss : 0.4643370509147644
Accuracy : 0.8531249761581421

الملصقات غير مطلوبة عند استدعاء 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)