Apprentissage semi-supervisé (Vision par ordinateur)

ChatGPT a dit :

L’apprentissage semi-supervisé en vision par ordinateur est une approche qui combine un petit ensemble de données étiquetées avec une grande quantité de données non étiquetées pour entraîner des modèles capables de comprendre et d’analyser des images. Cette méthode permet de réduire la dépendance aux annotations coûteuses et chronophages, tout en améliorant les performances des algorithmes. En exploitant les informations contenues dans les images non étiquetées, l’apprentissage semi-supervisé facilite des tâches comme la classification, la détection ou la segmentation d’objets, même lorsque les données labellisées sont limitées.

Fonctions :

  • Vision par ordinateur (exemple)

    La vision par ordinateur est un domaine de l'intelligence artificielle qui permet aux machines de comprendre et d’interpréter des images ou des vidéos, comme reconnaître des objets, détecter des visages, ou analyser des scènes, afin d'automatiser des tâches visuelles complexes. Par exemple, un système de vision par ordinateur peut analyser des photos de routes pour détecter les panneaux de signalisation et aider à la conduite autonome.

    Exemple de code :

    import tensorflow as tf
    import numpy as np
    
    # Charger MNIST
    (x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
    
    # Normalisation + reshape (batch, 28,28,1)
    x_train = x_train.astype("float32") / 255.
    x_test = x_test.astype("float32") / 255.
    x_train = np.expand_dims(x_train, -1)
    x_test = np.expand_dims(x_test, -1)
    
    num_classes = 10
    y_train_cat = tf.keras.utils.to_categorical(y_train, num_classes)
    y_test_cat = tf.keras.utils.to_categorical(y_test, num_classes)
    
    # Séparer 10% étiquetés, 90% non étiquetés
    num_labeled = int(0.1 * len(x_train))
    x_labeled, y_labeled = x_train[:num_labeled], y_train_cat[:num_labeled]
    x_unlabeled = x_train[num_labeled:]
    
    # Modèle simple CNN
    def create_model():
        model = tf.keras.Sequential([
            tf.keras.layers.Conv2D(32, 3, padding="same", activation="relu", input_shape=(28,28,1)),
            tf.keras.layers.MaxPooling2D(),
            tf.keras.layers.Conv2D(64, 3, padding="same", activation="relu"),
            tf.keras.layers.MaxPooling2D(),
            tf.keras.layers.Flatten(),
            tf.keras.layers.Dense(128, activation="relu"),
            tf.keras.layers.Dense(num_classes)
        ])
        return model
    
    model = create_model()
    
    optimizer = tf.keras.optimizers.Adam()
    loss_fn = tf.keras.losses.CategoricalCrossentropy(from_logits=True)
    
    batch_size = 64
    consistency_weight = 1.0
    epochs = 10
    
    # Fonction d'augmentation simple : rotation + bruit
    def augment(images):
        images = tf.image.rot90(images, k=1)
        noise = tf.random.normal(shape=tf.shape(images), mean=0.0, stddev=0.1)
        return images + noise
    
    # Dataset pour étiquetés
    labeled_dataset = tf.data.Dataset.from_tensor_slices((x_labeled, y_labeled))
    labeled_dataset = labeled_dataset.shuffle(1000).batch(batch_size).repeat()
    
    # Dataset pour non-étiquetés
    unlabeled_dataset = tf.data.Dataset.from_tensor_slices(x_unlabeled)
    unlabeled_dataset = unlabeled_dataset.shuffle(10000).batch(batch_size).repeat()
    
    # Dataset test
    test_dataset = tf.data.Dataset.from_tensor_slices((x_test, y_test_cat)).batch(batch_size)
    
    # Iterator
    labeled_iter = iter(labeled_dataset)
    unlabeled_iter = iter(unlabeled_dataset)
    
    # Training loop
    for epoch in range(epochs):
        print(f"Epoch {epoch+1}/{epochs}")
        total_loss = 0.0
        steps_per_epoch = num_labeled // batch_size
        
        for step in range(steps_per_epoch):
            x_lab, y_lab = next(labeled_iter)
            x_unlab = next(unlabeled_iter)
            
            with tf.GradientTape() as tape:
                # Prédiction sur données étiquetées
                logits_lab = model(x_lab, training=True)
                loss_sup = loss_fn(y_lab, logits_lab)
                
                # Prédiction sur données non étiquetées
                logits_unlab = model(x_unlab, training=True)
                logits_unlab_aug = model(augment(x_unlab), training=True)
                
                prob = tf.nn.softmax(logits_unlab, axis=-1)
                prob_aug = tf.nn.softmax(logits_unlab_aug, axis=-1)
                
                loss_consistency = tf.reduce_mean(tf.square(prob - prob_aug))
                
                loss = loss_sup + consistency_weight * loss_consistency
            
            grads = tape.gradient(loss, model.trainable_variables)
            optimizer.apply_gradients(zip(grads, model.trainable_variables))
            
            total_loss += loss.numpy()
        
        print(f"Loss: {total_loss/steps_per_epoch:.4f}")
    
    # Évaluation
    test_acc_metric = tf.keras.metrics.CategoricalAccuracy()
    for x_batch, y_batch in test_dataset:
        logits = model(x_batch, training=False)
        test_acc_metric.update_state(y_batch, logits)
    print(f"Test accuracy: {test_acc_metric.result().numpy():.4f}")

    Explication du code :

    import tensorflow as tf importe la bibliothèque TensorFlow, un framework majeur pour le machine learning et le deep learning.
    
    import numpy as np importe la bibliothèque NumPy, utile pour la manipulation efficace des tableaux et des opérations numériques.
    
    
    1. Chargement et préparation des données MNIST
    (x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data() charge le jeu de données MNIST, constitué d’images de chiffres manuscrits pour l’entraînement et le test. x_train = x_train.astype("float32") / 255. et x_test = x_test.astype("float32") / 255. normalisent les pixels des images pour que leurs valeurs soient entre 0 et 1. x_train = np.expand_dims(x_train, -1) et x_test = np.expand_dims(x_test, -1) ajoutent une dimension au tableau pour indiquer que les images sont en niveau de gris (canal unique). num_classes = 10 définit le nombre de classes, ici 10 pour les chiffres de 0 à 9. y_train_cat = tf.keras.utils.to_categorical(y_train, num_classes) et y_test_cat = tf.keras.utils.to_categorical(y_test, num_classes) convertissent les étiquettes en vecteurs one-hot, adaptés pour la classification multi-classes.
    2. Séparation entre données étiquetées et non étiquetées
    num_labeled = int(0.1 * len(x_train)) calcule 10 % du jeu d’entraînement qui sera considéré comme étiqueté. x_labeled, y_labeled = x_train[:num_labeled], y_train_cat[:num_labeled] extrait ces données étiquetées. x_unlabeled = x_train[num_labeled:] récupère les 90 % restants sans étiquette (non utilisés directement pour la perte supervisée).
    3. Définition du modèle CNN simple
    La fonction create_model() crée un réseau de neurones convolutifs (CNN) simple avec : - Deux couches convolutives avec ReLU et max-pooling, - Une couche flatten pour aplatir, - Une couche dense de 128 neurones avec ReLU, - Une couche finale dense avec 10 sorties (logits) correspondant aux classes.
    4. Configuration de l’entraînement
    optimizer = tf.keras.optimizers.Adam() utilise l’optimiseur Adam pour la mise à jour des poids. loss_fn = tf.keras.losses.CategoricalCrossentropy(from_logits=True) définit la fonction de perte de type entropie croisée adaptée aux logits. batch_size = 64, consistency_weight = 1.0 (poids de la perte de consistance), epochs = 10 définissent les paramètres d’entraînement.
    5. Augmentation des images
    La fonction augment(images) effectue une rotation à 90° des images et ajoute un bruit aléatoire normal, pour créer une version modifiée d’une image afin de renforcer la robustesse du modèle.
    6. Préparation des datasets TensorFlow
    - labeled_dataset contient les données étiquetées, mélangées et découpées en batchs, répétées à l’infini. - unlabeled_dataset contient les données non étiquetées, également mélangées et batchées. - test_dataset contient les données de test batchées.
    7. Boucle d’entraînement semi-supervisé
    Pour chaque époque : - Pour chaque batch de données étiquetées et non étiquetées, on calcule : - logits_lab, prédictions sur données étiquetées → perte supervisée loss_sup avec la fonction d’entropie croisée. - logits_unlab et logits_unlab_aug, prédictions sur données non étiquetées normales et augmentées. - Les probabilités issues de ces logits sont calculées via softmax. - Une perte de consistance loss_consistency est calculée comme la moyenne des carrés des différences entre les deux distributions, pour encourager la stabilité des prédictions malgré l’augmentation. - La perte totale est la somme de la perte supervisée et de la perte de consistance pondérée par consistency_weight. - Le gradient est calculé et appliqué pour mettre à jour les poids du modèle. - La perte moyenne par époque est affichée.
    8. Évaluation finale
    On calcule la précision sur l’ensemble de test en comparant les prédictions aux vraies étiquettes. print(f"Test accuracy: {test_acc_metric.result().numpy():.4f}") affiche la précision finale. Ce code met en œuvre une méthode d’apprentissage semi-supervisé combinant supervision sur un petit sous-ensemble d’exemples étiquetés avec une régularisation par consistance sur un plus grand ensemble non étiqueté, améliorant ainsi la robustesse et la performance du modèle.