Coborârea dublă explicată | R-bloggeri

URMĂREȘTE-NE
16,065FaniÎmi place
1,142CititoriConectați-vă

Aceasta este o postare despre faimosul fenomen dublu coborâre în învățarea automată. Îmi propun să construiesc cel mai simplu exemplu posibil de dublă coborâre și să explic exact cum apare și de ce nu contrazice compromisul bias-variance.

Am fost inspirat să scriu această postare de un videoclip excelent de la Welch Labs intitulat Ce greșesc cărțile despre AI. În ciuda titlului provocator, videoclipul explică de ce dubla coborâre nu înseamnă cu adevărat că cărțile sunt greșite. O postare pe blog de Alex Shtoff conține cod Python detaliat pentru un exemplu care este similar cu cel din videoclip.

Nu am putut găsi o postare la fel de bună cu cod R și se pare că există o mulțime de informații greșite despre coborârea dublă online, așa că m-am gândit să-mi scriu propria postare. În loc să port exemplul Shtoff, am decis și să creez ceva puțin mai simplu. Cât de simplu poate fi un model, dar totuși să prezinte fenomenul de coborâre dublă? Pentru a răspunde la asta, trebuie să explic ce este dubla coborâre.

Una dintre ideile cheie în învățarea automată este compromis de părtinire-varianță (ei întreabă despre asta în fiecare interviu). Aceasta este ideea că, pe măsură ce un model devine din ce în ce mai complicat, se va potrivi din ce în ce mai bine cu datele de antrenament (linia galbenă din imaginea de mai jos indică eroarea de antrenament), dar după un anumit punct va deveni supraadaptat și va funcționa mai rău la datele de testare nevăzute (linia roșie din imagine indică eroarea de testare). Deoarece în general doriți ca modelul să funcționeze bine pe datele de testare nevăzute, aceasta este o problemă. Înseamnă că trebuie să fii conservator și să alegi un model care nu este prea amenajat.

Din punct de vedere conceptual, ar trebui să vă gândiți la o curbă în formă de U. Doriți să selectați modelul din partea de jos a U.

Pe măsură ce rețelele neuronale au devenit populare, oamenii au început să observe că există mai mult decât atât. Pe măsură ce adăugați din ce în ce mai mulți parametri rețelei dvs. neuronale, vedeți curba în formă de U. Dar, dincolo de un anumit punct, eroarea de testare începe să scadă din nou.

Aceasta se numește coborâre dublă. Pe măsură ce rețeaua dvs. devine din ce în ce mai mare, se oprește supraadaptarea. De fapt, dacă faceți rețeaua suficient de mare, poate avea chiar o eroare de testare mai mică decât modelul din partea de jos a U. Acesta este unul dintre motivele pentru care acele companii nenorocite de AI adună GPU-uri astfel încât să poată antrena modele cu miliarde de parametri. Aceste modele sunt doar mai bine decât modelele mai mici.

Dar de ce? Ce se întâmplă?

Din punct de vedere conceptual, vă puteți gândi la datele de antrenament dintr-o problemă de învățare automată ca o matrice $n times k$ și un vector $n$. Rândurile matricei $n times k$ corespund punctelor de date. Coloanele corespund valorilor fiecărei caracteristici. $n$-vector $y$ conține rezultatul dorit pentru fiecare punct de date.

Când $n > k$, vă aflați în situația tradițională găsită în statistici. Aveți mai multe puncte de date decât caracteristici. În acest caz, probabil că nu există niciun model care să se potrivească exact cu datele dvs. Trebuie să faceți compromisuri făcând un fel de proces de mediere, care este aproximativ ceea ce face regresia liniară.

Când $k > n$, aveți prea multe caracteristici. În acest caz, probabil că vor exista multe modele care se potrivesc exact cu datele tale. Majoritatea algoritmilor de învățare automată fie vor lua o medie a acestor modele, fie vor selecta unul dintre ele ca cel mai bun model.

Când $n$ și $k$ sunt egali, aveți exact la fel de multe caracteristici ca puncte de date. La prima ordine, aveți $n$ ecuații în $n$ necunoscute. În general, va exista o singură soluție, așa că aveți o singură alegere posibilă de model.

Pentru a spune altfel, o problemă de învățare automată cu $n$ puncte de date și $k$ caracteristici are aproximativ

(left( begin{matrice} max(n, k) \ min(n, k) end{matrice} right))

grade de libertate deoarece acesta este numărul de submatrici de rang complet dintr-o matrice generică $n times k$. Aceste grade de libertate pot fi „mediate peste” sau exploatate într-un fel pentru a reduce varianța modelului adaptat. Mulți algoritmi de învățare automată funcționează prin adăugarea de caracteristici la un set de date existent (acest lucru poate fi făcut și manual, caz în care se numește inginerie de caracteristici.) Pe măsură ce numărul de caracteristici adăugate crește, există mai multe oportunități de mediere. Aceste oportunități pot fi uneori exploatate pentru a produce un model mai performant.

