3 Régressions pénalisées (ou sous contraintes)
Nous considérons toujours le modèle linéaire
\[Y=\beta_0+\beta_1X_1+\dots+\beta_dX_d+\varepsilon\] Lorsque \(d\) est grand ou que les variables sont linéairement dépendantes, les estimateurs des moindres carrées peuvent être mis en défaut. Les méthodes pénalisées ou sous contraintes consistent alors à restreindre l’espace sur lequel on minimise ce critère. On va alors chercher le vecteur \(\beta\) qui minimise
\[\sum_{i=1}^n \left(y_i-\beta_0-\sum_{j=1}^dx_{ij}\beta_j\right)^2\quad\text{sous la contrainte }\quad\sum_{j=1}^d\beta_j^2\leq t\] ou de façon équivalente (dans le sens où il existe une équivalence entre \(t\) et \(\lambda\))
\[\sum_{i=1}^n \left(y_i-\beta_0-\sum_{j=1}^dx_{ij}\beta_j\right)^2+\lambda\sum_{j=1}^d\beta_j^2.\] Les estimateurs obtenus sont les estimateurs ridge. Les estimateurs lasso s’obtiennent en remplaçant la contrainte ou la pénalité par une norme 1 (\(\sum_{j=1}^d|\beta_j|\)). Nous présentons dans cette partie les étapes principales qui permettent de faire ce type de régression avec R. Le package le plus souvent utilisé est glmnet
.
3.1 Ridge et lasso avec glmnet
On considère le jeu de données ozone.txt
où on cherche à expliquer la concentration maximale en ozone relevée sur une journée (variable maxO3
) par d’autres variables essentiellement météorologiques.
<- read.table("data/ozone.txt")
ozone head(ozone)
maxO3 T9 T12 T15 Ne9 Ne12 Ne15 Vx9 Vx12 Vx15 maxO3v
20010601 87 15.6 18.5 18.4 4 4 8 0.6946 -1.7101 -0.6946 84
20010602 82 17.0 18.4 17.7 5 5 7 -4.3301 -4.0000 -3.0000 87
20010603 92 15.3 17.6 19.5 2 5 4 2.9544 1.8794 0.5209 82
20010604 114 16.2 19.7 22.5 1 1 0 0.9848 0.3473 -0.1736 92
20010605 94 17.4 20.5 20.4 8 8 7 -0.5000 -2.9544 -4.3301 114
20010606 80 17.7 19.8 18.3 6 6 7 -5.6382 -5.0000 -6.0000 94
vent pluie
20010601 Nord Sec
20010602 Nord Sec
20010603 Est Sec
20010604 Nord Sec
20010605 Ouest Sec
20010606 Ouest Pluie
Contrairement à la plupart des autres package R qui permettent de faire de l’apprentissage, le package glmnet
n’autorise pas l’utilisation de formules
: il faut spécifier explicitement la matrice des \(X\) et le vecteur des \(Y\). On peut obtenir la matrice des \(X\) et notamment le codage des variables qualitatives avec la fonction model.matrix
:
<- model.matrix(maxO3~.,data=ozone)[,-1]
ozone.X <- ozone$maxO3 ozone.Y
Charger le package
glmnet
et à l’aide de la fonctionglmnet
calculer les estimateursridge
etlasso
.library(glmnet) <- glmnet(ozone.X,ozone.Y,alpha=0) mod.R <- glmnet(ozone.X,ozone.Y,alpha=1) mod.L
Analyser les sorties qui se trouvent dans les arguments
lambda
etbeta
deglmnet
.La fonction
glmnet
calcule tous les estimateurs pour une grille de valeurs delambda
spécifiée ici :$lambda |> head() mod.R
[1] 22007.27 20052.20 18270.82 16647.69 15168.76 13821.21
On peut récupérer les valeurs de
beta
associées à chaque valeur de la grille avec$beta[,1] mod.R
T9 T12 T15 Ne9 Ne12 6.376767e-36 5.523924e-36 4.867402e-36 -6.821464e-36 -7.994984e-36 Ne15 Vx9 Vx12 Vx15 maxO3v -5.839057e-36 5.706014e-36 4.387350e-36 3.970583e-36 6.892387e-37 ventNord ventOuest ventSud pluieSec -5.830507e-36 -1.022483e-35 1.519222e-35 2.772246e-35
Visualiser les chemins de régularisation des estimateurs
ridge
etlasso
. On pourra utiliser la fonctionplot
.plot(mod.R,label=TRUE)
plot(mod.L,label=TRUE)
plot(mod.R,xvar="lambda",label=TRUE)
plot(mod.L,xvar="lambda",label=TRUE)
Sélectionner les paramètres de régularisation à l’aide de la fonction
cv.glmnet
. On pourra notamment faire unplot
de l’objet et expliquer le graphe obtenu.Commençons par ridge :
<- cv.glmnet(ozone.X,ozone.Y,alpha=0) ridgeCV plot(ridgeCV)
On visualise les erreurs quadratiques calculées par validation croisée 10 blocs en fonction de
lambda
(échelle logarithmique). Deux traites verticaux sont représentés :celui de gauche correspond à la valeur de `lambda` qui minimise l’erreur quadratique ;
celui de droite correspond à la plus grande valeur de `lambda` telle que l’erreur ne dépasse pas l’erreur minimale + 1 écart-type estimé de cette erreur.
D’un point de vu pratique, cela signifie que l’utilisateur peut choisir n’importe quelle valeur de
lambda
entre les deux traits verticaux. Si on veut diminuer la complexité du modèle on choisira la valeur de droite. On peut obtenir ces deux valeurs avec$lambda.min ridgeCV
[1] 9.750588
$lambda.1se ridgeCV
[1] 43.20116
On peut faire de même pour le lasso :
<- cv.glmnet(ozone.X,ozone.Y,alpha=1) lassoCV plot(lassoCV)
On souhaite prédire la variable cible pour de nouveaux individus, par exemple les 25ème et 50ème individus du jeu de données. Calculer les valeurs prédites pour ces deux individus.
Une première approche pourrait consister à réajuster le modèle sur toutes les données pour la valeur de
lambda
sélectionnée. Cette étape est en réalité déjà effectuée par la fonctioncv.glmnet
. Il suffit par conséquent d’appliquer la fonctionpredict
à l’objet obtenu aveccv.glmnet
en spécifiant la valeur delambda
souhaitée. Par exemple pour ridge :predict(ridgeCV,newx = ozone.X[50:51,],s="lambda.min")
lambda.min 20010723 90.10981 20010724 96.74374
predict(ridgeCV,newx = ozone.X[50:51,],s="lambda.1se")
lambda.1se 20010723 93.23058 20010724 96.21185
On peut faire de même pour le lasso :
predict(lassoCV,newx = ozone.X[50:51,],s="lambda.min")
lambda.min 20010723 87.18766 20010724 98.11351
predict(lassoCV,newx = ozone.X[50:51,],s="lambda.1se")
lambda.1se 20010723 87.44713 20010724 95.61077
A l’aide d’une validation croisée, comparer les performances des estimateurs MCO, ridge et lasso. On pourra utiliser les données
ozone_complet.txt
qui contiennent plus d’individus et de variables.<- read.table("data/ozone_complet.txt",sep=";") |> na.omit() ozone1 <- model.matrix(maxO3~.,data=ozone1)[,-1] ozone1.X <- ozone1$maxO3 ozone1.Y
On crée une fonction qui calcule les erreurs quadratiques par validations croisée des 3 procédures d’estimation.
<- function(data,form){ cv.ridge.lasso set.seed(1234) <- model.matrix(form,data=data)[,-1] data.X <- data$maxO3 data.Y <- caret::createFolds(1:nrow(data),k=10) blocs <- matrix(0,ncol=3,nrow=nrow(data)) |> as.data.frame() prev names(prev) <- c("lin","ridge","lasso") for (k in 1:10){ <- data[-blocs[[k]],] app <- data[blocs[[k]],] test <- data.X[-blocs[[k]],] app.X <- data.Y[-blocs[[k]]] app.Y <- data.X[blocs[[k]],] test.X <- data.Y[blocs[[k]]] test.Y <- cv.glmnet(app.X,app.Y,alpha=0) ridge <- cv.glmnet(app.X,app.Y,alpha=1) lasso <- lm(form,data=app) lin <- tibble(lin=predict(lin,newdata=test), prev[blocs[[k]],] ridge=as.vector(predict(ridge,newx=test.X)), lasso=as.vector(predict(lasso,newx=test.X))) }<- prev |> mutate(obs=data$maxO3) |> summarise_at(1:3,~mean((obs-.)^2)) err return(err) }
cv.ridge.lasso(ozone1,form=formula(maxO3~.))
lin ridge lasso 1 184.3755 192.4984 191.5436
On remarque que les approches régularisées n’apportent rien par rapport aux estimateurs MCO ici. Ceci peut s’expliquer par le fait que le nombre de variables n’est pas très important.
Refaire la question précédente en considérant toutes les interactions d’ordre 2.
cv.ridge.lasso(ozone1,form=formula(maxO3~.^2))
lin ridge lasso 1 185.0517 168.7122 166.0982
Les méthodes régularisées permettent ici de diminuer les erreurs quadratiques de manière intéressante. Cela vient certainement du fait du nombre de variables explicatives qui est beaucoup plus important lorsqu’on prend en compte toutes les interactions d’ordre 2, nous en avons en effet 253 :
<- model.matrix(maxO3~.^2,data=ozone1)[,-1] ozone2.X dim(ozone2.X)
[1] 1366 253
3.2 Reconstruction d’un signal
Le fichier signal.csv
contient un signal que l’on peut représenter par une fonction \(m:\mathbb R\to\mathbb R\). On le visualise
<- read_csv("data/signal.csv")
signal ggplot(signal)+aes(x=x,y=y)+geom_line()
Plaçons nous dans le cas où on ne dispose que d’une version bruitée de ce signal. La courbe n’est pas observée mais on dispose d’un échantillon \((x_i,y_i),i=1,\dots,n\) généré selon le modèle
\[y_i=m(x_i)+\varepsilon_i.\]
Le fichier ech_signal.csv
contient \(n=60\) observations issues de ce modèle. On représente les données et la courbe
<- read_csv("data/ech_signal.csv")
donnees ggplot(signal)+aes(x=x,y=y)+geom_line()+
geom_point(data=donnees,aes(x=X,y=Y))
Nous cherchons dans cette partie à reconstruire le signal à partir de l’échantillon. Bien entendu, vu la forme du signal, un modèle linéaire de la forme \[
y_i=\beta_0+\beta_1x_i+\varepsilon_i
\] n’est pas approprié. De nombreuses approches en traitement du signal proposent d’utiliser une base
ou dictionnaire
représentée par une collection de fonctions \(\{\psi_j(x)\}_{j=1,\dots,K}\) et de décomposer le signal dans cette base :
\[m(x)\approx \sum_{j=1}^K \beta_j\psi_j(x).\]
Pour un dictionnaire donné, on peut alors considérer un modèle linéaire
\[ y_i=\sum_{j=1}^K \beta_j\psi_j(x_i)+\varepsilon_i. \tag{3.1}\]
Le problème est toujours d’estimer les paramètres \(\beta_j\) mais les variables sont maintenant définies par les élements du dictionnaire. Il existe différents types de dictionnaire, dans cet exercice nous proposons de considérer la base de Fourier définie par
\[\psi_0(x)=1,\quad \psi_{2j-1}(x)=\cos(2j\pi x)\quad\text{et}\quad \psi_{2j}(x)=\sin(2j\pi x),\quad j=1,\dots,K.\]
Écrire une fonction R qui admet en entrée :
- une grille de valeurs de
x
(un vecteur) - une valeur de
K
(un entier positif)
et qui renvoie en sortie une matrice qui contiennent les valeurs du dictionnaire pour chaque valeur de
x
. Cette matrice devra donc contenir2K
colonnes et le nombre de lignes sera égal à la longueur du vecteurx
.<- function(K,x){ mat.dict <- matrix(0,nrow=length(x),ncol=2*K) |> as_tibble() res for (j in 1:K){ 2*j-1] <- cos(2*j*pi*x) res[,2*j] <- sin(2*j*pi*x) res[, }return(res) }
- une grille de valeurs de
On fixe
K=25
. Calculer les estimateurs des moindres carrés du modèle (Équation 3.1).Il suffit d’ajuster le modèle linéaire où les variables explicatives sont données par le dictionnaire :
<- mat.dict(25,donnees$X) |> mutate(Y=donnees$Y) D25 <- lm(Y~.,data=D25) mod.lin
Représenter le signal estimé. Commenter le graphe.
<- mat.dict(25,signal$x) S25 <- predict(mod.lin,newdata = S25) prev.MCO <- signal |> mutate(MCO=prev.MCO) |> rename(signal=y) signal1 <- signal1 |> pivot_longer(-x,names_to="meth",values_to="y") signal2 ggplot(signal2)+aes(x=x,y=y)+geom_line(aes(color=meth))+ scale_y_continuous(limits = c(-2,2))+geom_point(data=donnees,aes(x=X,y=Y))
Le signal estimé a tendance à surajuster les données. Cela vient du fait que on estime 51 paramètres avec seulement 60 observations.
Calculer les estimateurs lasso et représenter le signal issu de ces estimateurs.
On regarde tout d’abord le
chemin de régularisation
des estimateurs lasso.25 <- model.matrix(Y~.,data=D25)[,-1] X<- glmnet(X.25,D25$Y,alpha=1) lasso1 plot(lasso1)
Il semble que quelques coefficients quittent la valeur 0 bien avant les autres. On effectue maintenant la validation croisée pour sélectionner le paramètre \(\lambda\).
<- cv.glmnet(X.25,D25$Y,alpha=1) lasso.cv plot(lasso.cv)
On calcule les prévisions et on trace le signal.
<- as.vector(predict(lasso.cv,newx=as.matrix(S25))) prev.lasso $lasso <- prev.lasso signal1<- signal1 |> pivot_longer(-x,names_to="meth",values_to="y") signal2 ggplot(signal2)+aes(x=x,y=y)+geom_line(aes(color=meth))+ scale_y_continuous(limits = c(-2,2))+geom_point(data=donnees,aes(x=X,y=Y))
L’algorithme lasso a permis de corriger le problème de sur-apprentissage.
Identifier les coefficients lasso sélectionnés qui ne sont pas nuls.
<- which(coef(lasso.cv)!=0) v.sel v.sel
[1] 1 2 4 5 6 8 21 28 30 36 37 38 40
Ajouter les signaux ajustés par les algorithme Ridge, PCR et PLS. Comparer les performances.
On commence par la
régression ridge
:set.seed(1234) <- cv.glmnet(X.25,D25$Y,alpha=0) ridge.cv plot(ridge.cv) # on est en bord de grille
<- cv.glmnet(X.25,D25$Y,alpha=0,lambda = exp(seq(-4,0,length=100))) ridge.cv plot(ridge.cv)
<- as.vector(predict(ridge.cv,newx=as.matrix(S25))) prev.ridge
On effectue la
PCR
:library(pls) <- pcr(Y~.,data=D25,validation="CV") pcr.fit <- which.min(pcr.fit$validation$PRESS) ncomp.pcr ncomp.pcr
[1] 39
<- predict(pcr.fit,newdata=S25,ncomp=ncomp.pcr) prev.pcr
Puis la
PLS
:<- plsr(Y~.,data=D25,validation="CV") pls.fit <- which.min(pls.fit$validation$PRESS) ncomp.pls ncomp.pls
[1] 5
<- predict(pls.fit,newdata=S25,ncomp=ncomp.pls) prev.pls
On trace les signaux :
<- signal1 |> mutate(ridge=prev.ridge, signal1 pcr=prev.pcr, pls=prev.pls) <- signal1 |> pivot_longer(-x,names_to="meth",values_to="y") signal2 ggplot(signal2)+aes(x=x,y=y)+geom_line(aes(color=meth))+ scale_y_continuous(limits = c(-2,2))+geom_point(data=donnees,aes(x=X,y=Y))
On peut également obtenir les erreurs quadratiques (puisqu’on connait la vraie courbe)
|> summarise_at(-(1:2),~mean((.-signal)^2)) |> round(3) signal1
# A tibble: 1 × 5 MCO lasso ridge pcr pls <dbl> <dbl> <dbl> <dbl> <dbl> 1 598. 0.014 0.089 0.07 0.067
3.3 Régression logistique pénalisée
On considère le jeu de données sur la détection d’images publicitaires disponible ici https://archive.ics.uci.edu/ml/datasets/internet+advertisements.
<- read_delim("data/ad_data.txt",delim=",",
ad.data col_names = FALSE,na=c("?"),trim_ws = TRUE) |>
rename("Y"=last_col()) |>
mutate(Y=fct(Y))
La variable à expliquer est
summary(ad.data$Y)
ad. nonad.
459 2820
Cette variable est binaire. On considère une régression logistique pour expliquer cette variable. Le nombre de variables explicatives étant important, comparer les algorithmes du maximum de vraisemblance aux algorithmes de type ridge/lasso en faisant une validation croisée 10 blocs. On pourra utiliser comme critère de comparaison l’erreur de classification
, la courbe ROC
et l’AUC
. Il faudra également prendre des décisions pertinentes vis-à-vis des données manquantes…
On commence par regarder les données manquantes :
sum(is.na(ad.data))
[1] 2729
<- apply(is.na(ad.data),2,any)
var.na names(ad.data)[var.na]
[1] "X1" "X2" "X3" "X4"
<- apply(is.na(ad.data),1,any)
ind.na sum(ind.na)
[1] 920
On remarque que 920 individus ont au moins une donnée manquante alors que seules les 4 premières variables ont des données manquantes, on choisit donc de supprimer ces 4 variables.
<- ad.data[,var.na==FALSE]
ad.data1 dim(ad.data1)
[1] 3279 1555
sum(is.na(ad.data1))
[1] 0
On construit les matrices des variables explicatives pour les méthodes lasso et ridge (glmnet
veut les variables explicatives sous forme de matrices).
<- model.matrix(Y~.,data=ad.data1)[,-1]
X.ad <- ad.data1$Y Y.ad
Avant de faire la validation croisée, nous présentons la syntaxe de l’algorithme lasso
. Comme pour la régression, on utilise la fonction cv.glmnet
, il faut simplement ajouter l’argument family="binomial"
:
set.seed(1234)
<- cv.glmnet(X.ad,Y.ad,family="binomial",alpha=1)
lasso.cv plot(lasso.cv)
Par défaut le critère utilisé pour la classification binaire est celui de la déviance
. On peut utiliser d’autres critères comme l’erreur de classification
ou l’auc
en modifiant l’argument type.measure
. On gardera la déviance dans la suite. On peut maintenant faire la validation croisée 10 blocs pour calculer les prévisions des 3 algorithmes.
set.seed(5678)
<- caret::createFolds(1:nrow(ad.data1),k=10)
blocs <- matrix(0,ncol=3,nrow=nrow(ad.data1)) |> as.data.frame()
score names(score) <- c("MV","ridge","lasso")
for (k in 1:10){
print(k)
<- ad.data1[-blocs[[k]],]
app <- ad.data1[blocs[[k]],]
test <- X.ad[-blocs[[k]],]
app.X <- Y.ad[-blocs[[k]]]
app.Y <- X.ad[blocs[[k]],]
test.X <- Y.ad[blocs[[k]]]
test.Y <- cv.glmnet(app.X,app.Y,family="binomial",alpha=0)
ridge <- cv.glmnet(app.X,app.Y,family="binomial",alpha=1)
lasso <- glm(Y~.,data=app,family="binomial")
MV <- tibble(MV=predict(MV,newdata=test,type="response"),
score[blocs[[k]],] ridge=as.vector(predict(ridge,newx=test.X,type="response")),
lasso=as.vector(predict(lasso,newx=test.X,type="response")))
}
Le tibble score
contient, pour chaque individu, les prévisions des probabilités a posteriori \[\mathbf P(Y=\text{nonad.}|X=x_i),\quad i=1,\dots,n.\]
On peut déduire de ce tableau les critères souhaités :
les
courbes ROC
:library(tidymodels) <- score |> score1 mutate(obs=ad.data1$Y) |> pivot_longer(-obs,names_to="Methode",values_to="score") |> group_by(Methode)|> score1 roc_curve(obs,score,event_level = "second") |> autoplot()
les
AUC
:|> group_by(Methode) |> score1 roc_auc(obs,score,event_level = "second") |> arrange(desc(.estimate))
# A tibble: 3 × 4 Methode .metric .estimator .estimate <chr> <chr> <chr> <dbl> 1 ridge roc_auc binary 0.981 2 lasso roc_auc binary 0.945 3 MV roc_auc binary 0.756
les
accuracy
:|> mutate(prev=fct_recode(as.character(round(score)),"ad."="0","nonad."="1")) |> score1 group_by(Methode) |> accuracy(obs,prev) |> arrange(desc(.estimate))
# A tibble: 3 × 4 Methode .metric .estimator .estimate <chr> <chr> <chr> <dbl> 1 lasso accuracy binary 0.970 2 ridge accuracy binary 0.970 3 MV accuracy binary 0.847
On remarque que les méthodes pénalisées sont nettement meilleures que l’approche classique par maximum de vraisemblance sur cet exemple.
On peut également faire le travail dans un environnement tidymodels :
# 1 recette pour normaliser
<-
rec_norm recipe(Y ~ ., data = ad.data1) |>
step_normalize()
# Definition du lasso et du ridge
<-
lasso_spec logistic_reg(penalty=tune(),mixture=1) |>
set_mode("classification") |>
set_engine("glmnet")
<-
ridge_spec logistic_reg(penalty=tune(),mixture=0) |>
set_mode("classification") |>
set_engine("glmnet")
<-
MV_spec logistic_reg(penalty=NULL,mixture=NULL) |>
set_mode("classification") |>
set_engine("glm")
# agregation des algorithme
<- workflow_set(
wflow preproc = list(norm=rec_norm),
models=list(lasso=lasso_spec,
ridge=ridge_spec)
#MV_spec)
)
# definition des blocs
set.seed(12345)
<- vfold_cv(ad.data1,v=10,repeats = 5)
blocs
<- wflow |>
results workflow_map(fn="tune_grid",
resamples=blocs,
grid=50,
metrics=metric_set(kap,f_meas,bal_accuracy,accuracy,roc_auc),
seed = 321,
control=control_grid(event_level = "first",save_pred = FALSE))
#On peut visualiser les résultats avec :
|>
results rank_results(select_best = TRUE) |>
select(wflow_id,.metric,mean) |>
mutate(mean=round(mean,3)) |>
pivot_wider(names_from = .metric,values_from = mean)
# A tibble: 2 × 6
wflow_id accuracy bal_accuracy f_meas kap roc_auc
<chr> <dbl> <dbl> <dbl> <dbl> <dbl>
1 norm_ridge 0.971 0.91 0.89 0.874 0.98
2 norm_lasso 0.971 0.915 0.889 0.872 0.962
|>
results rank_results(select_best = TRUE) |>
ggplot()+
aes(x=wflow_id,ymin=mean-std_err,ymax=mean+std_err,y=mean)+
geom_errorbar()+
facet_wrap(~.metric,scales = "free_y")+
scale_x_discrete(guide = guide_axis(angle = 90),name=NULL)+ylab("")
On peut maintenant choisir l’algorithme, par exemple ridge
:
<-
(best_result |>
results extract_workflow_set_result("norm_ridge") |>
select_best(metric="roc_auc"))
# A tibble: 1 × 2
penalty .config
<dbl> <chr>
1 0.0457 Preprocessor1_Model44
<-
(final |>
results extract_workflow("norm_ridge") |>
finalize_workflow(best_result) |>
fit(data=ad.data1))
══ Workflow [trained] ══════════════════════════════════════════════════════════
Preprocessor: Recipe
Model: logistic_reg()
── Preprocessor ────────────────────────────────────────────────────────────────
1 Recipe Step
• step_normalize()
── Model ───────────────────────────────────────────────────────────────────────
Call: glmnet::glmnet(x = maybe_matrix(x), y = y, family = "binomial", alpha = ~0)
Df %Dev Lambda
1 1554 0.00 200.700
2 1554 2.26 182.800
3 1554 2.48 166.600
4 1554 2.71 151.800
5 1554 2.97 138.300
6 1554 3.24 126.000
7 1554 3.55 114.800
8 1554 3.88 104.600
9 1554 4.24 95.340
10 1554 4.63 86.870
11 1554 5.05 79.150
12 1554 5.51 72.120
13 1554 6.00 65.710
14 1554 6.52 59.870
15 1554 7.10 54.550
16 1554 7.71 49.710
17 1554 8.38 45.290
18 1554 9.09 41.270
19 1554 9.85 37.600
20 1554 10.66 34.260
21 1554 11.51 31.220
22 1554 12.42 28.440
23 1554 13.39 25.920
24 1554 14.40 23.620
25 1554 15.45 21.520
26 1554 16.56 19.610
27 1554 17.71 17.860
28 1554 18.90 16.280
29 1554 20.13 14.830
30 1554 21.39 13.510
31 1554 22.69 12.310
32 1554 24.01 11.220
33 1554 25.36 10.220
34 1554 26.72 9.314
35 1554 28.10 8.487
36 1554 29.49 7.733
37 1554 30.89 7.046
38 1554 32.29 6.420
39 1554 33.70 5.850
40 1554 35.11 5.330
41 1554 36.51 4.857
42 1554 37.91 4.425
43 1554 39.30 4.032
44 1554 40.69 3.674
45 1554 42.07 3.347
46 1554 43.43 3.050
...
and 54 more lines.
3.4 Exercices
Exercice 3.1 (Estimateurs ridge pour le modèle linéaire) On considère le modèle de régression
\[ Y_i=\beta_1x_{i1}+\dots+\beta_px_{ip}+\varepsilon_i \] où les \(\varepsilon_i\) sont i.i.d de loi \(\mathcal N(0,\sigma^2)\). Pour \(\lambda\geq 0\), on note \(\hat\beta_R(\lambda)\) l’estimateur ridge défini par
\[ \hat\beta_R(\lambda)=\mathop{\mathrm{argmin}}_\beta\sum_{i=1}^n\left(y_i-\sum_{j=1}^px_{ij}\beta_j\right)^2+\lambda\sum_{j=1}^p\beta_j^2. \]
Exprimer \(\hat\beta_R(\lambda)\) en fonction de \(\mathbb X\), \(\mathbb Y\) et \(\lambda\).
Le critère à minimiser se réécrit \[\mathcal C(\beta)=(\mathbb Y-\mathbb X\beta)^t (\mathbb Y-\mathbb X\beta)+\lambda\beta^t\beta.\] L’estimateur ridge est donc solution de \[-2\mathbb X^t\mathbb Y+2\mathbb X^t\mathbb X\beta+2\lambda\beta=0,\] d’où \[\hat\beta_R(\lambda)=(\mathbb X^t\mathbb X+\lambda I)^{-1}\mathbb X^t\mathbb Y.\]
Étudier le biais et la variance de \(\hat\beta_R(\lambda)\) en fonction de \(\lambda\). On pourra également faire la comparaison avec l’estimateur des MCO.
Comme \(\mathbb Y=\mathbb X\beta+\varepsilon\), on obtient
\[ \begin{aligned} \mathbf E[\hat\beta_R(\lambda)]-\beta & =(\mathbb X^t\mathbb X+\lambda I)^{-1}\mathbb X^t\mathbb X\beta-\beta \\ & =\left[(\mathbb X^t\mathbb X+\lambda I)^{-1}(\mathbb X^t\mathbb X-(\mathbb X^t\mathbb X+\lambda I))\right]\beta \\ & = -\lambda(\mathbb X^t\mathbb X+\lambda I)^{-1}\beta. \end{aligned} \]
De même, on obtient pour la variance \[ \mathbf V(\hat\beta_R(\lambda))=\sigma^2(\mathbb X^t\mathbb X+\lambda\mathbb I)^{-1}\mathbb X^t\mathbb X(\mathbb X^t\mathbb X+\lambda\mathbb I)^{-1}. \] La variance diminue lorsque \(\lambda\) augmente, mais on remarque une augmentation du bais par rapport à l’estimateur des moindres carrés (et réciproquement lorsque \(\lambda\) diminue).
On suppose que la matrice \(\mathbb X\) est orthogonale. Exprimer les estimateurs \(\hat\beta_{R,j}(\lambda)\) en fonction des estimateurs des MCO \(\hat\beta_j, j=1,\dots,p\). Interpréter.
Si \(\mathbb X\) est orthogonale, alors \[\hat\beta_R(\lambda)=\frac{1}{1+\lambda}\mathbb X^t\mathbb Y=\frac{\hat\beta_{MCO}}{1+\lambda}.\]
Exercice 3.2 (Estimateurs lasso dans le cas orthogonal) Cet exercice est inspiré de Giraud (2015). On rappelle qu’une fonction \(F:\mathbb R^n\to\mathbb R\) est convexe si \(\forall x,y\in\mathbb R^n\), \(\forall\lambda\in[0,1]\) on a \[F(\lambda x+(1-\lambda) y)\leq \lambda F(x)+(1-\lambda)F(y).\] On définit la sous-différentielle d’une fonction convexe \(F\) par \[\partial F(x)=\{w\in\mathbb R^n:F(y)\geq F(x)+\langle w,y-x\rangle\textrm{ pour tout }y\in\mathbb R^n\}.\] On admettra que les minima d’une fonction convexe \(F:\mathbb R^n\to\mathbb R\) sont caractérisés par \[x^\star\in\mathop{\mathrm{argmin}}_{x\in\mathbb R^n}F(x)\Longleftrightarrow 0\in \partial F(x^\star)\] et que \(\partial F(x)=\{\nabla F(x)\}\) lorsque \(F\) est différentiable en \(x\).
Montrer que pour \(x\in\mathbb R\) \[ \partial |x|=\left\{ \begin{array}{ll} \textrm{signe}(x) & \textrm{si } x\neq 0 \\ \left[-1;1\right] & \textrm{sinon,} \end{array}\right. \] où \(\text{signe}(x)=\mathbf 1_{x>0}-\mathbf 1_{x\leq 0}\).
\(x\mapsto|x|\) est dérivable partout sauf en 0 donc \(\partial |x|=\textrm{signe}(x)1\) si \(x\neq 0\). De plus, si \(x=0\) \[\partial|x|=\{w\in\mathbb R:|y|\geq \langle w,y\rangle\ \forall y\in\mathbb R\}=\{w\in\mathbb R:|y|\geq wy\ \forall y\in\mathbb R\}=[-1,1].\]
Soit \(x\in\mathbb R^n\).
Montrer que \[\partial\|x\|_1=\{w\in\mathbb R^n:\langle w,x\rangle=\|x\|_1\text{ et }\|w\|_\infty\leq 1\}.\] On pourra utiliser que pour tout \(p,q\) tels que \(1/p+1/q=1\) on a \[\|x\|_p=\sup\left\{\langle w,x\rangle:\|w\|_q\leq 1\right\}.\]
On montre la double inclusion. Soit \(w\) tel que \(\langle w,x\rangle=\|x\|_1\) et \(\|w\|_\infty=1\). On a \(\forall y\in\mathbb R^n\), \[\|y\|_1\geq \langle w,y\rangle=\langle w,y-x+x\rangle=\|x\|_1+\langle w,y-x\rangle.\] Donc \(w\in\partial\|x\|_1\). Inversement, soit \(w\in\partial\|x\|_1\). Par définition \[\partial\|x\|_1=\{w\in\mathbb R^n:\|y\|_1\geq \langle w,y-x\rangle+\|x\|_1\ \forall y\in\mathbb R^n\}.\] Pour \(y=0\) et \(y=2x\), on a donc \[\|x\|_1\leq\langle w,x\rangle\quad\textrm{et}\quad 2\|x\|_1\geq \langle w,x\rangle+\|x\|_1\] d’où \(\|x\|_1=\langle x,w\rangle=\sum_iw_ix_i\). De plus en posant \(\tilde w=(0,\dots,0,\text{signe}(w_i),0,\dots,0)\) où la coordonnée non nulle correspond au \(\max_i(|w_i|)\) on a \(\|w\|_\infty=\langle w,\tilde w\rangle\) et \(\|\tilde w\|_\infty=\|\tilde w\|_1=1\). De plus \[\|\tilde w\|_1\geq \|x\|_1+\langle w,\tilde{w}-x\rangle=\|w\|_\infty\quad\Longrightarrow \|w\|_\infty\leq \|\tilde w\|_1=1.\]
En déduire \[\partial\|x\|_1=\{w\in\mathbb R^n:w_j=\textrm{signe}(x_j)\textrm{ si }x_j\neq 0, w_j\in[-1,1]\textrm{ si }x_j=0\}.\]
On a
\[ \begin{aligned} \partial\|x\|_1 & =\{w\in\mathbb R^n:\langle w,x\rangle=\|x\|_1\text{ et }\|w\|_\infty\leq 1\}\\ & = \{w\in\mathbb R^n:\sum_{i=1}^n(w_ix_i-|x_i|)=0\text{ et }\|w\|_\infty\leq 1\}. \end{aligned} \] Or si \(\|w\|_\infty\leq 1\) alors \(w_ix_i-|x_i|\leq 0\) \(\forall i=1,\dots,n\). Donc
\[ \begin{aligned} \partial\|x\|_1 & =\{w\in\mathbb R^n:(w_ix_i-|x_i|)=0,i=1,\dots,n\text{ et }\|w\|_\infty\leq 1\}\\ &=\{w\in\mathbb R^n:w_j=\textrm{signe}(x_j)1\textrm{ si }x_j\neq 0, w_j\in[-1,1]\textrm{ si }x_j=0\}. \end{aligned} \]
Étant données \(n\) observations \((x_i,y_i),i=1,\dots,n\) telles que \(x_i\in\mathbb R^p\) et \(y_i\in\mathbb R\) on rappelle que l’estimateur lasso \(\hat\beta(\lambda)\) est construit en minimisant \[\mathcal L(\beta)=\|Y-\mathbb X\beta\|_2^2+\lambda\|\beta\|_1. \tag{3.2}\]
On admettra que la sous-différentielle \(\partial \mathcal L(\beta)\) est donnée par \[\partial \mathcal L(\beta)=\left\{-2\mathbb X^t(Y-\mathbb X\beta)+\lambda z:z\in\partial\|\beta\|_1\right\}.\] Montrer que \(\hat\beta(\lambda)\) vérifie \[\mathbb X^t\mathbb X\hat\beta(\lambda)=\mathbb X^tY-\frac{\lambda}{2}\hat z\] où \(\hat z\in\mathbb R^p\) vérifie \[ \hat z_j\left\{ \begin{array}{ll} =\textrm{signe}(\hat\beta_j(\lambda)) & \textrm{si } \hat\beta_j(\lambda)\neq 0 \\ \in\left[-1;1\right] & \textrm{sinon.} \end{array}\right. \]
D’après les indications, on a \(0\in\partial \mathcal L(\hat\beta(\lambda))\). Donc il existe \(\hat z\in\partial\|\hat\beta(\lambda)\|_1\) tel que \[-2\mathbb X^t(Y-\mathbb X\hat\beta(\lambda))+\lambda \hat z=0\quad\Longleftrightarrow\quad \mathbb X^t\mathbb X\hat\beta(\lambda)=\mathbb X^tY-\frac{\lambda}{2}\hat z.\]
On suppose maintenant que la matrice \(\mathbb X\) est orthogonale.
Montrer que \[\textrm{signe}(\hat\beta_j(\lambda))=\textrm{signe}(\mathbb X_j^tY)\quad\textrm{lorsque }\hat\beta_j(\lambda)\neq 0\] et \(\hat\beta_j(\lambda)=0\) si et seulement si \(|\mathbb X_j^tY|\leq \lambda/2\).
\(\mathbb X\) étant orthogonale, on a pour \(\hat\beta_j(\lambda)\neq 0\) \[\hat\beta_j(\lambda)+\frac{\lambda}{2}\textrm{signe}(\hat\beta_j(\lambda))=\hat\beta_j(\lambda)\left(1+\frac{\lambda}{2|\hat\beta_j(\lambda)|}\right)=\mathbb X_j^tY,\] donc \(\hat\beta_j(\lambda)\) est du signe de \(\mathbb X_j^tY\). De plus si \(\hat\beta_j(\lambda)=0\) alors \(\mathbb X^t_jY=\frac{\lambda}{2}\hat z_j\) avec \(\hat z_j\in[-1,1]\). Donc \[|\mathbb X^t_jY|=\left|\frac{\lambda}{2}\hat z_j\right|\leq\frac{\lambda}{2}.\] A l’inverse si \(|\mathbb X_j^tY|\leq \lambda/2\) et si \(\hat\beta_j(\lambda)\neq 0\) alors \[\left|\hat\beta_j(\lambda)\left(1+\frac{\lambda}{2|\hat\beta_j(\lambda)|}\right)\right|=|\hat\beta_j(\lambda)|+\frac{\lambda}{2}=|\mathbb X^t_jY|\leq\frac{\lambda}{2}.\] Donc \(\hat\beta_j(\lambda)=0\).
En déduire \[\hat\beta_j(\lambda)=\mathbb X_j^tY\left(1-\frac{\lambda}{2|\mathbb X_j^tY|}\right)_+,\quad j=1,\dots,p\] où \((x)_+=\max(x,0)\). Interpréter ce résultat.
On obtient donc \[\hat\beta_j(\lambda)=\mathbb X_j^tY-\frac{\lambda}{2}\,\frac{\mathbb X_j^tY}{|\mathbb X_j^tY|}=\mathbb X_j^tY\left(1-\frac{\lambda}{2|\mathbb X_j^tY|}\right)\] si \(\mathbb X_j^tY\geq \frac{\lambda}{2}\) et \(\hat\beta_j(\lambda)=0\) sinon. D’où \[\hat\beta_j(\lambda)=\mathbb X_j^tY\left(1-\frac{\lambda}{2|\mathbb X_j^tY|}\right)_+,\quad j=1,\dots,d.\]
Exercice 3.3 (Unicité de l’estimateur lasso) Cet exercice est inspiré de Giraud (2015). Soit \(\hat\beta^{1}(\lambda)\) et \(\hat\beta^{2}(\lambda)\) deux solutions qui minimisent l’Équation 3.2. Soit \(\hat\beta=(\hat\beta^{1}(\lambda)+\hat\beta^{2}(\lambda))/2\).
Montrer que si \(\mathbb X \hat\beta^{1}(\lambda)\neq\mathbb X \hat\beta^{2}(\lambda)\) alors \[\|\mathbb Y-\mathbb X\hat\beta\|_2^2+\lambda\|\hat\beta\|_1<\frac{1}{2}\left(\|\mathbb Y-\mathbb X\hat\beta^1(\lambda)\|_2^2+\lambda\|\hat\beta^1(\lambda)\|_1+\|\mathbb Y-\mathbb X\hat\beta^2(\lambda)\|_2^2+\lambda\|\hat\beta^2(\lambda)\|_1\right).\] On pourra utiliser la convexité (forte) de \(x\mapsto\|x\|_2^2\).
On a
\[ \begin{aligned} \|\mathbb Y-\mathbb X\hat\beta\|_2^2+\lambda\|\hat\beta\|_1= & \left\|\frac{1}{2}(\mathbb Y-\mathbb X\hat\beta^1(\lambda))+\frac{1}{2}(\mathbb Y-\mathbb X\hat\beta^2(\lambda))\right\|_2^2+\lambda\left\|\frac{1}{2}(\hat\beta^1(\lambda)+\hat\beta^2(\lambda))\right\|_1 \\ < & \frac{1}{2}\left\|\mathbb Y-\mathbb X\hat\beta^1(\lambda))\right\|_2^2+\frac{1}{2}\left\|\mathbb Y-\mathbb X\hat\beta^2(\lambda))\right\|_2^2+\frac{1}{2}\lambda\|\hat\beta^1(\lambda)\|_1+\frac{1}{2}\lambda\|\hat\beta^2(\lambda)\|_1 \end{aligned} \] en utilisant la stricte convexité de \(x\mapsto\|x\|_2^2\) et l’inégalité triangulaire.
En déduire que \(\mathbb X \hat\beta^{1}(\lambda)=\mathbb X \hat\beta^{2}(\lambda)\).
Donc si \(\mathbb X \hat\beta^{1}(\lambda)\neq\mathbb X \hat\beta^{2}(\lambda)\) alors \[\|\mathbb Y-\mathbb X\hat\beta\|_2^2+\lambda\|\hat\beta\|_1< \|\mathbb Y-\mathbb X\hat\beta^1(\lambda)\|_2^2+\lambda\|\hat\beta^1(\lambda)\|_1\] ce qui est impossible par définition de \(\hat\beta^1(\lambda)\).