Exemple de réseau convolutionnel avec PyTorch. On construit des données simples d'images représentant des disques et des carrés puis on entraîne un petit réseau à trois couches pour qu'il distingue les deux classes.
Tout d'abord on importe les librairies PyTorch, NumPy et MatplotLib :
import torch
import torch.nn.functional as F
import torch.nn as nn
import numpy as np
import matplotlib.pyplot as plt
Fonction pour créer les données images : la fonction CreateData renvoie un tenseur I de taille Nx1x32x32 contenant les images et un vecteur L contenant les classes (0 ou 1)
def CreateData(N):
M = 32
I = np.zeros([N,1,M,M])
L = np.zeros(N)
ech = np.linspace(0,1,M)
X,Y = np.meshgrid(ech,ech)
for k in range(N):
r = 1/8+np.random.rand(1)/8 # random radius
c = r+(1-2*r)*np.random.rand(2,1) # random center position
if np.random.rand(1)<.5:
# circle
I[k,0,:,:] = ((X-c[0])**2+(Y-c[1])**2) < r**2
L[k] = 0
else:
# square
I[k,0,:,:] = np.maximum(np.abs(X-c[0]),np.abs(Y-c[1])) < r
L[k] = 1
return I, L
On crée 2500 images et on affiche les 5 premières :
N = 100
I, L = CreateData(N)
for k in range(5):
plt.subplot(1, 5, k+1)
plt.imshow(np.squeeze(I[k,0,:,:]))
plt.show()
On convertit les données en tenseurs PyTorch :
I = torch.from_numpy(I.astype('float32'))
L = torch.from_numpy(L.astype('long'))
On sépare les données en deux parties : données d'entraînement pour les 90 premières, et données de test pour les 10 dernières :
Ntest = len(L)-10
Itrain = I[:Ntest,:,:,:]
Ltrain = L[:Ntest]
Itest = I[Ntest:,:,:,:]
Ltest = L[Ntest:]
A présent on définit les variables du réseau : deux filtres de convolution fa et fb et une matrice m. On ajoute requires_grad=True pour indiquer qu'on va avoir besoin de calculer le gradient de la fonction de coût par rapport à ces variables.
fa = torch.randn(16,1,5,5,requires_grad=True)
fb = torch.randn(32,16,5,5,requires_grad=True)
m = torch.randn(8*8*32,2,requires_grad=True)
On définit maintenant une fonction pour le réseau. Cette fonction correspond au calcul "forward" du réseau : l'entrée est supposé être un tenseur I de taille (batch_size,1,32,32), auquel on va appliquer successivement les opérations des différentes couches du réseau.
def mycnn(I): # input I shape: (N,1,32,32)
# first layer
out = F.conv2d(I, fa, padding=2) # out shape: (N,16,32,32)
out = nn.BatchNorm2d(16, affine=False)(out) # out shape: (N,16,32,32)
out = torch.relu(out) # out shape: (N,16,32,32)
out = F.max_pool2d(out,2,stride=2) # out shape: (N,16,16,16)
# second layer
out = F.conv2d(out, fb, padding=2) # out shape: (N,32,16,16)
out = nn.BatchNorm2d(32, affine=False)(out) # out shape: (N,32,16,16)
out = torch.relu(out) # out shape: (N,32,16,16)
out = F.max_pool2d(out,2,stride=2) # out shape: (N,32,8,8)
# flatten
out = out.reshape(out.size(0), -1) # out shape: (N,32*8*8)
# third layer
out = out.mm(m) # out shape: (N,2)
return out
On passe à présent à l'entraînement du réseau :
niter = 100
lossrec = np.zeros(niter)
learning_rate = 1e-2
for t in range(niter):
out = mycnn(Itrain)
loss = nn.CrossEntropyLoss()(out,Ltrain)
lossrec[t] = loss.item()
print(t,lossrec[t])
loss.backward()
fa.data -= learning_rate * fa.grad.data # fa <- fa - eta * gradient_fa(loss)
fa.grad.zero_()
fb.data -= learning_rate * fb.grad.data # fb <- fb - eta * gradient_fb(loss)
fb.grad.zero_()
m.data -= learning_rate * m.grad.data # m <- m - eta * gradient_m(loss)
m.grad.zero_()
0 30.527368545532227 1 28.56959342956543 2 26.738861083984375 3 25.08894920349121 4 23.530702590942383 5 22.035707473754883 6 20.602123260498047 7 19.22312355041504 8 17.922061920166016 9 16.724319458007812 10 15.747651100158691 11 14.91407585144043 12 14.087448120117188 13 13.364411354064941 14 12.765649795532227 15 12.231484413146973 16 11.758696556091309 17 11.31386947631836 18 10.891640663146973 19 10.489226341247559 20 10.124588012695312 21 9.77418327331543 22 9.434625625610352 23 9.103911399841309 24 8.784868240356445 25 8.478388786315918 26 8.195160865783691 27 7.927760601043701 28 7.665294647216797 29 7.411813735961914 30 7.168008804321289 31 6.9280266761779785 32 6.698916912078857 33 6.476353645324707 34 6.261178016662598 35 6.039685249328613 36 5.833250045776367 37 5.6365766525268555 38 5.447880744934082 39 5.264623165130615 40 5.090309143066406 41 4.932889461517334 42 4.784624099731445 43 4.641351699829102 44 4.5012383460998535 45 4.359932899475098 46 4.220130920410156 47 4.0836591720581055 48 3.951399087905884 49 3.824629306793213 50 3.702787160873413 51 3.584390878677368 52 3.4689130783081055 53 3.3577311038970947 54 3.2494120597839355 55 3.143336772918701 56 3.0405328273773193 57 2.9414520263671875 58 2.8444621562957764 59 2.7517850399017334 60 2.6620891094207764 61 2.574885845184326 62 2.4895339012145996 63 2.406500816345215 64 2.3254735469818115 65 2.246687173843384 66 2.1700539588928223 67 2.095825433731079 68 2.0228726863861084 69 1.9519327878952026 70 1.8821570873260498 71 1.81484055519104 72 1.7512398958206177 73 1.694072961807251 74 1.6452248096466064 75 1.6110941171646118 76 1.593713402748108 77 1.564404845237732 78 1.545613408088684 79 1.4711331129074097 80 1.4382684230804443 81 1.3533761501312256 82 1.3168412446975708 83 1.240610122680664 84 1.196004033088684 85 1.1308692693710327 86 1.0852550268173218 87 1.0281211137771606 88 0.9816290736198425 89 0.9303930401802063 90 0.8873701095581055 91 0.8431946039199829 92 0.8033459186553955 93 0.7638629674911499 94 0.7264719009399414 95 0.6898400187492371 96 0.6552538275718689 97 0.6207413077354431 98 0.5883398056030273 99 0.556555449962616
On affiche les valeurs de la fonction coût en fonction des itérations :
plt.plot(lossrec)
plt.show()
Le réseau est entraîné ; à présent on évalue les performances, d'abord on calcule le nombre d'erreurs sur les données d'apprentissage ; ensuite sur les données de test :
values, indices = torch.max(out, 1)
print("taux d'erreur sur données d'apprentissage:",(indices!=Ltrain).float().mean().item())
taux d'erreur sur données d'apprentissage: 0.08888889104127884
out = mycnn(Itest)
values, indices = torch.max(out, 1)
print("taux d'erreur sur données de test:",(indices!=Ltest).float().mean().item())
taux d'erreur sur données de test: 0.5
On voit ici que le taux d'erreurs sur les données d'apprentissage est assez faible mais beaucoup moins sur les données test. La taille N=100 des données n'est pas suffisante, et surtout l'apprentissage s'est fait toujours sur les mêmes données, ce qui aboutit à du surapprentissage.