Classer des images de vêtements
- Étude de cas 1
- 120 min
- Facultative · 1 étude de cas requise pour le certificat
Étude de cas 1 sur 4 · L'apprentissage profond et les transformeurs expliquésClasser des images de vêtements
1. Ce qu'est un réseau de neurones
2. Les tenseurs et PyTorch
3. Comment les réseaux de neurones apprennent
4. Diagnostiquer les problèmes d'entraînement
5. Représenter les séquences et le sens
6. Assembler un transformeur
7. Utiliser des modèles préentraînés
8. Sous le capot : les mathématiquesFacultatif
Ce que vous saurez faire
Créez un classificateur d’images, des fichiers bruts à un modèle testé et enregistré que vous pouvez présenter. Vous verrez pourquoi un réseau qui lit les pixels en ligne ne comprend pas bien l’image. Vous corrigerez ce problème avec un petit réseau convolutif. Vous découvrirez aussi ses échecs, testerez honnêtement une augmentation des données et utiliserez le modèle sur de nouvelles images.
Le carnet (notebook) Jupyter exécute chaque étape de cette page, avec le même code et les mêmes sorties. Il comprend aussi cinq tâches avec des vérifications. Ses cellules de préparation téléchargent les données (31 Mo) et les vérifient. Aucune configuration locale n’est donc nécessaire. Il s’exécute en environ 2 minutes sur l’ordinateur de test du cours. Prévoyez jusqu’à 10 minutes sur un processeur gratuit de Colab.
Avant de commencerCe qu’il faut savoir et avoir sous la main
Ce que vous devez savoir
Cette étude de cas utilise les modules 1 à 4. Voici les leçons les plus importantes :
- La forme d’un tenseur et la dimension du lot, dans Tenseurs et formes, et les erreurs de forme, dans La propagation avant.
- Les mini-lots, les époques,
model.eval()et les deux courbes, dans Lots, époques et validation. - L’arrêt anticipé et l’augmentation des données, dans Limiter le surapprentissage.
- L’examen des exemples et des étiquettes en premier, dans Déboguer l’expérience.
- La régression logistique et la matrice de confusion, dans le cours d’apprentissage automatique : Comprendre la classification et Les métriques de classification.
Ce qu’il vous faut sur votre ordinateur (seulement pour travailler localement; le carnet n’en a pas besoin)
- L’environnement
ticket-routerde Installer PyTorch, avec scikit-learn et matplotlib :python -m pip install scikit-learn==1.9.1 matplotlib. Le carnet les installe au besoin. - Le carnet, téléchargé à partir du lien ci-dessus. Sa cellule de préparation crée un dossier
clothing-imageset y télécharge les données. Pour recommencer, supprimez ce dossier.
Testé sur macOS avec Python 3.12, PyTorch 2.14.1 (processeur, 2 fils d’exécution) et scikit-learn 1.9.1, dans une nouvelle session Jupyter. Sur un autre ordinateur, les derniers chiffres de certains nombres peuvent varier.
La situation
Section intitulée « La situation »Brightloom est une place de marché en ligne pour les vêtements. Les vendeurs téléversent une photo de chaque produit et choisissent sa catégorie. Beaucoup choisissent la mauvaise catégorie, ou aucune. Un manteau classé dans « Shirt » (chemise) se retrouve dans la mauvaise section. Les clients qui cherchent des manteaux ne le voient jamais. Il se vend donc tard, ou pas du tout. Ines, responsable du catalogue, demande de l’aide : « La photo peut-elle nous indiquer la catégorie? »
Kwame, le scientifique des données, propose une suggestion, pas une décision. Quand le modèle est sûr, la page de téléversement remplit la catégorie. Le vendeur peut encore la changer. Quand le modèle hésite, la page demande : « Est-ce une chemise ou un manteau? » Une mauvaise suggestion coûte donc un clic au vendeur. Une mauvaise suggestion que le vendeur ne remarque pas coûte des ventes.
Brightloom est une entreprise inventée pour cette étude de cas. Les photos sont réelles : elles proviennent de Fashion-MNIST, un jeu de données public de photos de produits du catalogue de Zalando.
Ce que vous allez créer
Section intitulée « Ce que vous allez créer »Vous créerez un petit réseau convolutif qui lit une photo de produit et donne une probabilité pour chacune des dix catégories. Vous le comparerez à une référence de base et à un réseau multicouche simple, avec le même découpage et la même métrique. Ensuite, vous choisirez un seuil pour « remplir la catégorie », testerez le modèle une seule fois et l’enregistrerez avec une fiche d’expérience. Vous l’utiliserez aussi sur de nouvelles photos. À la fin, vous rédigerez une présentation de ce travail pour votre portfolio.
Les compétences viennent de ce cours : tenseurs et formes, boucle d’entraînement avec lots et mode d’évaluation, arrêt anticipé et augmentation des données. L’étude de cas ajoute une nouvelle idée : la convolution. Elle reprend aussi la matrice de confusion et les données de test du cours d’apprentissage automatique.
Les données
Section intitulée « Les données »Fashion-MNIST contient 60 000 images d’entraînement et 10 000 images de test. Chaque image comporte 28 × 28 pixels en niveaux de gris et possède l’une des dix étiquettes. Les auteurs l’ont conçu pour remplacer MNIST par un défi plus difficile. MNIST est un jeu classique de chiffres manuscrits.
Les fichiers ne sont pas des fichiers CSV. Ils utilisent un format simple appelé IDX : quelques octets d’en-tête, puis un octet par pixel, ligne par ligne. NumPy peut les lire directement. Le carnet n’a donc besoin d’aucun paquet supplémentaire. Torchvision, la bibliothèque d’images de PyTorch, peut aussi télécharger Fashion-MNIST. Mais cela ajoute une installation et masque l’étape enseignée ici : la transformation des octets en tenseur.
import gzipimport numpy as np
CLASSES = ["T-shirt/top", "Trouser", "Pullover", "Dress", "Coat", "Sandal", "Shirt", "Sneaker", "Bag", "Ankle boot"]
def read_images(path): """IDX images: 16 header bytes (a file code, count, rows, columns), then one byte per pixel.""" raw = gzip.open(path).read() count, rows, cols = (int.from_bytes(raw[i:i + 4], "big") for i in (4, 8, 12)) return np.frombuffer(raw, dtype=np.uint8, offset=16).reshape(count, rows, cols)
def read_labels(path): """IDX labels: 8 header bytes (a file code, count), then one byte per label.""" return np.frombuffer(gzip.open(path).read(), dtype=np.uint8, offset=8)
images = read_images("data/train-images-idx3-ubyte.gz")labels = read_labels("data/train-labels-idx1-ubyte.gz")test_images = read_images("data/t10k-images-idx3-ubyte.gz")test_labels = read_labels("data/t10k-labels-idx1-ubyte.gz")print(images.shape, images.dtype, labels.shape)print(test_images.shape, test_labels.shape)print("first image's label:", labels[0], CLASSES[labels[0]])La sortie :
(60000, 28, 28) uint8 (60000,)(10000, 28, 28) (10000,)first image's label: 9 Ankle bootCe que cela nous apprend. int.from_bytes(..., "big") transforme les 4 octets d’en-tête en un nombre. Le fichier lui-même indique donc 60 000 × 28 × 28. np.frombuffer lit le reste comme des octets non signés (uint8, de 0 à 255). Chaque étiquette est un nombre de 0 à 9, et CLASSES donne son nom.
Premières vérifications
Section intitulée « Premières vérifications »Avant de créer un modèle, Kwame vérifie la plage des pixels, le nombre d’images par classe et les doublons. Une image de test présente aussi dans le fichier d’entraînement rendrait le test trop facile.
print("pixel values from", images.min(), "to", images.max())print("training images per class:", np.bincount(labels).tolist())print("test images per class: ", np.bincount(test_labels).tolist())train_keys = {img.tobytes() for img in images}print("repeated images in the training file:", len(images) - len(train_keys))print("test images also in the training file:", sum(img.tobytes() in train_keys for img in test_images))La sortie :
pixel values from 0 to 255training images per class: [6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000]test images per class: [1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000]repeated images in the training file: 0test images also in the training file: 0Ce que cela nous apprend. Les dix classes sont parfaitement équilibrées. Choisir toujours une seule classe donne donc la bonne réponse 10 % du temps. L’exactitude est ainsi une mesure juste ici. Aucune image n’est répétée, et le fichier de test ne contient aucun doublon avec l’entraînement.
Examiner les images
Section intitulée « Examiner les images »Comme l’indique Déboguer l’expérience, examinez des exemples avant d’entraîner quoi que ce soit. Voici la première image de chaque classe.
Description textuelle du graphique
Dix petites images grises sur fond noir, chacune avec son nom de classe en dessous : un t-shirt/haut, un pantalon, un chandail à manches longues, une robe, un manteau, une sandale, une chemise, une chaussure de sport, un sac à main et une bottine. Chaque produit occupe la majeure partie du carré et est centré.
Même à cette taille, une personne peut reconnaître la plupart des produits. Certaines paires se ressemblent toutefois beaucoup. Une chemise, un t-shirt/haut et un chandail peuvent différer seulement par un col ou une manche de quelques pixels.
Images sous forme de tenseurs
Section intitulée « Images sous forme de tenseurs »Que voit le modèle? Des nombres. Kwame affiche un coin de 8 × 8 pixels de la première image, une bottine, là où commence le bout :
print(CLASSES[labels[0]])print(images[0, 16:24, 0:8])La sortie :
Ankle boot[[ 0 0 0 0 18 44 82 107] [ 0 57 187 208 224 221 224 208] [ 3 202 228 224 221 211 211 214] [ 98 233 198 210 222 229 229 234] [ 75 204 212 204 193 205 211 225] [ 48 203 183 194 213 197 185 190] [ 0 122 219 193 179 171 183 196] [ 0 0 74 189 212 191 175 172]]Description textuelle du graphique
À gauche, un carré de 8 sur 8 pixels gris : noir dans les coins supérieur et inférieur gauches, gris clair ailleurs. Il montre la courbe du bout d’une chaussure. À droite, les mêmes 64 pixels sous forme de nombres. Les pixels noirs valent 0; ceux de la bottine valent surtout entre 170 et 234. Le pixel encadré à la ligne 4, colonne 1 vaut 98, là où le bout commence.
Ce que cela nous apprend. Le fond vaut 0 et la bottine vaut environ 200. Une image contient 28 × 28 = 784 nombres de ce genre. Le modèle ne reçoit rien d’autre : aucune couleur, aucune indication « ceci est une bottine ».
Les couches d’images de PyTorch attendent un tenseur de forme (images, canaux, lignes, colonnes). Un canal est une couche de nombres qui donne une valeur pour chaque pixel. Une image en gris a un canal; une photo couleur en a trois, pour le rouge, le vert et le bleu. Une image a donc la forme (1, 28, 28), et un lot de 64 a la forme (64, 1, 28, 28). C’est la dimension du lot de Tenseurs et formes, avec deux dimensions de plus pour l’image.
Découpage et référence de base
Section intitulée « Découpage et référence de base »Le fichier de test reste fermé jusqu’à la fin, comme dans Données d’entraînement, de validation et de test. Dans le fichier d’entraînement, Kwame prélève un échantillon stratifié de 10 000 images pour l’entraînement et 5 000 autres pour la validation. Pourquoi pas les 55 000 images? Chaque réseau s’entraîne ainsi en environ une minute sur un processeur, et les idées restent les mêmes. Cela coûte quelques points d’exactitude, que la section sur les limites mesure.
import torchfrom sklearn.model_selection import train_test_split
rows = np.arange(len(labels))train_rows, rest = train_test_split(rows, train_size=10_000, stratify=labels, random_state=0)valid_rows, _ = train_test_split(rest, train_size=5_000, stratify=labels[rest], random_state=0)
def to_tensors(imgs, labs): """uint8 images (N, 28, 28) -> floats (N, 1, 28, 28) from 0 to 1, and int64 labels (N,).""" x = torch.tensor(imgs, dtype=torch.float32).unsqueeze(1) / 255 return x, torch.tensor(labs, dtype=torch.long)
x_train, y_train = to_tensors(images[train_rows], labels[train_rows])x_valid, y_valid = to_tensors(images[valid_rows], labels[valid_rows])x_test, y_test = to_tensors(test_images, test_labels)show_shape("x_train", x_train)show_shape("x_valid", x_valid)show_shape("x_test", x_test)print("validation images per class:", torch.bincount(y_valid).tolist())La sortie :
x_train shape (10000, 1, 28, 28)x_valid shape (5000, 1, 28, 28)x_test shape (10000, 1, 28, 28)validation images per class: [500, 500, 500, 500, 500, 500, 500, 500, 500, 500]unsqueeze(1) ajoute la dimension du canal à la position 1. La division par 255 ramène chaque pixel entre 0 et 1. show_shape() est la fonction utilitaire du carnet qui affiche une forme, comme nous le faisons dans ce cours.
Passons aux références de base. La classe majoritaire donne la bonne réponse 10 % du temps. La régression logistique, vue dans le cours d’apprentissage automatique, lit les 784 pixels comme 784 caractéristiques. flatten(1) conserve la dimension 0 (les images) et place le reste sur une seule ligne : (10000, 784).
from sklearn.linear_model import LogisticRegression
majority = int(torch.bincount(y_train).argmax())print("majority class, validation accuracy:", round((y_valid == majority).float().mean().item(), 4))logreg = LogisticRegression(max_iter=1000)logreg.fit(x_train.flatten(1).numpy(), y_train.numpy()) # 784 pixel features per imageprint("logistic regression, validation accuracy:", round(logreg.score(x_valid.flatten(1).numpy(), y_valid.numpy()), 4))La sortie :
majority class, validation accuracy: 0.1logistic regression, validation accuracy: 0.8346Ce que cela nous apprend. Un modèle linéaire sur les pixels bruts donne 83,5 % de bonnes réponses, soit environ 5 photos sur 6. C’est le résultat à dépasser. Un réseau doit faire nettement mieux pour justifier son coût.
La boucle d’entraînement, avec arrêt anticipé
Section intitulée « La boucle d’entraînement, avec arrêt anticipé »fit() reprend la boucle de Lots, époques et validation : Adam avec un taux d’apprentissage de 0,001, des lots mélangés de 64, les cinq étapes et une passe d’évaluation après chaque époque. Elle ajoute l’arrêt anticipé de Limiter le surapprentissage. Elle copie les poids chaque fois que la perte de validation atteint un nouveau minimum. Elle s’arrête après 5 époques sans nouveau minimum, puis rétablit les meilleurs poids. Le carnet contient la fonction complète; son cœur est la boucle que vous connaissez :
for start in range(0, len(order), batch_size): batch = order[start:start + batch_size] images, labels = x_train[batch], y_train[batch] if flip: # data augmentation: mirror about half of the batch mirror = torch.rand(len(batch)) < 0.5 images = torch.where(mirror[:, None, None, None], images.flip(-1), images) optimizer.zero_grad() loss = loss_fn(model(images), labels) loss.backward() optimizer.step()Un réseau multicouche
Section intitulée « Un réseau multicouche »D’abord, le réseau des modules précédents, avec des pixels à la place des fréquences de mots. nn.Flatten() transforme chaque image en une ligne de 784 nombres. Une couche cachée de 128 neurones ReLU suit, puis 10 scores. On appelle souvent un tel réseau de couches entièrement connectées un perceptron multicouche, ou MLP.
class MLP(nn.Module): def __init__(self, hidden=128): super().__init__() self.layers = nn.Sequential(nn.Flatten(), nn.Linear(784, hidden), nn.ReLU(), nn.Linear(hidden, 10))
def forward(self, x): return self.layers(x)
set_seed(0)mlp = MLP()print("weights:", count(mlp))mlp_history, mlp_best = fit(mlp, x_train, y_train, x_valid, y_valid)print("stopped after epoch", len(mlp_history), "| best:", mlp_best)La sortie :
weights: 101770stopped after epoch 24 | best: {'epoch': 19, 'train_loss': 0.2207, 'train_acc': 0.9249, 'valid_loss': 0.3723, 'valid_acc': 0.8712}Ce que cela nous apprend. Le MLP atteint 87,1 % à l’époque 19, soit 3,7 points de plus que la régression logistique. L’arrêt anticipé a attendu 5 époques de plus, puis a rétabli les poids de l’époque 19.
Le problème : un réseau aplati ne sait pas où se trouve un pixel
Section intitulée « Le problème : un réseau aplati ne sait pas où se trouve un pixel »Regardez encore nn.Flatten(). Elle place les 784 pixels sur une ligne, et la première couche donne à chaque position ses propres poids. Rien n’indique au réseau que le pixel 100 est voisin du pixel 101. Kwame vérifie cela avec une expérience étrange. Il place les pixels de chaque image dans le même ordre aléatoire fixe. Un pantalon ressemble alors à du bruit. Puis il entraîne à nouveau le même MLP :
Description textuelle du graphique
À gauche : un pantalon gris sur fond noir. À droite : les mêmes valeurs de pixels dispersées dans le carré, comme du bruit télévisuel, sans aucune forme restante.
scrambled pixels, best: {'epoch': 19, 'train_loss': 0.225, 'train_acc': 0.9209, 'valid_loss': 0.3661, 'valid_acc': 0.8708}87,1 %, comme avant. Pour le MLP, une image et sa copie mélangée représentent le même problème. Il n’a jamais utilisé la disposition de l’image. Il doit donc apprendre « un col ici » séparément pour chaque position, à partir d’exemples. Le coût apparaît quand un produit n’est pas exactement à la même place que dans les photos d’entraînement. Kwame déplace chaque image de validation de quelques pixels vers la droite :
for pixels in (1, 2, 3): moved = torch.roll(x_valid, shifts=pixels, dims=3) # the background is black, so this moves the item print(f"moved {pixels} pixel(s) right: MLP accuracy {evaluate(mlp, moved, y_valid)[1]:.4f}")La sortie :
moved 1 pixel(s) right: MLP accuracy 0.7602moved 2 pixel(s) right: MLP accuracy 0.4308moved 3 pixel(s) right: MLP accuracy 0.2330Une chemise déplacée de deux pixels reste une chemise pour toute personne. Le MLP passe de 87 % à 43 %. Ses poids correspondent à des positions, pas à des formes.
L’idée : un petit détecteur de motifs qui glisse
Section intitulée « L’idée : un petit détecteur de motifs qui glisse »Imaginez que vous cherchez le mot « remboursement » sur une page avec une loupe. Vous regardez une petite partie, déplacez un peu la loupe, puis regardez à nouveau. Vous utilisez les mêmes yeux partout et trouvez le mot où qu’il soit.
Une convolution fait cela avec un filtre : une petite grille de poids, ici de 3 × 3. Le filtre couvre 3 × 3 pixels et multiplie chaque pixel par son poids. Il additionne ensuite les neuf produits, ce qui donne un nombre. Puis il se déplace d’un pixel et recommence, sur toute l’image. Le résultat est une nouvelle grille de 28 × 28, la carte de réponse. Ses valeurs sont élevées là où le motif apparaît, et faibles ailleurs. Les mêmes neuf poids servent à chaque position. Le motif est donc trouvé où qu’il soit, et neuf poids couvrent toute l’image. Le nom vient des mathématiques, où cette somme glissante s’appelle une convolution. Un réseau avec ces couches est un réseau de neurones convolutif (CNN). Il apprend les poids de ses filtres comme tous ses autres poids.
Voici une étape avec un filtre que Kwame a créé à la main : −1 à gauche, +1 à droite. Il réagit à un bord gauche, là où des pixels sombres rencontrent des pixels clairs.
Description textuelle du graphique
À gauche : une image de pantalon de 28 × 28, avec un petit carré de 3 × 3 encadré sur le bord gauche d’une jambe. Au centre : les neuf valeurs de pixels de ce carré : 0,00, 0,02, 0,98 sur la première ligne; 0,00, 0,00, 0,92 sur la deuxième; 0,00, 0,47, 0,88 sur la troisième. Multipliées par le filtre, qui contient −1, 0, 1 sur chaque ligne, elles donnent 2,78 : 0,98 + 0,92 + 0,88, car la colonne gauche vaut 0 et la colonne centrale est multipliée par 0. À droite : la carte de réponse, gris moyen là où l’image est uniforme, blanche le long des bords gauches des jambes du pantalon et noire le long de leurs bords droits, avec le pixel correspondant encadré.
Quand la fenêtre est uniforme, entièrement noire ou claire, les colonnes gauche et droite s’annulent, et le résultat vaut 0. Le carnet vous demande de calculer vous-même la valeur 2,78.
Le réseau utilise deux autres idées. La mise en commun maximale conserve la plus grande valeur de chaque carré de 2 × 2. Elle divise la hauteur et la largeur par deux, ce qui réduit l’effet d’un déplacement d’un pixel. Une deuxième convolution lit les cartes de réponse de la première. Elle peut ainsi trouver des motifs composés de motifs, comme un col formé de bords.
Un petit réseau convolutif
Section intitulée « Un petit réseau convolutif »Le réseau de Kwame a deux couches de convolution, avec 16 puis 32 filtres. Chacune est suivie de ReLU et d’une mise en commun maximale. Viennent ensuite les couches connues qui produisent 10 scores. Il affiche la forme après chaque couche, pour un lot de 64, afin de ne pas la deviner :
class SmallCNN(nn.Module): def __init__(self): super().__init__() self.features = nn.Sequential( nn.Conv2d(1, 16, kernel_size=3, padding=1), nn.ReLU(), nn.MaxPool2d(2), # 16 filters on 1 channel nn.Conv2d(16, 32, kernel_size=3, padding=1), nn.ReLU(), nn.MaxPool2d(2), # 32 filters on 16 channels ) self.classify = nn.Sequential(nn.Flatten(), nn.Linear(32 * 7 * 7, 64), nn.ReLU(), nn.Linear(64, 10))
def forward(self, x): return self.classify(self.features(x))
set_seed(0)cnn = SmallCNN()x = x_train[:64]show_shape("input", x)for layer in cnn.features: x = layer(x) show_shape(type(layer).__name__, x)show_shape("scores", cnn(x_train[:64]))print("weights:", count(cnn), "| first convolution:", count(cnn.features[0]))La sortie :
input shape (64, 1, 28, 28)Conv2d shape (64, 16, 28, 28)ReLU shape (64, 16, 28, 28)MaxPool2d shape (64, 16, 14, 14)Conv2d shape (64, 32, 14, 14)ReLU shape (64, 32, 14, 14)MaxPool2d shape (64, 32, 7, 7)scores shape (64, 10)weights: 105866 | first convolution: 160Ce que cela nous apprend. nn.Conv2d(1, 16, kernel_size=3) crée 16 filtres de 3 × 3 sur 1 canal. Chaque filtre donne une carte de réponse; le nombre de canaux passe donc de 1 à 16. padding=1 ajoute une bordure de zéros. La fenêtre couvre ainsi chaque pixel, et la taille reste de 28 × 28. La mise en commun la réduit à 14, puis le deuxième bloc à 7. Les dernières couches reçoivent donc 32 × 7 × 7 = 1 568 nombres. La première convolution a 16 × 9 poids + 16 biais = 160. Le réseau complet a 105 866 poids, soit environ autant que les 101 770 du MLP. Toute différence vient de l’utilisation des poids, pas de leur nombre.
Erreur courante
La dimension de canal manquante
Symptôme : Lena, une nouvelle développeuse de l’équipe, fournit au réseau des images de forme (64, 28, 28). Elles viennent directement du fichier, sans unsqueeze(1). « PyTorch va comprendre. » L’exécution s’arrête avec :
RuntimeError: Given groups=1, weight of size [16, 1, 3, 3], expected input[1, 64, 28, 28] to have 1 channels, but got 64 channels insteadCause : nn.Conv2d accepte une entrée à trois dimensions comme une image sans lot : (canaux, lignes, colonnes). Elle a donc lu les 64 images comme une image de 64 canaux, mais ses filtres attendent 1 canal. L’erreur nomme la forme construite, [1, 64, 28, 28].
Correction : donnez explicitement la dimension de lot et celle de canal : x.unsqueeze(1) donne (64, 1, 28, 28). Affichez la forme avant la première couche.
Ce que cela signifie pour un vrai service d’IA : la page de téléversement envoie une photo à la fois. Une photo de forme (1, 28, 28), sans dimension de lot, passe les deux convolutions. Elle échoue seulement dans les dernières couches : mat1 and mat2 shapes cannot be multiplied (32x49 and 1568x64) (les formes des deux matrices ne permettent pas leur multiplication). Ce message pointe loin de la cause. Définissez explicitement la forme à l’entrée des images dans le service, comme le fait suggest() ci-dessous avec reshape(1, 1, 28, 28).
Courbes d’entraînement
Section intitulée « Courbes d’entraînement »Kwame entraîne le CNN avec la même fonction fit() et la même graine aléatoire. Cela prend environ 40 secondes sur l’ordinateur de test.
cnn_history, cnn_best = fit(cnn, x_train, y_train, x_valid, y_valid)print("stopped after epoch", len(cnn_history), "| best:", cnn_best)La sortie :
stopped after epoch 24 | best: {'epoch': 19, 'train_loss': 0.1723, 'train_acc': 0.9396, 'valid_loss': 0.3221, 'valid_acc': 0.8912}- CNN (tirets : entraînement)
- MLP (tirets : entraînement)
Description textuelle du graphique
Quatre courbes sur 24 époques. Toutes commencent entre environ 0,6 et 0,66. La perte de validation du CNN descend à 0,40 à l’époque 4, à 0,35 à l’époque 10 et à son minimum, 0,322, à l’époque 19. Elle remonte ensuite entre 0,33 et 0,38. La perte d’entraînement du CNN continue de descendre, jusqu’à 0,15 à l’époque 24. La perte de validation du MLP reste au-dessus de celle du CNN à partir de l’époque 4, avec un minimum de 0,372 à l’époque 19; sa perte d’entraînement descend à environ 0,21. Les deux courbes de validation sont irrégulières : certaines époques sont moins bonnes que la précédente.
Ce que cela nous apprend. Les deux courbes de chaque réseau descendent ensemble au début, puis se séparent. La perte d’entraînement continue de descendre tandis que la perte de validation cesse de diminuer. C’est le portrait du surapprentissage du module 3. La perte de validation du CNN est inférieure à celle du MLP à presque chaque époque. Les époques irrégulières (8, 12, 17) expliquent pourquoi l’arrêt anticipé attend 5 époques plutôt qu’une seule.
Et les images déplacées? Le même test, avec les deux réseaux :
moved 1 pixel(s) right: MLP 0.7602 CNN 0.8572moved 2 pixel(s) right: MLP 0.4308 CNN 0.7346moved 3 pixel(s) right: MLP 0.2330 CNN 0.4678Le CNN perd beaucoup moins, mais il n’est pas à l’abri. À trois pixels, il échoue lui aussi sur la moitié des images. La mise en commun tolère un petit déplacement. Mais chaque photo d’entraînement était centrée, alors le réseau n’a jamais appris davantage. Le script de calcul des résultats a aussi entraîné le CNN sur les pixels mélangés. Il descend à 85,4 %, sous le MLP, car ses filtres examinent maintenant des voisins qui ne sont pas réellement voisins. C’est la disposition qu’il utilise.
Une comparaison équitable
Section intitulée « Une comparaison équitable »Chaque modèle utilise les mêmes 5 000 images de validation, après la même séparation. L’exactitude sert de mesure, avec la perte à côté :
model weights valid_acc valid_loss majority class 0 0.1000 NaNlogistic regression 7850 0.8346 NaN MLP 101770 0.8712 0.3723 small CNN 105866 0.8912 0.3221Description textuelle du graphique
Un graphique à quatre colonnes. Classe majoritaire : 10 %. Régression logistique : 83,5 %. MLP : 87,1 %. Petit CNN : 89,1 %.
Ce que cela nous apprend. Chaque étape apporte un vrai gain. Une couche cachée ajoute 3,7 points, et les convolutions ajoutent encore 2,0 points avec environ le même nombre de poids. La perte du CNN est aussi nettement inférieure (0,322 contre 0,372). Il prend toutefois plus de temps : environ 40 secondes contre 5 pour le MLP, car chaque filtre travaille à 784 positions.
Là où il échoue
Section intitulée « Là où il échoue »La matrice de confusion du cours d’apprentissage automatique, maintenant avec dix classes : une ligne par classe réelle, une colonne par classe prédite. La diagonale contient les bonnes réponses.
Description textuelle du graphique
Dix lignes et dix colonnes. Bonnes réponses sur la diagonale, sur 500 chacune : t-shirt/haut 421, pantalon 493, chandail 435, robe 452, manteau 418, sandale 474, chemise 330, espadrille 476, sac 477, bottine 480. Les plus grandes erreurs : chemises prédites comme t-shirt/haut 51, comme chandail 50 et comme manteau 48; t-shirts/hauts prédits comme chemise 46; manteaux prédits comme chandail 41; chandails comme manteau 30; manteaux comme chemise 28; chandails comme chemise 23; bottines comme espadrille 19; sandales comme espadrille 16; espadrilles comme bottine 15. Les pantalons, les sacs et les trois classes de chaussures ont peu d’erreurs.
Ce que cela nous apprend. Deux familles causent la plupart des difficultés. Les hauts : seulement 330 chemises sur 500 sont classées « Shirt » (chemise), soit un rappel de 0,66. Les chemises deviennent des t-shirts (51), des chandails (50) et des manteaux (48), et 46 t-shirts deviennent des chemises. Les chaussures : bottines, espadrilles et sandales sont parfois confondues. Les pantalons et les sacs sont presque toujours bien classés. Un seul chiffre d’exactitude, 89,1 %, cache une catégorie où une chemise sur trois est mal classée.
Kwame examine les erreurs elles-mêmes : d’abord les cinq dont le réseau est le plus certain, puis ses cinq erreurs les plus certaines sur les chemises.
Description textuelle du graphique
Dix images, chacune encadrée, avec l’étiquette, la réponse du modèle et sa probabilité. Les cinq premières : une bottine classée comme espadrille (1,00), un t-shirt/haut classé comme robe (1,00), une espadrille classée comme bottine (1,00), une autre bottine classée comme espadrille (1,00) et une sandale classée comme espadrille (1,00). Les cinq dernières, toutes étiquetées Shirt : un vêtement sombre et étroit classé comme pantalon (1,00), un classé comme manteau (0,99) et trois classés comme chandail (0,99, 0,99, 0,98). Plusieurs ressemblent à la réponse du modèle : un haut long et étroit qui pourrait être une robe, des chemises à manches longues sans col visible qui ressemblent à des chandails, et une bottine basse qui ressemble à une espadrille montante.
Ce que cela nous apprend. Regardez les images avant de blâmer le modèle. À 28 × 28 pixels, une chemise à manches longues sans col visible ressemble à un chandail. Une bottine basse ressemble à une espadrille. Certaines étiquettes semblent douteuses, comme la « chemise » sombre et étroite que le réseau classe comme pantalon. Le réseau est certain de ces réponses (0,98 à 1,00). Une probabilité élevée ne prouve donc pas qu’une réponse est bonne. Quelques-unes tromperaient aussi une personne. Pour Ines, cela signifie que les suggestions de hauts demandent le plus d’attention.
Augmentation des données : un retournement
Section intitulée « Augmentation des données : un retournement »Une chemise retournée de gauche à droite reste une chemise. Une copie retournée fournit donc gratuitement une image d’entraînement supplémentaire avec une étiquette correcte, comme dans Limiter le surapprentissage. fit() contient déjà les lignes nécessaires : torch.rand(len(batch)) < 0.5 choisit environ la moitié de chaque lot. images.flip(-1) inverse ces images selon la dernière dimension, celle des colonnes. Kwame entraîne le même réseau avec le retournement et la même graine aléatoire :
set_seed(0)cnn_flip = SmallCNN()flip_history, flip_best = fit(cnn_flip, x_train, y_train, x_valid, y_valid, flip=True)print("without flip:", cnn_best)print("with flip: ", flip_best)La sortie :
without flip: {'epoch': 19, 'train_loss': 0.1723, 'train_acc': 0.9396, 'valid_loss': 0.3221, 'valid_acc': 0.8912}with flip: {'epoch': 16, 'train_loss': 0.2482, 'train_acc': 0.9103, 'valid_loss': 0.3206, 'valid_acc': 0.8854}La perte est un peu plus faible et l’exactitude un peu moins bonne. Une seule exécution ne permet pas de trancher, car une autre graine donne un autre résultat. Le script de calcul des résultats a donc entraîné les deux versions avec cinq graines. La règle de Kwame : conserver le retournement seulement si son gain dépasse la variation entre les graines.
| Graine | 0 | 1 | 2 | 3 | 4 | Moyenne |
|---|---|---|---|---|---|---|
| Sans retournement, exactitude de validation | 89,1 % | 88,9 % | 89,2 % | 87,7 % | 88,7 % | 88,7 % |
| Avec retournement, exactitude de validation | 88,5 % | 89,0 % | 89,4 % | 88,6 % | 88,5 % | 88,8 % |
| Différence (points) | −0,6 | 0,0 | +0,1 | +0,8 | −0,2 | +0,04 |
Ce que cela nous apprend. Le retournement ne fait aucune différence mesurable ici : +0,04 point en moyenne, avec des différences de −0,6 à +0,8 selon la graine. La perte de validation moyenne est un peu plus faible avec le retournement (0,315 contre 0,322), pour trois graines sur cinq. C’est trop petit pour compter pour Ines.
Pourquoi si peu? Les données donnent une explication. Un court script compte quelle moitié de chaque image est la plus claire. Dans le fichier d’entraînement, les 6 000 bottines et 99,4 % des espadrilles pointent vers la gauche. Le retournement crée des bottines pointant vers la droite, qui n’apparaissent jamais dans ce catalogue ni ses données de test. L’étiquette reste correcte, mais les nouvelles images ne ressemblent pas aux photos que le modèle verra. Sur de vraies photos de vendeurs, où les chaussures pointent dans les deux directions, le retournement pourrait compter beaucoup plus.
Selon la règle, le modèle final est le CNN sans retournement : l’entraînement est plus simple et les résultats ne sont pas moins bons.
Le point de fonctionnement
Section intitulée « Le point de fonctionnement »Ines ne veut pas une catégorie pour chaque photo. Elle veut la remplir quand le modèle est certain, et poser une question au vendeur sinon. La probabilité la plus élevée du modèle mesure cette « certitude ». Sur les images de validation, Kwame vérifie quatre seuils :
top = valid_probs.max(1).valuesfor threshold in (0.5, 0.8, 0.9, 0.95): sure = top >= threshold in_top2 = (valid_probs[~sure].topk(2, dim=1).indices == y_valid[~sure, None]).any(1) print(f"threshold {threshold}: filled in {sure.float().mean():.3f}, right there {(valid_pred[sure] == y_valid[sure]).float().mean():.4f}, " f"others: true class in the top 2 {in_top2.float().mean():.4f}")La sortie :
threshold 0.5: filled in 0.976, right there 0.9035, others: true class in the top 2 0.7227threshold 0.8: filled in 0.819, right there 0.9556, others: true class in the top 2 0.8826threshold 0.9: filled in 0.735, right there 0.9763, others: true class in the top 2 0.9011threshold 0.95: filled in 0.662, right there 0.9861, others: true class in the top 2 0.9201Ce que cela nous apprend. Un seuil plus élevé remplit la catégorie de moins de photos, mais donne plus de bonnes réponses. Ines et Kwame choisissent 0,9. Environ 3 photos sur 4 (73,5 %) reçoivent une catégorie, et 97,6 % de ces catégories sont correctes. Pour le quart restant, le vendeur voit les deux catégories les plus probables. La bonne se trouve parmi elles dans 90 % des cas.
Le test final, une seule fois
Section intitulée « Le test final, une seule fois »Tous les choix sont faits : le CNN sans retournement, sa meilleure époque, le seuil de 0,9. Kwame ouvre les 10 000 images de test une seule fois :
test_probs = predict(cnn, x_test)test_pred = test_probs.argmax(1)sure = test_probs.max(1).values >= 0.9in_top2 = (test_probs[~sure].topk(2, dim=1).indices == y_test[~sure, None]).any(1)print("test accuracy:", round((test_pred == y_test).float().mean().item(), 4))print(f"filled in {sure.float().mean():.3f}, right there {(test_pred[sure] == y_test[sure]).float().mean():.4f}, " f"others: true class in the top 2 {in_top2.float().mean():.4f}")test_cm = confusion_matrix(y_test, test_pred)print("recall per class:", dict(zip(CLASSES, (test_cm.diagonal() / test_cm.sum(1)).round(3).tolist())))La sortie :
test accuracy: 0.8879filled in 0.725, right there 0.9742, others: true class in the top 2 0.9038recall per class: {'T-shirt/top': 0.827, 'Trouser': 0.981, 'Pullover': 0.842, 'Dress': 0.893, 'Coat': 0.853, 'Sandal': 0.966, 'Shirt': 0.661, 'Sneaker': 0.95, 'Bag': 0.936, 'Ankle boot': 0.97}Ce que cela nous apprend. Sur 10 000 photos qu’aucune étape n’a utilisées, le CNN classe correctement 88,8 % des images. C’est un peu moins que ses 89,1 % en validation, comme prévu. Au seuil choisi, il remplit la catégorie de 72,5 % des photos, et 97,4 % de ces catégories sont correctes. Les chemises restent la classe faible (rappel de 0,661). Ce sont les chiffres à communiquer, et rien ne change après les avoir lus.
Enregistrer le modèle et le compte rendu d’expérience
Section intitulée « Enregistrer le modèle et le compte rendu d’expérience »La page de téléversement de Brightloom chargera le modèle dans un autre programme, peut-être des mois plus tard. Kwame enregistre donc deux choses. La première, ce sont les poids, avec state_dict() : un dictionnaire des tenseurs du réseau, par nom de couche. Ce n’est pas l’objet Python complet. Tout code qui utilise la même classe SmallCNN peut donc le charger. La deuxième est un compte rendu d’expérience : tout ce qu’il faut pour reproduire et juger le résultat. Enregistrer et examiner les résultats traite des deux en détail.
torch.save(cnn.state_dict(), "fashion_cnn.pt")record = { "model": "SmallCNN: conv 16 (3x3) - pool - conv 32 (3x3) - pool - linear 64 - linear 10", "weights": count(cnn), "data": {"name": "Fashion-MNIST (Zalando Research, MIT License)", "sha256": FILES, "train": "10,000 of the training file, stratified, random_state=0", "valid": "5,000 others of the training file, stratified, random_state=0", "test": "the 10,000 test images"}, "training": {"optimizer": "Adam", "lr": 0.001, "batch_size": 64, "max_epochs": 30, "patience": 5, "seed": 0, "flip": False, "best_epoch": cnn_best["epoch"], "torch_threads": torch.get_num_threads()}, "results": {"valid_acc": cnn_best["valid_acc"], "valid_loss": cnn_best["valid_loss"], "test_acc": round((test_pred == y_test).float().mean().item(), 4), "fill_in_threshold": 0.9, "test_share_filled_in": round(sure.float().mean().item(), 4)}, "versions": {"python": platform.python_version(), "torch": torch.__version__, "numpy": np.__version__, "scikit-learn": sklearn.__version__}, "limits": "28 x 28 grey catalogue photos, one centred product on black; not tested on real seller photos.",}Path("experiment.json").write_text(json.dumps(record, indent=2))print(sorted(p.name for p in Path(".").iterdir() if not p.name.startswith(".")))print("fashion_cnn.pt:", Path("fashion_cnn.pt").stat().st_size, "bytes")La sortie :
['data', 'experiment.json', 'fashion_cnn.pt']fashion_cnn.pt: 427333 bytesIl charge ensuite les poids dans un nouveau réseau, comme le ferait la page de téléversement, et compare ses réponses :
loaded = SmallCNN()loaded.load_state_dict(torch.load("fashion_cnn.pt", weights_only=True))loaded.eval() # evaluation mode before any usesame = torch.equal(predict(loaded, x_test).argmax(1), test_pred)print("same predictions on all 10,000 test images:", same)La sortie :
same predictions on all 10,000 test images: TrueCe que cela nous apprend. Le fichier fait 427 Ko : 105 866 poids de 4 octets chacun, plus les noms des couches. weights_only=True charge uniquement les tenseurs. Un fichier provenant de quelqu’un d’autre ne peut donc pas exécuter de code au chargement. Et loaded.eval() se trouve dans le code de chargement, pas dans la mémoire d’une personne, comme le conseillait le module 4.
Une prédiction sur de nouvelles images
Section intitulée « Une prédiction sur de nouvelles images »La page de téléversement appellerait suggest() : une photo entre, puis une catégorie à remplir ou deux choix à proposer sortent. Kwame lui donne les huit premières images de test. Le modèle ne s’est jamais entraîné sur elles.
def suggest(model, image, threshold=0.9): """One 28 x 28 image (0..255) -> what the upload page shows, and the top 3 probabilities.""" x = torch.tensor(np.asarray(image), dtype=torch.float32).reshape(1, 1, 28, 28) / 255 probs = predict(model, x)[0] top = probs.topk(3) choices = [(CLASSES[c], round(p, 3)) for p, c in zip(top.values.tolist(), top.indices.tolist())] shown = f"fill in: {choices[0][0]}" if choices[0][1] >= threshold else f"ask: {choices[0][0]} or {choices[1][0]}?" return shown, choices
for i in range(8): shown, choices = suggest(loaded, test_images[i]) print(f"{i} label {CLASSES[test_labels[i]]:<11} -> {shown:<28} {choices}")La sortie :
0 label Ankle boot -> fill in: Ankle boot [('Ankle boot', 0.976), ('Sneaker', 0.016), ('Sandal', 0.007)]1 label Pullover -> fill in: Pullover [('Pullover', 0.938), ('Coat', 0.06), ('Shirt', 0.001)]2 label Trouser -> fill in: Trouser [('Trouser', 1.0), ('Dress', 0.0), ('Sandal', 0.0)]3 label Trouser -> fill in: Trouser [('Trouser', 1.0), ('Dress', 0.0), ('Sandal', 0.0)]4 label Shirt -> ask: Shirt or Coat? [('Shirt', 0.867), ('Coat', 0.056), ('T-shirt/top', 0.031)]5 label Trouser -> fill in: Trouser [('Trouser', 1.0), ('Dress', 0.0), ('Pullover', 0.0)]6 label Coat -> fill in: Coat [('Coat', 0.994), ('Pullover', 0.005), ('Shirt', 0.001)]7 label Shirt -> ask: Coat or Shirt? [('Coat', 0.59), ('Shirt', 0.391), ('Pullover', 0.014)]Description textuelle du graphique
Huit images avec les deux réponses les plus probables du modèle. Une bottine : bottine 0,98, chaussure de sport 0,02. Un chandail : chandail 0,94, manteau 0,06. Trois pantalons : pantalon 1,00 chacun. Une chemise : chemise 0,87, manteau 0,06. Un manteau : manteau 0,99, chandail 0,01. Une deuxième chemise, encadrée : manteau 0,59, chemise 0,39.
Ce que cela nous apprend. La catégorie est remplie pour six photos, et les six réponses sont bonnes. Les deux chemises restent sous 0,9, alors la page pose une question. Pour la photo 4, elle demande « Chemise ou manteau? », et le premier choix du modèle est bon. Pour la photo 7, le premier choix, manteau, est faux. Mais « chemise » est le deuxième choix, donc le vendeur peut quand même le sélectionner en un clic. Voilà le point de fonctionnement en action : le doute du modèle va à une personne plutôt qu’au mauvais rayon.
Recommandations
Section intitulée « Recommandations »Kwame écrit à Ines dans ses mots à elle :
- Utilisez le modèle pour suggérer des catégories sur la page de téléversement. Remplissez la catégorie quand le modèle est sûr à au moins 90 %. Cela concerne environ 7 photos sur 10, avec environ 97 bonnes réponses sur 100. Sinon, affichez les deux catégories les plus probables. La bonne figure parmi elles environ 9 fois sur 10.
- Attendez-vous à plus de problèmes avec les hauts. Une chemise sur trois est prise pour un t-shirt, un chandail ou un manteau. Pour les chemises, la page posera souvent une question au lieu de remplir la catégorie. C’est le résultat le plus sûr.
- Avant le lancement, testez-le sur les photos de Brightloom. Tous les chiffres ici viennent de petites photos de catalogue, en gris, avec le produit centré. Étiquetez quelques centaines de vraies photos de vendeurs. Mesurez de nouveau avant que quelqu’un se fie à ces chiffres.
- Laissez le vendeur décider. La page doit permettre de modifier facilement la suggestion. L’équipe devrait compter à quelle fréquence les vendeurs la modifient.
Et voici à quoi ne pas l’utiliser : rejeter ou masquer une annonce, juger si une photo est authentique, ou traiter des photos de personnes.
Limites et prochaines étapes
Section intitulée « Limites et prochaines étapes »- Des photos minuscules et nettes. Les photos Fashion-MNIST font 28 × 28 pixels. Elles sont grises, centrées, sur fond noir, avec un seul produit chacune. Les photos des vendeurs sont grandes, en couleur, et prises sur des lits, des planchers et des cintres. Ce modèle ne fonctionnera pas sur elles sans nouvelles données. Le test de décalage montre sa fragilité, même face à un déplacement de 3 pixels.
- Un échantillon des données. Les réseaux se sont entraînés sur 10 000 images pour garder le carnet (notebook) Jupyter court. Le script de calcul des résultats a entraîné le même CNN une fois sur les 55 000 images hors validation. Il a atteint 92,0 % d’exactitude en validation à l’époque 9, soit environ trois points de plus, en environ 2 minutes. Ici, davantage de données a aidé plus que tout changement d’architecture.
- Un seul catalogue. Les produits viennent d’une seule boutique européenne en ligne, vers 2017. Les styles, cultures et gammes de prix hors de cette boutique sont sous-représentés. Toutes les chaussures pointent dans le même sens.
- Un regard trop précoce sur les données de test. Pendant la préparation de cette étude de cas, une ancienne version du script a affiché l’exactitude de test du modèle avec retournement (87,7 %). Cela s’est produit avant de fixer la règle ci-dessus. La règle repose seulement sur les cinq graines aléatoires de validation. Le résultat de test présenté ici est le seul pour le modèle choisi par cette règle. Un processus strict exclut toutefois même un seul regard précoce. Considérez donc ce chiffre de test comme un peu moins indépendant qu’il devrait l’être. Dans votre travail, écrivez la règle et enregistrez-la dans un commit (enregistrement des changements) avant tout chiffre de test.
- Les étiquettes. Certaines étiquettes semblent fausses, et certaines photos sont ambiguës même pour une personne. Cela limite l’exactitude possible, surtout pour les hauts.
- Les prochaines étapes. Pour de vraies photos, partez d’un réseau préentraîné sur des millions d’images. Faites ensuite son réglage fin sur les photos étiquetées de Brightloom. L’étude de cas Faire le réglage fin d’un petit transformeur mesure l’apport du préentraînement pour le texte. La même idée fonctionne pour les images. Surveillez ensuite à quelle fréquence les vendeurs modifient la suggestion, par catégorie.
Utilisation responsable
Section intitulée « Utilisation responsable »Ce modèle trie des photos de produits; il ne juge personne. Gardez cette limite :
- Une suggestion, pas une décision. Le vendeur confirme ou modifie chaque catégorie. Ne masquez pas, ne déclassez pas et ne rejetez pas une annonce à cause du modèle.
- Des produits seulement. Ne l’utilisez pas sur des photos de personnes. Il n’a jamais été entraîné ni testé pour cela. Classer des vêtements portés soulève aussi des questions de confidentialité et d’équité auxquelles cette étude de cas ne répond pas.
- Des erreurs inégales. Les erreurs ne sont pas réparties également : les chemises posent beaucoup plus de problèmes que les pantalons. Si certains vendeurs vendent surtout des chemises, leurs annonces reçoivent plus de questions et de mauvaises suggestions. Présentez le taux d’erreur par catégorie et surveillez-le après le lancement.
- Des affirmations honnêtes. Dites « 88,8 % sur les images de test Fashion-MNIST », jamais « 88,8 % sur Brightloom ». Le deuxième résultat n’a pas été mesuré.
Faites-en un projet de portfolio
Section intitulée « Faites-en un projet de portfolio »Cette étude de cas montre à un employeur ce que vous savez faire avec des données d’images. Vous partez d’octets bruts pour obtenir un modèle testé et enregistré, puis une décision honnête. Personnalisez-la avec un changement. Par exemple, utilisez les 55 000 images d’entraînement, ajoutez une troisième couche de convolution ou essayez un décalage d’un ou deux pixels comme augmentation. Rendez ensuite le projet public et facile à lire en cinq minutes.
Quoi mettre dans le dépôt :
README.md, la page que les gens lisent en premier (modèle ci-dessous).- Le carnet, exécuté du début à la fin, avec ses sorties.
experiment.jsonproduit par le carnet, et la matrice de confusion sous forme d’image.fashion_cnn.pt(427 Ko), avec la classeSmallCNNet la version de PyTorch qui le charge. Ou seulement le code, car le lecteur peut le réentraîner en une minute.- Une courte fiche de modèle, comme dans Documenter les limites d’utilisation du cours d’apprentissage automatique : utilisation prévue, utilisations exclues, données, évaluation, limites.
- La source et la licence des données : Fashion-MNIST, Zalando Research, MIT License. Un lien et les sommes de contrôle suffisent; ne copiez pas les 31 Mo.
Un modèle de README :
# Suggesting product categories from photos
## ProblemAn online marketplace wants to suggest one of ten clothing categories from theproduct photo: fill it in when the model is sure, ask the seller otherwise.
## Data and licenceFashion-MNIST (Zalando Research, 2017), 70,000 grey 28x28 catalogue photos,10 classes, MIT License, github.com/zalandoresearch/fashion-mnist. Trained on astratified sample of 10,000; validated on 5,000; tested once on the 10,000 test images.
## ApproachBaselines (majority 10%, logistic regression 83.5%), an MLP (87.1%) and a smallCNN (89.1%), all with the same split; early stopping on validation loss. Aleft-right flip tested over five seeds: no measurable gain, so not used.
## Results (test set, used once)Accuracy 88.8%. At a 0.9 threshold: 72.5% of photos filled in, 97.4% of them right;for the others, the true class is in the top two 90% of the time. Weakest class:shirt (recall 0.66).
## LimitsTiny, grey, centred catalogue photos; not tested on real seller photos.Comment le décrire en entrevue : « J’ai construit un petit réseau convolutif qui suggère une catégorie de vêtements à partir d’une photo de produit. Il a dépassé la régression logistique et un réseau simple avec la même séparation des données. Il a obtenu 88,8 % sur des données de test mises de côté. Quand il remplit la catégorie tout seul, il a raison 97 % du temps. J’ai aussi montré pourquoi le réseau simple échoue quand un article bouge. Et j’ai trouvé que les chemises causent la plupart des erreurs. L’augmentation par retournement n’a pas aidé sur ces données, et j’ai mesuré pourquoi. »
Pour aller plus loin :
- Entraînez le modèle sur les 55 000 images. Tracez l’exactitude en validation selon le nombre d’images d’entraînement (5 000, 10 000, 20 000, 55 000).
- Essayez un décalage d’un ou deux pixels comme augmentation. Faites d’abord une prédiction : aidera-t-il davantage le test d’images déplacées que l’exactitude sans déplacement?
- Examinez les 16 filtres de la première couche de convolution sous forme d’images de 3 × 3 pixels. Examinez aussi leurs cartes de réponse pour une chemise.
- Mesurez la durée d’une prédiction, comme dans Les coûts de l’inférence, et comparez-la à celle du MLP.
Vérification des connaissances
Section intitulée « Vérification des connaissances »Vérifiez votre compréhension
Le MLP a atteint la même exactitude en validation (87,1 %) sur des images aux pixels réordonnés selon un ordre aléatoire fixe. Qu'est-ce que cela montre, et qu'est-ce qu'une convolution change?
Afficher la réponse
Cela montre que le MLP n’a jamais utilisé la disposition de l’image. nn.Flatten() donne à chaque position de pixel ses propres poids. Rien n’indique au réseau quels pixels sont voisins. Une image mélangée lui pose donc le même problème. Il doit apprendre chaque motif séparément à chaque position. C’est pourquoi déplacer les images de deux pixels a fait passer son exactitude de 87 % à 43 %. Une convolution fait glisser un petit filtre sur l’image. Elle multiplie les pixels voisins par les mêmes poids partout. Elle utilise donc la disposition et trouve un motif où qu’il soit. Avec la mise en commun, elle tolère de petits décalages (73 % à deux pixels). Sur les pixels mélangés, le CNN a perdu cet avantage et est tombé à 85,4 %.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Le retournement a donné +0,8 point avec une graine et -0,6 avec une autre. Kwame a décidé de ne pas l'utiliser. Avait-il raison, et pourquoi le retournement pourrait-il aider davantage sur de vraies photos de vendeurs?
Afficher la réponse
Oui. Sur cinq graines, le gain moyen était de +0,04 point. Les résultats individuels variaient entre −0,6 et +0,8. Un gain plus petit que la variation entre les graines n’est pas un gain mesuré. L’entraînement plus simple l’emporte donc. Les données expliquent aussi le résultat. Chaque bottine et presque chaque chaussure de sport de Fashion-MNIST pointe vers la gauche. Le retournement a créé des chaussures pointant vers la droite, absentes des photos de test de ce catalogue. L’étiquette restait juste, mais les images ne ressemblaient pas aux vraies. Les photos des vendeurs montrent des chaussures dans les deux sens. Dans ce cas, le retournement ajoute des exemples réalistes et pourrait aider.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Ines demande pourquoi la page ne remplit pas simplement la catégorie la plus probable pour chaque photo. Répondez avec les chiffres.
Afficher la réponse
Environ 11 photos sur 100 recevraient alors une mauvaise catégorie, et beaucoup de vendeurs ne le remarqueraient pas. Ces produits se retrouveraient dans le mauvais rayon. Avec un seuil de 0,9, la catégorie est remplie pour environ 7 photos sur 10. Parmi elles, 97,4 % sont bien classées sur les données de test. Pour les 3 autres sur 10, la page demande au vendeur de choisir entre les deux catégories les plus probables. La bonne figure parmi elles environ 9 fois sur 10. Le modèle travaille donc seul là où il est fiable. Sinon, il confie son doute à une personne. Le seuil est un choix d’affaires : un seuil plus élevé remplit moins de catégories et produit moins d’erreurs silencieuses.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Récapitulatif
- Vous avez lu les fichiers d’images bruts dans un tenseur de forme (images, 1, 28, 28). Vous avez vérifié les données avant tout modèle : classes équilibrées, aucun doublon.
- Avec une même séparation et une même mesure : classe majoritaire 10 %, régression logistique 83,5 %, MLP 87,1 % et petit CNN 89,1 %. Le MLP et le CNN ont environ le même nombre de poids.
- Une convolution est un petit détecteur de motifs qui glisse sur l’image. Le MLP ignorait la disposition (même exactitude sur des pixels mélangés) et échouait quand les articles bougeaient. Le CNN utilise la disposition.
- La matrice de confusion et les erreurs réelles ont montré ses faiblesses : chemises confondues avec t-shirts, chandails et manteaux, et certaines étiquettes ambiguës.
- Un retournement gardait chaque étiquette juste, mais n’apportait aucun gain mesurable sur cinq graines. Toutes les chaussures de ce catalogue pointent dans le même sens. Le CNN sans augmentation a été retenu.
- Le test, utilisé une seule fois : 88,8 %. Avec un seuil de 0,9, la catégorie est remplie pour 72,5 % des photos, avec 97,4 % de bonnes réponses. Le modèle est enregistré sous forme de
state_dictavec un compte rendu d’expérience. Il redonne les mêmes réponses après chargement et suggère des catégories pour de nouvelles photos.
Ensuite, un autre type de données : une table de faits. Un réseau de neurones doit alors prouver qu’il vaut son coût face au gradient boosting (amplification par gradient).
Leçon suivante : Réseau profond ou gradient boosting?