Vérification des versions¶

In [1]:
#version de python
import sys
print(sys.version)
3.11.9 | packaged by Anaconda, Inc. | (main, Apr 19 2024, 16:40:41) [MSC v.1916 64 bit (AMD64)]
In [2]:
#version de pytorch
import torch
print(torch.__version__)
2.3.0+cpu

Chargement et préparation des données¶

Importation, inspection¶

In [3]:
#modif. du dossier de travail
import os
os.chdir("C:/Users/ricco/Desktop/demo")

#librairie pandas
import pandas

#chargement de la première feuille de données
#individus et variables actives
X = pandas.read_excel("cars_autoencoder.xlsx",header=0,index_col=0,sheet_name=0)

#affichage des 10 premières lignes
print(X.head(10))
                puissance  cylindree  vitesse  longueur  hauteur  poids
Modele                                                                 
CITRONC5              210       2946      230       475      148   1589
LAGUNA                165       1998      218       458      143   1320
CITRONC4              138       1997      207       426      146   1381
CLIO                  100       1461      185       382      142    980
CITRONC2               61       1124      158       367      147    932
MODUS                 113       1598      188       380      159   1170
MEGANECC              165       1998      225       436      141   1415
TWINGO                 60       1149      151       344      143    840
MONDEO                145       1999      215       474      143   1378
VECTRA                150       1910      217       460      146   1428
In [4]:
#informations sur les variables
X.info()
<class 'pandas.core.frame.DataFrame'>
Index: 28 entries, CITRONC5     to AUDIA8     
Data columns (total 6 columns):
 #   Column     Non-Null Count  Dtype
---  ------     --------------  -----
 0   puissance  28 non-null     int64
 1   cylindree  28 non-null     int64
 2   vitesse    28 non-null     int64
 3   longueur   28 non-null     int64
 4   hauteur    28 non-null     int64
 5   poids      28 non-null     int64
dtypes: int64(6)
memory usage: 1.5+ KB
In [5]:
#dimensions
print(X.shape)

#nombre d'observations
n = X.shape[0]

#nombre de variables
p = X.shape[1]
(28, 6)

Standardisation des variables¶

In [6]:
#outil centrage réduction
from sklearn.preprocessing import StandardScaler

#instanciation
std = StandardScaler()

#transformation
Z = std.fit_transform(X)

#affichage des 10 premières lignes
print(Z[:10,:])
[[ 0.84249298  0.87217654  0.86954849  0.96525293  0.02554647  0.9621173 ]
 [ 0.2271068  -0.106354    0.49971521  0.603188   -0.68975475 -0.01928404]
 [-0.1421249  -0.10738621  0.16070137 -0.07834598 -0.26057402  0.20326422]
 [-0.66178434 -0.6606482  -0.51732632 -1.01545521 -0.83281499 -1.25971696]
 [-1.19511902 -1.00850135 -1.34945121 -1.33492426 -0.11751377 -1.43483691]
 [-0.48400611 -0.51923608 -0.424868   -1.05805108  1.59920915 -0.56653386]
 [ 0.2271068  -0.106354    0.71545129  0.13463339 -0.97587523  0.32730751]
 [-1.20879427 -0.98269622 -1.56518729 -1.82477681 -0.68975475 -1.77048346]
 [-0.04639816 -0.1053218   0.40725689  0.94395499 -0.68975475  0.19231922]
 [ 0.02197808 -0.19718806  0.46889577  0.64578387 -0.26057402  0.37473583]]
In [7]:
#tranformer les variables Z en tensor
tensor_Z = torch.FloatTensor(Z)

#qui est d'un type particulier
print(type(tensor_Z))
<class 'torch.Tensor'>
In [8]:
#vérif. dimensions
print(tensor_Z.shape)
torch.Size([28, 6])

Auto-encodeur avec PyTorch¶

Elaboration de la structure¶

In [9]:
#Auto-encodeur
class MyEncoder(torch.nn.Module):
    #constructeur
    def __init__(self,p):
        #appel du constructeur de l'ancêtre
        super(MyEncoder,self).__init__()
        #couche d'entrée (p variables) vers intermédiaire (2 neurones)
        self.layer1 = torch.nn.Linear(p,2)
        #fonction de transfert tangente hyperbolique
        self.ft1 = torch.nn.Tanh()
        #couche vers sortie (p neurones) - linéaire
        self.layer2 = torch.nn.Linear(2,p)
        
    #premier forward
    #couche entrée -> couche cachée
    def forward_1(self,x):
        #application de la combinaison linéaire
        comb_lin_1 = self.layer1(x)
        #appliquer la fonction tanh
        return self.ft1(comb_lin_1)
    
    #second forward
    #couche cachée -> couche sortie
    def forward_2(self,x_prim):
        #puis seconde combinaison linéaire
        comb_lin_2 = self.layer2(x_prim)
        return comb_lin_2


    #calcul de la sortie du réseau
    #à partir d'une matrice x en entrée
    def forward(self,x):        
        #premier forward
        out_1 = self.forward_1(x)
        #second forward
        out_2 = self.forward_2(out_1)
        #return
        return out_2

