Environnement et packages¶
In [40]:
# activer l'environnement
using Pkg
Pkg.activate("env_julia_flux")
Activating project at `c:\Users\ricco\Desktop\demo\env_julia_flux`
In [41]:
# 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 [da1fdf0e] FreqTables v1.0.0 [91a5bcdd] Plots v1.41.6 [fdbf4ff8] XLSX v0.11.10
Importation et préparation des données TRAIN¶
Importation¶
In [42]:
# packages
import DataFrames as DFR
import XLSX
# lecture des données
dfTrain = DFR.DataFrame(XLSX.readtable("./breast_train.xlsx"))
# premières lignes
DFR.describe(dfTrain)
7×7 DataFrame
| Row | variable | mean | min | median | max | nmissing | eltype |
|---|---|---|---|---|---|---|---|
| Symbol | Union… | Any | Union… | Any | Int64 | DataType | |
| 1 | ucellsize | 3.0401 | 1 | 1.0 | 10 | 0 | Int64 |
| 2 | ucellshape | 3.18546 | 1 | 2.0 | 10 | 0 | Int64 |
| 3 | mgadhesion | 2.67419 | 1 | 1.0 | 10 | 0 | Int64 |
| 4 | sepics | 3.11529 | 1 | 2.0 | 10 | 0 | Int64 |
| 5 | normnucl | 2.7995 | 1 | 1.0 | 10 | 0 | Int64 |
| 6 | mitoses | 1.49875 | 1 | 1.0 | 10 | 0 | Int64 |
| 7 | classe | begnin | malignant | 0 | String |
Standardisation des descripteurs¶
In [43]:
# matrice des descripteurs
XTrain = Float32.(dfTrain[:,1:(end-1)])
DFR.describe(XTrain)
6×7 DataFrame
| Row | variable | mean | min | median | max | nmissing | eltype |
|---|---|---|---|---|---|---|---|
| Symbol | Float32 | Float32 | Float64 | Float32 | Int64 | DataType | |
| 1 | ucellsize | 3.0401 | 1.0 | 1.0 | 10.0 | 0 | Float32 |
| 2 | ucellshape | 3.18546 | 1.0 | 2.0 | 10.0 | 0 | Float32 |
| 3 | mgadhesion | 2.67419 | 1.0 | 1.0 | 10.0 | 0 | Float32 |
| 4 | sepics | 3.11529 | 1.0 | 2.0 | 10.0 | 0 | Float32 |
| 5 | normnucl | 2.7995 | 1.0 | 1.0 | 10.0 | 0 | Float32 |
| 6 | mitoses | 1.49875 | 1.0 | 1.0 | 10.0 | 0 | Float32 |
In [44]:
import Statistics
# moyennes
moy = Statistics.mean(Matrix(XTrain),dims=1)
moy
1×6 Matrix{Float32}:
3.0401 3.18546 2.67419 3.11529 2.7995 1.49875
In [45]:
# écarts-type
sigma = Statistics.std(Matrix(XTrain),dims=1)
sigma
1×6 Matrix{Float32}:
3.00392 2.96643 2.80149 2.17264 3.05558 1.58808
In [46]:
# centrage et réduction
ZTrain = (XTrain .- moy) ./ sigma
DFR.describe(ZTrain,:mean,:std,:min,:max)
6×5 DataFrame
| Row | variable | mean | std | min | max |
|---|---|---|---|---|---|
| Symbol | Float32 | Float32 | Float32 | Float32 | |
| 1 | ucellsize | -1.51626e-8 | 1.0 | -0.679147 | 2.31694 |
| 2 | ucellshape | -2.00176e-8 | 1.0 | -0.736733 | 2.29722 |
| 3 | mgadhesion | -4.57118e-8 | 1.0 | -0.597605 | 2.61497 |
| 4 | sepics | -1.58348e-8 | 1.0 | -0.973605 | 3.16883 |
| 5 | normnucl | 3.88028e-8 | 1.0 | -0.588922 | 2.35651 |
| 6 | mitoses | -5.82602e-9 | 1.0 | -0.314057 | 5.35318 |
In [47]:
# mettre sous la forme d'une matrice
# et il faut transposer !!!
ZTrain = Matrix(ZTrain)'
# vérifier
size(ZTrain)
(6, 399)
Codage de la variable cible¶
In [48]:
# vérification -- fréquence absolue des classes
# utilisation du package FreqTables
import FreqTables as FT
FT.freqtable(dfTrain.classe)
2-element Named Vector{Int64}
Dim1 │
──────────┼────
begnin │ 267
malignant │ 132
In [49]:
# variable cible -- recodage en 0/1
yTrain = Float32.(dfTrain.classe .== "malignant")
# et somme sur la variable recodée en 0/1
println("\nSomme")
println(Statistics.sum(yTrain))
Somme 132.0
In [50]:
# mettre yTrain sous la forme d'une matrice
# avec la bonne conformation
yTrain = reshape(yTrain,1,:)
# dimensions
size(yTrain)
(1, 399)
Perceptron simple avec Flux¶
Structure du réseau et paramétrage¶
In [51]:
# définir un perceptron simple
import Flux
# Chain n'est pas nécessaire ici
# mais pour être cohérent avec la suite (cf. PMC)
model_ps = Flux.Chain(
Flux.Dense(6 => 1, Flux.sigmoid)
)
# définir la fonction de perte
loss_ps(X,y) = Flux.mse(model_ps(X),y)
# définir l'algo d'optimisation
# une descente de gradient améliorée
opt_ps = Flux.setup(Flux.Adam(0.01), model_ps)
(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], Float32[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], Float32[0.0], (0.9, 0.999))), σ = ()),),)
In [52]:
# vérification, structure du réseau
model_ps.layers
(Dense(6 => 1, σ),)
In [53]:
# coefficients initiaux (couche entrée -> couche de sortie)
model_ps[1].weight
1×6 Matrix{Float32}:
0.490448 0.801712 0.122542 0.448223 -0.501278 -0.572844
In [54]:
# intercept initial (bias)
model_ps[1].bias
1-element Vector{Float32}:
0.0
Entraînement du modèle¶
In [55]:
# vecteur pour stocker la loss à chaque itération
loss_history = Float64[]
# nombre d'itérations
n_epochs = 1000
# boucle d'entraînement
for i in 1:n_epochs
# calcul du gradient
grads = Flux.gradient(model_ps) do m
Flux.mse(m(ZTrain), yTrain)
end
# mise à jour des poids synaptiques (coefficients)
Flux.update!(opt_ps,model_ps,grads[1])
# récupération de la loss courante
cur_loss = loss_ps(ZTrain,yTrain)
# 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 = 0.13366098701953888 Loss fin = 0.03610197827219963
Inspection des résultats¶
In [56]:
# courbe de décroissance de la loss
import Plots
Plots.plot(1:n_epochs,loss_history,title="Decroissance de la loss",label="")
In [57]:
# affichage des poids après entraînement
println("Coefficients :")
println(model_ps[1].weight)
#et du biais
println("\nIntercept :")
println(model_ps[1].bias)
Coefficients : Float32[1.6772531 2.439507 0.18046932 1.5186521 0.89737046 0.668222] Intercept : Float32[0.13474798]
In [58]:
# bien sûr on peut associer les coefficients aux variables
# les valeurs absolues => intensité de la relation avec la cible
# signes des coefs => sens de la relation
DFR.DataFrame(var=names(XTrain),coef=vec(model_ps[1].weight))
6×2 DataFrame
| Row | var | coef |
|---|---|---|
| String | Float32 | |
| 1 | ucellsize | 1.67725 |
| 2 | ucellshape | 2.43951 |
| 3 | mgadhesion | 0.180469 |
| 4 | sepics | 1.51865 |
| 5 | normnucl | 0.89737 |
| 6 | mitoses | 0.668222 |
Chargement et préparation des données TEST¶
In [59]:
# lecture de l'échantillon test
dfTest = DFR.DataFrame(XLSX.readtable("./breast_test.xlsx"))
# premières lignes
DFR.describe(dfTest)
7×7 DataFrame
| Row | variable | mean | min | median | max | nmissing | eltype |
|---|---|---|---|---|---|---|---|
| Symbol | Union… | Any | Union… | Any | Int64 | DataType | |
| 1 | ucellsize | 3.26 | 1 | 1.0 | 10 | 0 | Int64 |
| 2 | ucellshape | 3.23667 | 1 | 1.0 | 10 | 0 | Int64 |
| 3 | mgadhesion | 2.98333 | 1 | 1.0 | 10 | 0 | Int64 |
| 4 | sepics | 3.35 | 1 | 2.0 | 10 | 0 | Int64 |
| 5 | normnucl | 2.95667 | 1 | 1.0 | 10 | 0 | Int64 |
| 6 | mitoses | 1.71 | 1 | 1.0 | 10 | 0 | Int64 |
| 7 | classe | begnin | malignant | 0 | String |
In [60]:
# XTest
XTest = Float32.(dfTest[:,1:(end-1)])
names(XTest)
6-element Vector{String}:
"ucellsize"
"ucellshape"
"mgadhesion"
"sepics"
"normnucl"
"mitoses"
In [61]:
# standardisation avec les moyennes et écarts-type de train
ZTest = (XTest .- moy) ./ sigma
#puis sous la forme de matrice transposée
ZTest = Matrix(ZTest)'
# dimesions
size(ZTest)
(6, 300)
Prédiction sur l'échantillon test¶
In [62]:
# prédiction en test
pred_ps = model_ps(ZTest)
first(pred_ps,5)
5-element Vector{Float32}:
0.029839434
0.9988803
0.011804679
0.045362037
0.011804679
In [63]:
# traduire en prédiction
y_ps = ifelse.(vec(pred_ps) .>= 0.5, "malignant", "begnin")
first(y_ps,5)
5-element Vector{String}:
"begnin"
"malignant"
"begnin"
"begnin"
"begnin"
Performances prédictives¶
In [64]:
# accuracy
Statistics.mean(dfTest.classe .== y_ps)
0.9666666666666667
In [65]:
# ou encore la matrice de confusion avec FreqTables
FT.freqtable(dfTest.classe,y_ps)
2×2 Named Matrix{Int64}
Dim1 ╲ Dim2 │ begnin malignant
────────────┼─────────────────────
begnin │ 184 7
malignant │ 3 106
Preceptron multicouche¶
Structure du réseau¶
In [66]:
# Chain est nécessaire ici
# pour enchaîner les deux couches
model_pmc = Flux.Chain(
Flux.Dense(6 => 2, Flux.sigmoid),
Flux.Dense(2 => 1, Flux.sigmoid)
)
# définir la fonction de perte
loss_pmc(X,y) = Flux.mse(model_pmc(X),y)
# définir l'algo d'optimisation
# une descente de gradient améliorée
opt_pmc = Flux.setup(Flux.Adam(0.01), model_pmc)
(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))), σ = ()), (weight = 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))), bias = Leaf(Adam(eta=0.01, beta=(0.9, 0.999), epsilon=1.0e-8), (Float32[0.0], Float32[0.0], (0.9, 0.999))), σ = ())),)
In [67]:
# inspection de la structure
model_pmc.layers
(Dense(6 => 2, σ), Dense(2 => 1, σ))
In [68]:
# poids (coef) : couche d'entrée -> couche cachée
model_pmc[1].weight
2×6 Matrix{Float32}:
0.0172516 -0.779909 0.576837 0.396249 -0.0472927 -0.746339
0.505588 -0.19096 -0.734207 0.550847 -0.119603 -0.538707
In [69]:
# poids : couche cachée -> couche de sortie
model_pmc[2].weight
1×2 Matrix{Float32}:
0.109559 1.40793
Entraînement¶
In [70]:
# vecteur pour stocker la loss à chaque itération
loss_history_pmc = Float64[]
# nombre d'itérations
n_epochs = 1000
# boucle d'entraînement
for i in 1:n_epochs
# calcul du gradient
grads = Flux.gradient(model_pmc) do m
Flux.mse(m(ZTrain), yTrain)
end
# mise à jour des poids synaptiques (coefficients)
Flux.update!(opt_pmc,model_pmc,grads[1])
# récupération de la loss courante
cur_loss = loss_pmc(ZTrain,yTrain)
# stockage
push!(loss_history_pmc,cur_loss)
end
# affichage de la première valeur
println("Loss depart = $(loss_history_pmc[1])")
# et de la dernière
println("Loss fin = $(loss_history_pmc[end])")
Loss depart = 0.3525426983833313 Loss fin = 0.034496910870075226
Evaluation¶
In [71]:
# prédiction en test
# probas
pred_pmc = model_pmc(ZTest)
# converties en classes prédites
y_pmc = ifelse.(vec(pred_pmc) .>= 0.5, "malignant", "begnin")
# fréquences des classes prédites
FT.freqtable(y_pmc)
2-element Named Vector{Int64}
Dim1 │
──────────┼────
begnin │ 187
malignant │ 113
In [72]:
# accuracy
Statistics.mean(dfTest.classe .== y_pmc)
0.9666666666666667
In [73]:
# matrice de confusion
cm_pmc = FT.freqtable(dfTest.classe, y_pmc)
cm_pmc
2×2 Named Matrix{Int64}
Dim1 ╲ Dim2 │ begnin malignant
────────────┼─────────────────────
begnin │ 184 7
malignant │ 3 106
Inspection de la couche intermédiaire¶
In [74]:
# récupération de la structure intermédiaire (couche cachée)
repr_cc = Flux.Chain(model_pmc[1])
# vérification
repr_cc.layers
(Dense(6 => 2, σ),)
In [75]:
# coordonnées des individus TEST
coord_cc = repr_cc(ZTest)
coord_cc
2×300 Matrix{Float32}:
0.945858 0.000248021 0.99344 … 0.000963716 0.890286 0.99344
0.0386392 0.9986 0.0270332 0.991846 0.0722556 0.0270332
In [76]:
# sous forme transposée
# il s'agit d'une sorte de représentation "factorielle"
fact = Matrix(coord_cc)'
size(fact)
(300, 2)
In [77]:
# représentation des individus dans le plan
# avec les classes d'appartenance
les_m = (dfTest.classe .== "malignant")
les_b = (dfTest.classe .== "begnin")
# graphique
Plots.scatter(fact[les_m,1],fact[les_m,2],label="Malignant",markersize=4)
Plots.scatter!(fact[les_b,1],fact[les_b,2],label="Begnin",color=:green,markersize=4)
In [78]:
# rappel de la matrice de confusion
cm_pmc
2×2 Named Matrix{Int64}
Dim1 ╲ Dim2 │ begnin malignant
────────────┼─────────────────────
begnin │ 184 7
malignant │ 3 106