Introduction : votre premier projet de classification d'images
La classification d'images est une technologie fondamentale en vision par ordinateur. Ce guide vous montrera comment implémenter une solution de classification de fleurs à l'aide de l'architecture ResNet18, conçue pour être efficace et accessible. Nous utiliserons des ressources cloud à bas coût, éliminant le besoin d'un matériel GPU coûteux. Vous apprendrez à charger un modèle pré-entraîné, à l'adapter à votre tâche et à l'exécuter dans un environnement économique.
1. Configuration de l'environnement de travail
Pour exécuter ce projet, optez pour une plateforme cloud proposant des instances GPU à la consommation. Recherchez une offre incluant un environnement Python avec PyTorch et TorchVision pré-enstallés. Le coût horaire pour une instance de base avec un GPU partagé est généralement très modeste.
Pour les données, nous utiliserons le jeu de données Flowers102. Le téléchargement se fait directement via les utilitaires de TorchVision.
import torchvision.datasets as datasets
# Chargement des sous-ensembles d'entraînement et de test
train_dataset = datasets.Flowers102(root='./data', split='train', download=True)
test_dataset = datasets.Flowers102(root='./data', split='test', download=True)
2. Chargement et adaptation du modèle ResNet18
ResNet18 est un réseau de neurones convolutif profond mais relativement léger. La stratégie du transfer learning consiste à utiliser un modèle déjà entraîné sur un large corpus d'images (comme ImageNet) et à l'adapter à notre problème spécifique. Cela réduit considérablement le temps de calcul et la quantité de données nécessaires.
import torch
import torchvision.models as models
# Instanciation du modèle ResNet18 avec des poids pré-entraînés
resnet_model = models.resnet18(weights='IMAGENET1K_V1')
# Remplacement de la couche de classification finale
# La couche originale 'fc' a une entrée de 512 caractéristiques.
num_features = resnet_model.fc.in_features
resnet_model.fc = torch.nn.Linear(num_features, 102)
3. Inférence avec un modèle non affiné
Avant tout entraînement, testons les capacités du modèle pré-entraîné sur notre tâche. Il est crucial d'appliquer les mêmes transformations aux images que celles utilisées lors de l'entraînement du modèle sur ImageNet.
from PIL import Image
import torchvision.transforms as T
# Définition des transformations de prétraitement
preprocess_pipeline = T.Compose([
T.Resize(256),
T.CenterCrop(224),
T.ToTensor(),
T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
def predict(image_path):
# Charger et préparer l'image
input_img = Image.open(image_path)
input_tensor = preprocess_pipeline(input_img)
input_batch = input_tensor.unsqueeze(0) # Ajouter une dimension de lot
# Désactiver le calcul du gradient pour l'inférence
with torch.no_grad():
output = resnet_model(input_batch)
# Obtenir la classe prédite
probabilities = torch.nn.functional.softmax(output[0], dim=0)
predicted_class = torch.argmax(probabilities).item()
return predicted_class
# Exemple d'utilisation
# predicted_idx = predict("mon_image_de_fleur.jpg")
# print(f"Classe prédite (index) : {predicted_idx}")
4. Réglage fin (Fine-tuning) du modèle
Pour améliorer les performances sur notre jeu de données spécifique de fleurs, nous pouvons effectuer un réglage fin. Une approche courante est de « geler » les couches profondes du réseau et d'entraîner uniquement la dernière couche que nous avons ajoutée. Cela accélère l'entraînement et évite le surajustement sur des données limitées.
import torch.optim as optim
from torch.utils.data import DataLoader
# Gel des paramètres de toutes les couches sauf la dernière
for param in resnet_model.parameters():
param.requires_grad = False
# La couche finale est déjà par défaut entraînable après remplacement
# mais nous le spécifions explicitement pour la clarté
resnet_model.fc.requires_grad = True
# Préparation des chargeurs de données
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)
# Configuration de l'optimiseur et de la fonction de perte
optimizer = optim.Adam(resnet_model.fc.parameters(), lr=0.001)
criterion = torch.nn.CrossEntropyLoss()
# Boucle d'entraînement simplifiée
num_epochs = 5
for epoch in range(num_epochs):
resnet_model.train() # Mode entraînement
for images, labels in train_loader:
optimizer.zero_grad()
outputs = resnet_model(images)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
# Évaluation sur le jeu de test à chaque epoch (optionnel)
# ...
Conseils pour l'exécution sur CPU : Si vous n'avez pas de GPU, réduisez la taille du lot (batch_size), utilisez des images de plus petite résolution (par exemple, redimensionner à 128x128) et limitez le nombre d'epochs.
5. Évaluation et visualisation des résultats
Pour évaluer les performances du modèle affiné, générez une matrice de confusion. Cet outil permet de visualiser les types d'erreurs de classification commis par le modèle.
from sklearn.metrics import confusion_matrix
import matplotlib.pyplot as plt
import seaborn as sns
all_predictions = []
all_true_labels = []
resnet_model.eval() # Mode évaluation
with torch.no_grad():
for batch_imgs, batch_labels in test_loader:
batch_outputs = resnet_model(batch_imgs)
_, preds = torch.max(batch_outputs, 1)
all_predictions.extend(preds.cpu().numpy())
all_true_labels.extend(batch_labels.cpu().numpy())
# Création de la matrice de confusion
conf_matrix = confusion_matrix(all_true_labels, all_predictions)
plt.figure(figsize=(10, 8))
sns.heatmap(conf_matrix, annot=True, fmt='d', cmap='Blues')
plt.ylabel('Vraies étiquettes')
plt.xlabel('Étiquettes prédites')
plt.title('Matrice de confusion - Classification de fleurs')
plt.show()
Pour une démonstration interactive, vous pouvez utiliser la bilbiothèque Gradio pour créer une petite interface web permettant de télécharger une image et d'obtenir la prédiction en temps réel.
import gradio as gr
def run_inference(user_image):
# Appliquer le prétraitement et faire une prédiction
tensor = preprocess_pipeline(user_image)
batch = tensor.unsqueeze(0)
with torch.no_grad():
output = resnet_model(batch)
predicted_idx = torch.argmax(output, 1).item()
return f"Classe prédite (index) : {predicted_idx}"
# Création et lancement de l'interface
iface = gr.Interface(
fn=run_inference,
inputs=gr.Image(type="pil"),
outputs="text",
title="Démonstration ResNet18 - Classification de Fleurs"
)
iface.launch()