Paramétrage et instanciation¶

In [10]:
#fonction critère à optimiser - MSE
critere_rna = torch.nn.MSELoss()
In [11]:
# instanciation du modele
# p = le nombre de descripteurs => 6
rna = MyEncoder(p)
In [12]:
#algorithme d'optimisation
#on lui passe les paramètres à manipuler
optimiseur_rna = torch.optim.Adam(rna.parameters())

Valeur initiale de la perte¶

In [13]:
#projection avec les poids aléatoires
ZPred = rna.forward(tensor_Z)

#affichage
ZPred[:10,:]
Out[13]:
tensor([[-0.4863, -0.4191, -0.6232, -0.4683,  0.2391, -0.1625],
        [-0.6650, -0.7077, -0.6246, -0.1280, -0.0663, -0.4709],
        [-0.6239, -0.6417, -0.6242, -0.2059,  0.0035, -0.4002],
        [-0.8431, -0.9490, -0.6332,  0.1638, -0.3103, -0.7430],
        [-0.8625, -0.8983, -0.6462,  0.1170, -0.2364, -0.7139],
        [-0.7242, -0.5212, -0.6693, -0.3035,  0.2006, -0.3581],
        [-0.6131, -0.7463, -0.6050, -0.1017, -0.1373, -0.4746],
        [-0.8947, -1.0055, -0.6378,  0.2347, -0.3634, -0.8115],
        [-0.6312, -0.6196, -0.6296, -0.2266,  0.0352, -0.3870],
        [-0.5976, -0.5617, -0.6299, -0.2943,  0.0974, -0.3263]],
       grad_fn=<SliceBackward0>)
In [14]:
#valeur de la perte : observé vs. reconstitution
print(critere_rna(ZPred,tensor_Z))
tensor(1.1142, grad_fn=<MseLossBackward0>)

Entraînement du réseau¶

In [15]:
import numpy
#fonction pour apprentissage avec les paramètres :
#Z_in, instance de classe torch, critère à optimiser, algo d'optimisation...
#...et n_epochs nombre de passage sur la base
def train_session(Z_in,modele,criterion,optimizer,n_epochs=10000):
    #vecteur pour collecter le loss au fil des itérations
    losses = numpy.zeros(n_epochs)
    #itérer (boucle) pour optimiser - n_epochs fois sur la base
    for iter in range(n_epochs):
        #réinitialiser (ràz) le gradient
        #nécessaire à chaque passage sinon PyTorch accumule
        optimizer.zero_grad()
        #calculer la sortie du réseau
        Z_out = modele.forward(Z_in) #ou simplement modele()
        #calculer la perte
        perte = criterion(Z_out,Z_in)
        #collecter la perte calculée dans le vecteur losses
        losses[iter] = perte.item()
        #calcul du gradient et retropropagation
        perte.backward()
        #màj des poids synaptiques
        optimizer.step()
    #sortie de la boucle
    #renvoie le vecteur avec les valeurs de perte à chaque epoch
    return losses
In [16]:
#lancer l'apprentissage
pertes = train_session(tensor_Z,rna,critere_rna,optimiseur_rna)
In [17]:
#valeur finale de la perte
print(pertes[-1])
0.09925896674394608
In [18]:
#courbe de décroissance de la perte
import matplotlib.pyplot as plt
plt.plot(numpy.arange(0,pertes.shape[0]),pertes)
plt.title("Evolution fnct de perte")
plt.xlabel("Epochs")
plt.ylabel("Loss")
plt.show()
No description has been provided for this image

Analyse de la structure du réseau¶

In [19]:
# entrée -> cachée (poids synaptiques)
print(rna.layer1.weight)
Parameter containing:
tensor([[-0.0352, -0.0769, -0.0110, -0.0372, -0.1682, -0.0725],
        [ 0.0812,  0.0447,  0.0722,  0.0655, -0.1665,  0.0432]],
       requires_grad=True)
In [20]:
# entrée -> cachée (biais)
print(rna.layer1.bias)
Parameter containing:
tensor([0.0594, 0.0218], requires_grad=True)
In [21]:
# cachée -> sortie (poids synaptiques)
print(rna.layer2.weight)
Parameter containing:
tensor([[-1.6670,  2.2610],
        [-2.3754,  1.6074],
        [-1.2288,  2.4920],
        [-1.5783,  2.2018],
        [-3.2656, -2.5689],
        [-2.5200,  1.7039]], requires_grad=True)
In [22]:
# cachée -> sortie (biais)
print(rna.layer2.bias)
Parameter containing:
tensor([0.0510, 0.1044, 0.0214, 0.0471, 0.2373, 0.1107], requires_grad=True)

Exploitation des résultats¶

Représentation des individus dans l'espace réduit¶

In [23]:
# coordonnées "factorielles" des individus
ZFact = rna.forward_1(tensor_Z)

#dimensions
print(ZFact.shape)
torch.Size([28, 2])
In [24]:
#positionnement des individus dans le plan
fig, axes = plt.subplots(figsize=(8,8))
axes.set_xlim(-0.8,+0.8)
axes.set_ylim(-0.8,+0.8)

for i in range(ZFact.shape[0]):
    plt.annotate(X.index[i],(ZFact[i,0],ZFact[i,1]),fontsize=8)

plt.title('Position des véhicules dans le plan')
plt.xlabel('Coord 1')
plt.ylabel('Coord 2')
plt.show()
No description has been provided for this image

Reconstitution des données¶

In [25]:
# dans l'espace des variables standardisées
# attention, le résultat est un tensor, il faut le passer en numpy
Z_out = rna.forward(tensor_Z).detach().squeeze().numpy()
print(Z_out)
[[ 0.9534417   0.93105704  0.92136097  0.91887265  0.01450816  0.98737603]
 [ 0.30471158  0.10851523  0.39111406  0.300829   -0.8148198   0.11483908]
 [ 0.02473865 -0.03640842  0.05487291  0.02613201 -0.26215753 -0.0386911 ]
 [-0.7772301  -0.94523656 -0.65584934 -0.74199843 -0.819185   -1.002655  ]
 [-1.2910035  -1.2682925  -1.24368    -1.2439239  -0.05256239 -1.3450308 ]
 [-0.75563234 -0.38884223 -0.908673   -0.7414871   1.5015819  -0.41193336]
 [ 0.34410098  0.09250599  0.45702994  0.34085667 -1.0503544   0.09779334]
 [-1.4169495  -1.4958408  -1.3119286  -1.3613386  -0.5077206  -1.5864749 ]
 [ 0.33878216  0.15308934  0.4182589   0.33323577 -0.76530325  0.16212383]
 [ 0.28658143  0.19891882  0.31831944  0.27925566 -0.34646752  0.21084663]
 [ 0.26039487  0.18729807  0.285881    0.2534892  -0.28639087  0.1985403 ]
 [-0.39073104  0.01188725 -0.5787481  -0.39149716  1.6995754   0.01309332]
 [ 1.0047998   0.73511696  1.0968156   0.9777032  -1.0514753   0.7792753 ]
 [-0.42265207 -0.55981004 -0.3332305  -0.4017595  -0.64398515 -0.59386355]
 [-0.6241071  -0.52436006 -0.6466159  -0.6047181   0.35938734 -0.55597895]
 [-0.6514348   0.02263516 -0.96634084 -0.65281427  2.8457704   0.02482289]
 [-0.88373816 -0.94786394 -0.81060815 -0.84849083 -0.38134155 -1.0053146 ]
 [-1.01393    -1.0868185  -0.9303763  -0.9735149  -0.43455556 -1.1526899 ]
 [-1.5000597  -1.2736622  -1.5473355  -1.4529414   0.8059256  -1.350477  ]
 [ 0.03357074  0.04936725  0.02396144  0.0317177   0.07241131  0.05236989]
 [ 2.1986978   2.2068033   2.0941808   2.1167233   0.29234448  2.3403718 ]
 [ 0.51227415  0.00435632  0.7485808   0.5125059  -2.1417923   0.00399584]
 [ 0.4373072   0.6383015   0.3145775   0.41343498  0.92243624  0.6771743 ]
 [-1.3998269  -1.2958813  -1.38907    -1.3517867   0.286845   -1.3741901 ]
 [ 0.4342183   0.7724556   0.24145877  0.40525532  1.5169971   0.8196153 ]
 [ 1.1048027   1.0136132   1.1009915   1.0672218  -0.2660422   1.0748448 ]
 [ 1.216218    1.1404147   1.1994537   1.1739155  -0.18631265  1.2093394 ]
 [ 1.6728611   1.556875    1.6557957   1.6151229  -0.30708     1.6509564 ]]
In [26]:
#dans l'espace des variables initiales
#attention, Z_out est un tensor, on le passe en numpy
X_out = std.inverse_transform(Z_out)
In [27]:
#df avec les index et les noms des variables
df_out = pandas.DataFrame(X_out,columns=X.columns,index=X.index)
In [28]:
#affichage sans les décimales
df_out.style.format(precision=0)
Out[28]:
  puissance cylindree vitesse longueur hauteur poids
Modele            
CITRONC5 218 3003 232 473 148 1596
LAGUNA 171 2206 214 444 142 1357
CITRONC4 150 2066 204 431 146 1315
CLIO 92 1185 181 395 142 1050
CITRONC2 54 872 161 371 147 957
MODUS 93 1724 172 395 158 1212
MEGANECC 174 2191 217 446 140 1352
TWINGO 45 652 159 366 144 890
MONDEO 173 2249 215 445 142 1370
VECTRA 169 2294 212 443 145 1383
PASSAT 167 2282 211 442 146 1380
MERC_A 120 2113 183 411 160 1329
ALFA 156 222 2813 237 476 140 1539
AUDIA3 117 1559 191 411 143 1163
GOLF 103 1593 181 401 150 1173
MUSA 101 2123 170 399 168 1332
FIESTA 84 1183 175 390 145 1050
CORSA 74 1048 172 384 145 1009
PANDA 39 867 152 361 153 955
AVENSIS 151 2149 203 431 148 1340
CHRYS300 309 4239 270 529 150 1967
MAZDARX8 186 2105 226 454 133 1326
PTCRUISER 180 2719 212 449 154 1511
YARIS 46 846 157 366 150 949
VELSATIS 180 2849 210 449 158 1550
BMW530 229 3083 238 480 146 1620
MERC_E 237 3206 241 485 147 1657
AUDIA8 271 3609 256 506 146 1778

Identification des valeurs "atypiques"¶

In [29]:
#valeur du gap admis
#exprimé en écarts-type
gap = 0.6
In [30]:
#on utilise un formattage conditionnel
#basé sur un écart entre Z et Z_out
def formattage(col):
    #numéro de colonne
    j = X.columns.get_loc(col.name)
    #code de formattage
    code = numpy.empty(col.values.shape[0],dtype=str).tolist()
    for i in range(col.values.shape[0]):
        if (Z[i,j] < (Z_out[i,j]-gap)):
            code[i] = 'background-color: green'
        elif (Z[i,j] > (Z_out[i,j]+gap)):
            code[i] = 'background-color: blue'
    #renvoyer le code
    return code
In [31]:
#affichage du tableau avec mise en
#évidence des situations atypiques
X.style.apply(func=formattage,axis=0)
Out[31]:
  puissance cylindree vitesse longueur hauteur poids
Modele            
CITRONC5 210 2946 230 475 148 1589
LAGUNA 165 1998 218 458 143 1320
CITRONC4 138 1997 207 426 146 1381
CLIO 100 1461 185 382 142 980
CITRONC2 61 1124 158 367 147 932
MODUS 113 1598 188 380 159 1170
MEGANECC 165 1998 225 436 141 1415
TWINGO 60 1149 151 344 143 840
MONDEO 145 1999 215 474 143 1378
VECTRA 150 1910 217 460 146 1428
PASSAT 150 1781 221 471 147 1360
MERC_A 140 1991 201 384 160 1340
ALFA 156 250 3179 250 443 141 1410
AUDIA3 102 1595 185 421 143 1205
GOLF 75 1968 163 421 149 1217
MUSA 100 1910 179 399 169 1275
FIESTA 68 1399 164 392 144 1138
CORSA 70 1248 165 384 144 1035
PANDA 54 1108 150 354 154 860
AVENSIS 115 1995 195 463 148 1400
CHRYS300 340 5654 250 502 148 1835
MAZDARX8 231 1308 235 443 134 1390
PTCRUISER 223 2429 200 429 154 1595
YARIS 65 998 155 364 150 880
VELSATIS 150 2188 200 486 158 1735
BMW530 231 2979 250 485 147 1495
MERC_E 204 3222 243 482 146 1735
AUDIA8 280 3697 250 506 145 1770

Variables illustratives (supplémentaires)¶

