Skip to content
Snippets Groups Projects
TP_Classification.md 13.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • Alexandre MEYER's avatar
    Alexandre MEYER committed
    ---
    title: "Apprentissage et images (partie AM) - TP"
    description: "Partie A. Meyer"
    ---
    
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    ## Partie (I.a) Classification de points 2D
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    
    Un réseau de neurones est un très bon 'classifier'. Dans un exemple simple, nous voudrions reconnaitre la classe d'un point à partir de ses coordonnées 2D notées (x_1, x_2). Un point peut appartenir à 2 classes : classe 1 par exemple en bleu ou classe 2 par exemple en vert. Le réseau prend en entrée 2 valeurs (x_1, x_2) et sort 2 valeurs (suis-je_bleu?, suis-je_vert?). "suis-je_bleu?" sera représenté par un nombre réel entre 0 et 1 : proche de 0 indiquant que le point n’appartient pas à la classe, proche de 1 indiquant que le point appartient à la classe. Par exemple, une sortie \[0.3, 0.7\] sera tranché en "c'est un point de la classe 2".
    
      
    L'entrainement du réseau consistera à lui montrer toute une série de coordonnées de points avec les valeurs de classes associées. Le réseau va optimiser ses paramètres (poids) pour que le taux d'erreur devienne le plus petit possible.
    
      
    
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    ### Un neurone
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    
    Un neurone artificiel (ou un perceptron) reçoit des valeurs d’entrées, il les multiplie une à une par un poids, puis en fait la somme. Cette
    somme est passée à une fonction d'activation. Par exemple une fonction d'activation très simple peut-être de comparer la somme à un seuil. Si
    elle est inférieure, la valeur de sortie sera 0, 1 sinon. L’objectif de l’apprentissage/optimisation est de retrouver les poids qui ferons correspondre au mieux la sortie à partir des entrées sur une base de connaissance disponible.
    
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    ![Image alt](../images/dl_neuron.png)
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    ### Un réseau
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    
    Le principe du réseau de neurones est d’assembler entre-eux des neurones, pour leur faire apprendre des tâches complexes. Les neurones
    vont être regroupés en couches, une couche réalisant une tâche donnant un niveau d’abstraction supplémentaire pour la couche suivante. Par
    exemple, pour reconnaître une lettre, la couche la plus basse va repérer des morceaux de courbes et la couche supérieure estime que certaines
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    courbes ensembles forme un 'A' et non un 'S'. L’utilisation de plusieurs couches (layer en anglais) est appelée apprentissage profond/Deep Learning. 
    
    ![Image alt](../images/dl_layer.png)
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    Voir une explication plus détaillé dans le cours ou éventuellement sur internet, [la page Wikipedia par exemple.](https://fr.wikipedia.org/wiki/R%C3%A9seau_de_neurones_artificiels)
    
    Dans un 1er temps, allez jouer avec ["Playground classifier"](https://playground.tensorflow.org/) pour comprendre le principe de la classification de points avec un réseau de neurones profond.
    
    ![Image alt](../images/dl_playground.jpg)
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    
      
      
    Dans un 2e temps vous allez écrire votre classifier de points avec PyTorch.
    
      
      
    
      
      
    
    #### Les données
    
    Pour notre problème de reconnaitre la couleur d'un point, il faut des données d'apprentissage. [Le code de départ est donné ici](https://github.com/ucacaxm/DeepLearning_Vision_SimpleExamples/blob/master/src/classifier/classifier_pointcloud_empty.py).
    Ce code génère des points (les données) procéduralement, donc autant que l'on veut. La classe bleu sont les points dont les coordonnées sont inférieures à cosinus, et la classe verte sont les points au dessus de cosinus. Dans un "vrai" problème, ces données ne peuvent se générer, il faut les trouver quelque part ...
    
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    ![Image alt](../images/point_cloud.jpg)
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    #### L'apprentissage
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    
    L'apprentissage se passe en différentes phases.
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    * La définition du réseau.
    
    * La configuration de l'optimisation (optimizer), en général [Stochastic Gradient Descent](https://fr.wikipedia.org/wiki/Algorithme_du_gradient_stochastique) couplé à une fonction d'erreur.
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    * La phase d'entrainement optimise les poids de chaque neurone à partir des données d'entrée couplées à leur sortie. La fonction d'erreur sert de mesure à faire descendre.
    * Une phase d'évaluation **avec des données que le réseau n'a jamais vu** pour mesurer la qualité de l'apprentissage.
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    
    Le code ci-dessous donne un réseau minimaliste. Vous devrez l'améliorer pour qu'il soit plus efficace.
    ```
          # voir le code de départ : https://github.com/ucacaxm/DeepLearning_Vision_SimpleExamples/blob/master/src/classifier/classifier_pointcloud_empty.py
    
            ########################################################################################"
            # Copier/coller juste avant main
            class Net(nn.Module):
               def __init__(self):
                   super(Net, self).__init__()
                   self.fc1 = nn.Linear(2, 64)
                   self.fc2 = nn.Linear(64, 2)
    
               def forward(self, x):
                  x = F.relu(self.fc1(x))
                  x = self.fc2(x)
                  return x
    
    
    
            ########################################################################################"
            ############# NETWORK definition/configuration => à copier/coller dans le main 
            net = Net()
            print(net)
    
            ############# SGD config: Stochastic Gradient Descent Config
            criterion = nn.CrossEntropyLoss()
            optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
    
            ############ TRAINNING
            for epoch in range(5):  # loop over the dataset multiple times
                running_loss = 0.0
                for i in range(1000):         # iterate on mini batches. mani-batch = a subset of the database
                    inputs, labels = next_batch(128)
                    inputs = torch.from_numpy(inputs)
                    labels = torch.from_numpy(labels).long()
                   
                    # zero the parameter gradients
                    optimizer.zero_grad()
            
                    # forward + backward + optimize
                    outputs = net(inputs)
                    loss = criterion(outputs, torch.max(labels, 1)[1] )
                    loss.backward()
                    optimizer.step()
    
                    # print statistics
                    running_loss += loss.item()
                    if i % 100 == 99:    # print every 2000 mini-batches
                        print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 100))
                        running_loss = 0.0      
    
    
            ############ EVALUATION         
            TODO
            
            ############ DRAWING POINT CLOUD WITH ERROR
            TODO
    ```
    
    [Regardez la page des tutos de PyTorch](https://pytorch.org/tutorials/beginner/blitz/neural_networks_tutorial.html#sphx-glr-beginner-blitz-neural-networks-tutorial-py).
    
      
      
    
    #### Le résultat
    
    Le résultat sera le taux de bonne reconnaissance de points, mesuré avec des points jamais observés pendant l'apprentissage. Par exemple après un apprentissage nous obtenons un taux de 96% de bonne classification, les points rouges sur l'image suivante sont les points mal classifiés.
    
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    ![Image alt](../images/point_cloud_errorclassif.jpg)
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    
      
      
    Pour rendre le problème un peu plus dur vous pouvez augmenter la plage de génération des points en changeant ceci :
    ```
        x = np.array( [ 2.0*3.141592*np.random.ranf(), 2.0*np.random.ranf()-1 ])
        devient
        x = np.array( [ 4.0*3.141592*np.random.ranf(), 2.0*np.random.ranf()-1 ])
    ```
      
      
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    ![Image alt](../images/point_cloud_4pi.jpg)
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    ## Partie (I.b) Classification d'images
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    
    Pour ce 2e problème un peu plus concret, nous disposons d'images et nous voudrions reconnaitre la classe à laquelle elles appartiennent. Par
    exemple, pour reconnaitre le nombre à partir de l'image du nombre écrit ou reconnaitre la figure géométrique à partir d'un dessin, ou plus largement reconnaitre une famille d'objets (chat, voiture, avion, fourchette, etc.).
    
      
    
    Pour ce type de tâche, le réseau approprié est le ConvNET ou CNN : Convolution Neural Network. Vous pouvez lire des explications sur ce qu'est un CNN dans le cours ou éventuellement sur internet (par exemple une [explication intuitive ici](https://ujjwalkarn.me/2016/08/11/intuitive-explanation-convnets/) ).
    
    
    Pour du code avec pytorch, [regardez ici](https://pytorch.org/tutorials/beginner/blitz/cifar10_tutorial.html).
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    ![Image alt](../images/convnet.png)
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    ### Les données
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    Pour ce TP, nous vous invitons à utiliser [une base de données d'images issue d'un projet de L3 qui cherche à reconnaitre 5 formes dessinés](https://github.com/ucacaxm/DeepLearning_Vision_SimpleExamples/blob/master/data/shapes5_preprocessed.zip) : carré, cercle, triangle, sablier, étoile. Il y a que quelques centaines d'images par forme, c'est un bon challenge de voir ce que la reconnaissance donne avec finalement assez peu d'images. Il est également intéressant d'augmenter les données. Dans le cas d'images comme ici, vous pouvez faire de petites rotations aléatoires aux images pour en augmenter le nombre.
    
      
    Il est également possible d'utiliser différentes base de données plus classiques :
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    * MNIST : un base de donnée de chiffre manuscrits
    
    * la base de données de caractères [EMNIST](https://www.nist.gov/itl/iad/image-group/emnist-dataset). Elle est disponible également [ici avec les images rangées dans un répertoire dont le nom est le code ascii en hexa](https://s3.amazonaws.com/nist-srd/SD19/by_merge.zip).
    * Toutes les bases classiques de reconnaissance de catégorie d'images: [CIFAR-10](https://www.cs.toronto.edu/~kriz/cifar.html) ou CIFAR-100
    * Un peu plus de challenge avec [QuickDraw](https://www.kaggle.com/c/quickdraw-doodle-recognition/data|quickdraw-doodle-recognition) : une base de dessins manuels à reconnaitre, 300 classes, 73 Go de données vectoriels et $12000 de récompense ...
    
    Un exemple de code qui charge une base d'images, voir également la doc de [DataLoader](https://pytorch.org/docs/stable/data.html#) :
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    ```
        from torchvision import datasets, transforms
        from torch.autograd import Variable
        import torchvision
        from torchvision.datasets import ImageFolder
        from torch.utils.data import DataLoader
        from torchvision.transforms import ToTensor
        from torch.utils.data.sampler import SubsetRandomSampler
    
    
    
        class MyTransform(object):    # Votre propre fonction de transfo d'images utilisée en preprocessing (si besoin)
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
            def __call__(self, x):
                y = preprocess(x)
                return y
    
    
    
        def imshow(img):              # Pour afficher une image
            plt.figure(1)
            img = img / 2.0 + 0.5     # unnormalize
            npimg = img.numpy()
            plt.imshow(np.transpose(npimg, (1, 2, 0)))
            #plt.imshow(npimg)
            plt.show()
    
    
                
    
            transform_img = transforms.Compose([
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
                    MyTransform(),    # Votre propre fonction de transfo d'images utilisée en preprocessing
                    transforms.Resize(16),
                    #transforms.CenterCrop(256),
                    transforms.ToTensor(),
                    transforms.Normalize(mean=[0., 0., 0.],
                                         std=[0.5, 0.5, 0.5] )
                    ])
    
            mydata = ImageFolder(root="../data/shapes5_preprocessed", transform=transform_img)
            loader = DataLoader(mydata, batch_size=32, shuffle=True, num_workers=1)
    
    * [Un très bon tutoriel sur le chargement de données avec PyTorch](https://github.com/ncullen93/torchsample/blob/master/examples/Transforms%20with%20Pytorch%20and%20Torchsample.ipynb)
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    ### Le réseau 
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    Le code d'un réseau ressemble à ceci. Il y a la partie qui extrait les descripteurs (features) et la partie qui classifie.
    
    ```
    class Classifier(nn.Module):
        def __init__(self):
            super(Discriminator, self).__init__()
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
            self.features = nn.Sequential(
                # 3 input image channel, 6 output channels, 5x5 square convolution
                nn.Conv2d(3, 6, 5)
                nn.ReLU(),
                nn.MaxPool2d(2, 2),            
                nn.Conv2d(6, 16, 5)
    
                # ... à compléter
                # ... 
    
            )
    
            self.classifier = nn.Sequential(
                nn.Linear(16 * 5 * 5, 120),
                nn.ReLU(),
                nn.Linear(120, 84),
                nn.ReLU(),
                nn.Linear(84, 10)
            )
            print(self.features)
            print(self.classifier)
    
    
    
        def forward(self, input):
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
            x = self.features(x)
            x = x.view(x.size(0), -1)
            x = self.classifier(x)
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    De manière assez similaire au classifier de nuages de point plus haut, il faut entrainer le réseau en déclarant également le DataLoader,  l'optimiseur, la loss, etc. [Regardez le tutorial CIFAR10 de pytorch](https://github.com/pytorch/tutorials/blob/main/beginner_source/blitz/cifar10_tutorial.py)
    
    ```
    net = Classifier()
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
    
    for epoch in range(2):  # loop over the dataset multiple times
        running_loss = 0.0
        for i, data in enumerate(trainloader, 0):
            # get the inputs; data is a list of [inputs, labels]
            inputs, labels = data
    
            # zero the parameter gradients
            optimizer.zero_grad()
    
            # forward + backward + optimize
            outputs = net(inputs)
            loss = criterion(outputs, labels)
            loss.backward()
            optimizer.step()
    
            # print statistics
            running_loss += loss.item()
            if i % 2000 == 1999:    # print every 2000 mini-batches
                print(f'[{epoch + 1}, {i + 1:5d}] loss: {running_loss / 2000:.3f}')
                running_loss = 0.0
    
    print('Finished Training')
    
    ```
    
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    Il est intéressant de voir que chaque couche de convolutions devient de plus en plus spécifiques à l'objet :
    
    Alexandre MEYER's avatar
    Alexandre MEYER committed
    ![Image alt](../images/dl_cnn_progression.png)