Implémentation PyTorch : Apprentissage par Petits Échantillons pour la Classification d'Images

En raison du manque d'images étiquetées pour toutes les classes lors de l'entraînement, ces modèles peuvent être moins utiles dans des contextes réels. Nous souhaitons qu'un modèle puisse reconnaître des classes qu'il n'a jamais vues pendant l'entraînement, car il est presque impossible de s'entraîner sur des images de tous les objets potentiels. Le problème de l'apprentissage à partir de quelques exemples est appelé « apprentissage par petits échantillons » (Few-Shot Learning).

Qu'est-ce que l'apprentissage par petits échantillons ?

L'apprentissage par petits échantillons est un sous-domaine de l'apprentissage automatique. Il consiste à classer de nouvelles données avec seulement quelques échantillons d'entraînement et quelques données supervisées. Avec un nombre limité d'exemples, les modèles que nous créons peuvent néanmoins fonctionner correctement.

Imaginons le scénario suivant : dans le domaine médical, pour certaines maladies rares, il peut ne pas y avoir suffisamment d'images radiographiques pour l'entraînement. Dans de tels cas, construire un classifieur par petits échantillons est une solution idéale.

Variantes de l'apprentissage par petits échantillons

Les chercheurs distinguent généralement quatre types :

  1. Apprentissage N-échantillons (NSL)
  2. Apprentissage par petits échantillons (FSL)
  3. Apprentissage par un seul échantillon (OSL)
  4. Apprentissage sans échantillon (ZSL)

Quand on parle de FSL, on fait généralement référence à la classification N-ways-K-shot. N représente le nombre de classes, et K le nombre d'échantillons par classe à utiliser pour l'entraînement. L'apprentissage N-échantillons est donc un concept plus large que les autres. On peut considérer les apprentissages par petits échantillons, par un seul échantillon et sans échantillon comme des sous-domaines du NSL. L'apprentissage sans échantillon vise à classifier des classes non vues sans aucun exemple d'entraînement.

Dans l'apprentissage par un seul échantillon, chaque classe ne dispose que d'un seul exemple. L'apprentissage par petits échantillons utilise de 2 à 5 échantillons par classe, ce qui en fait une version plus flexible de l'apprentissage par un seul échantillon.

Méthodes d'apprentissage par petits échantillons

Généralement, deux approches sont envisagées pour résoudre un problème d'apprentissage par petits échantillons :

Approche au niveau des données (DLA)

Cette stratégie est simple : si les données sont insuffisantes pour créer un modèle robuste et éviter le sous-apprentissage ou le surapprentissage, il faut en ajouter. Ainsi, de nombreux problèmes FSL peuvent être résolus en exploitant un jeu de données de base plus vaste. La caractéristique principale de ce jeu de base est qu'il ne contient pas les classes constituant notre ensemble de support pour le défi FSL. Par exemple, si nous voulons classer un type d'oiseau particulier, le jeu de base peut contenir des images de nombrexu autres oiseaux.

Approche au niveau des paramètres (PLA)

Du point de vue des paramètres, les échantillons d'apprentissage par petits échantillons sont relativement faciles à surapprendre car ils évoluent dans un espace de grande dimension. Limiter l'espace des paramètres, utiliser la régularisation et des fonctions de perte appropriées peut aider à résoudre ce problème. Un petit nombre d'échantillons d'entraînement sera généralisé par le modèle.

Les performances peuvent être améliorées en guidant le modèle vers un espace de paramètres plus large. En raison du manque de données d'entraînement, les méthodes d'optimisation classiques peuvent ne pas produire de résultats précis.

Pour cette raison, nous entraînons notre modèle à trouver le meilleur chemin à travers l'espace des paramètres, ce qui produit les meilleures prédictions. Cette approche est appelée méta-apprentissage.

Algorithmes de classification d'images par petits échantillons

Il existe quatre méthodes courantes pour l'apprentissage par petits échantillons :

Méta-apprentissage indépendant du modèle (MAML)

Le principe du méta-apprentissage basé sur le gradient (GBML) est à la base de MAML. Dans le GBML, le méta-apprenant acquiert une expérience préalable en s'entraînant sur de nombreuses tâches et en apprenant des caractéristiques partagées via un modèle de base. Chaque fois qu'une nouvelle tâche doit être apprise, le méta-apprenant utilise son expérience existante et les données minimales fournies par la nouvelle tâche pour un ajustement fin par gradient.

En général, si nous initialisons les paramètres de manière aléatoire, l'algorithme ne convergera pas vers une bonne performance après quelques mises à jour. MAML tente de résoudre ce problème. En quelques étapes de gradient seulement et sans surapprentissage, MAML fournit une initialisation robuste pour le méta-apprenant, permettant un apprentissage rapide et optimal pour de nouvelles tâches.