Schematic ceea ce vă așteptați să vedeți este ceva de genul acesta:

plot.ts(-lchoose(pmax(10, 0:50), pmin(10, 0:50)), xlab="model size", ylab="test error", 
xaxt="n", yaxt="n", las=1)

Acest lucru sugerează că o modalitate ușoară de a obține o coborâre dublă să apară este crearea unui algoritm care adaugă caracteristici generate aleatoriu unui set de date, ceea ce vom face acum.

Vom genera un set de date univariate cu o formă cubică suportată pe $(-1, 1)$.

make_data <- function(n, sigma){
  x <- 2*(runif(n) - 0.5)
  y <- x * (x-0.6) * (x+0.6) + sigma * rnorm(n)
  data.frame(x=x, y=y)
}

Modelul nostru va fi foarte simplu. Vom genera caracteristici ale formularului

(f_b(x) = begin{cases} 1 & x > b\ 0 & x < b end{cases})

și adăugați-le la date. Vom folosi aceste funcții pentru a estima $y$ pentru noile valori de $x$. Următoarea funcție adaugă caracteristicile setului de date atunci când valorile lui $b$ sunt stocate într-un vector numit cutoffs.

model_matrix <- function(dat, cutoffs, s_noise=0){
  n <- nrow(dat)
  N <- length(cutoffs)
  extra_features <- outer(dat$x, cutoffs, ">") + s_noise*rnorm(N*n)
  cbind(1, dat$x, extra_features)
}

Pentru a evita adăugarea a două caracteristici identice, adăugăm o cantitate mică de zgomot controlat de parametru s_noise. Pentru a include un termen de interceptare, adăugăm și o coloană de unități, ceea ce este echivalent cu permiterea versiunilor inversate orizontal ale caracteristicilor.

Pentru a se potrivi modelului, adăugăm $K$ din aceste caracteristici la întâmplare. Folosim pseudoinversul Moore-Penrose (the ginv funcția de la MASS pachet) al matricei modelului pentru a obține un vector de coeficienți de regresie bb. Aceasta corespunde regresiei liniare când $n > k$. Când $n < k$ corespunde selectării modelului liniar cu cea mai mică normă care se potrivește datelor.

library(MASS)

fit_model <- function(dat, K, s_noise=0.01){
  # N is the number of random features added
  # dat is a univariate data set with columns x, y
  a <- min(dat$x)
  b <- max(dat$x)
  
  cutoffs <- runif(K)*(b-a) + a # random cutoffs

  bb <- ginv(model_matrix(dat, cutoffs, s_noise)) %*% dat$y
  list(bb=bb, cutoffs=cutoffs)
}

predict_model <- function(model, dat){
  # predict from the model from data set with column x
  model_matrix(dat, model$cutoffs, 0) %*% model$bb
}

Iată un exemplu care arată că într-adevăr putem potrivi datele simulate destul de bine cu acest model.

set.seed(2510)
dat <- make_data(50, 0.1)
model <- fit_model(dat, 20)
plot(dat, main="Training data and fitted model", las=1)
dat$pred <- predict_model(model, dat)
lines(dat$x(order(dat$x)), dat$pred(order(dat$x)), "l", col="red",
      lwd=2)

Aceasta arată potrivirea cu datele de antrenament, dar ceea ce ne interesează cu adevărat este potrivirea cu datele de testare nevăzute. Putem folosi următoarea funcție pentru a genera multe seturi de date de testare și pentru a calcula MSE-ul unui model preinstalat.

calc_test_error <- function(model, nrow_test, sigma_test, n_reps){
  
  mse <- rep(0, n_reps)
  for (i in 1:n_reps){
    dat_test <- make_data(nrow_test, sigma_test)
    pred <- predict_model(model, dat_test)
    mse(i) <- mean((pred - dat_test$y)^2)
  }
  mean(mse)
}

De asemenea, dorim o funcție pentru a calcula varianţă a fiecărui model. Aici, generăm multe seturi de date de antrenament din aceeași distribuție și calculăm varianța predicțiilor modelului în fiecare punct. O variație mai mare înseamnă că modelul captează mai mult zgomot (și mai puțin semnal) în date.

calc_variance <- function(n_pts, n_model, sigma, n_reps){
  
  x_test <- seq(-1, 1, len=n_pts)
  fitted <- matrix(0, nr=n_reps, nc=length(x_test))
  
  for (i in 1:n_reps){
    dat <- make_data(n_pts, sigma)
    model <- fit_model(dat, n_model)
    fitted(i,) <- predict_model(model, data.frame(x=x_test)) 
  }
  mean(apply(fitted, 2, var))
}

Acum totul este la locul lui. Următorul cod generează un set de date cu puncte de 10 USD și se potrivește modelelor cu un număr diferit de caracteristici suplimentare (de la 0 USD la 50 USD). Deoarece caracteristicile suplimentare sunt generate aleatoriu, fiecare potrivire a modelului se repetă de mai multe ori pentru a estima eroarea medie err pe datele de testare nevăzute și pe varianța medie.

