Environnement et packages¶
In [1]:
# activer l'environnement
using Pkg
Pkg.activate("env_julia_mljflux")
Activating project at `c:\Users\ricco\Desktop\demo\env_julia_mljflux`
In [2]:
# liste des packages installés
Pkg.status()
Status `C:\Users\ricco\Desktop\demo\env_julia_mljflux\Project.toml` [324d7699] CategoricalArrays v1.1.1 [a93c6f00] DataFrames v1.8.2 [587475ba] Flux v0.16.10 [add582a8] MLJ v0.23.2 [094fc8d1] MLJFlux v0.6.7 [23777cdb] MLJTransforms v0.1.6 [3bd65402] Optimisers v0.4.7 [91a5bcdd] Plots v1.41.6 [fdbf4ff8] XLSX v0.11.10
Importation et prépration des données¶
Importation, description¶
In [3]:
# packages
import DataFrames as DFR
import XLSX
# lecture des données
df = DFR.DataFrame(XLSX.readtable("./waveform.xlsx"))
# premières lignes
println(DFR.first(df,5))
5×22 DataFrame Row │ X01 X02 X03 X04 X05 X06 X07 X08 X09 X10 X11 X12 X13 X14 X15 X16 X17 X18 X19 X20 X21 onde │ Float64 Float64 Float64 Float64 Float64 Float64 Float64 Float64 Float64 Float64 Float64 Float64 Float64 Float64 Float64 Float64 Float64 Float64 Float64 Float64 Float64 String ─────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── 1 │ 0.8 1.31 1.01 2.91 3.01 5.81 7.09 4.86 5.23 2.89 1.78 2.17 1.74 -0.95 0.35 -1.58 0.72 -1.93 0.83 -1.14 1.12 A 2 │ -0.11 -0.66 1.06 1.67 2.38 1.9 1.39 -0.61 1.6 2.8 1.9 2.04 0.66 5.29 5.0 5.49 1.59 1.73 2.02 1.22 0.58 A 3 │ -1.2 0.08 0.94 0.39 0.53 2.95 0.9 1.96 0.58 1.57 3.49 1.67 3.18 3.43 5.35 4.42 3.68 2.84 3.5 0.56 1.39 A 4 │ -1.03 -0.21 0.71 2.15 -0.89 1.64 3.22 1.87 3.33 4.96 3.93 3.32 1.53 5.04 2.44 4.17 1.4 1.72 -1.0 2.37 0.04 C 5 │ 0.01 0.3 1.28 1.95 2.13 3.08 1.61 0.42 -0.39 2.19 3.83 3.72 4.92 3.02 4.26 3.12 4.15 0.89 -0.13 1.73 -1.03 A
In [4]:
# ou describe
DFR.describe(df)
22×7 DataFrame
| Row | variable | mean | min | median | max | nmissing | eltype |
|---|---|---|---|---|---|---|---|
| Symbol | Union… | Any | Union… | Any | Int64 | DataType | |
| 1 | X01 | 0.005144 | -3.34 | 0.01 | 3.94 | 0 | Float64 |
| 2 | X02 | 0.338746 | -3.25 | 0.34 | 3.88 | 0 | Float64 |
| 3 | X03 | 0.672438 | -4.2 | 0.66 | 4.72 | 0 | Float64 |
| 4 | X04 | 0.99161 | -3.84 | 0.94 | 5.75 | 0 | Float64 |
| 5 | X05 | 1.31089 | -3.48 | 1.12 | 6.5 | 0 | Float64 |
| 6 | X06 | 1.99731 | -2.76 | 1.86 | 7.62 | 0 | Float64 |
| 7 | X07 | 2.66181 | -3.32 | 2.5 | 8.76 | 0 | Float64 |
| 8 | X08 | 2.65923 | -3.52 | 2.72 | 7.84 | 0 | Float64 |
| 9 | X09 | 2.67209 | -3.38 | 2.81 | 7.9 | 0 | Float64 |
| 10 | X10 | 2.98867 | -1.79 | 3.0 | 7.63 | 0 | Float64 |
| 11 | X11 | 3.3366 | -1.48 | 3.17 | 9.06 | 0 | Float64 |
| 12 | X12 | 3.01361 | -1.69 | 3.0 | 7.4 | 0 | Float64 |
| 13 | X13 | 2.67891 | -2.61 | 2.83 | 7.5 | 0 | Float64 |
| 14 | X14 | 2.64863 | -2.82 | 2.7 | 7.75 | 0 | Float64 |
| 15 | X15 | 2.64767 | -2.56 | 2.49 | 8.72 | 0 | Float64 |
| 16 | X16 | 2.0005 | -2.99 | 1.82 | 7.86 | 0 | Float64 |
| 17 | X17 | 1.33503 | -3.56 | 1.2 | 6.74 | 0 | Float64 |
| 18 | X18 | 1.00062 | -4.08 | 0.94 | 6.2 | 0 | Float64 |
| 19 | X19 | 0.661482 | -3.5 | 0.62 | 5.28 | 0 | Float64 |
| 20 | X20 | 0.3573 | -3.57 | 0.35 | 4.65 | 0 | Float64 |
| 21 | X21 | -0.021378 | -3.88 | -0.03 | 4.01 | 0 | Float64 |
| 22 | onde | A | C | 0 | String |
In [5]:
# vérifier le schéma de la base pour MLJ
import MLJ
MLJ.schema(df)
┌───────┬────────────┬─────────┐ │ names │ scitypes │ types │ ├───────┼────────────┼─────────┤ │ X01 │ Continuous │ Float64 │ │ X02 │ Continuous │ Float64 │ │ X03 │ Continuous │ Float64 │ │ X04 │ Continuous │ Float64 │ │ X05 │ Continuous │ Float64 │ │ X06 │ Continuous │ Float64 │ │ X07 │ Continuous │ Float64 │ │ X08 │ Continuous │ Float64 │ │ X09 │ Continuous │ Float64 │ │ X10 │ Continuous │ Float64 │ │ X11 │ Continuous │ Float64 │ │ X12 │ Continuous │ Float64 │ │ X13 │ Continuous │ Float64 │ │ X14 │ Continuous │ Float64 │ │ X15 │ Continuous │ Float64 │ │ X16 │ Continuous │ Float64 │ │ X17 │ Continuous │ Float64 │ │ X18 │ Continuous │ Float64 │ │ X19 │ Continuous │ Float64 │ │ X20 │ Continuous │ Float64 │ │ X21 │ Continuous │ Float64 │ │ onde │ Textual │ String │ └───────┴────────────┴─────────┘
Structures et partition TRAIN/TEST¶
In [6]:
# isoler y et X dans des structures distinctes
y, X = MLJ.unpack(df,==(:onde))
# dimensions
println("Dim. de y = $(DFR.size(y))")
println("Dim. de X = $(DFR.size(X))")
Dim. de y = (5000,) Dim. de X = (5000, 21)
In [7]:
# convertir y en variable catégorielle pour la classification supervisée
# équivalent du type factor sous R
# utilisation du package CategoricalArrays
import CategoricalArrays as CA
y = CA.categorical(y)
# vérification des modalités
println(eltype(y))
CategoricalArrays.CategoricalValue{String, UInt32}
In [8]:
# indices pour partition en train et test
# stratify est possible parce y est catégorielle maintenant (et non plus string)
# si on met 0.1 on a 501 en TRAIN, d'où le 0.0999 (argh !!!)
idTrain, idTest = MLJ.partition(1:DFR.nrow(X),0.0999,shuffle=true,rng=42,stratify=y)
# structures y et X pour train/test
# par indexation avec les indices
yTrain, yTest = y[idTrain], y[idTest]
XTrain, XTest = X[idTrain,:], X[idTest,:]
# afficher les dimensions pour vérifications
println("Dim. de y = $(DFR.size(yTrain)) et $(DFR.size(yTest))")
println("Dim. de X = $(DFR.size(XTrain)) et $(DFR.size(XTest))")
Dim. de y = (500,) et (4500,) Dim. de X = (500, 21) et (4500, 21)
Standardisation¶
In [9]:
# classe de calcul pour la standardisation
Standardizer = @MLJ.load Standardizer pkg=MLJTransforms
┌ Info: For silent loading, specify `verbosity=0`. └ @ Main C:\Users\ricco\.julia\packages\MLJModels\AWkxi\src\loading.jl:159
import MLJTransforms ✔
MLJTransforms.Standardizer
In [10]:
# standardisation des données d'apprentissage
std = MLJ.machine(Standardizer(),XTrain)
# entraîner i.e. calculer moyennes et écarts-type
MLJ.fit!(std)
# transformer
ZTrain = MLJ.transform(std,XTrain)
# description
DFR.describe(ZTrain,:mean,:std)
┌ Info: Training machine(Standardizer(features = Symbol[], …), …). └ @ MLJBase C:\Users\ricco\.julia\packages\MLJBase\DCbte\src\machines.jl:499
21×3 DataFrame
| Row | variable | mean | std |
|---|---|---|---|
| Symbol | Float64 | Float64 | |
| 1 | X01 | -6.28664e-18 | 1.0 |
| 2 | X02 | -3.73035e-17 | 1.0 |
| 3 | X03 | -9.61453e-17 | 1.0 |
| 4 | X04 | 4.66294e-18 | 1.0 |
| 5 | X05 | -8.16847e-17 | 1.0 |
| 6 | X06 | -1.35003e-16 | 1.0 |
| 7 | X07 | -3.42837e-16 | 1.0 |
| 8 | X08 | -1.36335e-16 | 1.0 |
| 9 | X09 | 2.30038e-16 | 1.0 |
| 10 | X10 | -7.9714e-17 | 1.0 |
| 11 | X11 | -1.88072e-16 | 1.0 |
| 12 | X12 | -4.75307e-16 | 1.0 |
| 13 | X13 | -1.26826e-16 | 1.0 |
| 14 | X14 | -1.51934e-16 | 1.0 |
| 15 | X15 | 1.35003e-16 | 1.0 |
| 16 | X16 | 1.20792e-16 | 1.0 |
| 17 | X17 | -1.33227e-16 | 1.0 |
| 18 | X18 | 7.52731e-17 | 1.0 |
| 19 | X19 | 7.37188e-17 | 1.0 |
| 20 | X20 | 5.08482e-17 | 1.0 |
| 21 | X21 | -4.21885e-17 | 1.0 |
Modélisation¶
In [11]:
# chargement du modèle
# à partir de MLJFlux
import MLJFlux
NeuralNetworkClassifier = MLJ.@load NeuralNetworkClassifier pkg="MLJFlux"
import MLJFlux ✔
┌ Info: For silent loading, specify `verbosity=0`. └ @ Main C:\Users\ricco\.julia\packages\MLJModels\AWkxi\src\loading.jl:159
MLJFlux.NeuralNetworkClassifier
Instanciation et paramétrage¶
In [12]:
# instanciation avec paramétrage
# cf. https://fluxml.ai/MLJFlux.jl/dev/
# 2 neurones dans une seule couche cachée
# attention, spécifié comme un tuple "(2,)" --> "," obligatoire
import Flux
modele = NeuralNetworkClassifier(
builder = MLJFlux.MLP(; hidden=(2,), σ = Flux.tanh),
finaliser = Flux.softmax,
optimiser = Flux.Adam(0.01),
loss = Flux.crossentropy,
batch_size = 20,
epochs = 1000
)
NeuralNetworkClassifier(
builder = MLP(
hidden = (2,),
σ = tanh),
finaliser = NNlib.softmax,
optimiser = Optimisers.Adam(eta=0.01, beta=(0.9, 0.999), epsilon=1.0e-8),
loss = Flux.Losses.crossentropy,
epochs = 1000,
batch_size = 20,
lambda = 0.0,
alpha = 0.0,
rng = Random.TaskLocalRNG(),
optimiser_changes_trigger_retraining = false,
acceleration = ComputationalResources.CPU1{Nothing}(nothing),
embedding_dims = Dict{Symbol, Real}())
Préparation à l'entraînement¶
In [13]:
# préparer au lancement de l'entraînement
mach = MLJ.machine(modele,ZTrain,yTrain)
untrained Machine; caches model-specific representations of data
model: NeuralNetworkClassifier(builder = MLP(hidden = (2,), …), …)
args:
1: Source @485 ⏎ ScientificTypesBase.Table{AbstractVector{ScientificTypesBase.Continuous}}
2: Source @297 ⏎ AbstractVector{ScientificTypesBase.Multiclass{3}}
Entraînement sur les données TRAIN¶
In [14]:
# lancer l'entraînement du modèle
MLJ.fit!(mach)
┌ Info: Training machine(NeuralNetworkClassifier(builder = MLP(hidden = (2,), …), …), …).
└ @ MLJBase C:\Users\ricco\.julia\packages\MLJBase\DCbte\src\machines.jl:499
┌ Info: MLJFlux: converting input data to Float32
└ @ MLJFlux C:\Users\ricco\.julia\packages\MLJFlux\9HWUB\src\core.jl:294
Optimising neural net: 100%[=========================] Time: 0:00:30
trained Machine; caches model-specific representations of data
model: NeuralNetworkClassifier(builder = MLP(hidden = (2,), …), …)
args:
1: Source @485 ⏎ ScientificTypesBase.Table{AbstractVector{ScientificTypesBase.Continuous}}
2: Source @297 ⏎ AbstractVector{ScientificTypesBase.Multiclass{3}}
Décroissance de la "loss" (report)¶
In [15]:
# rapport
rapport = MLJ.report(mach)
keys(rapport)
(:training_losses,)
In [16]:
# courbe de décroissance de la loss
import Plots
Plots.plot(1:1000,rapport.training_losses[1:1000],title="Decroissance de la loss",label="")
Structure du réseau et poids synaptiques (fitted_params)¶
In [17]:
# paramètres appris
fp = MLJ.fitted_params(mach)
keys(fp)
(:chain,)
In [18]:
# structure du réseau
fp.chain
Chain(
Chain(
Dense(21 => 2, tanh), # 44 parameters
Dense(2 => 3), # 9 parameters
),
NNlib.softmax,
) # Total: 4 arrays, 53 parameters, 420 bytes.
In [19]:
# les couches
fp.chain.layers
(Chain(Dense(21 => 2, tanh), Dense(2 => 3)), NNlib.softmax)
In [20]:
# poids de la première couche
# entrée -> cachée
fp.chain.layers[1][1].weight
2×21 Matrix{Float32}:
0.0903711 -0.0219708 -0.0287282 … 0.0947618 0.0362732 0.0726964
-0.230598 0.0474596 0.208017 -0.126253 -0.212919 -0.117373
In [21]:
# biais (intercept)
fp.chain.layers[1][1].bias
2-element Vector{Float32}:
-0.2711948
1.6111445
In [22]:
# poids de la seconde couche
# cachée -> sortie
fp.chain.layers[1][2].weight
3×2 Matrix{Float32}:
4.53067 2.41938
0.0372478 4.04926
-6.916 -10.2303
In [23]:
# biais (intercept)
fp.chain.layers[1][2].bias
3-element Vector{Float32}:
1.137664
-1.062453
0.07133843
Evaluation en test¶
Application de la standardisation en test¶
In [24]:
# préparation des descripteurs de l'éch. test
ZTest = MLJ.transform(std,XTest)
# vérif. ?? pas du tout
# et c'est tout à fait normal
DFR.describe(ZTest,:mean,:std)
21×3 DataFrame
| Row | variable | mean | std |
|---|---|---|---|
| Symbol | Float64 | Float64 | |
| 1 | X01 | 0.0631805 | 1.03486 |
| 2 | X02 | 0.0084361 | 0.990996 |
| 3 | X03 | -0.0338438 | 0.961525 |
| 4 | X04 | -0.0737975 | 0.987057 |
| 5 | X05 | 0.00389172 | 0.99079 |
| 6 | X06 | -0.0547832 | 0.98203 |
| 7 | X07 | 0.00631206 | 0.996754 |
| 8 | X08 | -0.0240473 | 1.01188 |
| 9 | X09 | 0.0470839 | 0.997109 |
| 10 | X10 | -0.0278026 | 0.941908 |
| 11 | X11 | 0.0243741 | 0.975445 |
| 12 | X12 | 0.0406839 | 0.992951 |
| 13 | X13 | -0.00372665 | 0.966481 |
| 14 | X14 | 0.0297202 | 0.996019 |
| 15 | X15 | 0.0132654 | 0.977217 |
| 16 | X16 | 0.0218892 | 0.981901 |
| 17 | X17 | -0.0441636 | 0.977805 |
| 18 | X18 | -0.0718903 | 1.00514 |
| 19 | X19 | 0.00282934 | 1.00999 |
| 20 | X20 | -0.0188788 | 0.977144 |
| 21 | X21 | 0.022465 | 0.966729 |
Probas d'appartenance¶
In [25]:
# prédiction en test - probabilités (puisque softmax)
probs = MLJ.predict(mach,ZTest)
# vérif.
DFR.first(probs,5)
5-element CategoricalDistributions.UnivariateFiniteVector{ScientificTypesBase.Multiclass{3}, String, UInt32, Float32}:
UnivariateFinite{ScientificTypesBase.Multiclass{3}}(A=>0.0199, B=>0.978, C=>0.00192)
UnivariateFinite{ScientificTypesBase.Multiclass{3}}(A=>0.0954, B=>0.904, C=>0.000155)
UnivariateFinite{ScientificTypesBase.Multiclass{3}}(A=>0.023, B=>0.975, C=>0.0016)
UnivariateFinite{ScientificTypesBase.Multiclass{3}}(A=>0.584, B=>0.416, C=>1.16e-6)
UnivariateFinite{ScientificTypesBase.Multiclass{3}}(A=>0.286, B=>0.714, C=>1.39e-5)
Classes prédites¶
In [26]:
# prédiction en test - classe d'appartenance
pred = MLJ.predict_mode(mach,ZTest)
DFR.first(pred,5)
5-element CategoricalArrays.CategoricalArray{String,1,UInt32}:
"B"
"B"
"B"
"A"
"B"
Matrice de confusion et accuracy¶
In [27]:
# matrice de confusion
# attention transposée sur MLJ
MLJ.confusion_matrix(pred,yTest)
┌──────────────┐
│ Ground Truth │
┌─────────┼────┬────┬────┤
│Predicted│ A │ B │ C │
├─────────┼────┼────┼────┤
│ A │1114│145 │ 74 │
├─────────┼────┼────┼────┤
│ B │174 │1245│ 94 │
├─────────┼────┼────┼────┤
│ C │203 │ 92 │1359│
└─────────┴────┴────┴────┘
In [28]:
# accuracy
MLJ.accuracy(pred,yTest)
0.8262222222222222
Représentation intermédiaire - Couche cachée¶
Calcul des coordonnées des individus¶
In [29]:
# sorties intermédiaires du réseau
# attention, transformer les données en Matrix
# et transposer parce que Flux le souhaite ainsi
coord = fp.chain[1].layers[1](Matrix(ZTrain)')
# dimension -> 2 couches (cachée et sortie)
size(coord)
┌ Warning: Layer with Float32 parameters got Float64 input.
│ The input will be converted, but any earlier layers may be very slow.
│ layer = Dense(21 => 2, tanh)
│ summary(x) = 21×500 adjoint(::Matrix{Float64}) with eltype Float64
└ @ Flux C:\Users\ricco\.julia\packages\Flux\hrg9M\src\layers\stateless.jl:60
(2, 500)
In [30]:
# qqs valeurs
# forcément entre -1 et +1 puisque
# activation = tanh
coord[:,1:10]
2×10 Matrix{Float32}:
-0.7966 0.755963 -0.104458 0.938893 … 0.920955 -0.169109 0.204676
0.999782 -0.962109 -0.682212 -0.988619 0.128225 -0.77803 0.247974
Représentation dans le plan (2 neurones dans la couche cachée)¶
In [31]:
# liste des modalités de la variable cible
modalites = sort(unique(yTrain))
modalites
3-element CategoricalArrays.CategoricalArray{String,1,UInt32}:
"A"
"B"
"C"
In [32]:
# graphique vide
plt = Plots.scatter()
# enchaîner les modalités
for m in modalites
id = (yTrain .== m)
Plots.scatter!(plt,coord[1,id],coord[2,id],label = m,markersize=4)
end
# affichage
display(plt)