Votre première réseau de neurones sur processeur graphique (GPU). Guide pour les débutants

Votre première réseau de neurones sur processeur graphique (GPU). Guide pour les débutants
Dans cet article, je vais vous montrer comment configurer un environnement pour l'apprentissage automatique en 30 minutes, créer un réseau de neurones pour la reconnaissance d'images, puis exécuter ce même réseau sur un processeur graphique (GPU).

Pour commencer, définissons ce qu'est un réseau de neurones.

Dans notre cas, il s'agit d'un modèle mathématique, ainsi que de sa réalisation logicielle ou matérielle, construit selon le principe d'organisation et de fonctionnement des réseaux biologiques de neurones — des réseaux de cellules nerveuses d'un organisme vivant. Ce concept est né de l'étude des processus se déroulant dans le cerveau et de la tentative de modéliser ces processus.

Les réseaux de neurones ne sont pas programmés dans le sens habituel du terme, ils sont entraînés. La capacité d'apprentissage est l'un des principaux avantages des réseaux de neurones par rapport aux algorithmes traditionnels. Techniquement, l'apprentissage consiste à trouver les coefficients des connexions entre les neurones. Au cours de l'apprentissage, le réseau de neurones est capable de détecter des dépendances complexes entre les données d'entrée et de sortie, ainsi que de réaliser des généralisations.

Du point de vue de l'apprentissage automatique, un réseau de neurones représente un cas particulier des méthodes de reconnaissance de formes, d'analyse discriminante, de méthodes de clustering et d'autres approches.

Matériel

Tout d'abord, examinons le matériel. Nous avons besoin d'un serveur avec un système d'exploitation Linux installé. Le matériel pour les systèmes d'apprentissage automatique doit être suffisamment puissant et, par conséquent, cher. Pour ceux qui n'ont pas à disposition une bonne machine, je recommande de se tourner vers les offres des fournisseurs de cloud. Il est possible de louer le serveur nécessaire rapidement et de ne payer que pour le temps d'utilisation.

Dans les projets qui nécessitent la création de réseaux de neurones, j'utilise des serveurs d'un des fournisseurs de cloud russes. L'entreprise propose en location des serveurs cloud spécialement conçus pour l'apprentissage automatique avec de puissants processeurs graphiques (GPU) Tesla V100 de NVIDIA. En résumé : l'utilisation d'un serveur avec GPU peut être plusieurs dizaines de fois plus efficace (rapide) par rapport à un serveur similaire en termes de coût utilisant un CPU (le processeur central bien connu). Cela est dû aux caractéristiques de l'architecture GPU, qui gère les calculs plus rapidement.

Pour réaliser les exemples décrits ci-après, nous avons acquis un tel serveur pour quelques jours :

  • Disque SSD de 150 Go
  • RAM de 32 Go
  • Processeur Tesla V100 16 Go avec 4 cœurs

Nous avons installé Ubuntu 18.04 sur la machine.

Installation de l'environnement

Nous allons maintenant installer sur le serveur tout le nécessaire pour fonctionner. Comme notre article s'adresse avant tout aux débutants, j'expliquerai certains points qui leur seront particulièrement utiles.

Une grande partie du travail de configuration de l'environnement se fait via la ligne de commande. La plupart des utilisateurs utilisent Windows comme système d'exploitation. La console standard de ce système laisse à désirer. C'est pourquoi nous allons utiliser un outil pratique Cmder/. Téléchargez la version mini et lancez Cmder.exe. Ensuite, il faut se connecter au serveur via le protocole SSH :

ssh root@server-ip-or-hostname

Au lieu de server-ip-or-hostname, indiquez l'adresse IP ou le nom DNS de votre serveur. Ensuite, saisissez le mot de passe et, en cas de connexion réussie, nous devrions recevoir un message similaire.

Bienvenue sur Ubuntu 18.04.3 LTS (GNU/Linux 4.15.0-74-generic x86_64)

Le langage principal pour le développement de modèles de ML est Python. Et la plateforme la plus populaire pour son utilisation sous Linux est Anaconda.

Installons-le sur notre serveur.

Commençons par mettre à jour le gestionnaire de paquets local :

sudo apt-get update

Installons curl (outil en ligne de commande) :

sudo apt-get install curl

Téléchargez la dernière version de la distribution Anaconda :

