Versions¶
In [1]:
#version de Python
import sys
sys.version
Out[1]:
'3.12.7 | packaged by Anaconda, Inc. | (main, Oct 4 2024, 13:17:27) [MSC v.1929 64 bit (AMD64)]'
In [2]:
#version de numpy
import numpy as np
np.__version__
Out[2]:
'1.26.4'
In [3]:
#numba
import joblib
joblib.__version__
Out[3]:
'1.4.2'
Chargement et préparation des données¶
In [4]:
import sklearn.datasets as ds
covtype = ds.fetch_covtype()
In [5]:
#matrice des descripteurs
X = np.array(covtype.data)
X.shape
Out[5]:
(581012, 54)
In [6]:
#attribut classe
target = np.array(covtype.target)
target.shape
Out[6]:
(581012,)
In [7]:
#valeurs possibles
np.unique(target,return_counts=True)
Out[7]:
(array([1, 2, 3, 4, 5, 6, 7]), array([211840, 283301, 35754, 2747, 9493, 17367, 20510], dtype=int64))
In [8]:
#standardisation des X
from sklearn.preprocessing import StandardScaler
std = StandardScaler()
Z = std.fit_transform(X)
#dim.
Z.shape
Out[8]:
(581012, 54)
Régression logistique - Fisher Scoring¶
In [88]:
# code proposé par COPILOT, à la question :
#
# Peux-tu me proposer un code Python pour le calcul de la régression
# logistique binaire avec l'algorithme Fisher scoring ?
"""
def fit_reg(Z, y, max_iter=5, tol=1e-6):
# copie locale
X = np.copy(Z)
# ajouter la constante
#X = np.hstack([np.ones((X.shape[0], 1)), X])
X = np.concatenate((np.ones((X.shape[0],1)),X),axis=1)
# Initialisation des poids
beta = np.zeros(X.shape[1])
for i in range(max_iter):
# Calcul des probabilités prédictives
p = 1.0/(1.0 + np.exp(-1.0 * X @ beta))
# Calcul de la matrice de la pente (gradient)
gradient = X.T @ (y - p)
# Calcul de la matrice Hessienne - argh la taille de W
#W = np.diag(p * (1 - p))
#H = X.T @ W @ X
#solution moins gourmande en espace mémoire
proba = p * (1 - p)
M = X.T * proba
H = M @ X
# Mise à jour des poids avec Fisher scoring
beta_new = beta + np.linalg.inv(H) @ gradient
# Condition d'arrêt
if np.sum(np.abs(beta_new - beta)) < tol:
break
beta = beta_new
return beta
"""
Régresion logistique binaire¶
In [9]:
#régression logistique binaire
#max_iter est passé à 10 par rapport à la vidéo "Numba"
def fit_reg(Z, y, max_iter=10, tol=1e-6):
# copie locale
X = np.copy(Z)
# ajouter la constante
X = np.concatenate((np.ones((X.shape[0],1)),X),axis=1)
# Initialisation des poids
beta = np.zeros(X.shape[1])
for i in range(max_iter):
# Calcul des probabilités prédictives
p = 1.0/(1.0 + np.exp(-1.0 * X.dot(beta)))
# Calcul de la matrice de la pente (gradient)
gradient = X.transpose().dot(y - p)
#calcul de la matricie Hessienne
#solution moins gourmande en espace mémoire
proba = p * (1 - p)
M = X.T * proba
H = M.dot(X)
# sécurisation - rajouter une constante dans la diagonale
for j in range(H.shape[0]):
H[j,j] += 1e-6
# Mise à jour des poids avec Fisher scoring
beta_new = beta + np.linalg.inv(H).dot(gradient)
# Condition d'arrêt
if np.mean(np.abs(beta_new - beta)) < tol:
break
beta = beta_new
return beta
Régression one-vs-rest -- Non-parallélisée¶
In [10]:
#on part du principe que les classes sont identifiées par 1, 2, 3, ....
def one_vs_rest(Z, cible, max_iter=10, tol=1e-6):
nb_classes = np.max(cible)
#pour chaque modalité de la variable cible
lst_coefs = []
for k in range(nb_classes):
y = (cible==(k+1))
lst_coefs.append(fit_reg(Z,y,max_iter,tol))
#return liste des coefficients
return lst_coefs
In [11]:
#entraînement sur nos données
coefs_ = one_vs_rest(Z,target)
print(len(coefs_))
7
In [12]:
#coefs sous-forme matricielle
betas_ = np.array(coefs_)
betas_.shape
Out[12]:
(7, 55)
Fonction de prédiction¶
In [13]:
# fonction de prédiction
def pred_reg(Z,betas):
# copie locale
X = np.copy(Z)
# ajouter la constante
X = np.concatenate((np.ones((X.shape[0],1)),X),axis=1)
# produit matriciel
logit = X @ betas.T
#max. par ligne
id_max = np.argmax(logit,axis=1)
# prediction
return id_max + 1
In [14]:
# appliquer
pred = pred_reg(Z,betas_)
np.unique(pred,return_counts=True)
Out[14]:
(array([1, 2, 3, 4, 5, 6, 7], dtype=int64), array([205641, 305579, 49147, 1222, 438, 3711, 15274], dtype=int64))
In [15]:
# accuracy
np.mean(target == pred)
Out[15]:
0.7153587189249103
Parallélisation avec joblib (process)¶
In [16]:
from joblib import Parallel, delayed
#one_vs_rest parallélisée - technologie processus
def one_vs_rest_jb_process(Z, cible, max_iter=10, tol=1e-6):
nb_classes = np.max(cible)
#instanciation de la classe pour le traitement parallèle
#on peut restreindre le nombre de ressources à solliciter (n_jobs)
#technologie PROCESSUS
processes = Parallel(n_jobs=nb_classes,prefer="processes")
#le résultat est récupéré sous la forme d'une liste de vecteurs de coefficients
#les items sont dans l'ordre des soumissions, pas de réorganisation à faire
lst_coefs = processes(delayed(fit_reg)(Z,cible==(k+1),max_iter,tol) for k in range(nb_classes))
#return liste des coefficients
return lst_coefs
In [17]:
#entraînement
coefs_bis_ = one_vs_rest_jb_process(Z,target)
In [18]:
#vérification
np.mean(target == pred_reg(Z,np.array(coefs_bis_)))
Out[18]:
0.7153587189249103
Parallélisation avec joblib (thread)¶
In [19]:
#one_vs_rest parallélisée, technologie thread
def one_vs_rest_jb_thread(Z, cible, max_iter=10, tol=1e-6):
nb_classes = np.max(cible)
#instanciation de la classe pour le traitement parallèle
#technologie THREADS
threads = Parallel(n_jobs=nb_classes,prefer="threads")
#le résultat est récupéré sous la forme d'une liste cf. le param. "return_as"
#liste de vecteurs de coefficients ici
#les vecteurs sont dans l'ordre des soumissions et non pas d'achèvement
#par conséquent pas de réorganisation à faire
lst_coefs = threads(delayed(fit_reg)(Z,cible==(k+1),max_iter,tol) for k in range(nb_classes))
#return liste des coefficients
return lst_coefs
In [20]:
#entraînement
coefs_ter_ = one_vs_rest_jb_thread(Z,target)
In [21]:
#vérification
np.mean(target == pred_reg(Z,np.array(coefs_ter_)))
Out[21]:
0.7153587189249103
Durées d'exécution¶
In [22]:
# durée d'excution - SANS parallélisation
%timeit -r 10 -n 1 one_vs_rest(Z, target)
11.9 s ± 800 ms per loop (mean ± std. dev. of 10 runs, 1 loop each)
In [23]:
# durée d'excution - AVEC parallélisation / PROCESSUS
%timeit -r 10 -n 1 one_vs_rest_jb_process(Z, target)
8.4 s ± 130 ms per loop (mean ± std. dev. of 10 runs, 1 loop each)
In [24]:
# durée d'excution - AVEC parallélisation / THREAD
%timeit -r 10 -n 1 one_vs_rest_jb_thread(Z, target)
8.63 s ± 220 ms per loop (mean ± std. dev. of 10 runs, 1 loop each)