Les étapes sont les suivantes :

  1. Au début de chaque épisode, le méta-apprenant crée une copie C de lui-même.
  2. C s'entraîne sur cet épisode (à l'aide du modèle de base).
  3. C effectue des prédictions sur l'ensemble de requête.
  4. La perte calculée à partir de ces prédictions est utilisée pour mettre à jour C.
  5. Cela se poursuit jusqu'à ce que tous les épisodes soient terminés.

Le principal avantage de cette technique est qu'elle est considérée comme indépendante du choix de l'algorithme de méta-apprentissage. Ainsi, la méthode MAML est largement utilisée pour de nombreux algorithmes d'apprentissage automatique nécessitant une adaptation rapide, en particulier les réseaux de neurones profonds.

Réseaux d'appariement (Matching Networks)

La première méthode d'apprentissage par métrique créée pour résoudre les problèmes FSL est le réseau d'appariement (MN).

Lors de l'utilisation d'un réseau d'appariement pour résoudre un problème d'apprentissage par petits échantillons, un grand jeu de données de base est nécessaire.

Après avoir divisé ce jeu de données en plusieurs épisodes, pour chaque épisode, le réseau d'appariement effectue les opérations suivantes :

  • Chaque image de l'ensemble de support et de l'ensemble de requête est envoyée à un CNN qui produit des embeddings de caractéristiques.
  • L'image de requête utilise le modèle entraîné sur l'ensemble de support pour calculer la distance cosinus des embeddings, et une classification est effectuée via softmax.
  • La perte d'entropie croisée des résultats de classification est rétropropagée à travers le CNN pour mettre à jour le modèle d'embedding.

Les réseaux d'appariement peuvent ainsi apprendre à construire des embeddings d'images. Cette méthode permet de classer des photos sans aucune connaissance a priori des classes, simplement en comparant quelques instances de chaque classe.

Comme les classes varient d'un épisode à l'autre, le réseau d'appariement calcule les attributs (caractéristiques) importants pour la discrimination des classes. En revanche, lors d'une classification standard, l'algorithme sélectionne des caractéristiques propres à chaque classe.

Réseaux prototypiques (Prototypical Networks)

Semblable aux réseaux d'appariement, le réseau prototypique (PN) améliore les performances par quelques modifications. Le PN obtient de meilleurs résultats que le MN, mais leur processus d'entraînement est essentiellement le même : il compare certains embeddings d'images de requête provenant de l'ensemble de support. Cependant, le réseau prototypique offre une stratégie différente.

Dans le PN, nous devons créer des prototypes de classes : l'embedding d'une classe est créé en faisant la moyenne des embeddings des images de cette classe. Ensuite, seuls ces prototypes de classes sont utilisés pour comparer les embeddings des images de requête. Pour les problèmes d'apprentissage par un seul échantillon, cela est comparable au réseau d'appariement.

Réseau de relations (Relation Network)

Le réseau de relations (RN) peut être considéré comme une synthèse des recherches mentionnées ci-dessus. Le RN est basé sur l'idée du PN mais inclut des améliorations algorithmiques significatives.

La fonction de distance utilisée est apprise, contrairement aux études précédentes où elle était prédéfinie. Le module de relations est placé au-dessus du module d'embedding, qui calcule les embeddings et les prototypes de classes à partir des images d'entrée.

Le module de relations entraînable (fonction de distance) prend en entrée l'embedding de l'image de requête et les prototypes de chaque classe, et produit un score de relation pour chaque classification. Ce score est ensuite passé dans un softmax pour obtenir une prédiction.

Apprentissage sans échantillon avec Open-AI CLIP

CLIP (Contrastive Language-Image Pre-Training) est un réseau de neurones entraîné sur diverses paires (image, texte). Il peut prédire le segment de texte le plus pertinent pour une image donnée sans être optimisé directement pour cette tâche (similaire aux capacités sans échantillon de GPT-2 et 3).

CLIP atteint les performances du ResNet50 original sur ImageNet en mode « zero-shot », sans utiliser aucun exemple étiqueté. Il surmonte plusieurs défis majeurs en vision par ordinateur. Nous allons maintenant implémenter un modèle de classification simple avec PyTorch.

Importation des bibliothèques

! pip install ftfy regex tqdm
! pip install git+https://github.com/openai/CLIP.git
import numpy as np
import torch
from pkg_resources import packaging

print("Torch version:", torch.__version__)

Chargement du modèle

import clip
clip.available_models() # liste les noms des modèles CLIP disponibles
model, preprocess = clip.load("ViT-B/32")  
model.cuda().eval()  
input_resolution = model.visual.input_resolution  
context_length = model.context_length  
vocab_size = model.vocab_size  

print("Model parameters:", f"{np.sum([int(np.prod(p.shape)) for p in model.parameters()]):,}")  
print("Input resolution:", input_resolution)  
print("Context length:", context_length)  
print("Vocab size:", vocab_size)

Prétraitement des images

