¡Google I/O es una envoltura! Póngase al día con las sesiones de TensorFlow Ver sesiones

Escritura de conjuntos de datos personalizados

Siga esta guía para crear un nuevo conjunto de datos (ya sea en TFDS o en su propio repositorio).

Consulte nuestra lista de conjuntos de datos para ver si el conjunto de datos que desea ya está presente.

TL;RD

La forma más fácil de escribir un nuevo conjunto de datos es usar la CLI de TFDS :

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

Para usar el nuevo conjunto de datos con tfds.load('my_dataset') :

  • tfds.load detectará y cargará automáticamente el conjunto de datos generado en ~/tensorflow_datasets/my_dataset/ (por ejemplo, por tfds build ).
  • Alternativamente, puede import my.project.datasets.my_dataset para registrar su conjunto de datos:
import my.project.datasets.my_dataset  # Register `my_dataset`

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

Visión de conjunto

Los conjuntos de datos se distribuyen en todo tipo de formatos y en todo tipo de lugares, y no siempre se almacenan en un formato que esté listo para alimentar una canalización de aprendizaje automático. Introduzca TFDS.

TFDS procesa esos conjuntos de datos en un formato estándar (datos externos -> archivos serializados), que luego se pueden cargar como canalización de aprendizaje automático (archivos serializados -> tf.data.Dataset ). La serialización se realiza una sola vez. El acceso posterior leerá directamente esos archivos preprocesados.

La mayor parte del preprocesamiento se realiza automáticamente. Cada conjunto de datos implementa una subclase de tfds.core.DatasetBuilder , que especifica:

  • De dónde provienen los datos (es decir, sus URL);
  • Aspecto del conjunto de datos (es decir, sus características);
  • Cómo se deben dividir los datos (por ejemplo, TRAIN y TEST );
  • y los ejemplos individuales en el conjunto de datos.

Escribe tu conjunto de datos

Plantilla predeterminada: tfds new

Utilice la CLI de TFDS para generar los archivos de plantilla de Python necesarios.

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

Este comando generará una nueva carpeta my_dataset/ con la siguiente estructura:

my_dataset/
    __init__.py
    my_dataset.py # Dataset definition
    my_dataset_test.py # (optional) Test
    dummy_data/ # (optional) Fake data (used for testing)
    checksum.tsv # (optional) URL checksums (see `checksums` section).

Busque TODO(my_dataset) aquí y modifíquelo en consecuencia.

Ejemplo de conjunto de datos

Todos los conjuntos de datos se implementan como tfds.core.GeneratorBasedBuilder , una subclase de tfds.core.DatasetBuilder que se encarga de la mayoría de los repetitivos. Es compatible con:

  • Conjuntos de datos pequeños/medianos que se pueden generar en una sola máquina (este tutorial).
  • Conjuntos de datos muy grandes que requieren generación distribuida (usando Apache Beam , consulte nuestra guía de conjunto de datos enorme )

Aquí hay un ejemplo mínimo de clase de conjunto de datos:

