כתיבת מערכי נתונים מותאמים אישית

עקוב אחר המדריך הזה כדי ליצור מערך נתונים חדש (ב-TFDS או במאגר משלך).

בדוק את רשימת מערכי הנתונים שלנו כדי לראות אם מערך הנתונים שאתה רוצה כבר קיים.

TL;DR

הדרך הקלה ביותר לכתוב מערך נתונים חדש היא להשתמש ב- TFDS CLI :

cd path/to/my/project/datasets/
tfds new my_dataset  # Create `my_dataset/my_dataset.py` template files
# [...] Manually modify `my_dataset/my_dataset_dataset_builder.py` to implement your dataset.
cd my_dataset/
tfds build  # Download and prepare the dataset to `~/tensorflow_datasets/`

כדי להשתמש במערך הנתונים החדש עם tfds.load('my_dataset') :

  • tfds.load יזהה ויטען אוטומטית את מערך הנתונים שנוצר ב- ~/tensorflow_datasets/my_dataset/ (למשל על ידי tfds build ).
  • לחלופין, תוכל import my.project.datasets.my_dataset כדי לרשום את מערך הנתונים שלך:
import my.project.datasets.my_dataset  # Register `my_dataset`

ds = tfds.load('my_dataset')  # `my_dataset` registered

סקירה כללית

מערכי נתונים מופצים בכל מיני פורמטים ובכל מיני מקומות, והם לא תמיד מאוחסנים בפורמט שמוכן להזין לתוך צינור למידת מכונה. הכנס ל-TFDS.

TFDS מעבד את מערכי הנתונים הללו לפורמט סטנדרטי (נתונים חיצוניים -> קבצים בסידרה), אשר לאחר מכן ניתן לטעון כצינור למידת מכונה (קבצים בסידרה -> tf.data.Dataset ). הסידרה מתבצעת פעם אחת בלבד. גישה שלאחר מכן תקרא מאותם קבצים שעובדו מראש ישירות.

רוב העיבוד המקדים מתבצע באופן אוטומטי. כל מערך נתונים מיישם תת-סיווג של tfds.core.DatasetBuilder , המציין:

  • מהיכן מגיעים הנתונים (כלומר כתובות האתרים שלו);
  • איך נראה מערך הנתונים (כלומר התכונות שלו);
  • כיצד יש לפצל את הנתונים (למשל TRAIN ו- TEST );
  • והדוגמאות הבודדות במערך הנתונים.

כתוב את מערך הנתונים שלך

תבנית ברירת מחדל: tfds new

השתמש ב- TFDS CLI כדי ליצור את קובצי תבנית הפיתון הנדרשים.

cd path/to/project/datasets/  # Or use `--dir=path/to/project/datasets/` below
tfds new my_dataset

פקודה זו תיצור תיקיית my_dataset/ חדשה עם המבנה הבא:

my_dataset/
    __init__.py
    README.md # Markdown description of the dataset.
    CITATIONS.bib # Bibtex citation for the dataset.
    TAGS.txt # List of tags describing the dataset.
    my_dataset_dataset_builder.py # Dataset definition
    my_dataset_dataset_builder_test.py # Test
    dummy_data/ # (optional) Fake data (used for testing)
    checksum.tsv # (optional) URL checksums (see `checksums` section).

חפש כאן TODO(my_dataset) ושנה בהתאם.

דוגמה למערך נתונים

כל מערכי הנתונים מיושמים תת-מחלקות של tfds.core.DatasetBuilder , אשר דואג לרוב ה-boilerplate. זה תומך:

  • מערכי נתונים קטנים/בינוניים שניתן להפיק במכונה בודדת (מדריך זה).
  • מערכי נתונים גדולים מאוד הדורשים יצירה מבוזרת (באמצעות Apache Beam , עיין במדריך מערכי הנתונים הענק שלנו)

הנה דוגמה מינימלית של בונה מערכי נתונים המבוסס על tfds.core.GeneratorBasedBuilder :