Nous allons fournir au modèle 8 images d'exemple avec leurs descriptions textuelles et comparer la similarité entre les caractéristiques correspondantes.

Le tokenizer n'est pas sensible à la casse, nous pouvons librement donner n'importe quelle description textuelle appropriée.

import os  
import skimage  
import IPython.display  
import matplotlib.pyplot as plt  
from PIL import Image  
import numpy as np  

from collections import OrderedDict  
import torch  

%matplotlib inline  
%config InlineBackend.figure_format = 'retina'  

# images de skimage à utiliser et leurs descriptions textuelles  
descriptions = {  
    "page": "a page of text about segmentation",  
    "chelsea": "a facial photo of a tabby cat",  
    "astronaut": "a portrait of an astronaut with the American flag",  
    "rocket": "a rocket standing on a launchpad",  
    "motorcycle_right": "a red motorcycle standing in a garage",  
    "camera": "a person looking at a camera on a tripod",  
    "horse": "a black-and-white silhouette of a horse",  
    "coffee": "a cup of coffee on a saucer"  
}
original_images = []  
images = []  
texts = []  
plt.figure(figsize=(16, 5))  

for filename in [filename for filename in os.listdir(skimage.data_dir) if filename.endswith(".png") or filename.endswith(".jpg")]:  
    name = os.path.splitext(filename)[0]  
    if name not in descriptions:  
        continue  

    image = Image.open(os.path.join(skimage.data_dir, filename)).convert("RGB")  
       
    plt.subplot(2, 4, len(images) + 1)  
    plt.imshow(image)  
    plt.title(f"{filename}\n{descriptions[name]}")  
    plt.xticks([])  
    plt.yticks([])  
       
    original_images.append(image)  
    images.append(preprocess(image))  
    texts.append(descriptions[name])  

plt.tight_layout()

Visualisation des résultats

Nous normalisons les images, tokenisons chaque entrée de texte, et effectuons une propagation avant du modèle pour obtenir les caractéristiques des images et du texte.

image_input = torch.tensor(np.stack(images)).cuda()  
text_tokens = clip.tokenize(["This is " + desc for desc in texts]).cuda()  

with torch.no_grad():  
    image_features = model.encode_image(image_input).float()  
    text_features = model.encode_text(text_tokens).float()

Nous normalisons les caractéristiques et calculons le produit scalaire pour chaque paire, ce qui donne la similarité cosinus.

image_features /= image_features.norm(dim=-1, keepdim=True)  
text_features /= text_features.norm(dim=-1, keepdim=True)  
similarity = text_features.cpu().numpy() @ image_features.cpu().numpy().T  

count = len(descriptions)  

plt.figure(figsize=(20, 14))  
plt.imshow(similarity, vmin=0.1, vmax=0.3)  
plt.yticks(range(count), texts, fontsize=18)  
plt.xticks([])  
for i, image in enumerate(original_images):  
    plt.imshow(image, extent=(i - 0.5, i + 0.5, -1.6, -0.6), origin="lower")  
for x in range(similarity.shape[1]):  
    for y in range(similarity.shape[0]):  
        plt.text(x, y, f"{similarity[y, x]:.2f}", ha="center", va="center", size=12)  

for side in ["left", "top", "right", "bottom"]:  
    plt.gca().spines[side].set_visible(False)  

plt.xlim([-0.5, count - 0.5])  
plt.ylim([count + 0.5, -2])  

plt.title("Cosine similarity between text and image features", size=20)

Classification d'images sans échantillon

from torchvision.datasets import CIFAR100  
cifar100 = CIFAR100(os.path.expanduser("~/.cache"), transform=preprocess, download=True)  
text_descriptions = [f"This is a photo of a {label}" for label in cifar100.classes]  
text_tokens = clip.tokenize(text_descriptions).cuda()  
with torch.no_grad():  
    text_features = model.encode_text(text_tokens).float()  
    text_features /= text_features.norm(dim=-1, keepdim=True)  

text_probs = (100.0 * image_features @ text_features.T).softmax(dim=-1)  
top_probs, top_labels = text_probs.cpu().topk(5, dim=-1)  
plt.figure(figsize=(16, 16))  
for i, image in enumerate(original_images):  
    plt.subplot(4, 4, 2 * i + 1)  
    plt.imshow(image)  
    plt.axis("off")  

    plt.subplot(4, 4, 2 * i + 2)  
    y = np.arange(top_probs.shape[-1])  
    plt.grid()  
    plt.barh(y, top_probs[i])  
    plt.gca().invert_yaxis()  
    plt.gca().set_axisbelow(True)  
    plt.yticks(y, [cifar100.classes[index] for index in top_labels[i].numpy()])  
    plt.xlabel("probability")  

plt.subplots_adjust(wspace=0.5)  
plt.show()

On peut observer que la classification est très efficace.

Étiquettes: PyTorch CLIP MAML Few-Shot Learning Zero-shot learning

Publié le 13 août à 14h33