class MyDataset(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 tfds.core.DatasetInfo(
        builder=self,
        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',
      }

Veamos en detalle los 3 métodos abstractos para sobrescribir.

_info : metadatos del conjunto de datos

_info devuelve tfds.core.DatasetInfo que contiene los metadatos del conjunto de datos.

def _info(self):
  return tfds.core.DatasetInfo(
      builder=self,
      # Description and homepage used for documentation
      description="""
      Markdown description of the dataset. The text will be automatically
      stripped and dedent.
      """,
      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,
      # Bibtex citation for the dataset
      citation=r"""
      @article{my-awesome-dataset-2020,
               author = {Smith, John},}
      """,
  )

La mayoría de los campos deben explicarse por sí mismos. Algunas precisiones:

  • features : esto especifica la estructura del conjunto de datos, la forma,... Admite tipos de datos complejos (audio, video, secuencias anidadas,...). Consulte las funciones disponibles o la guía del conector de funciones para obtener más información.
  • disable_shuffling : consulte la sección Mantener el orden del conjunto de datos .
  • citation : Para encontrar la cita de BibText :
    • Busque en el sitio web del conjunto de datos las instrucciones de citas (utilícelas en formato BibTex).
    • Para artículos de arXiv : busque el artículo y haga clic en el enlace BibText en el lado derecho.
    • Busque el documento en Google Scholar y haga clic en las comillas dobles debajo del título y en la ventana emergente, haga clic en BibTeX .
    • Si no hay un artículo asociado (por ejemplo, solo hay un sitio web), puede usar el editor en línea de BibTeX para crear una entrada BibTeX personalizada (el menú desplegable tiene un tipo de entrada en Online ).

Mantener el orden del conjunto de datos

De forma predeterminada, los registros de los conjuntos de datos se mezclan cuando se almacenan para que la distribución de clases sea más uniforme en el conjunto de datos, ya que a menudo los registros que pertenecen a la misma clase son contiguos. Para especificar que el conjunto de datos debe ordenarse por la clave generada proporcionada por _generate_examples , el campo disable_shuffling debe establecerse en True . De forma predeterminada, se establece en False .

def _info(self):
  return tfds.core.DatasetInfo(
    # [...]
    disable_shuffling=True,
    # [...]
  )

Tenga en cuenta que deshabilitar la reproducción aleatoria tiene un impacto en el rendimiento, ya que los fragmentos ya no se pueden leer en paralelo.

_split_generators : descarga y divide datos

Descarga y extracción de datos de origen

La mayoría de los conjuntos de datos necesitan descargar datos de la web. Esto se hace usando el argumento de entrada tfds.download.DownloadManager de _split_generators . dl_manager tiene los siguientes métodos:

  • download : compatible con http(s):// , ftp(s)://
  • extract : actualmente admite archivos .zip , .gz y .tar .
  • download_and_extract : Igual que dl_manager.extract(dl_manager.download(urls))

Todos esos métodos devuelven tfds.core.Path (alias para epath.Path ), que son objetos similares a pathlib.Path .

Esos métodos admiten una estructura anidada arbitraria ( list , dict ), como:

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/'),
}

Descarga y extracción manual

Algunos datos no se pueden descargar automáticamente (por ejemplo, requieren un inicio de sesión); en este caso, el usuario descargará manualmente los datos de origen y los colocará en manual_dir/ (el valor predeterminado es ~/tensorflow_datasets/downloads/manual/ ).

Luego se puede acceder a los archivos a través 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)
    ...

La ubicación de manual_dir se puede personalizar con tfds build --manual_dir= o usando tfds.download.DownloadConfig .

Leer archivo directamente

dl_manager.iter_archive lee archivos secuencialmente sin extraerlos. Esto puede ahorrar espacio de almacenamiento y mejorar el rendimiento en algunos sistemas de archivos.

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

fobj tiene los mismos métodos que with open('rb') as fobj: (por ejemplo fobj.read() )

Especificar divisiones de conjuntos de datos

Si el conjunto de datos viene con divisiones predefinidas (p. ej., MNIST tiene divisiones de train y test ), consérvelas. De lo contrario, solo especifique una sola división tfds.Split.TRAIN . Los usuarios pueden crear dinámicamente sus propias subdivisiones con la API de subdivisiones (por ejemplo, split='train[80%:]' ).

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 : generador de ejemplo

_generate_examples genera los ejemplos para cada división a partir de los datos de origen.