class Builder(tfds.core.GeneratorBasedBuilder):
  """DatasetBuilder for my_dataset dataset."""

  VERSION = tfds.core.Version('1.0.0')
  RELEASE_NOTES = {
      '1.0.0': 'Initial release.',
  }

  def _info(self) -> tfds.core.DatasetInfo:
    """Dataset metadata (homepage, citation,...)."""
    return self.dataset_info_from_configs(
        features=tfds.features.FeaturesDict({
            'image': tfds.features.Image(shape=(256, 256, 3)),
            'label': tfds.features.ClassLabel(
                names=['no', 'yes'],
                doc='Whether this is a picture of a cat'),
        }),
    )

  def _split_generators(self, dl_manager: tfds.download.DownloadManager):
    """Download the data and define splits."""
    extracted_path = dl_manager.download_and_extract('http://data.org/data.zip')
    # dl_manager returns pathlib-like objects with `path.read_text()`,
    # `path.iterdir()`,...
    return {
        'train': self._generate_examples(path=extracted_path / 'train_images'),
        'test': self._generate_examples(path=extracted_path / 'test_images'),
    }

  def _generate_examples(self, path) -> Iterator[Tuple[Key, Example]]:
    """Generator of examples for each split."""
    for img_path in path.glob('*.jpeg'):
      # Yields (key, example)
      yield img_path.name, {
          'image': img_path,
          'label': 'yes' if img_path.name.startswith('yes_') else 'no',
      }

שים לב שעבור כמה פורמטים ספציפיים של נתונים, אנו מספקים בוני נתונים מוכנים לשימוש כדי לטפל ברוב עיבוד הנתונים.

בואו נראה בפירוט את 3 השיטות המופשטות להחלפה.

_info : מטא נתונים של מערך נתונים

_info מחזירה את tfds.core.DatasetInfo המכיל את המטא נתונים של מערך הנתונים .

def _info(self):
  # The `dataset_info_from_configs` base method will construct the
  # `tfds.core.DatasetInfo` object using the passed-in parameters and
  # adding: builder (self), description/citations/tags from the config
  # files located in the same package.
  return self.dataset_info_from_configs(
      homepage='https://dataset-homepage.org',
      features=tfds.features.FeaturesDict({
          'image_description': tfds.features.Text(),
          'image': tfds.features.Image(),
          # Here, 'label' can be 0-4.
          'label': tfds.features.ClassLabel(num_classes=5),
      }),
      # If there's a common `(input, target)` tuple from the features,
      # specify them here. They'll be used if as_supervised=True in
      # builder.as_dataset.
      supervised_keys=('image', 'label'),
      # Specify whether to disable shuffling on the examples. Set to False by default.
      disable_shuffling=False,
  )

רוב השדות צריכים להיות מובנים מאליהם. כמה דיוקים:

כתיבת הקובץ BibText CITATIONS.bib :

  • חפש באתר מערך הנתונים עבור הוראת ציטוט (השתמש בזה בפורמט BibTex).
  • עבור ניירות arXiv : מצא את הנייר ולחץ על הקישור BibText בצד ימין.
  • מצא את המאמר ב- Google Scholar ולחץ על המירכאות הכפולות מתחת לכותרת ובחלון הקופץ, לחץ על BibTeX .
  • אם אין נייר משויך (לדוגמה, יש רק אתר אינטרנט), אתה יכול להשתמש בעורך BibTeX Online כדי ליצור ערך BibTeX מותאם אישית (בתפריט הנפתח יש סוג ערך Online ).

עדכון קובץ TAGS.txt :

  • כל התגים המותרים ממולאים מראש בקובץ שנוצר.
  • הסר את כל התגים שאינם חלים על מערך הנתונים.
  • תגים חוקיים מופיעים ב- tensorflow_datasets/core/valid_tags.txt .
  • כדי להוסיף תג לרשימה זו, שלח יח"צ.

שמור על סדר הנתונים