cd /tmp
curl –O https://repo.anaconda.com/archive/Anaconda3-2019.10-Linux-x86_64.sh

Lancez l'installation :

bash Anaconda3-2019.10-Linux-x86_64.sh

Pendant le processus d'installation, vous devrez accepter le contrat de licence. Lors de l'installation réussie, vous devriez voir ceci :

Merci d'avoir installé Anaconda3 !

De nombreux frameworks ont été créés pour le développement de modèles de ML, nous travaillons avec les plus populaires : PyTorch et Tensorflow.

L'utilisation d'un framework permet d'accélérer le développement et d'utiliser déjà des outils prêts à l'emploi pour des tâches standard.

Dans cet exemple, nous allons travailler avec PyTorch. Installons-le :

conda install pytorch torchvision cudatoolkit=10.1 -c pytorch

Nous devons maintenant lancer Jupyter Notebook — un outil de développement populaire parmi les spécialistes du ML. Il permet d'écrire du code et de voir immédiatement les résultats de son exécution. Jupyter Notebook fait partie d'Anaconda et est déjà installé sur notre serveur. Il faut se connecter à lui depuis notre système de bureau.

Pour cela, nous allons d'abord lancer Jupyter sur le serveur en spécifiant le port 8080 :

jupyter notebook --no-browser --port=8080 --allow-root

Ensuite, en ouvrant un nouvel onglet dans notre console Cmder (menu supérieur — dialogue de nouvelle console), nous nous connecterons au serveur via SSH sur le port 8080 :

ssh -L 8080:localhost:8080 root@server-ip-or-hostname

En exécutant la première commande, nous recevrons des liens pour ouvrir Jupyter dans notre navigateur :

Pour accéder au notebook, ouvrez ce fichier dans un navigateur :
        file:///root/.local/share/jupyter/runtime/nbserver-18788-open.html
    Ou copiez et collez l'un de ces URL :
        http://localhost:8080/?token=cca0bd0b30857821194b9018a5394a4ed2322236f116d311
     ou http://127.0.0.1:8080/?token=cca0bd0b30857821194b9018a5394a4ed2322236f116d311

Utilisons le lien pour localhost:8080. Copiez le chemin complet et collez-le dans la barre d'adresse du navigateur local de votre PC. Jupyter Notebook s'ouvrira.

Créons un nouveau notebook : New — Notebook — Python 3.

Vérifions le bon fonctionnement de tous les composants que nous avons installés. Entrons un exemple de code PyTorch dans Jupyter et exécutons-le (bouton Run) :

from __future__ import print_function
import torch
x = torch.rand(5, 3)
print(x)

Le résultat devrait être à peu près comme ceci :

Votre première réseau de neurones sur processeur graphique (GPU). Guide pour les débutants

Si vous obtenez un résultat similaire, cela signifie que nous avons tout configuré correctement et que nous pouvons commencer à développer le réseau neuronal !

Créons un réseau neuronal

Nous allons créer un réseau neuronal pour la reconnaissance d'images. Nous baserons notre travail sur ce qui suit guide.

Pour entraîner le réseau, nous utiliserons le jeu de données public CIFAR10. Il comprend les classes : « avion », « voiture », « oiseau », « chat », « cerf », « chien », « grenouille », « cheval », « bateau », « camion ». Les images dans CIFAR10 ont une taille de 3x32x32, c'est-à-dire des images couleur à 3 canaux de 32x32 pixels.

Votre première réseau de neurones sur processeur graphique (GPU). Guide pour les débutants
Pour notre travail, nous utiliserons le paquet PyTorch conçu pour travailler avec des images — torchvision.

Nous allons procéder comme suit :

  • Chargement et normalisation des ensembles de données d'entraînement et de test
  • Définition du réseau neuronal
  • Entraînement du réseau sur les données d'entraînement
  • Test du réseau sur les données de test
  • Répétition de l'entraînement et des tests en utilisant le GPU

Tout le code ci-dessous sera exécuté dans Jupyter Notebook.

Chargement et normalisation de CIFAR10

Copiez et exécutez le code suivant dans Jupyter :


import torch
import torchvision
import torchvision.transforms as transforms