set.seed(2510)
K <- 50
model_reps <- 10
dat <- make_data(10, 0.1)
err <- vars <- matrix(0, nr=K+1, nc=model_reps)
for (i in 0:K){
  print(i)
  for (j in 1:model_reps){
    model <- fit_model(dat, i)
    err(i+1, j) <- calc_test_error(model, 100, 0.1, 1000)
    vars(i+1, j) <- calc_variance(10, i, 0.1, 1000)
  }
}

Rularea codului durează câteva minute. Iată intriga finală care arată comportamentul clasic de coborâre dublă.

plot_col <- rep("blue", K+1)
plot_col(1:9) <- "forestgreen" # used later
df <- data.frame(vars=rowMeans(log(vars)), 
                 err=rowMeans(log(err)), 
                 plot_col=plot_col)

plot(0:K, df$err, "l", xlab="Number of added features",
     ylab="log(test error)", las=1, main="Double Descent",
     col="blue", lwd=2)
abline(v=8, lty=2, col="red")

Linia punctată roșie arată că eroarea maximă de test are loc la $K=8$ caracteristici adăugate. De ce 8$? Acest lucru se datorează faptului că matricea noastră model are $K+2$ coloane și avem $10$ puncte de date. Deci, când $K=8$ obținem o matrice de model pătrat, ceea ce înseamnă că există exact un model posibil care poate scoate valorile $y$ de antrenament având în vedere valorile $x$ de antrenament. Acesta este punctul în care are loc cantitatea maximă de supraadaptare.

De asemenea, rețineți că modelele cu un număr foarte mare de caracteristici adăugate au avut cea mai mică eroare de testare; chiar mai jos decât modelele cu doar câteva caracteristici adăugate. La fel ca în învățarea profundă reală!

Următorul grafic arată cum se modifică varianța modelului odată cu numărul de caracteristici suplimentare adăugate. Observați că această diagramă are aceeași formă ca și diagrama de coborâre dublă. Pe măsură ce adăugăm mai multe caracteristici, variația scade.

plot(0:K, df$vars, "l", xlab="Number of added features",
     ylab="log(variance)", las=1, main="Model variance",
     lwd=2, col="darkgreen")
abline(v=8, lty=2, col="red")

Aceasta este cheia pentru a înțelege de ce coborârea dublă nu contrazice noțiunea tradițională a compromisului bias-varianță. Când trasăm curba în formă de U în compromisul de polarizare-varianță, este varianţă a modelului pe care ar trebui să-l trasăm pe axa $x$, nu „complexitatea modelului” sau „dimensiunea modelului” sau o altă noțiune de acest fel.

df$K <- 0:K
df <- df(order(df$vars), )
plot(df$vars, df$err, col=df$plot_col,
     pch=19, xlab="log(variance)", ylab="log(test error)",
     las=1, cex=1.3)

loess_fit <- loess(df$err ~ df$vars, span = 0.6)
lines(df$vars, predict(loess_fit), col=rgb(0, 0, 0, 0.1), lwd=10)

Când trasăm eroarea de test în funcție de varianță, obținem curba familiară în formă de U până la urmă. Punctele verzi sunt modelele care se află sub pragul $K=8$. Punctele albastre sunt modelele care sunt deasupra pragului. Împreună formează un model familiar frumos.

Acest exemplu simplu ilustrează câteva idei despre învățarea profundă. O mulțime de cercetări sunt în prezent dedicate întrebării de ce rețelele neuronale profunde sunt atât de eficiente în practică. O idee care a fost sugerată este așa-numita ipoteza biletului de loterie. Aceasta postulează că o rețea neuronală profundă inițializată cu ponderi aleatorii calculează în esență un număr mare de caracteristici aleatorii, la fel ca modelul de mai sus. Doar întâmplător, unele dintre aceste caracteristici sunt probabil utile pentru predicție. Procesul de instruire a rețelei tinde să promoveze aceste caracteristici utile, conducând la un model eficient. Cu cât rețeaua este mai mare, cu atât sunt create mai multe caracteristici aleatorii și cu atât este mai mare șansa ca unele dintre ele să fie utile.

(Totuși, ca de obicei, diavolul este în detalii. Dacă am mări dimensiunea setului nostru de date în exemplul de mai sus, am descoperi rapid că caracteristicile noastre aleatoare vor fi zero aproape peste tot. Modul foarte inteligent de configurat rețelele neuronale profunde face ca acest lucru să nu se întâmple în cazul rețelei neuronale, dar acesta este un subiect pentru altă zi.)

Oricum, sper că această postare te-a convins că dubla coborâre nu este atât de misterioasă (ea reiese din doar câteva rânduri de cod R) și că compromisul bias-variance este foarte viu!

Dominic Botezariu
Dominic Botezariuhttps://www.noobz.ro/
Creator de site și redactor-șef.

Cele mai noi știri

Pe același subiect

LĂSAȚI UN MESAJ

Vă rugăm să introduceți comentariul dvs.!
Introduceți aici numele dvs.