Il Boosting è un metodo generale per la creazione di un modello di previsione complesso che utilizza componenti di costruzione semplici chiamati weak learner.
Quessto algoritmo inizia a fittare un weak learner sul training data.
Successivamente il weak learner viene nuovamente adattato, ma dando più peso (importanza) alle osservazioni adattate male/non classificate.
Questo processo viene ripetuto finché non viene raggiunta una regola di arresto.
In questo capitolo andremo ad analizzare tre tipi di algoritmi: * boosting with squared error loss (L2-boosting) utilizza alberi di regressione come weak learners
boosting with exponential loss (AdaBoost.M1) utilizza alberi di classificazione come weak learners
gradient boosting (Algoritmo generale)
L2-boosting costruisce con gli alberi di regressione un modello adattando ripetutamente ad un albero di regressione ai residuals. Questo è diverso dalle Random Forest perché ogni albero sta cercando di correggere gli errori commessi dall’insieme di alberi precedentemente coltivati
Inizializzo \(\hat{f}(x)= \bar{y}\) and \(r_i = y_i - \bar{y}\) for \(i=1,\ldots,n\)
Facciamo un ciclo For per \(b=1,2,\ldots, B\) e ripetiamo:
L’output del modello è: \[\hat{f}(x) = \bar{y}+ \sum_{b=1}^{B} \lambda \hat{f}^b(x)\]
Parametri di tuning per il boosting:
Il numero di alberi number of trees \(B\): A differenza del bagging e della random forest il boosting può overfittare se \(B\) risulta essere troppo elevato tuttavia questo accade molto lentamente al crescere di \(B\) ma conviene sempre utilizzare la cross-validation per individuare questo parametro.
Il parametro di restringimento shrinkage parameter \(\lambda\): Questo parametro è positivo e prossimo allo zero e controlla il tasso con cui l’algoritmo apprende. I valori piu comuni sono 0.01 o 0.001, la scelta di questo dipende dal problema ma come linea guida possiamo generalizzare (anche se bisogna sempre analizzare problema per problema) che ad un piccolo \(\lambda\) dovranno corrispondere un numero più elevato di alberi \(B\) per avere una buona performance.
Il numero di split number of splits \(d\): Questo parametro controlla la complessità del singolo albero su cui poi bisognerà fare ensamble. Spesso \(d = 1\) lavora bene in quanto creiamo degli alberi con una sola divisione (chiamati stump), ma potrebbe essere necessario aumentare questo parametro in alcuni casi dove la variabile di risposta risulta complicata.
library(rpart)
rm(list=ls())
set.seed(123)
n = 100
x=sort(runif(n)*2*pi)
y=sin(x)+rnorm(n, sd=0.5)
plot(y~x)
curve(sin(x), min(x),max(x), col="red", add=T)
Qui sotto c'è una applicazione che mostra come i tre paratri influenzano lo sviluppo dell'algoritmo (questa applicazione è stata fatta con la versione graduita di shinyapp quindi mi scuso per eventuali disagi fornisco anche il codice .Rmd cosi da poter agevolare la visualizzazione: codice.Rmd)
rm(list=ls())
# data import
als <- read.table("http://web.stanford.edu/~hastie/CASI_files/DATA/ALS.txt",header=TRUE)
# split into training and test
train = als[als$testset==F,-1]
n = nrow(train)
test = als[als$testset==T,-1]
m = nrow(test)
Usiamo la funzione gbm()
per fittare il modello boosting sui dati ALS impostando:
# library gbm
library(gbm)
set.seed(123)
# tuning parameters
d = 4
nu = 0.02
B = 500
K = 10
# boosted regression trees fit
fit <- gbm(dFRS ~ .,
distribution = "gaussian",
data = train,
n.trees = B,
interaction.depth = d,
shrinkage = nu,
bag.fraction = 1,
cv.folds= K)
print(fit)
## gbm(formula = dFRS ~ ., distribution = "gaussian", data = train,
## n.trees = B, interaction.depth = d, shrinkage = nu, bag.fraction = 1,
## cv.folds = K)
## A gradient boosted model with gaussian loss function.
## 500 iterations were performed.
## The best cross-validation iteration was 292.
## There were 369 predictors of which 136 had non-zero influence.
# predictions at each step b
yhats = sapply(1:B, function(b) predict(fit, newdata = test, n.trees = b))
# test MSE at each step b
MSEs = apply(yhats, 2, function(yhat) mean( (test$dFRS - yhat)^2))
plot(1:B, MSEs, xlab="Number of trees", ylab="test MSE", type="l")
which.min(MSEs)
## [1] 188
# bstop
bstop = gbm.perf(fit, method="cv")
## var rel.inf
## Onset.Delta Onset.Delta 30.443296
## alsfrs.score.slope alsfrs.score.slope 6.051237
## mean.slope.svc.liters mean.slope.svc.liters 3.774028
## mean.slope.weight mean.slope.weight 3.098220
## last.slope.weight last.slope.weight 3.003234
## last.alsfrs.score last.alsfrs.score 2.939656
## last.slope.bp.systolic last.slope.bp.systolic 2.691271
## mean.slope.fvc.liters mean.slope.fvc.liters 2.058366
## mean.speech mean.speech 2.033955
## max.dressing max.dressing 2.012729
# fitted functions
plot(fit, i.var="Onset.Delta", n.trees=bstop)
plot(fit, i.var="last.slope.weight", n.trees=bstop)
L’algoritmo AdaBoost.M1 algorithm (Freund and Schapire, 1997), è stato sviluppato per problemi di classificazione binaria \(Y \in \{-1,1\}\) e utilizza alberi di classificazione come weak learners.
Il boosting fu originariamente pensato per aumentare la performance dei weak learners nei problemi di classificazione binaria.
L’idea è stata quella di addestrare il cassificatore in modo sequenziale ed ad ogni passo modificare il training set in modo da dare più peso ai valori classificati male.
Il classificatore finale è calcolato con una media ponderata in base ai voti dei classificatori.
1. Inizializzo i pesi delle osservazioni pari a \(w_i=1/n, i=1,\ldots,n\)
Creo un ciclo For per \(b=1,\ldots,B\) e ripeto:
Calcolo \[\alpha^{b} = \log[ (1- \mathrm{Err}^{b})/\mathrm{Err}^{b}]\]
Partiamo imponendo \(\hat{f}^0(x)=0\), e impostiamo \(B\) e il parametro di restringimento (shrinkage parameter) \(\lambda>0\) come abbiamo fatto negli algoritmi precedenti
Creo ciclo For per \(b=1,\ldots,B\) e ripeto i seguenti steps:
Calcoliamo il gradiente negativo della funzione di perdita (pointwise negative gradient of the loss function) per ogni punti fittati \[r_i = -\frac{\partial L(y_i,f_i)}{\partial f_i}\left|_{f_i=\hat{f}^{b-1}(x_i)}\right., \quad i=1,\ldots,n\]
Approssimiamo qeusti valori attraverso un albero \(g\) con una profondità \(d\): \[(x_1,r_1),\ldots,(x_n,r_n)\rightarrow \hat{g}(x)\]
Aggiorniamo i dati dello step precedente del ciclo \[\hat{f}^b(x) = \hat{f}^{b-1}(x) + \lambda \hat{g}(x)\]
L’output alla fine dei ciclo è \(\hat{f}^b(x), b=1,\ldots,B\)
Loss functions
gbm
implementa due tipologie di funzioni di perdità \(L\) (che poi di fatto sono quelle più usate):
Si può notare che la funzione di perdità di Laplace non è differenziabile quando \(y=f(x)\); i valori negtivi del gradiente vengono posti uguali a 0.