transform = transforms.Compose(
    [transforms.ToTensor(),
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
                                        download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4,
                                          shuffle=True, num_workers=2)

testset = torchvision.datasets.CIFAR10(root='./data', train=False,
                                       download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=4,
                                         shuffle=False, num_workers=2)

classes = ('avion', 'voiture', 'oiseau', 'chat',
           'serval', 'chien', 'grenouille', 'cheval', 'navire', 'camion')

La réponse doit être la suivante :

Téléchargement de https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz vers ./data/cifar-10-python.tar.gz
Extraction de ./data/cifar-10-python.tar.gz vers ./data
Fichiers déjà téléchargés et vérifiés

Affichons quelques images d'entraînement pour vérification :


import matplotlib.pyplot as plt
import numpy as np

# fonctions pour afficher une image

def imshow(img):
    img = img / 2 + 0.5     # dénormaliser
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg, (1, 2, 0)))
    plt.show()

# obtenir quelques images d'entraînement aléatoires
dataiter = iter(trainloader)
images, labels = dataiter.next()

# afficher les images
imshow(torchvision.utils.make_grid(images))
# afficher les étiquettes
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))

Votre première réseau de neurones sur processeur graphique (GPU). Guide pour les débutants

Définition du réseau neuronal

Examinons d'abord comment fonctionne un réseau de neurones pour la reconnaissance d'images. C'est un réseau simple à propagation directe. Il reçoit les données d'entrée, les passe à travers plusieurs couches successivement, puis, enfin, produit les sorties.

Votre première réseau de neurones sur processeur graphique (GPU). Guide pour les débutants

Créons un réseau similaire dans notre environnement :


import torch.nn as nn
import torch.nn.functional as F

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3, 6, 5)
        self.pool = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = x.view(-1, 16 * 5 * 5)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

net = Net()

Définissons également la fonction de perte et l'optimiseur


import torch.optim as optim

criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)

Entraînement du réseau sur les données d'entraînement

Nous commençons l'entraînement de notre réseau de neurones. Je note qu'après l'exécution de ce code, il faudra attendre un certain temps jusqu'à ce que le processus soit terminé. Cela m'a pris 5 minutes. L'entraînement du réseau nécessite du temps.

 pour epoch dans range(2): # boucle sur l'ensemble de données plusieurs fois

 running_loss = 0.0
 pour i, données dans enumerate(trainloader, 0):
 # obtenir les entrées ; données est une liste de [entrées, étiquettes]
 entrées, étiquettes = données

 # zéro les gradients des paramètres
 optimizer.zero_grad()

 # avant + arrière + optimiser
 sorties = net(entrées)
 perte = criterion(sorties, étiquettes)
 perte.backward()
 optimizer.step()

 # imprimer des statistiques
 running_loss += perte.item()
 si i % 2000 == 1999: # imprimer tous les 2000 mini-lots
 print('[%d, ] perte : %.3f' %
 (epoch + 1, i + 1, running_loss / 2000))
 running_loss = 0.0

print('Formation terminée')

Nous obtiendrons ce résultat :

Votre première réseau de neurones sur processeur graphique (GPU). Guide pour les débutants

Enregistrons notre modèle entraîné :

PATH = './cifar_net.pth'
torch.save(net.state_dict(), PATH)

Test du réseau sur les données de test

Nous avons entraîné le réseau en utilisant un ensemble de données d'entraînement. Mais nous devons vérifier si le réseau a vraiment appris quelque chose.

Nous allons vérifier cela en prédisant l'étiquette de classe que le réseau de neurones retourne et en la comparant à la vérité. Si la prédiction est correcte, nous ajoutons l'échantillon à la liste des bonnes prédictions.
Affichons une image de l'ensemble de test :

dataiter = iter(testloader)
images, labels = dataiter.next()

# afficher les images
imshow(torchvision.utils.make_grid(images))
print('Vérité de terrain : ', ' '.join('%5s' % classes[labels[j]] for j in range(4)))

Votre première réseau de neurones sur processeur graphique (GPU). Guide pour les débutants

Demandons maintenant au réseau de neurones de nous dire ce qu'il y a sur ces images :


net = Net()
net.load_state_dict(torch.load(PATH))

outputs = net(images)

_, predicted = torch.max(outputs, 1)

print('Prédiction : ', ' '.join('%5s' % classes[predicted[j]]
                              for j in range(4)))

Votre première réseau de neurones sur processeur graphique (GPU). Guide pour les débutants

Les résultats semblent assez bons : le réseau a correctement identifié trois images sur quatre.