In [32]:
#variables illustratives
VarSupp = pandas.read_excel("cars_autoencoder.xlsx",header=0,index_col=0,sheet_name="VAR.ILLUS")
VarSupp.info()
<class 'pandas.core.frame.DataFrame'>
Index: 28 entries, CITRONC5     to AUDIA8     
Data columns (total 3 columns):
 #   Column     Non-Null Count  Dtype 
---  ------     --------------  ----- 
 0   co2        28 non-null     int64 
 1   prix       28 non-null     int64 
 2   carburant  28 non-null     object
dtypes: int64(2), object(1)
memory usage: 896.0+ bytes
In [33]:
#données - coord. embedding + var. illus
dfSupp = pandas.DataFrame(ZFact.detach().squeeze().numpy(),index=VarSupp.index,columns=["F1","F2"])
dfSupp.info()
<class 'pandas.core.frame.DataFrame'>
Index: 28 entries, CITRONC5     to AUDIA8     
Data columns (total 2 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   F1      28 non-null     float32
 1   F2      28 non-null     float32
dtypes: float32(2)
memory usage: 448.0+ bytes
In [34]:
#concaténation avec les var. supp.
dfSupp = pandas.concat([dfSupp,VarSupp],axis=1)
dfSupp.info()
<class 'pandas.core.frame.DataFrame'>
Index: 28 entries, CITRONC5     to AUDIA8     
Data columns (total 5 columns):
 #   Column     Non-Null Count  Dtype  
---  ------     --------------  -----  
 0   F1         28 non-null     float32
 1   F2         28 non-null     float32
 2   co2        28 non-null     int64  
 3   prix       28 non-null     int64  
 4   carburant  28 non-null     object 
dtypes: float32(2), int64(2), object(1)
memory usage: 1.1+ KB
In [35]:
#appréhension des variables catégorielles
import seaborn as sns
sns.scatterplot(data=dfSupp,x="F1",y="F2",hue="carburant")
Out[35]:
<Axes: xlabel='F1', ylabel='F2'>
No description has been provided for this image
In [36]:
#appréhension des variables quantitatives
sns.scatterplot(data=dfSupp,x="F1",y="F2",hue="prix",legend=False)
Out[36]:
<Axes: xlabel='F1', ylabel='F2'>
No description has been provided for this image

Individus supplémentaires¶

In [37]:
#chargement + affichage
IndSupp = pandas.read_excel("cars_autoencoder.xlsx",header=0,index_col=0,sheet_name="IND.ILLUS")
IndSupp
Out[37]:
puissance cylindree vitesse longueur hauteur poids
Modele
P407 136 1997 212 468 145 1415
P307CC 180 1997 225 435 143 1490
P1007 75 1360 165 374 161 1181
P607 204 2721 230 491 145 1723
In [38]:
#transformation 1 - standardisation
#avec les param. (moyenne, écart-type) calculés sur les ind. actifs
ZIndSupp = std.transform(IndSupp)
ZIndSupp
Out[38]:
array([[-0.1694754 , -0.10738621,  0.31479857,  0.81616737, -0.40363426,
         0.32730751],
       [ 0.43223553, -0.10738621,  0.71545129,  0.11333545, -0.68975475,
         0.60093242],
       [-1.00366555, -0.76490092, -1.13371512, -1.1858387 ,  1.88532964,
        -0.52640221],
       [ 0.76044149,  0.63993037,  0.86954849,  1.30601992, -0.40363426,
         1.45099381]])
In [39]:
#calcul des coordonnées "factorielles"
FIndSupp = rna.forward_1(torch.FloatTensor(ZIndSupp))
FIndSupp
Out[39]:
tensor([[ 0.0838,  0.1594],
        [ 0.1124,  0.2468],
        [-0.0687, -0.5299],
        [-0.1115,  0.3716]], grad_fn=<TanhBackward0>)
In [40]:
#positionnement des individus dans le plan
fig, axes = plt.subplots(figsize=(8,8))
axes.set_xlim(-0.8,+0.8)
axes.set_ylim(-0.8,+0.8)

#individus actifs
for i in range(ZFact.shape[0]):
    plt.annotate(X.index[i],(ZFact[i,0],ZFact[i,1]),fontsize=8,c="silver")

#individus supplémentaires
for i in range(FIndSupp.shape[0]):
    plt.annotate(IndSupp.index[i],(FIndSupp[i,0],FIndSupp[i,1]),fontsize=8,c="blue")


plt.title('Position des véhicules dans le plan')
plt.xlabel('Coord 1')
plt.ylabel('Coord 2')
plt.show()
No description has been provided for this image