כברירת מחדל, הרשומות של מערכי הנתונים עוברות ערבוב בעת אחסון על מנת להפוך את התפלגות המחלקות לאחידה יותר על פני מערך הנתונים, מכיוון שלעתים קרובות רשומות השייכות לאותה מחלקה הן רציפות. על מנת לציין שיש למיין את מערך הנתונים לפי המפתח שנוצר על ידי _generate_examples , יש להגדיר את השדה disable_shuffling כ- True . כברירת מחדל הוא מוגדר כ- False .

def _info(self):
  return self.dataset_info_from_configs(
    # [...]
    disable_shuffling=True,
    # [...]
  )

זכור שלהשבתת ערבוב יש השפעה על הביצועים מכיוון שלא ניתן יותר לקרוא רסיסים במקביל.

_split_generators : מוריד ומפצל נתונים

הורדה וחילוץ של נתוני מקור

רוב מערכי הנתונים צריכים להוריד נתונים מהאינטרנט. זה נעשה באמצעות ארגומנט הקלט tfds.download.DownloadManager של _split_generators . dl_manager יש את השיטות הבאות:

  • download : תומך ב- http(s):// , ftp(s)://
  • extract : תומך כעת בקבצי .zip , .gz ו- .tar .
  • download_and_extract : זהה ל- dl_manager.extract(dl_manager.download(urls))

כל השיטות הללו מחזירות tfds.core.Path (כינויים עבור epath.Path ), שהם אובייקטים דמויי pathlib.Path .

שיטות אלו תומכות במבנה מקונן שרירותי ( list , dict ), כמו:

extracted_paths = dl_manager.download_and_extract({
    'foo': 'https://example.com/foo.zip',
    'bar': 'https://example.com/bar.zip',
})
# This returns:
assert extracted_paths == {
    'foo': Path('/path/to/extracted_foo/'),
    'bar': Path('/path/extracted_bar/'),
}

הורדה וחילוץ ידני

חלק מהנתונים לא ניתנים להורדה אוטומטית (למשל דורש התחברות), במקרה זה, המשתמש יוריד ידנית את נתוני המקור ויציב אותם ב- manual_dir/ (ברירת המחדל היא ~/tensorflow_datasets/downloads/manual/ ).

לאחר מכן ניתן לגשת לקבצים דרך dl_manager.manual_dir :

class MyDataset(tfds.core.GeneratorBasedBuilder):

  MANUAL_DOWNLOAD_INSTRUCTIONS = """
  Register into https://example.org/login to get the data. Place the `data.zip`
  file in the `manual_dir/`.
  """

  def _split_generators(self, dl_manager):
    # data_path is a pathlib-like `Path('<manual_dir>/data.zip')`
    archive_path = dl_manager.manual_dir / 'data.zip'
    # Extract the manually downloaded `data.zip`
    extracted_path = dl_manager.extract(archive_path)
    ...

ניתן להתאים את מיקום manual_dir באמצעות tfds build --manual_dir= או באמצעות tfds.download.DownloadConfig .

קרא את הארכיון ישירות

dl_manager.iter_archive קורא ארכיון ברצף מבלי לחלץ אותם. זה יכול לחסוך בשטח אחסון ולשפר את הביצועים במערכות קבצים מסוימות.

for filename, fobj in dl_manager.iter_archive('path/to/archive.zip'):
  ...

fobj יש אותן שיטות כמו with open('rb') as fobj: (למשל fobj.read() )

ציון פיצולי מערך נתונים

אם מערך הנתונים מגיע עם פיצולים מוגדרים מראש (לדוגמה, MNIST יש פיצולי train test ), שמור אותם. אחרת, ציין רק פיצול all . משתמשים יכולים ליצור באופן דינמי חלוקות משנה משלהם עם ממשק ה-API של subsplit (למשל split='train[80%:]' ). שים לב שכל מחרוזת אלפביתית יכולה לשמש כשם מפוצל, מלבד all שהוזכר לעיל.