Voyons maintenant comment le réseau se comporte sur l'ensemble des données.


correct = 0
total = 0
with torch.no_grad():
    for data in testloader:
        images, labels = data
        outputs = net(images)
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

print('Précision du réseau sur les 10000 images de test : %d %%' % (
    100 * correct / total))

Votre première réseau de neurones sur processeur graphique (GPU). Guide pour les débutants

Il semble que le réseau sait quelque chose et fonctionne. S'il devait déterminer les classes au hasard, la précision serait de 10%.

Regardons maintenant quels classes le réseau identifie le mieux :

class_correct = list(0. pour i dans range(10))
class_total = list(0. pour i dans range(10))
avec torch.no_grad():
 pour données dans testloader:
 images, étiquettes = données
 sorties = net(images)
 _, prédit = torch.max(sorties, 1)
 c = (prédit == étiquettes).squeeze()
 pour i dans range(4):
 étiquette = étiquettes[i]
 class_correct[étiquette] += c[i].item()
 class_total[étiquette] += 1


pour i dans range(10):
 print('Précision de %5s : %%' % (
 classes[i], 100 * class_correct[i] / class_total[i]))

Votre première réseau de neurones sur processeur graphique (GPU). Guide pour les débutants

Il semble que le réseau identifie le mieux les voitures et les navires : 71% de précision.

Donc, le réseau fonctionne. Essayons maintenant de porter son fonctionnement sur un processeur graphique (GPU) et voyons ce qui changera.

Entraînement du réseau de neurones sur GPU

Tout d'abord, expliquons brièvement ce qu'est CUDA. CUDA (Compute Unified Device Architecture) est une plateforme de calcul parallèle développée par NVIDIA, conçue pour des calculs généraux sur des unités de traitement graphique (GPU). Grâce à CUDA, les développeurs peuvent considérablement accélérer les applications de calcul en exploitant les capacités des GPU. Sur notre serveur que nous avons acquis, cette plateforme est déjà installée.

Définissons d'abord notre GPU comme le premier appareil CUDA visible.

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
# En supposant que nous sommes sur une machine CUDA, cela devrait imprimer un appareil CUDA :
print(device)

Votre première réseau de neurones sur processeur graphique (GPU). Guide pour les débutants

Envoyons le réseau vers le GPU :

net.to(device)

Nous devrons également envoyer les entrées et les cibles à chaque étape sur le GPU :

inputs, labels = data[0].to(device), data[1].to(device)

Lançons la réentraînement du réseau déjà sur le GPU :

import torch.optim as optim

criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
pour epoch dans range(2): # boucle sur l'ensemble de données plusieurs fois

 running_loss = 0.0
 pour i, données dans enumerate(trainloader, 0):
 # obtenir les entrées ; données est une liste de [entrées, étiquettes]
 entrées, étiquettes = données[0].to(device), données[1].to(device)

 # zéro les gradients des paramètres
 optimizer.zero_grad()

 # avant + arrière + optimiser
 sorties = net(entrées)
 perte = criterion(sorties, étiquettes)
 perte.backward()
 optimizer.step()

 # imprimer des statistiques
 running_loss += perte.item()
 si i % 2000 == 1999: # imprimer tous les 2000 mini-lots
 print('[%d, ] perte : %.3f' %
 (epoch + 1, i + 1, running_loss / 2000))
 running_loss = 0.0

print('Formation terminée')

Cette fois-ci, l'entraînement du réseau a duré environ 3 minutes. Rappelons que la même étape sur un processeur ordinaire durait 5 minutes. La différence n'est pas significative, cela se produit parce que notre réseau n'est pas si grand. Lors de l'utilisation de grands ensembles pour l'entraînement, la différence de vitesse entre un GPU et un processeur traditionnel augmentera.

Voilà, c'est tout. Ce que nous avons pu réaliser :

  • Nous avons examiné ce qu'est un GPU et choisi un serveur sur lequel il est installé ;
  • Nous avons configuré l'environnement logiciel pour créer un réseau de neurones ;
  • Nous avons créé un réseau de neurones pour la reconnaissance d'images et l'avons entraîné ;
  • Nous avons réentraîné le réseau en utilisant un GPU et avons gagné en vitesse.

Je serai ravi de répondre à vos questions dans les commentaires.

Source : habr.com

Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS 🔥 Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster