# Utiliser Datasets avec TensorFlow

Ce document est une introduction rapide à l'utilisation de `datasets` avec TensorFlow, avec un accent particulier sur la manière d'obtenir des objets `tf.Tensor` à partir de nos datasets, et de diffuser des données depuis les objets `Dataset` de Hugging Face vers des méthodes Keras telles que `model.fit()`.

## Format du jeu de données

Par défaut, les jeux de données renvoient des objets Python classiques : des entiers, des flottants, des chaînes de caractères, des listes, etc.

Pour obtenir des tenseurs TensorFlow à la place, vous pouvez définir le format du jeu de données sur `tf` :

```py
>>> from datasets import Dataset
>>> data = [[1, 2],[3, 4]]
>>> ds = Dataset.from_dict({"data": data})
>>> ds = ds.with_format("tf")
>>> ds[0]
{'data': <tf.Tensor: shape=(2,), dtype=int64, numpy=array([1, 2])>}
>>> ds[:2]
{'data': <tf.Tensor: shape=(2, 2), dtype=int64, numpy=
array([[1, 2],
       [3, 4]])>}
```

> [!TIP]
> Un objet [Dataset](/docs/datasets/v5.2.0/fr/package_reference/main_classes#datasets.Dataset) est un conteneur d'un tableau Arrow, qui permet des lectures rapides depuis les tableaux du jeu de données vers des tenseurs TensorFlow.

Cela peut être utile pour convertir votre jeu de données en dictionnaire d'objets `Tensor`, ou pour écrire un générateur afin de charger des échantillons TF à partir de celui-ci. Si vous souhaitez convertir l'ensemble du jeu de données en `Tensor`, il suffit d'interroger le jeu de données complet :

```py
>>> ds[:]
{'data': <tf.Tensor: shape=(2, 2), dtype=int64, numpy=
array([[1, 2],
       [3, 4]])>}
```

### Tableaux N-dimensionnels

Si votre jeu de données est constitué de tableaux N-dimensionnels, vous constaterez qu’ils sont considérés par défaut comme le même tenseur si la forme est fixe :

```py
>>> from datasets import Dataset
>>> data = [[[1, 2],[3, 4]],[[5, 6],[7, 8]]]  # fixed shape
>>> ds = Dataset.from_dict({"data": data})
>>> ds = ds.with_format("tf")
>>> ds[0]
{'data': <tf.Tensor: shape=(2, 2), dtype=int64, numpy=
 array([[1, 2],
        [3, 4]])>}
```

Sinon, un jeu de données au format TensorFlow renvoie un `RaggedTensor` au lieu d’un tenseur unique :

```py
>>> from datasets import Dataset
>>> data = [[[1, 2],[3]],[[4, 5, 6],[7, 8]]]  # varying shape
>>> ds = Dataset.from_dict({"data": data})
>>> ds = ds.with_format("torch")
>>> ds[0]
{'data': <tf.RaggedTensor [[1, 2], [3]]>}
```

Cependant, cette logique nécessite souvent des comparaisons de formes et des copies de données lentes. Pour l’éviter, vous devez utiliser explicitement le type de fonctionnalité `Array` et spécifier la forme de vos tenseurs :

```py
>>> from datasets import Dataset, Features, Array2D
>>> data = [[[1, 2],[3, 4]],[[5, 6],[7, 8]]]
>>> features = Features({"data": Array2D(shape=(2, 2), dtype='int32')})
>>> ds = Dataset.from_dict({"data": data}, features=features)
>>> ds = ds.with_format("tf")
>>> ds[0]
{'data': <tf.Tensor: shape=(2, 2), dtype=int64, numpy=
 array([[1, 2],
        [3, 4]])>}
>>> ds[:2]
{'data': <tf.Tensor: shape=(2, 2, 2), dtype=int64, numpy=
 array([[[1, 2],
         [3, 4]],
 
        [[5, 6],
         [7, 8]]])>}
```

### Autres types de fonctionnalités

Les données [ClassLabel](/docs/datasets/v5.2.0/fr/package_reference/main_classes#datasets.ClassLabel) sont correctement converties en tenseurs :

```py
>>> from datasets import Dataset, Features, ClassLabel
>>> labels = [0, 0, 1]
>>> features = Features({"label": ClassLabel(names=["negative", "positive"])})
>>> ds = Dataset.from_dict({"label": labels}, features=features) 
>>> ds = ds.with_format("tf")  
>>> ds[:3]
{'label': <tf.Tensor: shape=(3,), dtype=int64, numpy=array([0, 0, 1])>}
```

Les chaînes de caractères et les objets binaires sont également pris en charge :

```py
>>> from datasets import Dataset, Features 
>>> text = ["foo", "bar"]
>>> data = [0, 1] 
>>> ds = Dataset.from_dict({"text": text, "data": data})  
>>> ds = ds.with_format("tf") 
>>> ds[:2]
{'text': <tf.Tensor: shape=(2,), dtype=string, numpy=array([b'foo', b'bar'], dtype=object)>,
 'data': <tf.Tensor: shape=(2,), dtype=int64, numpy=array([0, 1])>}
```

Vous pouvez également formater explicitement certaines colonnes et laisser les autres colonnes non formatées :

```py
>>> ds = ds.with_format("tf", columns=["data"], output_all_columns=True)
>>> ds[:2]
{'data': <tf.Tensor: shape=(2,), dtype=int64, numpy=array([0, 1])>,
 'text': ['foo', 'bar']}
```

Les objets de type chaîne et binaire ne sont pas modifiés, car PyTorch ne prend en charge que les nombres.

Les types de fonctionnalités [Image](/docs/datasets/v5.2.0/fr/package_reference/main_classes#datasets.Image) et [Audio](/docs/datasets/v5.2.0/fr/package_reference/main_classes#datasets.Audio) sont également pris en charge.

> [!TIP]
> Pour utiliser le type de fonctionnalité [Image](/docs/datasets/v5.2.0/fr/package_reference/main_classes#datasets.Image), vous devez installer l'extra `vision` via `pip install datasets[vision]`.

```py
>>> from datasets import Dataset, Features, Audio, Image
>>> images = ["path/to/image.png"] * 10
>>> features = Features({"image": Image()})
>>> ds = Dataset.from_dict({"image": images}, features=features) 
>>> ds = ds.with_format("tf")  
>>> ds[0]
{'image': <tf.Tensor: shape=(512, 512, 4), dtype=uint8, numpy=
 array([[[255, 215, 106, 255],
         [255, 215, 106, 255],
         ...,
         [255, 255, 255, 255],
         [255, 255, 255, 255]]], dtype=uint8)>}
>>> ds[:2]
{'image': <tf.Tensor: shape=(2, 512, 512, 4), dtype=uint8, numpy=
 array([[[[255, 215, 106, 255],
          [255, 215, 106, 255],
          ...,
          [255, 255, 255, 255],
          [255, 255, 255, 255]]]], dtype=uint8)>}
```

> [!TIP]
> Pour utiliser le type de fonctionnalité [Audio](/docs/datasets/v5.2.0/fr/package_reference/main_classes#datasets.Audio), vous devez installer l'extra `audio` via `pip install datasets[audio]`.

```py
>>> from datasets import Dataset, Features, Audio, Image
>>> audio = ["path/to/audio.wav"] * 10
>>> features = Features({"audio": Audio()})
>>> ds = Dataset.from_dict({"audio": audio}, features=features) 
>>> ds = ds.with_format("tf")  
>>> ds[0]["audio"]["array"]
<tf.Tensor: shape=(202311,), dtype=float32, numpy=
array([ 6.1035156e-05,  1.5258789e-05,  1.6784668e-04, ...,
       -1.5258789e-05, -1.5258789e-05,  1.5258789e-05], dtype=float32)>
>>> ds[0]["audio"]["sampling_rate"]
<tf.Tensor: shape=(), dtype=int32, numpy=44100>
```

## Chargement des données

Bien que vous puissiez charger des échantillons et des lots individuels simplement en indexant votre jeu de données, cela ne fonctionnera pas si vous souhaitez utiliser des méthodes Keras comme `fit()` et `predict()`. Vous pourriez écrire une fonction générateur qui mélange et charge des lots depuis votre jeu de données et `fit()` dessus, mais cela semble être beaucoup de travail inutile. À la place, si vous souhaitez diffuser les données de votre jeu de données à la volée, nous recommandons de convertir votre jeu de données en `tf.data.Dataset` à l'aide de la méthode `to_tf_dataset()`.

La classe `tf.data.Dataset` couvre un large éventail de cas d'utilisation - elle est souvent créée à partir de tenseurs en mémoire, ou en utilisant une fonction de chargement pour lire des fichiers sur disque ou sur un stockage externe. Le jeu de données peut être transformé arbitrairement avec la méthode `map()`, ou des méthodes comme `batch()` et `shuffle()` peuvent être utilisées pour créer un jeu de données prêt pour l'entraînement. Ces méthodes ne modifient en rien les données stockées - au lieu de cela, elles construisent un graphe de pipeline de données qui sera exécuté lorsque le jeu de données sera parcouru, généralement pendant l'entraînement ou l'inférence du modèle. Cela diffère de la méthode `map()` des objets `Dataset` de Hugging Face, qui exécute la fonction map immédiatement et enregistre les nouvelles colonnes ou les colonnes modifiées.

Étant donné que l’ensemble du pipeline de prétraitement des données peut être compilé dans un `tf.data.Dataset`, cette approche permet un chargement et un entraînement des données massivement parallèles et asynchrones. Toutefois, l’exigence de compilation en graphe peut constituer une limitation, en particulier pour les tokenizers Hugging Face, qui ne sont généralement pas (encore !) compilables dans le cadre d’un graphe TF. Par conséquent, nous recommandons habituellement de prétraiter le jeu de données en tant que jeu de données Hugging Face, où des fonctions Python arbitraires peuvent être utilisées, puis de le convertir en `tf.data.Dataset` par la suite à l’aide de `to_tf_dataset()` afin d’obtenir un jeu de données par lots prêt pour l’entraînement. Pour voir des exemples de cette approche, veuillez consulter les [exemples](https://github.com/huggingface/transformers/tree/main/examples) ou les [notebooks](https://huggingface.co/docs/transformers/notebooks) pour `transformers`.

### Utilisation de `to_tf_dataset()`

L’utilisation de `to_tf_dataset()` est simple. Une fois votre jeu de données prétraité et prêt, il suffit de l’appeler ainsi :

```py
>>> from datasets import Dataset
>>> data = {"inputs": [[1, 2],[3, 4]], "labels": [0, 1]}
>>> ds = Dataset.from_dict(data)
>>> tf_ds = ds.to_tf_dataset(
            columns=["inputs"],
            label_cols=["labels"],
            batch_size=2,
            shuffle=True
            )
```

L'objet `tf_ds` retourné ici est désormais entièrement prêt pour l'entraînement et peut être transmis directement à `model.fit()`. Notez que vous avez défini la taille du lot lors de la création du jeu de données, il n'est donc pas nécessaire de la spécifier lors de l'appel de `fit()` :

```py
>>> model.fit(tf_ds, epochs=2)
```

Pour une description complète des arguments, veuillez consulter la documentation de [to_tf_dataset()](/docs/datasets/v5.2.0/fr/package_reference/main_classes#datasets.Dataset.to_tf_dataset). Dans de nombreux cas, vous devrez également ajouter un `collate_fn` à votre appel. Il s’agit d’une fonction qui prend plusieurs éléments du jeu de données et les combine en un seul lot. Lorsque tous les éléments ont la même longueur, le collateur par défaut intégré suffit, mais pour des tâches plus complexes, un collateur personnalisé peut s’avérer nécessaire. En particulier, de nombreuses tâches comportent des échantillons de longueurs de séquence variables, ce qui nécessite un [data collator](https://huggingface.co/docs/transformers/main/en/main_classes/data_collator) capable de rembourrer correctement les lots. Vous pouvez en trouver des exemples dans les [examples](https://github.com/huggingface/transformers/tree/main/examples) et les [notebooks](https://huggingface.co/docs/transformers/notebooks) NLP de `transformers`, où les longueurs de séquence variables sont très courantes.

Si vous constatez que le chargement avec `to_tf_dataset` est lent, vous pouvez également utiliser l'argument `num_workers`. Ce dernier lance plusieurs sous-processus pour charger les données en parallèle. Cette fonctionnalité est récente et encore quelque peu expérimentale - veuillez ouvrir un ticket si vous rencontrez des bugs lors de son utilisation !

### Quand utiliser to_tf_dataset

Le lecteur attentif aura peut-être remarqué à ce stade que nous avons proposé deux approches pour atteindre le même objectif – si vous souhaitez passer votre jeu de données à un modèle TensorFlow, vous pouvez soit convertir le jeu de données en `Tensor` ou `dict` de `Tensors` à l’aide de `.with_format('tf')`, soit convertir le jeu de données en `tf.data.Dataset` avec `to_tf_dataset()`. L’une ou l’autre de ces options peut être transmise à `model.fit()`, alors laquelle devriez-vous choisir ?

L’élément clé à retenir est que lorsque vous convertissez l’ensemble du jeu de données en `Tensor`s, celui-ci devient statique et entièrement chargé en RAM. C’est simple et pratique, mais si l’une des conditions suivantes s’applique, vous devriez probablement utiliser `to_tf_dataset()` à la place :

- Votre jeu de données est trop volumineux pour tenir en RAM. `to_tf_dataset()` ne transmet qu'un seul lot à la fois, ce qui permet de traiter même des jeux de données très volumineux avec cette méthode.
- Vous souhaitez appliquer des transformations aléatoires à l’aide de `dataset.with_transform()` ou de `collate_fn`. C’est courant dans plusieurs modalités, comme les augmentations d’images lors de l’entraînement de modèles de vision, ou le masquage aléatoire lors de l’entraînement de modèles de langage masqués. L’utilisation de `to_tf_dataset()` appliquera ces transformations au moment où un lot est chargé, ce qui signifie que les mêmes échantillons recevront des augmentations différentes à chaque fois qu’ils sont chargés. C’est généralement ce que vous souhaitez.
- Vos données ont une dimension variable, comme les textes d’entrée en NLP qui sont composés d’un nombre variable de jetons. Lorsque vous créez un lot avec des échantillons de dimension variable, la solution standard consiste à compléter les échantillons les plus courts jusqu’à la longueur du plus long. Lorsque vous transmettez des échantillons d’un jeu de données avec `to_tf_dataset`, vous pouvez appliquer ce remplissage à chaque lot via votre `collate_fn`. Cependant, si vous souhaitez convertir un tel jeu de données en `Tensor` denses, vous devrez compléter les échantillons jusqu’à la longueur de l’échantillon le plus long de *l’ensemble du jeu de données !* Cela peut entraîner une quantité énorme de remplissage, ce qui gaspille de la mémoire et réduit la vitesse de votre modèle.

### Limites et restrictions

Actuellement, `to_tf_dataset()` renvoie toujours un jeu de données par lots - nous ajouterons bientôt la prise en charge des jeux de données non lotis !

