🗂️ 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.
import numpy as np
# Exemple de données
x_train = np.random.rand(1000, 28, 28, 1).astype("float32")
y_train = np.random.randint(0, 10, size=(1000,), dtype="int32")
x_valid = np.random.rand(500, 28, 28, 1).astype("float32")
y_valid = np.random.randint(0, 10, size=(500,), dtype="int32")
# 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 de démonstration pour Keras"
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. Relire le fichier .h5​
with h5py.File("dataset.h5", "r") as f:
x_train = f["x_train"][:] # On charge tout en mémoire vive
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.
4. Gros jeux de données : blocs de données (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 minibatchs afin de s'assurer qu'ils soient bien alignés (réduction du nombre de lectures).
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 minibatchs 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).
with h5py.File("dataset_gros.h5", "w") as f:
# Ici un chunk correspond Ă 1000 exemples
dset = f.create_dataset("x_train", shape = (50000, 128, 128, 3), chunks=(1000, 128, 128, 3))
dset_y = f.create_dataset("y_train", shape = (50000), chunks=(1000,))
# On peut remplir les données du fichier .h5 séquentiellement comme cela :
for i in range(0, 50000, 1000):
dset[i:i+1000] = np.random.rand(1000, 128, 128, 3).astype("float32")
dset_y[i:i+1000] = np.random.randint(0, 10, size=(1000,), dtype="int32")
# dataset_gros.h5 fait 8 Go en tout
# Exemple de lecture partielle
with h5py.File("dataset_gros.h5", "r") as f:
print(f["x_train"][1000:1500])
# On ne chargera en mémoire vive qu'un chunk de 1000 exemples pour extraire ces données
Comment utiliser les chunks avec Keras​
Si on fournit à la méthode fit un objet qui retourne une minibatch à la fois, Keras appelle cet objet automatiquement à chaque itération. On peut donc lire un bloc HDF5 par minibatch 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=100):
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 minibatchs dans nos données
def __len__(self):
return int(np.ceil(self.n / self.batch_size))
# Méthode retournant la minibatch indexée par idx
def __getitem__(self, idx):
start = idx * self.batch_size
end = min(start + self.batch_size, self.n)
with h5py.File(self.h5_path, "r") as f:
x = f["x_train"][start:end] # cela ne va charger que le chunk correspondant
y = f["y_train"][start:end] # cela ne va charger que le chunk correspondant
return x, y
train_data = HDF5Sequence("dataset_gros.h5", batch_size=100)
model.fit(train_data, epochs=10)
Dans cet exemple :
- L'objet HDF5Sequence fournit les minibatchs ;
- 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 minibatch est lue. L'argument batch_size de fit n'est alors pas utilisé (la taille des minibatchs est définie dans HDF5Sequence).