…

Introduction


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.


SOMMAIRE


1.MÉTHODOLOGIE

1.1 Le choix du sujet et la sélection des données

1.2 L’objectif

1.3 L’importation des données et la création du schéma relationnel

1.4 Mise en contexte


2. MACHINE LEARNING

2.1 Machine Learning simple

2.2 Arbre de décision

2.3 Machine Learning avancé

2.4 Réseau de neurones artificiels



1. MÉTHODOLOGIE

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.

1.1 Le choix du sujet et la sélection des données


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.

1.2 L’objectif


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.

1.3 Le choix de variables et modification nécessaire pour l’analyse


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).


1.4 Mise en contexte


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.


2. MACHINE LEARNING


Le machine learning est une branche de l’intelligence artificielle qui vise à développer des modèles et des algorithmes capables d’apprendre à partir des données, sans programmation explicite. Il permet aux systèmes informatiques de s’améliorer automatiquement avec l’expérience, en identifiant des modèles et en prenant des décisions sans intervention humaine directe.

2.1 Machine Learning simple


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.


2.2 Arbre de décision


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%.

2.3 Machine Learning avancé



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 : “:~/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).



2.4 Réseau de neurones artificiels


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.

:~/stid3/s6/Apprentissage_statistique$ scp fic_pour_nn.txt :/home/master/sd2305/

Ensuite, j’ai dû me connecter sur iut d’avec cette commande : ssh .


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 : :~$ 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.