Este método normalmente leerá artefactos del conjunto de datos de origen (p. ej., un archivo CSV) y producirá tuplas (key, feature_dict) :

  • key : Ejemplo de identificador. Se utiliza para mezclar de forma determinista los ejemplos usando hash(key) o para ordenar por clave cuando la reproducción aleatoria está deshabilitada (consulte la sección Mantener el orden del conjunto de datos ). Debiera ser:
    • único : si dos ejemplos usan la misma clave, se generará una excepción.
    • determinista : no debería depender de download_dir , os.path.listdir order,... Generar los datos dos veces debería generar la misma clave.
    • comparable : si la reproducción aleatoria está deshabilitada, la tecla se usará para ordenar el conjunto de datos.
  • feature_dict : un dict que contiene los valores de ejemplo.
    • La estructura debe coincidir features= definida en tfds.core.DatasetInfo .
    • Los tipos de datos complejos (imagen, video, audio,...) se codificarán automáticamente.
    • Cada característica a menudo acepta múltiples tipos de entrada (por ejemplo, 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] ,...)
    • Consulte la guía del conector de funciones para obtener más información.
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'],
      }

Acceso a archivos y tf.io.gfile

Para admitir sistemas de almacenamiento en la nube, evite el uso de las operaciones de E/S integradas de Python.

En cambio, dl_manager devuelve objetos similares a pathlib directamente compatibles con el almacenamiento de 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())

Alternativamente, use la API tf.io.gfile en lugar de la integrada para operaciones con archivos:

