
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 . 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-hostnameAu 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 .
Installons-le sur notre serveur.
Commençons par mettre à jour le gestionnaire de paquets local :
sudo apt-get updateInstallons curl (outil en ligne de commande) :
sudo apt-get install curlTé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.shLancez l'installation :
bash Anaconda3-2019.10-Linux-x86_64.shPendant 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 : et .
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 pytorchNous 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-rootEnsuite, 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-hostnameEn 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 :

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 .
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.

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ésAffichons 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)))

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.

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 :

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))) 
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)))

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)) 
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])) 
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) 
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
