Pourquoi et Comment normaliser ces données – Détection d’objet sur image en PyTorch Partie 1

Aujourd’hui nous allons voir pourquoi et comment normaliser nos données pour faire du Deep Learning avec PyTorch.

En fait cette article fait partie d’une série sur les modèles de Classification Binaire en PyTorch avec :

  • une première partie sur la normalisation
  • une deuxième partie sur les modèles de Deep Learning (accessible ici)

Commençons donc cette première partie sur la normalisation des données.

Charger les données

Nous allons tout d’abord charger les données dont nous avons besoin.

On utilise pour cela le module datasets.

C’est un module integré à PyTorch qui permet de charger rapidement des jeux de données. Idéal pour s’entraîner à coder !

Le dataset qui nous intéresse s’appelle CIFAR-10. Il est composé de 60 000 images en couleur RGB et de taille 32×32; ils sont répartis en 10 classes (avion, voiture, oiseau, chat, cerf, chien, grenouille, cheval, bateau, camion), avec 6 000 images par classe.

from torchvision import datasets
from torchvision import transforms

data_path = '../data-unversioned/p1ch7/'

cifar10 = datasets.CIFAR10(
    data_path, train=True, download=True,
    transform=transforms.ToTensor()
    )

On indique plusieurs paramètres :

  • data_path, le répertoire sera enregistré le jeu de données cifar-10 
  • train = True, crée le jeu de données à partir de l’ensemble d’entraînement, si False crée à partir de l’ensemble de test.
  • download = True, télécharge l’ensemble de données depuis internet et le place dans le répertoire racine. Si le jeu de données est déjà téléchargé, il n’est pas téléchargé à nouveau.
  • transform = transforms.ToTensor(), permet d’initiliaser les images directement sous forme de Tenseur PyTorch (si rien n’est spécifié les images sont au format PIL.Image)

Soyons un peu plus précis, nous avons une variable cifar10 qui est un dataset contenant des tuples.

Ces tuples sont composés de :

  • un tenseur (qui représente l’image)
  • un int qui représente le label de l’image
img_t, index_label = cifar10[5]
type(img_t), type(index_label)

On a récupéré une des images du dataset, pourquoi ne pas l’afficher ?

On rappelle qu’un tenseur image est au format Couleur X Hauteur X Largeur. Pour afficher l’image, il est nécessaire de changer son format en Hauteur X Largeur X Couleur.

Pour ce faire, on utilise la fonction permute().

import matplotlib.pyplot as plt

plt.imshow(img_t.permute(1, 2, 0))
plt.show()

On peut aussi afficher le label associé à l’image :

index_label

La variable index_label est égale à 1. En fait nous avons récupéré l’index qui va nous permettre de connaître le nom du label.

Pour cela, il suffit de se réferer à cette liste :

label_names = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
label_names[index_label]

Notre image a pour label ‘automobile’. Jusqu’ici, tout semble cohérent !

Photo by Simon Infanger on Unsplash

Normaliser les données

Normaliser les données est une étape souvent oublié des Data Scientist alors qu’elle est essentielle pour faire un bon algorithme de Machine Learning.

La normalisation c’est le fait de modifier les données de chaque canal/tenseur de telle sorte que la moyenne soit nulle et l’écart-type égale à un.

On vous montre un exemple ci-dessous avec la normalisation d’une liste

…pour commencer, on calcule la moyenne et l’écart-type :

import numpy as np

l = [60, 9, 37, 14, 23, 4]

np.mean(l), np.std(l)

On obtient : (24.5, 19.102792117035317)

En fait, ce calcul va nous permettre appliquer la formule de normalisation suivante sur chaque élément de la liste :

(element – moyenne) / écart-type

l_norm = [(element - np.mean(l)) / np.std(l) for element in l]

print(l_norm)

On obtient : [1.86, -0.81, 0.65, -0.55, -0.08, -1.07]

Notre liste est maintenant normalisée.

On peut d’ailleurs vérifier que la moyenne est de 0 et l’écart-type de 1 :

np.mean(l_norm), np.std(l_norm)

On obtient : (0.0, 1.0)

Mais pourquoi voulons normaliser nos données ?

En fait il y a deux raisons principales :

  • le fait de normaliser nos données les inclues toutes dans la même plage que nos fonctions d’activation, généralement entre 0 et 1. Cela permet d’avoir moins souvent des gradients non nuls lors de l’entraînement et, par conséquent, les neurones de notre réseau apprendront plus rapidement.
  • en normalisant chaque canal de manière à ce qu’ils aient la même distribution, on s’assure que les informations du canal peuvent être mélangées et mises à jour lors de la descente de gradient (back propagation) en utilisant un même learning rate (taux d’apprentissage).

Rappel : on appelle canal un groupe de tenseur. Dans notre cas chaque image correspond a un tenseur.

Photo by Diana Parkhouse on Unsplash

L’avantage PyTorch

Normaliser manuellement

Avec PyTorch on peut normaliser l’ensemble de nos données assez rapidement.

On va créer le canal de tenseur dont nous parlions dans la partie précédente.

Au fait, si ton objectif est d'apprendre le Deep Learning - j’ai préparé pour toi le Plan d’action pour Maîtriser les Réseaux de neurones.

7 jours de conseils gratuits d’un ingénieur spécialisé en Intelligence Artificielle pour apprendre à maîtriser les réseaux de neurones à partir de zéro :

  • Planifie ton apprentissage
  • Structure tes projets
  • Développe tes algorithmes d’Intelligence Artificielle

J’ai basé ce programme sur des faits scientifiques, des approches éprouvées par des chercheurs mais également mes propres techniques que j'ai conçues au fil de mes expériences dans le domaine du Deep Learning.

Pour y accéder, clique ici :

RECEVOIR MON PLAN D'ACTION

RECEVOIR MON PLAN D'ACTION

À présent, on peut revenir à ce que je mentionnais précédemment.

Pour cela, on utilise la fonction stack() en indiquant chacun des tenseurs de notre variable cifar10 :

import torch

imgs = torch.stack([img_t for img_t, _ in cifar10], dim=3)
imgs.shape

On obtient un canal qui contient 50 000 images au format 3x32x32.

En fait ce canal est un tenseur. C’est un tenseur qui contient d’autres tenseurs 😉

Grâce à ce canal, on va pouvoir calculer la moyenne de tous les tenseurs :

imgs.view(3, -1).mean(dim=1)

On obtient trois moyennes : tensor([0.4914, 0.4822, 0.4465])

Chacunes représentent les moyennes de chaque couleur : R G B.

Même chose pour l’écart-type :

imgs.view(3, -1).std(dim=1)

On obtient trois écart-types : tensor([0.2470, 0.2435, 0.2616])

Nul besoin de réécrire la formule de normalisation,  la bibliothèque PyTorch s’occupe de tout !

On utilise simplement la fonction Normalize() du module transforms en indiquant la moyenne et l’écart-type :

norm = transforms.Normalize((0.4915, 0.4823, 0.4468), (0.2470, 0.2435, 0.2616))

On peut ensuite normaliser une image…

out = norm(img_t)

… ou toutes les images du canal en même temps :

imgs_norm = torch.stack([norm(img_t) for img_t, _ in cifar10], dim=3)

Finalement on peut vérifier que notre canal est bien normaliser avec une moyenne à 0 et un écart-type à 1 :

print(imgs_norm.mean(), imgs_norm.std())

Normaliser automatiquement

Si l’on connaît la moyenne et l’écart-type on peut directement appliquer la normalisation au moment de charger les tenseurs.

Il suffit d’ajouter la fonction Normalize() lorsqu’on initialise le dataset comme suit :

transformed_cifar10 = datasets.CIFAR10(
    data_path, train=True, download=True,
    transform=transforms.Compose([
        transforms.ToTensor(),
        transforms.Normalize((0.4915, 0.4823, 0.4468),
                             (0.2470, 0.2435, 0.2616))
        ]))

Vous l’avez vu, si l’on veut appeler plusieurs fois le module transforms sur un objet il faut regrouper ces appels dans la fonction Compose()

La fonction Compose() permet de réaliser plusieurs transformations à la fois.

Dénormaliser

Nous avons donc notre dataset normalisé prêt à être utilisé mais avant cela… affichons notre image normalisée pour voir ce que cela donne :