Se debe preferir Pathlib a tf.io.gfile (ver .

Dependencias adicionales

Algunos conjuntos de datos requieren dependencias de Python adicionales solo durante la generación. Por ejemplo, el conjunto de datos SVHN usa scipy para cargar algunos datos.

Si está agregando un conjunto de datos al repositorio de TFDS, use tfds.core.lazy_imports para mantener pequeño el paquete tensorflow-datasets . Los usuarios instalarán dependencias adicionales solo según sea necesario.

Para usar lazy_imports :

  • Agregue una entrada para su conjunto de datos en DATASET_EXTRAS en setup.py . Esto hace que los usuarios puedan hacer, por ejemplo, pip install 'tensorflow-datasets[svhn]' para instalar las dependencias adicionales.
  • Agregue una entrada para su importación a LazyImporter y a LazyImportsTest .
  • Use tfds.core.lazy_imports para acceder a la dependencia (por ejemplo, tfds.core.lazy_imports.scipy ) en su DatasetBuilder .

Datos corrompidos

Algunos conjuntos de datos no están perfectamente limpios y contienen algunos datos corruptos (por ejemplo, las imágenes están en archivos JPEG pero algunos son JPEG no válidos). Estos ejemplos deben omitirse, pero deje una nota en la descripción del conjunto de datos sobre cuántos ejemplos se eliminaron y por qué.

Configuración/variantes del conjunto de datos (tfds.core.BuilderConfig)

Algunos conjuntos de datos pueden tener múltiples variantes u opciones sobre cómo los datos se procesan previamente y se escriben en el disco. Por ejemplo, cycle_gan tiene una configuración por pares de objetos ( cycle_gan/horse2zebra , cycle_gan/monet2photo ,...).

Esto se hace a través tfds.core.BuilderConfig s:

  1. Defina su objeto de configuración como una subclase de tfds.core.BuilderConfig . Por ejemplo, MyDatasetConfig .

    @dataclasses.dataclass
    class MyDatasetConfig(tfds.core.BuilderConfig):
      img_size: Tuple[int, int] = (0, 0)
    
  2. Defina el miembro de clase BUILDER_CONFIGS = [] en MyDataset que enumera MyDatasetConfig s que expone el conjunto de datos.

    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. Utilice self.builder_config en MyDataset para configurar la generación de datos (p. ej. shape=self.builder_config.img_size ). Esto puede incluir establecer diferentes valores en _info() o cambiar el acceso a los datos de descarga.

Notas:

  • Cada configuración tiene un nombre único. El nombre completo de una configuración es dataset_name/config_name (por ejemplo coco/2017 ).
  • Si no se especifica, se usará la primera configuración en BUILDER_CONFIGS (por ejemplo tfds.load('c4') por defecto es c4/en )

Consulte anli para ver un ejemplo de un conjunto de datos que usa BuilderConfig s.

Versión

La versión puede referirse a dos significados diferentes:

  • La versión de datos original "externa": por ejemplo, COCO v2019, v2017,...
  • La versión de código TFDS "interna": por ejemplo, cambie el nombre de una función en tfds.features.FeaturesDict , corrija un error en _generate_examples

Para actualizar un conjunto de datos:

  • Para actualización de datos "externos": Múltiples usuarios pueden querer acceder a un año/versión específico simultáneamente. Esto se hace usando un tfds.core.BuilderConfig por versión (p. ej., coco/2017 , coco/2019 ) o una clase por versión (p. ej., Voc2007 , Voc2012 ).
  • Para actualización de código "interna": los usuarios solo descargan la versión más reciente. Cualquier actualización de código debe aumentar el atributo de clase VERSION (por ejemplo, de 1.0.0 a VERSION = tfds.core.Version('2.0.0') ) siguiendo el control de versiones semántico .

Agregar una importación para el registro

No olvide importar el módulo del conjunto de datos a su proyecto __init__ para que se registre automáticamente en tfds.load , tfds.builder .

import my_project.datasets.my_dataset  # Register MyDataset

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

Por ejemplo, si está contribuyendo a tensorflow/datasets , agregue la importación del módulo a __init__.py de su subdirectorio (por ejemplo image/__init__.py .

Compruebe si hay problemas comunes de implementación

Verifique los problemas comunes de implementación .

Pruebe su conjunto de datos

Descargar y preparar: tfds build

Para generar el conjunto de datos, ejecute tfds build desde el directorio my_dataset/ :

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

Algunas banderas útiles para el desarrollo:

  • --pdb : ingresa al modo de depuración si se genera una excepción.
  • --overwrite : elimina los archivos existentes si el conjunto de datos ya se generó.
  • --max_examples_per_split : genera solo los primeros X ejemplos (predeterminado en 1), en lugar del conjunto de datos completo.
  • --register_checksums : registra las sumas de verificación de las URL descargadas. Solo debe usarse durante el desarrollo.

Consulte la documentación de CLI para obtener una lista completa de indicadores.

Sumas de comprobación

Se recomienda registrar las sumas de verificación de sus conjuntos de datos para garantizar el determinismo, ayuda con la documentación,... Esto se hace generando el conjunto de datos con --register_checksums (ver sección anterior).

Si está publicando sus conjuntos de datos a través de PyPI, no olvide exportar los archivos checksums.tsv (por ejemplo, en el package_data de su setup.py ).

Prueba unitaria de tu conjunto de datos

tfds.testing.DatasetBuilderTestCase es un TestCase base para ejercitar completamente un conjunto de datos. Utiliza "datos ficticios" como datos de prueba que imitan la estructura del conjunto de datos de origen.

  • Los datos de prueba deben colocarse en my_dataset/dummy_data/ y deben imitar los artefactos del conjunto de datos de origen tal como se descargaron y extrajeron. Se puede crear de forma manual o automática con un script ( script de ejemplo ).
  • Asegúrese de usar datos diferentes en sus divisiones de datos de prueba, ya que la prueba fallará si las divisiones de su conjunto de datos se superponen.
  • Los datos de prueba no deben contener ningún material protegido por derechos de autor . En caso de duda, no cree los datos utilizando material del conjunto de datos original.
import tensorflow_datasets as tfds
from . import my_dataset


class MyDatasetTest(tfds.testing.DatasetBuilderTestCase):
  """Tests for my_dataset dataset."""
  DATASET_CLASS = my_dataset.MyDataset
  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 dummy_data/my_dataset dir.
      'name2': 'file2',
  }


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

Ejecute el siguiente comando para probar el conjunto de datos.

python my_dataset_test.py

Envianos tus comentarios

Continuamente intentamos mejorar el flujo de trabajo de creación de conjuntos de datos, pero solo podemos hacerlo si somos conscientes de los problemas. ¿Qué problemas, errores encontró al crear el conjunto de datos? ¿Había una parte que era confusa, repetitiva o no funcionaba la primera vez? Comparta sus comentarios en github .