#dossier de travail
import os
os.chdir("C:/Users/ricco/Desktop/demo")
#chargement des données
import pandas
df = pandas.read_table("data.txt",sep='\t')
df.shape
(11069, 8316)
#partition apprentissage-test
from sklearn.model_selection import train_test_split
dfTrain, dfTest = train_test_split(df,train_size=1000,random_state=0,stratify=df.target)
#vérif.
#on est dans un exercice de style ici, d'où ces proportions app/test
print(dfTrain.shape)
print(dfTest.shape)
(1000, 8316) (10069, 8316)
#distribution des classes
dfTrain.target.value_counts()
neg 935 pos 65 Name: target, dtype: int64
#fréquences relatives
#accuracy du modèle par défaut = 93.5%
dfTrain.target.value_counts(normalize=True)
neg 0.935 pos 0.065 Name: target, dtype: float64
#recoder la cible pour la régression PLS
#recodage 0/1 simple parce qu'évaluation avec courbe ROC
#capacité du modèle à attribuer un score plus élévés aux individus de la classe cible
#pas de problème de seuil d'affectation
import numpy
yTrain = numpy.asarray(dfTrain.target == 'pos').astype(int)
#vérif.
yTrain[:20]
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0])
#verif. 2
numpy.sum(yTrain)
65
#matrice des X
XTrain = dfTrain.iloc[:,1:]
#vérif.
XTrain.shape
(1000, 8315)
#régression PLS - solution initiale : 1 composante pour l'instant
from sklearn.cross_decomposition import PLSRegression
plsInit = PLSRegression(n_components = 1)
plsInit.fit(XTrain,yTrain)
#coefficients de la régression
plsInit.coef_
c:\Users\ricco\anaconda3\envs\env_tutos\lib\site-packages\sklearn\cross_decomposition\_pls.py:503: FutureWarning: The attribute `coef_` will be transposed in version 1.3 to be consistent with other linear models in scikit-learn. Currently, `coef_` has a shape of (n_features, n_targets) and in the future it will have a shape of (n_targets, n_features). warnings.warn(
array([[ 0. ], [ 0. ], [ 0. ], ..., [ 0. ], [-0.00012556], [-0.0001751 ]])
#constante de la régression
plsInit.intercept_
array([0.065])
#valeur de l'AUC en resubstitution
from sklearn import metrics
metrics.roc_auc_score(yTrain, plsInit.predict(XTrain))
0.9958535582064993
#définir un K-fold stratifié
from sklearn.model_selection import StratifiedKFold
skf = StratifiedKFold(n_splits=5,shuffle=True,random_state=0)
skf.get_n_splits(XTrain,yTrain)
5
#AUC-s en validation croisée
from sklearn.model_selection import cross_val_score
auc_res = cross_val_score(plsInit, XTrain, yTrain, scoring='roc_auc',cv=skf)
auc_res
array([0.97614151, 0.96503497, 0.94282188, 0.98971617, 0.98354587])
#AUC synthèse en validation croisée
numpy.mean(auc_res)
0.9714520773344304
#AUC sur l'échantillon test
yTest = numpy.asarray(dfTest.target=='pos').astype(int)
XTest = dfTest.iloc[:,1:]
#
metrics.roc_auc_score(yTest, plsInit.predict(XTest))
0.9719458869255511
#nombre max d'axes
max_components = 10
#auc en resubstitution
auc_resub = numpy.zeros(max_components)
#auc en validation croisée
auc_cv = numpy.zeros(max_components)
#itérer
for k in range(max_components):
#régression PLS sur la totalité des individus
pls = PLSRegression(n_components=k+1)
pls.fit(XTrain,yTrain)
#auc en resubstitution
auc_resub[k] = metrics.roc_auc_score(yTrain, pls.predict(XTrain))
#auc en validation croisée
auc_cv[k] = numpy.mean(cross_val_score(pls, XTrain, yTrain, scoring='roc_auc',cv=skf))
#résultats
print(auc_resub)
print(auc_cv)
[0.99585356 0.9993912 0.99990128 1. 1. 1. 1. 1. 1. 1. ] [0.97145208 0.98395722 0.98099548 0.97844508 0.97589469 0.97597696 0.9747429 0.97359111 0.97202797 0.97112299]
#graphique - s'intéresser au paramètre de régularisation est toujours important
import matplotlib.pyplot as plt
plt.plot(numpy.arange(1,max_components+1,1),auc_resub,marker='.',label='Resub.')
plt.plot(numpy.arange(1,max_components+1,1),auc_cv,marker='.',label='Cross-val.')
plt.xlabel("# components")
plt.ylabel('ROC AUC')
plt.legend()
plt.show()
#régression PLS - 2 composantes cette fois-ci
plsOpt = PLSRegression(n_components = 2)
plsOpt.fit(XTrain,yTrain)
#coefficients de la régression
print(plsOpt.coef_,'\n')
#constante
print(plsOpt.intercept_)
[[ 0. ] [ 0. ] [ 0. ] ... [ 0. ] [-0.00016245] [-0.00016625]] [0.065]
c:\Users\ricco\anaconda3\envs\env_tutos\lib\site-packages\sklearn\cross_decomposition\_pls.py:503: FutureWarning: The attribute `coef_` will be transposed in version 1.3 to be consistent with other linear models in scikit-learn. Currently, `coef_` has a shape of (n_features, n_targets) and in the future it will have a shape of (n_targets, n_features). warnings.warn(
#performances en test
metrics.roc_auc_score(yTest, plsOpt.predict(XTest))
0.9774529942259497
On aurait pu utiliser le modèle issu dans la régression PLS pour classer les individus supplémentaires, en comparant le score d'affectation avec la moyenne de la variable recodée. Mais puisque qu'avec la régression PLS on dispose d'un espace de représentation intermédiaire, nous pouvons en tirer profit.
#PLS produit un syst. de représentation factorielle
#avec une propriété de réduction de dimension
#elle est construite en tenant compte de(s) variable(s) cible(s) y
ZTrain = plsOpt.transform(XTrain)
ZTrain.shape
(1000, 2)
#projection des individus dans l'espace factoriel
import matplotlib.pyplot as plt
plt.scatter(ZTrain[:,0], ZTrain[:,1], marker='.')
plt.title("Plan factoriel PLS")
plt.xlabel('Composante 1')
plt.ylabel('Composante 2')
plt.show()
#graphique - séparabilité des classes
plt.scatter(ZTrain[:,0], ZTrain[:,1], marker='.',c=numpy.array(['red','green'])[yTrain])
plt.title("Plan factoriel PLS + Classes d'appartenance")
plt.xlabel('Composante 1')
plt.ylabel('Composante 2')
plt.show()
#les facteurs (définis à partir des X) sont orthogonaux
numpy.corrcoef(ZTrain,rowvar=False)
array([[ 1.0000000e+00, -4.9009449e-16], [-4.9009449e-16, 1.0000000e+00]])
On pourrait aussi construire un cercle des corrélations pour identifier les variables qui déterminent les facteurs, et par conséquent la séparabilité des classes.
Une ADL dans un espace à 8315 explicatives pour 1000 obs. aurait été très instable.
#partir sur un schéma d'affectation cette fois-ci
#à partir de la représentation factorielle
#analyse discriminante PLS
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
adl = LinearDiscriminantAnalysis()
adl.fit(ZTrain,yTrain)
#coefficients
adl.coef_
array([[4.53363764, 1.9962874 ]])
#constante
adl.intercept_
array([-35.50206944])
#équation explicite de la frontière de séparation des classes
#dixit l'analyse discriminante linéaire
#pente et constante
a = -adl.coef_[0][0]/adl.coef_[0][1]
b = -adl.intercept_[0]/adl.coef_[0][1]
#
print(f"{a} * x1 + {b}")
-2.271034544237145 * x1 + 17.78404725158195
#représentation de la frontière
#dans l'espace factoriel de la PLS
plt.scatter(ZTrain[:,0], ZTrain[:,1], marker='.',c=numpy.array(['red','green'])[yTrain])
plt.plot([0,30],[a*0+b,a*30+b])
plt.title("Plan factoriel - Frontière de séparation des classes")
plt.xlabel('Composante 1')
plt.ylabel('Composante 2')
plt.show()
On pourrait monter un "pipeline" pour enchaîner la PLS + l'analyse discriminante linéaire.
#coord. des individus en test
#dans le plan factoriel
ZTest = plsOpt.transform(XTest)
ZTest.shape
(10069, 2)
#représentation des points supplémentaires (de l'éch. test) dans la plan
plt.scatter(ZTest[:,0], ZTest[:,1], marker='.',s=10)
plt.title("Plan factoriel - Individus de l'ech. test")
plt.xlabel('Composante 1')
plt.ylabel('Composante 2')
plt.show()
#prédiction + évaluation = accuracy ici
adl.score(ZTest,yTest)
0.9637501241434104
#détail - matrice de confusion
#visibilité sur la structure de l'erreur
metrics.ConfusionMatrixDisplay.from_predictions(yTest, adl.predict(ZTest))
<sklearn.metrics._plot.confusion_matrix.ConfusionMatrixDisplay at 0x16c73b2b010>
#vérif. de l'accuracy
(9365+339)/10069
0.9637501241434104
#dans la plan factoriel
#classements corrects et erronés
plt.scatter(ZTest[:,0], ZTest[:,1], marker='.',s=10,c=numpy.array(['red','green'])[numpy.array(dfTest.target=='pos').astype(int)])
plt.plot([0,20],[a*0+b,a*20+b])
plt.title("Plan factoriel - Frontière de séparation des classes")
plt.xlabel('Composante 1')
plt.ylabel('Composante 2')
plt.show()
Définie sur l'espace initial (les variables originelles) de représentation.
#par comparaison une régression logistique
#par défaut dans sklearn = pénalisée Ridge
#https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.html
from sklearn.linear_model import LogisticRegression
clf = LogisticRegression()
clf.fit(XTrain,yTrain)
#perfs
clf.score(XTest,yTest)
0.9433906048266958