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

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

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

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

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

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

  • A تحويل البيانات يبني مجموعة بيانات من واحد أو أكثر 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 الكائن هو بيثون iterable. هذا يجعل من الممكن استهلاك عناصرها باستخدام حلقة 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

أو عن طريق إنشاء صراحة بيثون مكرر باستخدام 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 ) يجري معبأة تلقائيا التنسورات و list المخرجات (على سبيل المثال القيم المرجعة من الدالات المعرفة من قبل المستخدم) يتم بالإكراه في tuple . ونتيجة لذلك، إذا كنت ترغب في الحصول على list المدخلات إلى أن تعامل على أنها هيكل، تحتاج إلى تحويله إلى tuple ، وإذا كنت ترغب في الحصول على list الإخراج إلى أن يكون مكون واحد، فأنت بحاجة لحزم صراحة باستخدام tf.stack .

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

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 1 7 9 5 5 3 3 9 5]
[1 3 4 4 6 1 3 2 9 7]
[3 8 3 1 3 6 2 3 9 4]
[7 3 3 2 9 5 5 6 9 1]
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

انظر تحميل صفائف نمباي لمزيد من الأمثلة.

وإذا كان كل من نوبات إدخال البيانات الخاصة بك في الذاكرة، وأبسط طريقة لإنشاء 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 [==============================] - 1s 0us/step
26435584/26421880 [==============================] - 1s 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 shapes: ((28, 28), ()), types: (tf.float64, tf.uint8)>

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

مصدر بيانات آخر شائع التي يمكن بسهولة تناولها باعتبارها 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 منشئ تحويل مولد الثعبان لتعمل بكامل طاقتها 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.5118  1.1338  0.4652 -0.1266  2.1672  0.6211]
1 : [-0.6128 -0.9794  0.8448 -2.0378  1.0311  0.3236 -1.2445]
2 : [-0.1676 -1.7922  0.9232  0.975   0.0882 -1.2209  2.9198]
3 : [ 1.3887  0.4012 -0.0488  0.6349  0.5027  0.445  -1.4106 -1.1119]
4 : [ 0.8127 -0.3852  2.5075 -0.9225 -1.3582  2.1959  0.3715  0.7565]
5 : [-0.7891  1.4259 -0.949  -0.7526 -1.6112  0.5935 -0.1808 -2.4721]
6 : [-0.2006  0.1787 -0.8684  0.7611  0.1345 -1.9513  0.0824 -0.2069]

إخراج الأول هو 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())
[18  3  9  2 21 24 10 16  1 25]

[[-0.1558  0.      0.      0.      0.      0.      0.      0.      0.    ]
 [ 0.      0.      0.      0.      0.      0.      0.      0.      0.    ]
 [-0.3243  0.8113  1.3543  1.4365  1.7064  0.2669 -0.9977  0.      0.    ]
 [-1.0384  0.6793  0.      0.      0.      0.      0.      0.      0.    ]
 [ 0.      0.      0.      0.      0.      0.      0.      0.      0.    ]
 [-1.1779  0.7456 -0.6888  0.      0.      0.      0.      0.      0.    ]
 [ 0.      0.      0.      0.      0.      0.      0.      0.      0.    ]
 [-1.3821  0.      0.      0.      0.      0.      0.      0.      0.    ]
 [-0.3428 -1.3348  0.      0.      0.      0.      0.      0.      0.    ]
 [-1.7189 -0.9511  0.3775  0.3023 -0.8825 -1.002  -0.349  -0.2439 -0.6234]]

للمثال أكثر واقعية، في محاولة التفاف 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 [==============================] - 8s 0us/step
228827136/228813984 [==============================] - 8s 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

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

و 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 [==============================] - 0s 0us/step
7913472/7904079 [==============================] - 0s 0us/step

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

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

تستخدم العديد من المشاريع 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 ، و تحميل الباندا 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 الوظيفة هي واجهة عالية المستوى لقراءة مجموعة من الملفات 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': [0 1 0 0]
features:
  'sex'               : [b'male' b'female' b'male' b'male']
  'age'               : [16. 38. 22. 28.]
  'n_siblings_spouses': [4 1 0 0]
  'parch'             : [1 5 0 0]
  'fare'              : [39.6875 31.3875  8.05    7.8958]
  'class'             : [b'Third' b'Third' b'Third' b'Third']
  'deck'              : [b'unknown' b'unknown' b'unknown' b'unknown']
  'embark_town'       : [b'Southampton' b'Southampton' 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 1]
  'fare'              : [26.25   13.      6.8583 55.    ]
  'class'             : [b'Second' b'Second' b'Third' b'First']

وهناك أيضا المستوى الأدنى 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 ينتج كل عمود من كل سطر من الملف، والذي قد لا يكون مرغوبا فيه، على سبيل المثال إذا يبدأ الملف مع خط رأس ينبغي تجاهلها، أو إذا لم يكن هناك حاجة بعض الأعمدة في الإدخال. هذه الخطوط والحقول يمكن إزالتها مع 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/daisy/54377391_15648e8d18.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/daisy/18474740346_ffdaa18032.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/tulips/212720516_df4965ebda_n.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/daisy/14554906452_35f066ffe9_n.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/dandelion/3393564906_f2df184b76_n.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'dandelion'

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

