Une agence bancaire est un point de contact important entre les
clients et la banque. En plus de fournir des services bancaires
traditionnels tels que les dépôts, les retraits et les prêts, les
agences bancaires peuvent également jouer un rôle crucial dans la
gestion des relations avec les clients et la promotion des produits et
services bancaires.
Dans le contexte actuel, la majorité des firmes
importantes analyse la “data” et quel que soit le secteur (sport,
économique, social…). Les banques gèrent des données importantes sur les
clients, les transactions et les activités financières. La collecte,
l’analyse et l’utilisation de ces données peuvent fournir de précieuses
informations aux banques pour mieux comprendre les besoins de leurs
clients, leurs comportements, améliorer leur expérience utilisateur, et
à optimiser leurs opérations et leur rentabilité.
Les banques
utilisent ces données pour créer des modèles d’analyse prédictive qui
leur permettent d’anticiper les besoins des clients et de développer des
produits et des services personnalisés en conséquence.
Lors de cette étude, nous allons lettre en application d’un
processus de Datamining, selon plusieurs methode (qui seront presenté
dans le sommaire). Chaque partie aura ca conclusion (ce qui explique
pourquoi il y n’y aura pas de conclusion générale).
Ainsi, cette
étude en fonction de la methode utiliser sera soit sur un individu moyen
(explication dans la partie 2.1) de chaque agence bancaire “Crédit
Agricole” de la région PACA ou sur le fichier de base.
Afin d’effectuer une analyse basé sur l’apprentissage statistique
pour l’I.A. la plus proche de la réalité, j’ai pris la décision de
supprimer quelques variables et valeur (explication dans le points 2.1).
J’ai aussi fait le choix de prendre une base de données que je
connais et où j’ai fait plusieurs sae avec (explication dans la partie
1.1).
Enfin, j’ai importé les données sur PHPGadmin (application
Web réalisée en langage PHP destinée à faciliter la gestion du SGBD
PostgreSQL), et requêter les données que j’avais besoin pour l’analyse.
J’ai choisi une base de données portant sur les banques et sa
clientèle puisque :
• J’ai toujours aimé le domaine des banques,
l’économie en général et analyser / comprendre leurs actions
• Dans
le monde professionnel, le secteur des banques recherche beaucoup de
“data Analystes” où ils peuvent être amener à faire ce type d’analyse,
ainsi, je me familiarise avec ces demandes qui pourraient m’arriver dans
le monde professionnel.
• Je connais bien cette base de données
puisque lorsque nos tuteurs nous laissent le choix de la base de données
je prends souvent celle-ci pour les raisons citée.
L’objectif de cette sae est d’expliquer, comprendre et trouvé des
similitudes et de prédire (à des fins décisionnelles) entre les banques
de la région paca en fonction de plusieurs variables (ancienneté,
catégorie socioprofessionnelle, …) pour que les décisions soient
optimales (pour les clients) et bénéfiques (pour la société).
Les
résultats de cette analyse pourraient aider les banques à améliorer et
optimiser leur compréhension des facteurs qui influencent la rentabilité
de leurs agences bancaires et à développer des stratégies, proposer de
programme de fidélité personnalisé pour améliorer leurs performances.
La base est composée de 88 058 individus, et de 28 variables, donc
beaucoup trop d’individu et de variables.
Ainsi, avec l’accord de
mon tuteur, en fonction de méthode utilisé de faire un “individu moyen”
pour chaque agence bancaire et d’autre part de supprimer des variables
“inutiles” dans l’analyse pouvant fausser certaines conclusions (plus
d’explication dans la partie 2.1).
Mais avant de commencer toute analyse, il me semble primordial de
comprendre le sujet.
Ainsi, je vous dans cette partie vous
présenter les principales données étudiées, leur définition, l’origine
de la base de données…
L’origine de la base de données:
Comme expliqué rapidement ci-dessus, notre professeur
Pierre-Michel Bousquet nous a transmis cette base de données pour un
autre projet. Elle fait partie d’un ensemble de données structurées et
organisées, sur les clients de l’entreprise “Crédit Agricole” s’appelant
« base_clts » datant du 31 janvier 2019.
Téléchargement des données :
https://guacamole.univ-avignon.fr/nextcloud/index.php/s/wZ9P3kop4dKqmAt
Definition des principales varialbles selon l’insee:
Le « age » est l’âge du client.
Le terme « li_regrp_csp » désigne la catégorie
professionnelle du client, ici il y en a 8: • Agriculteurs exploitants/
agr
• Artisants, commerçants et chefs d’entreprise / art
•
Autres personnes sans activité professionnelle / sans_act
• Cadres
et professions intellectuelles supérieures / cadres
• Employés /
employes
• Ouvriers / ouvrier
• Professions Intermédiaires /
prof_intermediaires
• Retraités / retraites
Le « classe risque » est indicateur du risque lié au client (16 niveaux).
Le « tp assurance » signifie si le clients oui (1) ou non (0) à une Assurance en cours.
Le « tp epargne » signifie si le clients oui (1) ou non (0) à une Epargne en cours.
Le « mt rentabilite » est le montant de rentabilité du client.
Le « mt epargne disponible » est le montant d’épargne disponible du client.
Le « identifiant » est le nombre de client dans l’agence.
Une fois ces étapes passée, l’analyse peut enfin commencer.
Le machine learning est le processus qu’un algorithmes
apprennent des données pour effectuer des tâches sans programmation
explicite. Il commence par la collecte de données, puis les prétraite
pour les rendre utilisables. Un modèle est choisi et entraîné sur un
ensemble de données, ajustant ses paramètres pour minimiser les erreurs.
Ensuite, le modèle est évalué sur des données non vues, et ce processus
itératif de formation, évaluation et ajustement est souvent répété pour
améliorer les performances du modèle au fil du temps.
Dans mon cas
le “train” (apprentissage est 75%) et les predictions “test” (25%).
library(ade4)
library(corrplot)
library(DBI)
library(factoextra)
library(RPostgreSQL)
library(readr)
library(ggplot2)
library(tidyverse)
library(cluster)
library(vegan)
library(dplyr)
library(tidyr)
library(cluster)
library(rpart)
c=dbConnect(RPostgreSQL::PostgreSQL(),dbname="iut2203125")
query='SELECT * FROM "S4_enquete"."pred_variable"'
Z <- dbGetQuery(c,query)
#table(Z$classe_risque)
Z=na.omit(Z)
Z$classe_risque[Z$classe_risque=="A"]="1"
Z$classe_risque[Z$classe_risque=="B"]="2"
Z$classe_risque[Z$classe_risque=="C"]="3"
Z$classe_risque[Z$classe_risque=="D"]="4"
Z$classe_risque[Z$classe_risque=="E"]="5"
Z$classe_risque[Z$classe_risque=="F"]="6"
Z$classe_risque[Z$classe_risque=="G"]="7"
Z$classe_risque[Z$classe_risque=="H"]="8"
Z$classe_risque[Z$classe_risque=="I"]="9"
Z$classe_risque[Z$classe_risque=="J"]="10"
Z$classe_risque[Z$classe_risque=="K"]="11"
Z$classe_risque[Z$classe_risque=="L"]="12"
Z$classe_risque[Z$classe_risque=="V"]="13"
Z$classe_risque[Z$classe_risque=="VM"]="14"
Z$classe_risque[Z$classe_risque=="W"]="15"
Z$classe_risque[Z$classe_risque=="Y"]="16"
Z$classe_risque=as.numeric(Z$classe_risque)
#summary(Z$classe_risque)
#dim(Z)
#colnames(Z)
X=data.frame(
Z[,-c(1,4,5,6,7,9,29)],
acm.disjonctif(data.frame(Z[,c(4,5,7)]))
)
Y=cut(Z$classe_risque,breaks = c(1,9,13,16),include.lowest = T)
table(Y)
## Y
## [1,9] (9,13] (13,16]
## 80645 5023 2391
Ci-dessus est représenté l’effectif dans chaque classe, on a
donc pour la classe tranche [1,9] (==> pas a risque) 80645 clients,
pour la tranche [9,13] (==> risqué) 5023 clients et pour la dernière
tranche [13,15] (==> très risqué) 2391 clients.
Autrement dit,
sur toute la base de données, il y a 7 414 clients à risque (donc 2391
vraiment à risque).
K=5
ech=sample(1:K,nrow(X),replace = T)
#table(ech)
TauxErreurCrossValidation=rep(0,K)
for (iter in 1:K){
#print(iter)
X_train=X[ech !=iter,]
Y_train=Y[ech !=iter]
X_test=X[ech==iter,]
Y_test=Y[ech==iter]
model = rpart(Y_train~.,data=data.frame(X_train),method="class")
predictions=predict(model,data.frame(X_test),type="class")
Y_predit=predictions
w=table(Y_predit,Y_test)
TauxErreurGlobale = 1-sum(diag(w))/sum(w)
TauxErreurCrossValidation[iter]=TauxErreurGlobale
}
#w
#TauxErreurCrossValidation
#summary(TauxErreurCrossValidation)
Nous pouvons voir avec l’encadré rouge le nombre de données dans
chaque échantillon, pour la suite de mes explications, je vais utiliser
le dernier échantillon (puisque c’est le dernier dans la boucle). Dans
l’encadrer le bleu est représenté les prédictions grâce à la machine
learning (Y_Predit) et la clef/la réalité (Y_test).
Dans l’encadrer
vert nous voyons le taux d’erreur pour chaque échantillon (donc 5 taux
d’erreur), par exemple le dernier échantillon a un taux d’erreur de 5 %,
donc 95 % de bonne réponse.
Je vais détailler les résultats
ci-dessous.
model = rpart(Y_train~.,data=data.frame(X_train),method="class")
predictions=predict(model,data.frame(X_test),type="class")
Y_predit=predictions
w=table(Y_predit,Y_test)
w
## Y_test
## Y_predit [1,9] (9,13] (13,16]
## [1,9] 15943 561 155
## (9,13] 175 423 3
## (13,16] 68 9 371
TauxErreurGlobale2 = 1-sum(diag(w))/sum(w)#au totalement il a un taux d'erreur de 5% sur plus de 17000 clients
(sum(w[2,])+sum(w[3,]))/sum(w)#il a un taux d'erreur de 5% de clients a risque
## [1] 0.05923876
sum(w[3,])/sum(w)# et 2% de clients très a risque
## [1] 0.0252993
(sum(w[2,2])+sum(w[3,3]))/(sum(w[2,])+sum(w[3,]))# et sur ses 5 % il avais raison 80%
## [1] 0.7569113
sum(w[3,3])/sum(w[3,])# et sur ses 2 % il avais raison 83%
## [1] 0.828125
#Taux_moyen_erreur
#mean(TauxErreurCrossValidation)
summary(TauxErreurCrossValidation)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.05177 0.05357 0.05483 0.05462 0.05571 0.05721
boxplot(TauxErreurCrossValidation)
NB: J’ai aussi commenté le code si cela est plus compréhensible
(avec le code et l’explication de chaque étape du
raisonnement)..
D’une part, nous pouvons voir que le taux d’erreur reste toujours
entre 0.051 et 0.060 (ce qui montre qu’il n’y a pas de valeur aberrante
et que le processus fonctionne bien), pour une moyenne d’erreur globale
de 0.053 (5 %), ce qui est très bien.
Par ailleurs, pour les
clients à risque (tranche de 9 à 16) il a alerté que 5 % et sur c’est 5
%, il avait raison 80 % des fois, de la même manière pour les clients à
gros risque (13 à 16) il a alerté 2 % et avais raison 83 % des fois.
Enfin, on peut remarquer grâce au tableau de contingence que la classe
qui donne qui est le plus dur a détermine et la classe à risque (entre 9
et 13).
On peut donc en conclure que le processus de machine learning
est efficace puisqu’il ne déclenche pas tout le temps, cependant sur les
16 000, lorsqu’il alerte, c’est très souvent vrai.
Par ailleurs,
comme on a vu tout au long de cette partie, il est d’autant efficace
puisque dans le domaine bancaire, il y a tellement de facteurs qui
rentre en compte que, même pour les grandes entreprises, c’est jamais
blanc ou noir.
Un arbre de décision permet de représenter une hiérarchie de
décisions basées sur les caractéristiques des données.
Il divise
récursivement l’ensemble de données en sous-groupes en choisissant les
caractéristiques les plus discriminantes à chaque étape. Ces divisions
successives forment une structure arborescente où les feuilles
représentent les groupes finaux.
Arbre de décision avec une complexité de 0.01
model = rpart(Y_train~.,data=data.frame(X_train),method="class", control = rpart.control(cp = 0.01))
plot(model,branch=1,uniform=T,xpd=NA)
text(model,fancy=F,all=T,use.n=T,cex=0.75,xpd=NA)
Arbre de décision avec une complexité de 0.0001
model = rpart(Y_train~.,data=data.frame(X_train),method="class", control = rpart.control(cp = 0.0001))
plot(model,branch=1,uniform=T,xpd=NA)
text(model,fancy=F,all=T,use.n=T,cex=0.75,xpd=NA)
On voit donc que le 3ème donnent trop de noeud et est donc
difficile et long pour interpréter . Je vais donc vous faire une
explication de l’arbre pour le 1 er (qui me parait le mieux pour
l’interpretation).
On voit grâce à ce tableau les “variables
décisionnelles”, par exemple la variable la plus décisionnelle de notre
arbre (celle qui créer le premier noeud) est le nombre de mouvements de
débit des 12 derniers mois. Lorsque la valeur de cette variable est
supérieure ou égale à 0.1667 alors l’individu (ici le client) va sur le
nœud de gauche sinon celui de droite, et ainsi de suite, jusqu’à ce que
l’individu n’ait plus de nœud et la nous savons à qu’elle classe risque
l’individu fait partie.
Faisons une mise en situation : Un individu
ayant comme nombre de mouvements de débit des 12 derniers mois est 1 et
son montant épargne dispo est 2 alors cet individu fait partie des
clients pas a risque (comme 57 690 autres personnes du fichier), avec
une certitude de 57660/57660+1179 (nombre de clients mal prédit faisant
en faite partie des clients à risque.) +329 (nombre de clients mal
prédit faisant en faite partie des clients très à risque) = 0.97 donc
97%.
Dans la suite de ce rapport nous allons pousser un peu plus loin le
machine learning en utilisant des méthodes, des analyses et des
procédures d’exécution plus “avancée” qu’utilisé dans la partie 4.1 et
4.2.
En effet, dans cette première partie nous allons décider
qu’elle est le modèle (parmi plusieurs modèles présentés ci-dessous) le
plus efficace. Puis nous analyserons qu’elles sont ces points forts et
faible sous forme de graphique (contrairement à la partie ci-dessus).
Les modèles que nous allons comparer ont le même objectif que l’arbre
de décision présenté dans la partie ci dessus (c’est-à-dire à prédire
des résultats en fonction de données d’apprentissage passé) mais d’une
manière différente : - Le modèle SVM vise à trouver un hyperplan optimal
pour séparer les classes
- l’ADL cherche à maximiser la distance
entre les moyennes des classes.
- L’arbre de décision qui est la
méthode deja utilisé dans la partie 4.1
- Les forêts aléatoires
combinent de multiples arbres de décision pour une meilleure
précision
- KNN classe les données en fonction de la similarité avec
ses voisins les plus proches.
Très rapidement, j’ai vu que j’allais être bloqué par la contraite du
temps et de mon environnement de travail.
En effet, comme expliqué
dans les parties ci-dessus nous travaillons sur un njeu de données de
plus de 88000 lignes (individus) et 71 variables.
Du coup, lorsque je veux déterminer le meilleur modèle l’execution de
celui-ci était très long. Alors j’ai fait la “méthode” tmux qui permet
de lancer une session en arrière-plan même si nous sommes déconnectés de
notre session rstudio.
Ainsi nous allons dans le terminal créer une session tmux dans mon
espace docker : “iut2203125@docker:~/stid3/s6/Apprentissage_statistique”
J’ai fait ces commandes pour lancer mon programme stocker dans un
fichier R nommer pgm_tmux.R
Creation d’une session = tmux
Lancement du script= Rscript
pgm_tmux.R
Sortir de la session Ctrl b puis d
Revenir à la
session = tmux attach
Le resultat sera stocker dans le fichier “resultat.txt”
calcul=function(K=10){
RES=NULL
ech=sample(1:K,nrow(X),replace = T)
for (iter in 1:K){
print(paste("iteration",iter))
res_1_bloc=NULL
###
X_train=X[ech != iter,]
Y_train=Y[ech != iter]
X_test=X[ech == iter,]
Y_test=Y[ech == iter]
### arbres
library(rpart)
model = rpart(Y_train~.,data=data.frame(X_train),method="class")
predictions=predict(model,data.frame(X_test),type="class")
Y_predit=predictions
w=table(Y_predit,Y_test)
TauxErreurGlobale = 1-sum(diag(w))/sum(w)
res_1_bloc=c(res_1_bloc,TauxErreurGlobale)
### ADL
library(MASS)
model=lda(X_train,Y_train)
predictions=predict(model,data.frame(X_test),type="class")
Y_predit=predictions$class
w=table(Y_predit,Y_test)
TauxErreurGlobale = 1-sum(diag(w))/sum(w)
res_1_bloc=c(res_1_bloc,TauxErreurGlobale)
### Knn
library(class)
Y_predit = knn(train=X_train, test=X_test, cl=Y_train) # pas de modèle dans les knn
w=table(Y_predit,Y_test)
TauxErreurGlobale = 1-sum(diag(w))/sum(w)
res_1_bloc=c(res_1_bloc,TauxErreurGlobale)
### SVM
library(e1071)
model = svm(X_train, Y_train,probability = TRUE)
Y_predit = predict(model, X_test, probability = FALSE)
w=table(Y_predit,Y_test)
TauxErreurGlobale = 1-sum(diag(w))/sum(w)
res_1_bloc=c(res_1_bloc,TauxErreurGlobale)
### randomForest
library(randomForest)
model=randomForest(X_train, Y_train)
Y_predit=predict(model, X_test)
w=table(Y_predit,Y_test)
TauxErreurGlobale = 1-sum(diag(w))/sum(w)
res_1_bloc=c(res_1_bloc,TauxErreurGlobale)
### Reg Log # UNIQUEMENT SUR CIBLE BINAIRE.
if (nlevels(Y_train)==2){
model = glm(Y_train~., family=binomial(link="logit"),data=as.data.frame(X_train))
p=predict.glm(model,X_test,type="response")
Y_predit=as.factor(p>0.5)
levels(Y_predit)=levels(Y_test)
w=table(Y_predit,Y_test)
TauxErreurGlobale = 1-sum(diag(w))/sum(w)
res_1_bloc=c(res_1_bloc,TauxErreurGlobale)
}
RES=rbind(RES,res_1_bloc)
}
return(RES)
}
#RES=calcul(5)
#write.table(RES,"resultat.txt",row.names = F,col.names = F,sep=" ",quote=F)
graph=function(RES){
library(ggplot2)
erreur=c(RES)
methode=rep(c("1.arbre","2.LDA","3.KNN","4. SVM","5. Forest"),each=nrow(RES))
df=data.frame(methode, erreur)
ggplot(df, aes(x=methode, y=erreur)) +
stat_boxplot(geom = "errorbar",
width = 0.25) +
geom_boxplot() +
coord_flip()
}
RES=read.delim("/srv/alumni/iut2203125/stid3/s6/Apprentissage_statistique/resultat.txt",sep = " ",header=F)
#str(RES)
RES_mat <- as.matrix(RES)
rownames(RES_mat) <- rep("res_1_bloc", nrow(RES))
RES=RES_mat
graph(RES)
Une fois l’exécution sous tmux fini, je prends le résultat et
l’applique à ma fonction graph() préalablement créer.
Le graphique ci-dessus représente les 5 méthodes que l’on va comparer
afin de déterminer lequel est le plus efficace. Pour chaque méthode,
elle lui est associé un box plot.
Ainsi, grâce à ce graphique on peut faire le choix de la méthode la
plus performante.
On remarque assez facilement que celui où le taux
d’erreur et le plus faible et la méthode random forest, puisqu’elle ne
fait qu’environ 4.7% d’erreur.
On peut voir que la méthode de
l’arbre de décision la talonne avec environ 5.5% d’erreur, puis la
méthode SVM, LDA et KNN ensuite.
Pour la suite de cette partie on va se pencher sur la méthode random forest.
library(DBI)
library(RPostgreSQL)
library(ade4)
c=dbConnect(RPostgreSQL::PostgreSQL(),dbname="iut2203125")
query='SELECT * FROM "S4_enquete"."pred_variable"'
Z <- dbGetQuery(c,query)
#table(Z$classe_risque)
Z=na.omit(Z)
Z$classe_risque[Z$classe_risque=="A"]="1"
Z$classe_risque[Z$classe_risque=="B"]="2"
Z$classe_risque[Z$classe_risque=="C"]="3"
Z$classe_risque[Z$classe_risque=="D"]="4"
Z$classe_risque[Z$classe_risque=="E"]="5"
Z$classe_risque[Z$classe_risque=="F"]="6"
Z$classe_risque[Z$classe_risque=="G"]="7"
Z$classe_risque[Z$classe_risque=="H"]="8"
Z$classe_risque[Z$classe_risque=="I"]="9"
Z$classe_risque[Z$classe_risque=="J"]="10"
Z$classe_risque[Z$classe_risque=="K"]="11"
Z$classe_risque[Z$classe_risque=="L"]="12"
Z$classe_risque[Z$classe_risque=="V"]="13"
Z$classe_risque[Z$classe_risque=="VM"]="14"
Z$classe_risque[Z$classe_risque=="W"]="15"
Z$classe_risque[Z$classe_risque=="Y"]="16"
Z$classe_risque=as.numeric(Z$classe_risque)
#summary(Z$classe_risque)
#dim(Z)
#colnames(Z)
X=data.frame(
Z[,-c(1,4,5,6,7,9,29)],
acm.disjonctif(data.frame(Z[,c(4,5,7)]))
)
Y=cut(Z$classe_risque,breaks = c(1,9,13,16),include.lowest = T)
calcul=function(K=10){
F_score=function(){
res=rep(0,nlevels(Y_train))
w=table(Y_predit,Y_test)
for (k in 1:nlevels(Y_train)){
TP=w[k,k]
FP=sum(w[k,-k])
FN=sum(w[-k,k])
res[k]=(2*TP) / (2*TP + FP +FN)
}
return(res)
}
RES_error=NULL
RES_F_score=NULL
ech=sample(1:K,nrow(X),replace = T)
for (iter in 1:K){
#print(paste("iteration",iter))
res_1_bloc=NULL
###
X_train=X[ech != iter,]
Y_train=Y[ech != iter]
X_test=X[ech == iter,]
Y_test=Y[ech == iter]
### random
library(randomForest)
model=randomForest(X_train, Y_train)
Y_predit=predict(model, X_test)
w=table(Y_predit,Y_test)
TauxErreurGlobale = 1-sum(diag(w))/sum(w)
RES_error=c(RES_error,TauxErreurGlobale)
RES_F_score=rbind(RES_F_score,F_score())
}
return(list(RES_error,RES_F_score))
}
RES=calcul(5)
graph_erreur=function(){
library(ggplot2)
erreur=c(RES[[1]])
methode=rep("1.arbre",each=length(RES[[1]]))
df=data.frame(methode, erreur)
ggplot(df, aes(x=methode, y=erreur)) +
stat_boxplot(geom = "errorbar",
width = 0.25) +
geom_boxplot() +
coord_flip()
}
graph_F_score=function(){
library(ggplot2)
F_score=c(RES[[2]])
modalite=rep(levels(Y),each=nrow(RES[[2]]))
df=data.frame(modalite, F_score)
ggplot(df, aes(x=modalite, y=F_score)) +
stat_boxplot(geom = "errorbar",
width = 0.25) +
geom_boxplot() +
coord_flip()+
labs(title = "Distribution des F-scores par modalité (random forest)")
}
#graph_erreur()
graph_F_score()
Le graphique ci-dessus représente les F-score en fonction de chaque
modalité (ici des classes de risque).
Le F-score est une mesure
de performance utilisée en classification, combinant la précision et le
rappel (Vrai positif+faux négatif) en une seule valeur.
Ici, le F-score est calculé en utilisant la formule
\[
\frac{2 \times \text{TP}}{2 \times \text{TP} + \text{FP} + \text{FN}}
\] Où TP est le nombre de vrais positifs, FP est le nombre de
faux positifs et FN est le nombre de faux négatifs.
nb: le F-score sanctionne les systèmes peu “sensitifs” (qui ne
détecte pas assez l’évènement, lorsqu’il se produit).
Ainsi comme explique plus en détaille dans la partie “4.1 machine
learning simple” (sauf que l’analyse était sur le modèle arbre de
décision), nous remarquons les mêmes conclusions :
Il arrive très
bien à détecter les individus de la classe 1 (pas à risque) et plus
moins la classe 3 ( très risqué) mais plus difficilement la classe des
individus risqués et détecte mal les faux négatifs (missing event), ce
qui explique le mauvais f-score de cette classe (9-13).
Réseau de neurones à 2 modalités
Un réseau de neurones est un modèle d’apprentissage automatique
inspiré du fonctionnement du cerveau humain. Il est composé de plusieurs
couches de neurones interconnectés, où chaque neurone reçoit des
entrées, les pondère et les transforme pour produire une sortie
correspondant à la prediction.
Dans un premier temps nous avons changé le nombre de modalités pour
n’avoir que 2 variables cible (risqué et non risqué). J’ai fait cette
analyse/étude avant de passer à 3 modalités puisque je voulais voir s’il
pouvait y avoir potentiellement une amélioration des performances du
modèle en réduisant le risque de surajustement et en facilitant la
séparation des données en deux classes distinctes. Par ailleurs il
permet de simplifier le problème, améliorer l’interprétabilité du
modèle.
YY=(Y==levels(Y)[3])*1
table(YY)
## YY
## 0 1
## 85668 2391
#write.table(data.frame(YY,X),"fic_pour_nn.txt",row.names=F,col.names=F,sep=";",quote=F)
Ainsi nous avons 85668 clients pas risqués et 2391 risqués.
Comme pour la partie 4.3 j’étais limités par les sécurités et les
performances de l’environnement, ainsi pour effectuer le réseau de
neurones, je suis passé sur l’environnement IUTDEV
La première étape était d’envoyer le fichier contenant mon jeu de
donnée fait au préalable (depuis docker) sur IUTDEV. Ainsi j’ai utilisé
la commande Scp, qui permet de copier des fichiers entre deux machines
distantes via SSH, ici de mon environnement docker à mon environnement
IUTDEV.
iut2203125@docker:~/stid3/s6/Apprentissage_statistique$
scp fic_pour_nn.txt sd2305@iutdev.univ-avignon.fr:/home/master/sd2305/
Ensuite, j’ai dû me connecter sur iut d’avec cette commande : ssh sd2305@iutdev.univ-avignon.fr .
Pour finir j’ai modifié quelque variables avec le langage “VI” sur le
fichier nn.py.
J’ai notamment changé le nombre d’iteration (ici pour les résultats
obtenus 2000 iterations, avec des tirages aléatoires dans les données de
par groupe de 20 individus.
Et je l’ai lancer avec ce code avec : sd2305@iutdev:~$ python3 nn.py
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
# load the dataset, split into input (X) and output (y) variables
dataset = np.loadtxt('fic_pour_nn.txt', delimiter=';')
X = dataset[:,0:71]
y = dataset[:,0]
X = torch.tensor(X, dtype=torch.float32)
y = torch.tensor(y, dtype=torch.float32).reshape(-1, 1)
# define the model
model = nn.Sequential(
nn.Linear(8, 12),
nn.Tanh(),
nn.Linear(12, 8),
nn.ReLU(),
nn.Linear(8, 1),
nn.Sigmoid()
)
# class PimaClassifier(nn.Module):
# def __init__(self):
# super().__init__()
# self.hidden1 = nn.Linear(8, 12)
# self.act1 = nn.ReLU()
# self.hidden2 = nn.Linear(12, 8)
# self.act2 = nn.ReLU()
# self.output = nn.Linear(8, 1)
# self.act_output = nn.Sigmoid()
# def forward(self, x):
# x = self.act1(self.hidden1(x))
# x = self.act2(self.hidden2(x))
# x = self.act_output(self.output(x))
# return x
# model = PimaClassifier()
# train the model
loss_fn = nn.BCELoss() # binary cross entropy
optimizer = optim.Adam(model.parameters(), lr=0.001)
n_epochs = 2000
batch_size = 20
for epoch in range(n_epochs):
for i in range(0, len(X), batch_size):
Xbatch = X[i:i+batch_size]
y_pred = model(Xbatch)
ybatch = y[i:i+batch_size]
loss = loss_fn(y_pred, ybatch)
optimizer.zero_grad()
loss.backward()
optimizer.step()
print(f'Finished epoch {epoch}, latest loss {loss}')
# compute accuracy (no_grad is optional)
with torch.no_grad():
y_pred = model(X)
accuracy = (y_pred.round() == y).float().mean()
print(f"Accuracy {accuracy}")
explication du modèle :
Le modèle (reseau de neurones) comporte trois couches linéaires avec
des fonctions d’activation intermédiaires. Les couches intermédiaires
utilisent des fonctions d’activation Tanh() et ReLU() pour introduire de
la non-linéarité dans le modèle, tandis que la dernière couche utilise
une fonction d’activation Sigmoid() pour la classification
binaire.
Plus on applique différents modèles plus celui-ci sera optimal,
cependant, le risque est de faire du “suraprentissage”.
nb : Le surapprentissage est lorsque le modèle s’ajuste trop précisément aux données d’entraînement, capturant le bruit et aboutissant à une mauvaise performance sûre de nouvelles données.
Une fois l’entraînement terminé, le taux de précision (= Accurancy)
du modèle est calculé en divisant le nombre de prédictions correctes par
le nombre total de prédictions. Ce taux nous permet de connaitre la
performance de notre réseau de neurones, plus il est proche de 1 plus
les previsions sont exactes (et donc que le modèle est performant/
proche de réalités).
Interpretation d’une ligne, prenons pour exemple la epoch
1998:
“Finished epoch 1998” : Cela indique que le modèle a terminé la 1998
ème itération d’entraînement.
“latest loss 5.449” : Cela représente la perte (loss) calculée lors
de la dernière itération de l’époch 1998.
La perte est une mesure de l’erreur du modèle pendant l’entraînement.
Plus la perte est faible, mieux le modèle s’adapte aux données. Dans ce
cas, la perte est de 5.449.
Pour finir avec ce modèle, nous remarquons que nos prédictions sont
très bonnes puisqu’il y a 99,4% (Accuracy 0.994810) de bonnes
predictions!
On va maintenant essayer avec 3 modalités afin de voir si prévisions
sont aussi bonnes et qu’elle conclusion peut t-on retenir.
Réseau de neurones à 3 modalités
Ici (contrairement aux réseaux de neurones ci-dessus), le réseau de
neurones est multiclasses (3 modalités) mais son fonctionnement reste le
même pusiqu’il prendre des données d’entrée (données apprentissage), et
il essaye de prédire la classe de chaque individu
library(ade4)
library(corrplot)
library(DBI)
library(factoextra)
library(RPostgreSQL)
library(readr)
library(ggplot2)
library(tidyverse)
library(cluster)
library(vegan)
library(dplyr)
library(tidyr)
library(cluster)
library(rpart)
c=dbConnect(RPostgreSQL::PostgreSQL(),dbname="iut2203125")
query='SELECT * FROM "S4_enquete"."pred_variable"'
Z <- dbGetQuery(c,query)
#table(Z$classe_risque)
Z=na.omit(Z)
Z$classe_risque[Z$classe_risque=="A"]="1"
Z$classe_risque[Z$classe_risque=="B"]="2"
Z$classe_risque[Z$classe_risque=="C"]="3"
Z$classe_risque[Z$classe_risque=="D"]="4"
Z$classe_risque[Z$classe_risque=="E"]="5"
Z$classe_risque[Z$classe_risque=="F"]="6"
Z$classe_risque[Z$classe_risque=="G"]="7"
Z$classe_risque[Z$classe_risque=="H"]="8"
Z$classe_risque[Z$classe_risque=="I"]="9"
Z$classe_risque[Z$classe_risque=="J"]="10"
Z$classe_risque[Z$classe_risque=="K"]="11"
Z$classe_risque[Z$classe_risque=="L"]="12"
Z$classe_risque[Z$classe_risque=="V"]="13"
Z$classe_risque[Z$classe_risque=="VM"]="14"
Z$classe_risque[Z$classe_risque=="W"]="15"
Z$classe_risque[Z$classe_risque=="Y"]="16"
Z$classe_risque=as.numeric(Z$classe_risque)
#summary(Z$classe_risque)
#dim(Z)
#colnames(Z)
X=data.frame(
Z[,-c(1,4,5,6,7,9,29)],
acm.disjonctif(data.frame(Z[,c(4,5,7)]))
)
Y=cut(Z$classe_risque,breaks = c(1,9,13,16),include.lowest = T)
YY=Y
#table(YY)
YY=unclass(YY)
YY=as.character(YY)
#write.table(data.frame(X,YY),"fic3_pour_nn.txt",row.names=F,col.names=F,sep=",",quote=T)
import copy
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import torch
import torch.nn.functional as F
import torch.nn as nn
import torch.optim as optim
import tqdm
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import OneHotEncoder
# read data and apply one-hot encoding
data = pd.read_csv("fic3_pour_nn.txt", header=None)
X = data.iloc[:, 0:71]#selection des variables X trains
y = data.iloc[:, 71:]#selction de la variables explicatives
ohe = OneHotEncoder(handle_unknown='ignore', sparse_output=False).fit(y)
y = ohe.transform(y)
# convert pandas DataFrame (X) and numpy array (y) into PyTorch tensors
X = torch.tensor(X.values, dtype=torch.float32)
y = torch.tensor(y, dtype=torch.float32)
# split
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.7, shuffle=True)
class Multiclass(nn.Module):#==> addition de plusieurs fonctions pour le plus justement ajuster le modele
def __init__(self):
super().__init__()
self.hidden1 = nn.Linear(71, 20)
self.act1 = nn.Tanh()
self.hidden2 = nn.Linear(20, 20)
self.act2 = nn.ReLU()
self.hidden3 = nn.Linear(20, 20)
self.act3 = nn.Sigmoid()
self.output = nn.Linear(20, 3)
def forward(self, x):
x = self.act1(self.hidden1(x))
x = self.act2(self.hidden2(x))
x = self.act3(self.hidden3(x))
x = self.output(x)
return x
# loss metric and optimizer
model = Multiclass()
loss_fn = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
# prepare model and training parameters
n_epochs = 100
batch_size = 200
batches_per_epoch = len(X_train) // batch_size
best_acc = - np.inf # init to negative infinity
best_weights = None
train_loss_hist = []
train_acc_hist = []
test_loss_hist = []
test_acc_hist = []
# training loop
for epoch in range(n_epochs):
epoch_loss = []
epoch_acc = []
# set model in training mode and run through each batch
model.train()
with tqdm.trange(batches_per_epoch, unit="batch", mininterval=0) as bar:
bar.set_description(f"Epoch {epoch}")
for i in bar:
# take a batch
start = i * batch_size
X_batch = X_train[start:start+batch_size]
y_batch = y_train[start:start+batch_size]
# forward pass
y_pred = model(X_batch)
loss = loss_fn(y_pred, y_batch)
# backward pass
optimizer.zero_grad()
loss.backward()
# update weights
optimizer.step()
# compute and store metrics
acc = (torch.argmax(y_pred, 1) == torch.argmax(y_batch, 1)).float().mean()
epoch_loss.append(float(loss))
epoch_acc.append(float(acc))
bar.set_postfix(
loss=float(loss),
acc=float(acc)
)
# set model in evaluation mode and run through the test set
model.eval()
y_pred = model(X_test)
ce = loss_fn(y_pred, y_test)
acc = (torch.argmax(y_pred, 1) == torch.argmax(y_test, 1)).float().mean()
ce = float(ce)
acc = float(acc)
train_loss_hist.append(np.mean(epoch_loss))
train_acc_hist.append(np.mean(epoch_acc))
test_loss_hist.append(ce)
test_acc_hist.append(acc)
if acc > best_acc:
best_acc = acc
best_weights = copy.deepcopy(model.state_dict())
print(f"Epoch {epoch} validation: Cross-entropy={ce:.2f}, Accuracy={acc*100:.1f}%")
Interprétation d’une ligne, prenons pour exemple l’epoch 499:
Sur la première ligne de l’epoch nous voyons 5 informations
permettant de savoir où nous en sommes dans l’epoch:
La barre noire qui est en lien avec le pourcentage à sa gauche ainsi
que le nombre de mini-lots traités sur le nombre de mini-lot total (ici
3082/3082).
Cette barre évolue en temps réel en fonction de
l’avancement de l’epoch.
Nous avons aussi l’information du temps estimé restant et le temps
déjà passer pour cette epoch (00: 06 (==> temps passer sur l’epoch)
< 00: 00 (==>temps restant pour cette epoch)).
La dernière information est le nombre des minis-lots traité par
seconde (ici il est d’environ 487.07 minis-lots par seconde).
Les 2 valeurs restantes de la première ligne sont acc et la
loss:
Acc représente taux de précision moyenne sur cette époch (ici il
est de 90%). Celui-ci est calculé en divisant le nombre de prédictions
correctes par le nombre total de prédictions, puis de multiplier par 100
pour obtenir un pourcentage.
Loss represente la perte moyenne pour tous les mini-lots de cette
epoch. Sur cette époque elle est de 0.336.
Ces 2 dernières valeurs sont calculées pour chaque epoch et donc
peuvent fournir des informations sur la façon dont le modèle s’améliore
ou se dégrade au cours de l’apprentissage.
En effet, on peut voir que
l’epoch (=itération) 15 avait un taux de précision de 80% et une perte
de 0.469, donc on voit clairement qu’en fonction du nombre d’epoch
définit le modèle s’améliore ou non.
Passons maintenant à la deuxième ligne de l’epoch 499:
Les 2 valeurs contenues sur cette ligne cacule l’acc et la loss mais
sur l’ensemble des epochs passé. C’est donc la dernière epoch qui évalue
la performance du modèle.
Le calcule du taux de précision de toutes les epochs c’est le même
fonctionnement, cependant pour la loss (appele pour le global
cross-entropy) est légèrement différent.
En effet, elle est définie mathématiquement comme la moyenne des log-likelihoods négatifs des prédictions du modèle par rapport aux valeurs réelles.
\[
\text{Cross-Entropy Loss} = - \frac{1}{N} \sum_{i=1}^{N} \sum_{c=1}^{C}
y_{i,c} \log(\hat{p}_{i,c})
\]
Ainsi pour ce modèle à 3 modalités nous avons:
Une perte moyenne sur l’ensemble est de 0.20.
Une bonne prédiction de la classe de l’individu dans environ 94,6%
des cas.
Nos résultats sont donc moins bons (mais reste convenables) qu’avec 2
modalités. Cependant avec 3 modalités nous avons plus d’informations sur
les clients(s’il est risqué, très risqué ou pas des tous).
Ce choix de précision est important puisque en fonction de la volonté
on va plus opter pour un modèle à 2,3, 5, 10 modalités.
Pour finir on peut affirmer que les réseaux de neurones et beaucoup
plus rapides pour une efficacité casiement équivalentes.
En effet les méthodes initiales (SVM, Arbres, KNN…) ont mis plus de 12 h
pour 5 iterations alors que les réseaux de neurones et beaucoup plus
rapide 1 iteration en 30 secondes) et les résultats sont pourtant
similaires.