Environnement et packages¶
In [37]:
# activer l'environnement
using Pkg
Pkg.activate("env_julia_flux")
Activating project at `c:\Users\ricco\Desktop\demo\env_julia_flux`
In [38]:
# liste des packages installés
Pkg.status()
Status `C:\Users\ricco\Desktop\demo\env_julia_flux\Project.toml` [a93c6f00] DataFrames v1.8.2 [587475ba] Flux v0.16.10 [91a5bcdd] Plots v1.41.6 [fdbf4ff8] XLSX v0.11.10
Chargement et préparation des données¶
Importation, inspection¶
In [39]:
# packages
import DataFrames as DFR
import XLSX
# lecture des données
df = DFR.DataFrame(XLSX.readtable("./cars_autoencoder.xlsx"))
# premières lignes
println(DFR.size(df))
(28, 7)
In [40]:
# premières lignes
DFR.first(df,5)
5×7 DataFrame
| Row | Modele | puissance | cylindree | vitesse | longueur | hauteur | poids |
|---|---|---|---|---|---|---|---|
| String | Int64 | Int64 | Int64 | Int64 | Int64 | Int64 | |
| 1 | CITRONC5 | 210 | 2946 | 230 | 475 | 148 | 1589 |
| 2 | LAGUNA | 165 | 1998 | 218 | 458 | 143 | 1320 |
| 3 | CITRONC4 | 138 | 1997 | 207 | 426 | 146 | 1381 |
| 4 | CLIO | 100 | 1461 | 185 | 382 | 142 | 980 |
| 5 | CITRONC2 | 61 | 1124 | 158 | 367 | 147 | 932 |
In [41]:
# description
DFR.describe(df)
7×7 DataFrame
| Row | variable | mean | min | median | max | nmissing | eltype |
|---|---|---|---|---|---|---|---|
| Symbol | Union… | Any | Union… | Any | Int64 | DataType | |
| 1 | Modele | ALFA 156 | YARIS | 0 | String | ||
| 2 | puissance | 148.393 | 54 | 142.5 | 340 | 0 | Int64 |
| 3 | cylindree | 2101.04 | 998 | 1979.5 | 5654 | 0 | Int64 |
| 4 | vitesse | 201.786 | 150 | 200.5 | 250 | 0 | Int64 |
| 5 | longueur | 429.679 | 344 | 432.5 | 506 | 0 | Int64 |
| 6 | hauteur | 147.821 | 134 | 146.5 | 169 | 0 | Int64 |
| 7 | poids | 1325.29 | 840 | 1369.0 | 1835 | 0 | Int64 |
In [42]:
# variables actives
X = df[:,2:end]
names(X)
6-element Vector{String}:
"puissance"
"cylindree"
"vitesse"
"longueur"
"hauteur"
"poids"
Standardisation¶
In [43]:
import Statistics
# moyennes
moyenne = Statistics.mean(Matrix(X),dims=1)
println(moyenne)
# écarts-type (avec 1/n)
ecart_type = Statistics.std(Matrix(X),dims=1,corrected = false)
println(ecart_type)
[148.39285714285714 2101.035714285714 201.78571428571428 429.67857142857144 147.82142857142858 1325.2857142857142] [73.12481466922269 968.799606366517 32.44705263178454 46.952904650319994 6.990062187688683 274.0978523320845]
In [44]:
# centrage-réduction
Z = (X .- moyenne) ./ ecart_type
DFR.describe(Z)
6×7 DataFrame
| Row | variable | mean | min | median | max | nmissing | eltype |
|---|---|---|---|---|---|---|---|
| Symbol | Float64 | Float64 | Float64 | Float64 | Int64 | DataType | |
| 1 | puissance | 7.93016e-17 | -1.29085 | -0.0805863 | 2.62028 | 0 | Float64 |
| 2 | cylindree | 4.7581e-17 | -1.13856 | -0.12545 | 3.66739 | 0 | Float64 |
| 3 | vitesse | 2.45835e-16 | -1.59601 | -0.039625 | 1.48594 | 0 | Float64 |
| 4 | longueur | -3.96508e-16 | -1.82478 | 0.0600906 | 1.62549 | 0 | Float64 |
| 5 | hauteur | -1.7486e-15 | -1.9773 | -0.189044 | 3.02981 | 0 | Float64 |
| 6 | poids | 2.53765e-16 | -1.77048 | 0.159484 | 1.85961 | 0 | Float64 |
In [45]:
# dix premières lignes
DFR.first(Z,10)
10×6 DataFrame
| Row | puissance | cylindree | vitesse | longueur | hauteur | poids |
|---|---|---|---|---|---|---|
| Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | |
| 1 | 0.842493 | 0.872177 | 0.869548 | 0.965253 | 0.0255465 | 0.962117 |
| 2 | 0.227107 | -0.106354 | 0.499715 | 0.603188 | -0.689755 | -0.019284 |
| 3 | -0.142125 | -0.107386 | 0.160701 | -0.078346 | -0.260574 | 0.203264 |
| 4 | -0.661784 | -0.660648 | -0.517326 | -1.01546 | -0.832815 | -1.25972 |
| 5 | -1.19512 | -1.0085 | -1.34945 | -1.33492 | -0.117514 | -1.43484 |
| 6 | -0.484006 | -0.519236 | -0.424868 | -1.05805 | 1.59921 | -0.566534 |
| 7 | 0.227107 | -0.106354 | 0.715451 | 0.134633 | -0.975875 | 0.327308 |
| 8 | -1.20879 | -0.982696 | -1.56519 | -1.82478 | -0.689755 | -1.77048 |
| 9 | -0.0463982 | -0.105322 | 0.407257 | 0.943955 | -0.689755 | 0.192319 |
| 10 | 0.0219781 | -0.197188 | 0.468896 | 0.645784 | -0.260574 | 0.374736 |
In [47]:
# préparer pour Flux
# d'abord transposer
ZT = Float32.(Matrix(Z)')
println(size(ZT))
(6, 28)
Autoencodeur avec "Flux"¶
Structure et paramétrage¶
In [48]:
import Flux
# définir la structure du réseau
# encodeur - interieur activation tanh
encoder = Flux.Chain(Flux.Dense(6 => 2, Flux.tanh))
# décodeur - sortie activation linéaire par défaut
decoder = Flux.Chain(Flux.Dense(2 => 6))
# réseau complet
autoencoder = Flux.Chain(encoder,decoder)
Chain(
Chain(
Dense(6 => 2, tanh), # 14 parameters
),
Chain(
Dense(2 => 6), # 18 parameters
),
) # Total: 4 arrays, 32 parameters, 336 bytes.
In [49]:
# définir l'algo d'optimisation
# une descente de gradient améliorée
opt = Flux.setup(Flux.Adam(0.01), autoencoder)
(layers = ((layers = ((weight = Leaf(Adam(eta=0.01, beta=(0.9, 0.999), epsilon=1.0e-8), (Float32[0.0 0.0 … 0.0 0.0; 0.0 0.0 … 0.0 0.0], Float32[0.0 0.0 … 0.0 0.0; 0.0 0.0 … 0.0 0.0], (0.9, 0.999))), bias = Leaf(Adam(eta=0.01, beta=(0.9, 0.999), epsilon=1.0e-8), (Float32[0.0, 0.0], Float32[0.0, 0.0], (0.9, 0.999))), σ = ()),),), (layers = ((weight = Leaf(Adam(eta=0.01, beta=(0.9, 0.999), epsilon=1.0e-8), (Float32[0.0 0.0; 0.0 0.0; … ; 0.0 0.0; 0.0 0.0], Float32[0.0 0.0; 0.0 0.0; … ; 0.0 0.0; 0.0 0.0], (0.9, 0.999))), bias = Leaf(Adam(eta=0.01, beta=(0.9, 0.999), epsilon=1.0e-8), (Float32[0.0, 0.0, 0.0, 0.0, 0.0, 0.0], Float32[0.0, 0.0, 0.0, 0.0, 0.0, 0.0], (0.9, 0.999))), σ = ()),),)),)
Boucle d'entraînement¶
In [50]:
# vecteur pour stocker la loss à chaque itération
loss_history = Float64[]
# nombre d'itérations
n_epochs = 1000
# boucle d'entraînement avec fonction de coût MSE
for i in 1:n_epochs
# calcul du gradient
grads = Flux.gradient(autoencoder) do m
Flux.mse(m(ZT), ZT)
end
# mise à jour des poids synaptiques (coefficients)
Flux.update!(opt,autoencoder,grads[1])
# récupération de la loss courante
cur_loss = Flux.mse(autoencoder(ZT),ZT)
# stockage
push!(loss_history,cur_loss)
end
# affichage de la première valeur
println("Loss depart = $(loss_history[1])")
# et de la dernière
println("Loss fin = $(loss_history[end])")
Loss depart = 1.0398298501968384 Loss fin = 0.10130723565816879
In [51]:
# courbe de décroissance de la loss
import Plots
Plots.plot(1:n_epochs,loss_history,title="Decroissance de la loss",label="")
Inspection de la structure du réseau¶
In [52]:
# structure du réseau
autoencoder.layers
(Chain(Dense(6 => 2, tanh)), Chain(Dense(2 => 6)))
In [53]:
# poids (coefs.) couche entrée -> couche cachée
autoencoder.layers[1].layers[1].weight
2×6 Matrix{Float32}:
0.121293 0.0845914 0.0830813 0.0941039 -0.252134 0.0779762
-0.0447269 -0.147656 -0.0116939 -0.0688605 -0.313794 -0.107742
In [54]:
# intercept
autoencoder.layers[1].layers[1].bias
2-element Vector{Float32}:
0.011199294
0.11255823
In [55]:
# poids (coefs.) couche cachée -> couche sortie
autoencoder.layers[2].layers[1].weight
6×2 Matrix{Float32}:
1.69736 -1.00381
1.268 -1.44341
1.85964 -0.728047
1.69045 -0.931925
-1.72879 -2.02292
1.34617 -1.5084
In [56]:
# intercept
autoencoder.layers[2].layers[1].bias
6-element Vector{Float32}:
0.086312234
0.13462688
0.05689584
0.079145245
0.2199395
0.14034763
Représentation "factorielle" des individus - Embeddings¶
In [57]:
# représentation des individus dans l'espace réduit
# coordonnées => embeddings
coord = encoder(ZT)
size(coord)
(2, 28)
In [58]:
# projection
# on est bien dans (-1,+1) puisque activation tanh()
Plots.scatter(coord[1,:],coord[2,:],label="",markersize=0,lims=(-1,+1),
annotations=(coord[1,:],coord[2,:],Plots.text.(df.Modele,7,:black)))
Reconstitution des données - Détection des valeurs "atypiques"¶
Valeurs estimées par le modèle (qui joue le rôle de filtre)¶
In [59]:
# reconstitution
ZPred = autoencoder(ZT)
size(ZPred)
(6, 28)
In [60]:
# transposer
Pred = Matrix(ZPred)'
size(Pred)
(28, 6)
Ecarts¶
In [61]:
# calculer les écarts entre Z et Pred
ecart = (Z .- Pred)
ecart
28×6 DataFrame
3 rows omitted
| Row | puissance | cylindree | vitesse | longueur | hauteur | poids |
|---|---|---|---|---|---|---|
| Float64 | Float64 | Float64 | Float64 | Float64 | Float64 | |
| 1 | -0.154367 | -0.107444 | -0.0966282 | -0.00462545 | 0.00947681 | -0.0696125 |
| 2 | -0.0718321 | -0.20464 | 0.105243 | 0.293199 | 0.163939 | -0.127802 |
| 3 | -0.154773 | -0.0513307 | 0.1118 | -0.0973897 | 0.0401234 | 0.260661 |
| 4 | 0.122228 | 0.279601 | 0.151663 | -0.269339 | -0.0742716 | -0.273621 |
| 5 | 0.103226 | 0.27063 | -0.0926486 | -0.072135 | -0.0819527 | -0.0873999 |
| 6 | 0.275214 | -0.126878 | 0.50071 | -0.284872 | 0.0584809 | -0.143791 |
| 7 | -0.108554 | -0.180891 | 0.253312 | -0.216932 | 0.139965 | 0.242232 |
| 8 | 0.168761 | 0.460146 | -0.277636 | -0.49332 | -0.275834 | -0.25286 |
| 9 | -0.388294 | -0.257028 | -0.0228457 | 0.593269 | 0.114031 | 0.0278259 |
| 10 | -0.254137 | -0.382501 | 0.155216 | 0.368701 | 0.11286 | 0.177426 |
| 11 | -0.213404 | -0.496204 | 0.329006 | 0.644619 | 0.166226 | -0.0496302 |
| 12 | 0.298629 | -0.108647 | 0.591674 | -0.531469 | -0.0130621 | 0.0695836 |
| 13 | 0.40566 | 0.391836 | 0.400526 | -0.697545 | 0.088413 | -0.456551 |
| ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ | ⋮ |
| 17 | -0.209738 | 0.233693 | -0.347198 | 0.054799 | -0.162861 | 0.324124 |
| 18 | -0.049016 | 0.213042 | -0.189326 | 0.0137659 | -0.142477 | 0.090662 |
| 19 | 0.158409 | 0.224964 | -0.0978142 | -0.184841 | 0.142729 | -0.376119 |
| 20 | -0.495763 | -0.163566 | -0.238563 | 0.673122 | -0.0437861 | 0.216145 |
| 21 | 0.501374 | 1.52512 | -0.535653 | -0.515338 | -0.272332 | -0.39522 |
| 22 | 0.644723 | -0.812001 | 0.294687 | -0.235362 | 0.136491 | 0.230471 |
| 23 | 0.563633 | -0.315094 | -0.387762 | -0.438723 | -0.0230046 | 0.301057 |
| 24 | 0.258574 | 0.168819 | -0.0496766 | -0.0312023 | 0.0385955 | -0.245501 |
| 25 | -0.400499 | -0.669243 | -0.280167 | 0.82208 | -0.0603865 | 0.704467 |
| 26 | 0.0120391 | -0.135799 | 0.372536 | 0.0853529 | 0.111525 | -0.479783 |
| 27 | -0.488362 | -0.02441 | 0.0353052 | -0.105125 | -0.0799356 | 0.249218 |
| 28 | 0.14831 | 0.0867757 | -0.148121 | 0.0126749 | -0.156793 | -0.0228629 |
Visualisation des écarts¶
In [62]:
# ecart max
e_max = maximum(abs.(Matrix(ecart)))
1.5251186311280351
In [63]:
# visaulisation à l'aide d'un heatmap
Plots.heatmap(Matrix(ecart),
clim=(-e_max,+e_max),
color=:RdBu,
yflip=true,
yticks=(1:DFR.nrow(df),df.Modele),
xtick=(1:DFR.ncol(X),names(X)))
Visualisation en introduisant un seuil¶
In [64]:
# ou bien appliquer un seuil (ex. à 0.6 de part et d'autre de 0)
M = Matrix(ecart)
ecart_seuil = ifelse.(abs.(M) .< 0.6, 0, M)
28×6 Matrix{Real}:
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
⋮ ⋮
0 0 0 0.673122 0 0
0 1.52512 0 0 0 0
0.644723 -0.812001 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 -0.669243 0 0.82208 0 0.704467
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
In [65]:
# de nouveau le heatmap, plus lisible cette fois-ci
Plots.heatmap(ecart_seuil,
clim=(-e_max,+e_max),
color=:RdBu,
yflip=true,
yticks=(1:DFR.nrow(df),df.Modele),
xtick=(1:DFR.ncol(X),names(X)))
Variables illustratives (supplémentaires)¶
Chargement des données¶
In [66]:
# lecture des données -- variables supplémentaires
dfVarSupp = DFR.DataFrame(XLSX.readtable("./cars_autoencoder.xlsx","VAR.ILLUS"))
DFR.first(dfVarSupp,5)
5×4 DataFrame
| Row | Modele | co2 | prix | carburant |
|---|---|---|---|---|
| String | Int64 | Int64 | String | |
| 1 | CITRONC5 | 238 | 33000 | Essence |
| 2 | LAGUNA | 196 | 25350 | Essence |
| 3 | CITRONC4 | 142 | 23400 | Diesel |
| 4 | CLIO | 113 | 17600 | Diesel |
| 5 | CITRONC2 | 141 | 10700 | Essence |
In [67]:
# vérification cohérence des fichiers
# même obs. dans le même ordre puisque 0 incohérence
sum(df.Modele .!= dfVarSupp.Modele)
0
Variable qualitative¶
In [68]:
# liste des modalités de carburant
moda_carb = sort(unique(dfVarSupp.carburant))
moda_carb
2-element Vector{String}:
"Diesel"
"Essence"
In [69]:
# graphique dans le plan "factoriel"
# en fonction de carburant
plt = Plots.scatter()
for carb in moda_carb
id = (dfVarSupp.carburant .== carb)
Plots.scatter!(plt,coord[1,id],coord[2,id],label=carb)
end
display(plt)
Variable quantitative¶
In [70]:
Plots.scatter(coord[1,:],coord[2,:],label="",
marker_z=dfVarSupp.prix,color=:turbo,colorbar=true)
Individus supplémentaires¶
Chargement et préparation¶
In [71]:
# lecture des données -- individus supplémentaires
dfIndSupp = DFR.DataFrame(XLSX.readtable("./cars_autoencoder.xlsx","IND.ILLUS"))
dfIndSupp
4×7 DataFrame
| Row | Modele | puissance | cylindree | vitesse | longueur | hauteur | poids |
|---|---|---|---|---|---|---|---|
| String | Int64 | Int64 | Int64 | Int64 | Int64 | Int64 | |
| 1 | P407 | 136 | 1997 | 212 | 468 | 145 | 1415 |
| 2 | P307CC | 180 | 1997 | 225 | 435 | 143 | 1490 |
| 3 | P1007 | 75 | 1360 | 165 | 374 | 161 | 1181 |
| 4 | P607 | 204 | 2721 | 230 | 491 | 145 | 1723 |
In [72]:
# standardisation avec les paramètres des individus actifs
XSupp = dfIndSupp[:,2:end]
ZSupp = (XSupp .- moyenne) ./ ecart_type
ZTSupp = Float32.(Matrix(ZSupp)')
6×4 Matrix{Float32}:
-0.169475 0.432236 -1.00367 0.760441
-0.107386 -0.107386 -0.764901 0.63993
0.314799 0.715451 -1.13372 0.869548
0.816167 0.113335 -1.18584 1.30602
-0.403634 -0.689755 1.88533 -0.403634
0.327308 0.600932 -0.526402 1.45099
Projection dans l'espace réduit¶
In [73]:
# coordonnées dans l'espace "factoriel"
# couche cachée de l'autoencodeur
coord_supp = encoder(ZTSupp)
coord_supp
2×4 Matrix{Float32}:
0.208698 0.332305 -0.715043 0.513613
0.165956 0.239842 -0.167976 -0.144697
In [74]:
# projection dans le plan
# individus actifs
Plots.scatter(coord[1,:],coord[2,:],label="",markersize=0,lims=(-1,+1),
annotations=(coord[1,:],coord[2,:],Plots.text.(df.Modele,7,:silver)))
# individus supplémentaires
Plots.scatter!(coord_supp[1,:],coord_supp[2,:],label="",markersize=0,lims=(-1,+1),
annotations=(coord_supp[1,:],coord_supp[2,:],Plots.text.(dfIndSupp.Modele,7,:blue)))