def _split_generators(self, dl_manager):
  # Download source data
  extracted_path = dl_manager.download_and_extract(...)

  # Specify the splits
  return {
      'train': self._generate_examples(
          images_path=extracted_path / 'train_imgs',
          label_path=extracted_path / 'train_labels.csv',
      ),
      'test': self._generate_examples(
          images_path=extracted_path / 'test_imgs',
          label_path=extracted_path / 'test_labels.csv',
      ),
  }

_generate_examples : מחולל לדוגמה

_generate_examples יוצר את הדוגמאות עבור כל פיצול מנתוני המקור.

שיטה זו תקרא בדרך כלל חפצי נתונים של מקור (למשל קובץ CSV) ותניב (key, feature_dict) tuples:

  • key : מזהה לדוגמה. משמש כדי לערבב באופן דטרמיניסטי את הדוגמאות באמצעות hash(key) או כדי למיין לפי מפתח כאשר ערבוב מושבת (ראה סעיף שמירה על סדר הנתונים ). צריך להיות:
    • ייחודי : אם שתי דוגמאות משתמשות באותו מפתח, יועלה חריגה.
    • דטרמיניסטית : לא אמור להיות תלוי ב- download_dir , os.path.listdir סדר,... יצירת הנתונים פעמיים אמורה להניב את אותו מפתח.
    • דומה : אם ערבוב מושבת, המפתח ישמש למיון מערך הנתונים.
  • feature_dict : dict המכיל את הערכים לדוגמה.
    • המבנה צריך להתאים features= שהוגדר ב- tfds.core.DatasetInfo .
    • סוגי נתונים מורכבים (תמונה, וידאו, אודיו,...) יקודדו אוטומטית.
    • כל תכונה מקבלת לעתים קרובות סוגי קלט מרובים (למשל video accept /path/to/vid.mp4 , np.array(shape=(l, h, w, c)) , List[paths] , List[np.array(shape=(h, w, c)] , List[img_bytes] ,...)
    • עיין במדריך מחברי התכונות למידע נוסף.
def _generate_examples(self, images_path, label_path):
  # Read the input data out of the source files
  with label_path.open() as f:
    for row in csv.DictReader(f):
      image_id = row['image_id']
      # And yield (key, feature_dict)
      yield image_id, {
          'image_description': row['description'],
          'image': images_path / f'{image_id}.jpeg',
          'label': row['label'],
      }

גישה לקובץ ו- tf.io.gfile

על מנת לתמוך במערכות אחסון בענן, הימנע משימוש ב-Python ה-I/O ops המובנה.

במקום זאת, ה- dl_manager מחזיר אובייקטים דמויי pathlib התואמים ישירות לאחסון של Google Cloud:

path = dl_manager.download_and_extract('http://some-website/my_data.zip')

json_path = path / 'data/file.json'

json.loads(json_path.read_text())

לחלופין, השתמש ב- tf.io.gfile API במקום מובנה עבור פעולות קבצים:

יש להעדיף את Pathlib על tf.io.gfile (ראה רציונלי .

תלות נוספת

מערכי נתונים מסוימים דורשים תלות נוספת של Python רק במהלך היצירה. לדוגמה, מערך הנתונים של SVHN משתמש ב- scipy כדי לטעון נתונים מסוימים.

אם אתה מוסיף מערך נתונים למאגר TFDS, אנא השתמש ב- tfds.core.lazy_imports כדי לשמור על חבילת tensorflow-datasets קטנה. משתמשים יתקינו תלות נוספות רק לפי הצורך.

כדי להשתמש lazy_imports :

  • הוסף ערך עבור מערך הנתונים שלך ל- DATASET_EXTRAS ב- setup.py . זה עושה את זה כך שמשתמשים יכולים לבצע, למשל, pip install 'tensorflow-datasets[svhn]' כדי להתקין את התלות הנוספת.
  • הוסף ערך לייבוא ​​שלך ל- LazyImporter ול- LazyImportsTest .
  • השתמש tfds.core.lazy_imports כדי לגשת לתלות (לדוגמה, tfds.core.lazy_imports.scipy ) ב- DatasetBuilder שלך.

נתונים פגומים

חלק מערכי הנתונים אינם נקיים לחלוטין ומכילים כמה נתונים פגומים (לדוגמה, התמונות נמצאות בקובצי JPEG אך חלקן אינן חוקיות ב-JPEG). יש לדלג על דוגמאות אלו, אך השאירו הערה בתיאור מערך הנתונים כמה דוגמאות נשמטו ומדוע.

תצורה/וריאציות של ערכת נתונים (tfds.core.BuilderConfig)

לכמה מערכי נתונים עשויים להיות גרסאות מרובות, או אפשרויות לאופן שבו הנתונים מעובדים מראש ונכתבים לדיסק. לדוגמה, ל-cycle_gan יש תצורה אחת לכל זוגות אובייקטים ( cycle_gan/horse2zebra , cycle_gan/monet2photo ,...).

זה נעשה באמצעות tfds.core.BuilderConfig s:

  1. הגדר את אובייקט התצורה שלך כתת-מחלקה של tfds.core.BuilderConfig . לדוגמה, MyDatasetConfig .

    @dataclasses.dataclass
    class MyDatasetConfig(tfds.core.BuilderConfig):
      img_size: Tuple[int, int] = (0, 0)
    
  2. הגדר את חבר הכיתה BUILDER_CONFIGS = [] ב- MyDataset שמפרט MyDatasetConfig s שמערך הנתונים חושף.

    class MyDataset(tfds.core.GeneratorBasedBuilder):
      VERSION = tfds.core.Version('1.0.0')
      # pytype: disable=wrong-keyword-args
      BUILDER_CONFIGS = [
          # `name` (and optionally `description`) are required for each config
          MyDatasetConfig(name='small', description='Small ...', img_size=(8, 8)),
          MyDatasetConfig(name='big', description='Big ...', img_size=(32, 32)),
      ]
      # pytype: enable=wrong-keyword-args
    
  3. השתמש ב- self.builder_config ב- MyDataset כדי להגדיר יצירת נתונים (למשל shape=self.builder_config.img_size ). זה עשוי לכלול הגדרת ערכים שונים ב _info() או שינוי הגישה לנתוני הורדה.

הערות:

  • לכל תצורה יש שם ייחודי. השם המלא של תצורה הוא dataset_name/config_name (למשל coco/2017 ).
  • אם לא צוין, התצורה הראשונה ב- BUILDER_CONFIGS תשמש (למשל tfds.load('c4') ברירת המחדל היא c4/en )

ראה anli לדוגמא של מערך נתונים המשתמש ב- BuilderConfig s.

גִרְסָה

הגרסה יכולה להתייחס לשתי משמעויות שונות:

  • גרסת הנתונים המקורית "חיצונית": למשל COCO v2019, v2017,...
  • גרסת קוד TFDS "פנימית": למשל שנה שם תכונה ב- tfds.features.FeaturesDict , תקן באג ב- _generate_examples

כדי לעדכן מערך נתונים:

  • לעדכון נתונים "חיצוני": ייתכן שמספר משתמשים ירצו לגשת לשנה/גרסה ספציפית בו-זמנית. זה נעשה על ידי שימוש ב- tfds.core.BuilderConfig אחד לכל גרסה (למשל coco/2017 , coco/2019 ) או מחלקה אחת לכל גרסה (למשל Voc2007 , Voc2012 ).
  • לעדכון קוד "פנימי": משתמשים מורידים רק את הגרסה העדכנית ביותר. כל עדכון קוד צריך להגדיל את תכונת המחלקה VERSION (למשל מ 1.0.0 ל- VERSION = tfds.core.Version('2.0.0') ) בעקבות גירסה סמנטית .

הוסף ייבוא ​​לרישום

אל תשכח לייבא את מודול מערך הנתונים לפרויקט שלך __init__ כדי להירשם אוטומטית ב- tfds.load , tfds.builder .

import my_project.datasets.my_dataset  # Register MyDataset

ds = tfds.load('my_dataset')  # MyDataset available

לדוגמה, אם אתה תורם ל- tensorflow/datasets , הוסף את ייבוא ​​המודול לספריית המשנה שלו __init__.py (למשל image/__init__.py .

בדוק אם יש שיטות יישום נפוצות

אנא בדוק את שיטות היישום הנפוצות .

בדוק את מערך הנתונים שלך

הורד והכנה: tfds build

כדי ליצור את מערך הנתונים, הפעל tfds build מהספרייה my_dataset/ :

cd path/to/datasets/my_dataset/
tfds build --register_checksums

כמה דגלים שימושיים לפיתוח:

  • --pdb : היכנס למצב ניפוי באגים אם הועלה חריג.
  • --overwrite : מחק קבצים קיימים אם מערך הנתונים כבר נוצר.
  • --max_examples_per_split : צור רק את X הדוגמאות הראשונות (ברירת המחדל היא 1), במקום את מערך הנתונים המלא.
  • --register_checksums : רשום את סכומי הבדיקה של כתובות אתרים שהורדו. יש להשתמש רק בזמן הפיתוח.

עיין בתיעוד CLI לרשימה מלאה של דגלים.

סכומי המחאה

מומלץ לרשום את סכימי הבדיקה של מערכי הנתונים שלך כדי להבטיח דטרמיניזם, עזרה בתיעוד,... זה נעשה על ידי הפקת מערך הנתונים עם --register_checksums (ראה סעיף קודם).

אם אתה משחרר את מערכי הנתונים שלך דרך PyPI, אל תשכח לייצא את קבצי checksums.tsv (למשל ב- package_data של setup.py שלך).

בדוק ביחידה את מערך הנתונים שלך

tfds.testing.DatasetBuilderTestCase הוא TestCase בסיסי למימוש מלא של מערך נתונים. הוא משתמש ב"נתוני דמה" כנתוני בדיקה המחקים את המבנה של מערך הנתונים של המקור.

  • יש לשים את נתוני הבדיקה בספריית my_dataset/dummy_data/ ועליהם לחקות את חפצי הנתונים של המקור כפי שהורדו וחולצו. ניתן ליצור אותו באופן ידני או אוטומטי עם סקריפט ( סקריפט לדוגמה ).
  • הקפד להשתמש בנתונים שונים בפיצול נתוני הבדיקה שלך, מכיוון שהבדיקה תיכשל אם פיצולי הנתונים שלך חופפים.
  • נתוני הבדיקה לא צריכים להכיל חומר המוגן בזכויות יוצרים . אם יש ספק, אל תיצור את הנתונים באמצעות חומר ממערך הנתונים המקורי.
import tensorflow_datasets as tfds
from . import my_dataset_dataset_builder


class MyDatasetTest(tfds.testing.DatasetBuilderTestCase):
  """Tests for my_dataset dataset."""
  DATASET_CLASS = my_dataset_dataset_builder.Builder
  SPLITS = {
      'train': 3,  # Number of fake train example
      'test': 1,  # Number of fake test example
  }

  # If you are calling `download/download_and_extract` with a dict, like:
  #   dl_manager.download({'some_key': 'http://a.org/out.txt', ...})
  # then the tests needs to provide the fake output paths relative to the
  # fake data directory
  DL_EXTRACT_RESULT = {
      'name1': 'path/to/file1',  # Relative to my_dataset/dummy_data dir.
      'name2': 'file2',
  }


if __name__ == '__main__':
  tfds.testing.test_main()

הפעל את הפקודה הבאה כדי לבדוק את מערך הנתונים.

python my_dataset_test.py

שלח לנו משוב

אנו מנסים ללא הרף לשפר את זרימת העבודה של יצירת מערך הנתונים, אך נוכל לעשות זאת רק אם אנו מודעים לבעיות. באילו בעיות או שגיאות נתקלת במהלך יצירת מערך הנתונים? האם היה חלק מבלבל, או לא עבד בפעם הראשונה?

אנא שתף ​​את המשוב שלך על GitHub .