تجميع بسيط

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

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 = 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
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/data/experimental/ops/counter.py:66: scan (from tensorflow.python.data.experimental.ops.scan_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use `tf.data.Dataset.scan(...) instead
<BatchDataset shapes: ((None,), (None,)), types: (tf.int64, tf.string)>

منذ buffer_size 100، وحجم الدفعة 20، الدفعة الأولى لا تحتوي على العناصر مع فهرس أكثر من 120.

n,line_batch = next(iter(dataset))
print(n.numpy())
[49 10 47  5 41 22 24 12 63 95  6 81 93 33 32 46 62 38 34 59]

كما هو الحال مع 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:

[547 510 586 552  73 585 615 601 619 621]
[374 450 574 459 533 505 593 526 604 572]
[627 475 590 512 611 520 610 618]
[12 50 97 88 15 63 26 32 95  1]
[ 6 24 44 49 47 92 70 13 16 96]
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 0x7f97d44e5590>

بي إن جي

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

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:

[ 70 591   8 621 617   3  15  28 367 597]
[599 514 500 604  13 532 580  32 381 616]
[371  16 458 553 625 567   7 478 627 602]
[ 37 571  24   5  42 605 601  17 341 321]
[ 34 484  18  39 576 626 551 412 578 618]
[ 43  33 610  20  21 563  46 603  19 590]
[ 71  73  55  25 492 483  14 515  59  67]
[ 81  65   4 577  57  58  86 513  90 583]
[100 556 534  70 619  48 459  12 537  68]
[ 38  45  87 102  97 101 442  77 111 554]
[ 99  78 377  93  26 564  98 614  49  10]
[ 35  72  79 611 624 122  96 594   1 579]
[  2 542 581 125  83  23  51 139  36  91]
[124 486 575 129 152  92 134 592  89 133]
[ 82 144 109 164 159  61 429 607 522 150]
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 0x7f97d41d3610>

بي إن جي

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

و 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],   # 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 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 طريقة يمكن أن تتخذ مجموعة بيانات من قواعد البيانات وتتسطح قبل أن تتحول إلى مجموعة البيانات واحد:

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.9951 0.0049]

النهج الشائع للتدريب باستخدام مجموعة بيانات غير متوازنة هو موازنة ذلك. 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)
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/data/experimental/ops/interleave_ops.py:260: RandomDataset.__init__ (from tensorflow.python.data.ops.dataset_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use `tf.data.Dataset.random(...)`.

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

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

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

مشكلة واحدة مع ما ورد أعلاه experimental.sample_from_datasets النهج هو أنه يحتاج منفصلة 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 مجموعة البيانات قبل تطبيق أداة إعادة إعداد:

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

يعود أداة إعادة إعداد يخلق (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())
Proportion of examples rejected by sampler is high: [0.995117188][0.995117188 0.0048828125][0 1]
Proportion of examples rejected by sampler is high: [0.995117188][0.995117188 0.0048828125][0 1]
Proportion of examples rejected by sampler is high: [0.995117188][0.995117188 0.0048828125][0 1]
Proportion of examples rejected by sampler is high: [0.995117188][0.995117188 0.0048828125][0 1]
Proportion of examples rejected by sampler is high: [0.995117188][0.995117188 0.0048828125][0 1]
Proportion of examples rejected by sampler is high: [0.995117188][0.995117188 0.0048828125][0 1]
Proportion of examples rejected by sampler is high: [0.995117188][0.995117188 0.0048828125][0 1]
Proportion of examples rejected by sampler is high: [0.995117188][0.995117188 0.0048828125][0 1]
Proportion of examples rejected by sampler is high: [0.995117188][0.995117188 0.0048828125][0 1]
Proportion of examples rejected by sampler is high: [0.995117188][0.995117188 0.0048828125][0 1]
[0 1 0 0 1 1 0 0 1 1]
[0 0 0 1 1 1 0 1 1 1]
[1 0 0 0 1 0 1 1 0 1]
[0 0 0 0 0 0 0 0 1 1]
[0 1 1 0 1 0 1 0 0 1]
[1 0 1 1 0 1 1 1 0 1]
[1 1 0 0 0 1 0 1 1 0]
[1 0 1 0 1 0 1 0 0 1]
[0 0 0 0 0 1 1 0 1 0]
[1 0 1 1 0 1 0 0 1 0]

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

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

لتشمل مكرر بك في نقطة تفتيش، وتمرير مكرر إلى tf.train.Checkpoint المنشئ.

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 API يبسط العديد من جوانب خلق وتنفيذ نماذج تعلم الآلة. في .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.6006 - accuracy: 0.7965
Epoch 2/2
1875/1875 [==============================] - 3s 2ms/step - loss: 0.4616 - accuracy: 0.8414
<keras.callbacks.History at 0x7f9857337dd0>

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

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

loss, accuracy = model.evaluate(fmnist_train_ds)
print("Loss :", loss)
print("Accuracy :", accuracy)
1875/1875 [==============================] - 3s 1ms/step - loss: 0.4349 - accuracy: 0.8489
Loss : 0.43494418263435364
Accuracy : 0.8488500118255615

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

loss, accuracy = model.evaluate(fmnist_train_ds.repeat(), steps=10)
print("Loss :", loss)
print("Accuracy :", accuracy)
10/10 [==============================] - 0s 2ms/step - loss: 0.5229 - accuracy: 0.8188
Loss : 0.5228594541549683
Accuracy : 0.8187500238418579

ليس مطلوبا من التسميات في حين يدعو 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)