🗂️ Fichiers HDF5 (h5py)
Un fichier HDF5 (.h5) permet de stocker des tableaux NumPy (images, étiquettes, métadonnées) dans un seul fichier.
En apprentissage machine, c'est pratique pour :
- regrouper les données d'entraînement dans un format compact ;
- lire/écrire de très gros jeux de données sans tout charger en mémoire vive.
1. Import​
import h5py
2. Créer un fichier .h5​
Exemple avec des images 28x28 en niveaux de gris et des étiquettes de classes (ça me rappelle quelque chose🤔)
import numpy as np
# Création de données synthétiques (1000 exemples d'entraînement, 500 de validation)
x_train = np.random.randint(0, 256, size=(1000, 28, 28, 1), dtype="uint8")
y_train = np.random.randint(0, 10, size=(1000,), dtype="uint8")
x_valid = np.random.randint(0, 256, size=(500, 28, 28, 1), dtype="uint8")
y_valid = np.random.randint(0, 10, size=(500,), dtype="uint8")
# Création du fichier HDF5
with h5py.File("dataset.h5", "w") as f:
f.create_dataset("x_train", data=x_train)
f.create_dataset("y_train", data=y_train)
f.create_dataset("x_valid", data=x_valid)
f.create_dataset("y_valid", data=y_valid)
# Optionnel : métadonnées utiles
f.attrs["description"] = "Jeu de données synthétique qui ressemble à MNIST"
f.attrs["nb_classes"] = 10
Ce code crée un fichier dataset.h5 avec :
- un dataset
x_train; - un dataset
y_train; - un dataset
x_valid; - un dataset
y_valid; - des attributs (métadonnées) du fichier.
3. Lire le fichier .h5​
with h5py.File("dataset.h5", "r") as f:
print(f"Jeux de données : {f.keys()}") # Affiche la clef des différents *datasets* présents dans le fichier
print(f"Attributs : {dict(f.attrs).keys()}\n", ) # Affiche la clef des différent attributs
x_train = f["x_train"][:] # On charge tout en mémoire vive (⚠️ Ne pas faire ça avec un gros fichier)
y_train = f["y_train"][:]
x_valid = f["x_valid"][:]
y_valid = f["y_valid"][:]
print(f.attrs["description"])
print(x_train.shape) # (1000, 28, 28, 1)
print(y_train.shape) # (1000,)
print(x_valid.shape) # (500, 28, 28, 1)
print(y_valid.shape) # (500,)
On peut ensuite utiliser les variables x_train et y_train directement avec la fonction fit de Keras.
En faisant f["x_train"][:], on charge tout l'ensemble de données en mémoire vive, on ne veut pas faire ça quand on a beaucoup de données.
4. Gros jeux de données : 💡 blocs (chunks)​
Quand un jeu de données est trop gros pour la RAM, on n'utilise pas [:] (chargement complet).
En HDF5, on peut stocker les données en blocs (chunks) :
Il est judicieux de choisir comme taille des chunks un multiple de la taille des batchs afin de s'assurer qu'ils soient bien alignés (réduction du nombre de lectures). C'est une bonne habitude d'utiliser des multiples de 32 pour les batchs (les gpus sont souvent optimisés pour gérer des blocs de 32 éléments).
Des chunks trop petits multiplient les accès disque. Des chunks trop gros consomment plus de mémoire à chaque lecture.
Il est important de mélanger les données pour remplir le fichier .h5 (les batchs seront définies par l'ordre des données dans le fichier) et d'utiliser shuffle=False comme argument de la fonction fit (pour éviter des lectures inutiles).
Création d'un fichier h5 avec des chunks​
with h5py.File("dataset.h5", "w") as f:
# On doit initialiser des *datasets* avec les bonnes informations
# Ici un chunk correspond Ă 1024 exemples
dataset_x = f.create_dataset("x_train",
shape = (200000, 128, 128, 3),
chunks=(1024, 128, 128, 3),
dtype = "uint8")
dataset_y = f.create_dataset("y_train",
shape = (200000),
chunks=(1024,),
dtype = "uint8")
# On peut remplir les données du fichier .h5 séquentiellement, par bloc, comme cela :
for i in range(0, 200000, 1024):
dataset_x[i:i+1024] = np.random.randint(0, 256, size=(1024, 128, 128, 3), dtype="uint8")
dataset_y[i:i+1024] = np.random.randint(0, 10, size=(1024,), dtype="uint8")
# Ici on utilise des données synthétiques
# Mais on pourrait lire des fichiers au fur et Ă mesure pour remplir les blocs des *datasets*
# dataset.h5 fait plus de 9 Go mais on utilise peu de RAM pour faire ces opérations
# Exemple de lecture partielle
with h5py.File("dataset.h5", "r") as f:
print(f"Nombre d'exemples : {len(f["x_train"])}") # Ceci ne charge pas tout le dataset en mémoire
print(f"Taille des blocs: {f["x_train"].chunks}") # Permet de connaître la taille des blocs
print(f["x_train"][1024:1100]) # On ne chargera en mémoire vive qu'un chunk de 1024 exemples
print(f["x_train"][1024:2100]) # On ne chargera en mémoire vive que deux chunks de 1024 exemples
Comment utiliser les chunks avec Keras​
Si on fournit à la méthode fit un objet qui retourne une batch à la fois, Keras appelle cet objet automatiquement à chaque itération. On peut donc lire un bloc HDF5 par batch sans charger tout le fichier. Pour faire cela, nous créons ici une nouvelle classe HDF5Sequence qui hérite de la classe keras.utils.Sequence et qui permet de faire une lecture séquentielle des blocs du fichier .h5.
import keras
# Création de notre classe personnalisée permettant de lire séquentiellement nos données
class HDF5Sequence(keras.utils.Sequence):
# La méthode __init__ permet d'initialiser nos objets de cette classe (c'est le constructeur)
def __init__(self, h5_path, batch_size=128):
super().__init__()
self.h5_path = h5_path
self.batch_size = batch_size
with h5py.File(self.h5_path, "r") as f:
self.n = len(f["x_train"])
# Méthode retournant le nombre de batchs dans nos données
def __len__(self):
return int(np.ceil(self.n / self.batch_size))
# Méthode retournant la batch indexée par idx
def __getitem__(self, idx):
start = idx * self.batch_size
# la dernière batch peut être plus petite, la ligne suivante gère ce cas:
end = min(start + self.batch_size, self.n)
if not hasattr(self, 'f'): # Si on n'a pas encore ouvert le fichier h5, on l'ouvre
self.f = h5py.File(self.h5_path, 'r', swmr=True) # swmr=True aide pour le multi-process
x = self.f["x_train"][start:end] # cela ne va charger que le chunk correspondant
y = self.f["y_train"][start:end] # cela ne va charger que le chunk correspondant
# 🛠️ On peut réaliser d'autres étapes de pré-traitement sur x et y ici ⚙️
return x, y
# Méthode s'exécutant à la suppression de l'objet, on s'assure que l'on libère le fichier
def __del__(self):
if hasattr(self, 'f'):
self.f.close()
train_data = HDF5Sequence("dataset.h5", batch_size=64)
model.fit(train_data, epochs=10)
Dans cet exemple :
- L'objet HDF5Sequence fournit les batchs ;
- Keras orchestre automatiquement les epochs ;
- la mémoire vive reste plus stable, car on ne charge qu'une petite partie des données à la fois.
Lorsque model.fit reçoit un objet Sequence, c'est cet objet qui décide comment chaque batch est lue. L'argument batch_size de fit n'est alors pas utilisé (la taille des batchs est définie dans HDF5Sequence).