import matplotlib.pyplot as plt

img, ind = transformed_cifar10[12]

plt.imshow(img.permute(1, 2, 0))
plt.show()

L’image est assez incompréhensible… en plus d’être en 32×32, les couleurs n’ont pas l’air normal.

En fait, c’est normal !

Suite à la normalisation les pixels de chaque image (de chaque tenseurs) ont été modifié.

Mais alors comment faire si nous voulons vérifier nos images après normalisation ?

Eh bien il suffit de revenir en arrière, de dénormaliser.

Pour cela il suffit d’utiliser ces formules :

moyenne = – moyenne / écart-type

écart-type = 1 / écart-type

On peut appliquer cette formule directement avec la fonction Normalize() comme suit :

unorm = transforms.Normalize(mean=[-0.4915/0.2470, -0.4823/0.2435, -0.4468/0.2616],
                             std=[1/0.2470, 1/0.2435, 1/0.2616])

Ce qui nous donne une image en bonne et due forme :

plt.imshow(unorm(img).permute(1, 2, 0))
plt.show()
Photo by Tori Wise on Unsplash

Avant le Deep Learning

Gardons en tête notre objectif : le modèle de classification Binaire.

On a déjà nos données d’entraînement, on va donc charger les données de validation avec la fonction CIFAR10() et en indiquant train=False :

transformed_cifar10_val = datasets.CIFAR10(
    data_path, train=False, download=True,
    transform=transforms.Compose([
        transforms.ToTensor(),
        transforms.Normalize((0.4915, 0.4823, 0.4468),
                             (0.2470, 0.2435, 0.2616))
        ]))

Dans notre dans dataset il y a en tout 10 classes.

Nous voulons faire de la classification binaire, nous allons donc garder uniquement 2 de ces classes : cerf et cheval.

Notre modèle de Deep Learning va apprendre à détecter ces deux classes sur des images.

On extrait les images correspondant à ces classes depuis notre dataset :

label_map = {4: 0, 7: 1}
class_names = ['deer', 'horse']

cifar2 = [(img, label_map[label])
  for img, label in transformed_cifar10
  if label in [4, 7]]

cifar2_val = [(img, label_map[label])
    for img, label in transformed_cifar10_val
    if label in [4, 7]]

Finalement on peut afficher une des images de la classe ‘cerf’ :

img, ind = cifar2[90]

plt.imshow(unorm(img).permute(1, 2, 0))
plt.show()
print('classe : ', class_names[ind])
classe : deer

Il semble qu’on est sur la bonne voie !

On peut continuer vers la seconde partie de cette article avec la création de notre modèle de Classification Binaire en PyTorch.

sources :

Un dernier mot, si tu veux aller plus loin et apprendre le Deep Learning - j’ai préparé pour toi le Plan d’action pour Maîtriser les Réseaux de neurones.

7 jours de conseils gratuits d’un ingénieur spécialisé en Intelligence Artificielle pour apprendre à maîtriser les réseaux de neurones à partir de zéro :

  • Planifie ton apprentissage
  • Structure tes projets
  • Développe tes algorithmes d’Intelligence Artificielle

J’ai basé ce programme sur des faits scientifiques, des approches éprouvées par des chercheurs mais également mes propres techniques que j'ai conçues au fil de mes expériences dans le domaine du Deep Learning.

Pour y accéder, clique ici :

RECEVOIR MON PLAN D'ACTION

RECEVOIR MON PLAN D'ACTION

Tom Keldenich
Tom Keldenich

Ingénieur spécialisé en Intelligence Artificielle et passionné de données !

Fondateur du site Inside Machine Learning

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

This page will not stay online forever

Enter your email to receive for free

The PANE method for Deep Learning

* indicates required

 

You will receive one email per day for 7 days – then you will receive my newsletter.
Your information will never be given to third parties.

You can unsubscribe in 1 click from any of my emails.



Entre ton email pour recevoir gratuitement
la méthode PARÉ pour faire du Deep Learning


Tu recevras un email par jour pendant 7 jours - puis tu recevras ma newsletter.
Tes informations ne seront jamais cédées à des tiers.

Tu peux te désinscrire en 1 clic depuis n'importe lequel de mes emails.