Forelesningsnotat: Trening, testing, kryssvalidering, overtrening
Premiss
Jeg har laget meg en funksjon som er på formen
\[p(x) = c_0 + c_1 x + c_2x^2 + \cdots = \sum_{i=0}^\infty c_i x^i\]
Så har jeg trukket noen tall \(y = p(x) + \varepsilon\), der verdiene \(\varepsilon\) er normalfordelt.
Men jeg holder c´ene hemmelige for dere, og jeg holer standardavviket i normalfordelingen hemmelig for dere.
Datasettet
Dette er slik det datasettet jeg har trukket ser ut:
Det kan lastes ned her.
Overordnet oppgave for økta
Finne best mulig estimat for \(\{c_i\}\). Dette innebærer i hovedsak å finne ut hvor høye \(i\) vi skal gå til, altså hvor store polynomer.
Tilfeldig gjettet \(i_\mathrm{max}\)
= np.polyfit(x, y, 3)
p = np.linspace(-2.5, 2.5, 100)
x_v "o")
plt.plot(x, y, plt.plot(x_v, np.polyval(p, x_v))
Test av forskjellige polynomer
from lammps_logfile import get_color_value
"o")
plt.plot(x, y, for i in range(1,15):
= np.polyfit(x, y, i)
p = np.linspace(-2.5, 2.5, 200)
xv =get_color_value(i, 0, 15, cmap="jet"), label=f"{i}")
plt.plot(xv, np.polyval(p, xv), c plt.legend()
Vi skjønner kanskje at vi må stoppe et sted, men hvor?
Vi må bruke noen systematiske verktøy for å klare å ta en god beslutning om hvor vi skal stoppe polynomet vårt!
Mean squared error
Vi har et datasett med verdier \(\{x_i, y_i\}\). Vi lager oss et polynom \(p(x)\), som vi bruker til å estimere y-verdier, altså \(\hat{y} = f(x)\).
Vi ønsker å minimere
\[\sum_{i=1}^N (\hat{y}_i - y_i)^2 = \sum_{i=1}^N (f(x_i) - y_i)^2\]
Mean squared error
- Last inn datasettet trukket fra den hemmelige fordelingen.
- Plott datasettet for å verifisere at det er korrekt innlest.
- Gjør polynomtilpasning med \(p(x)=\sum_{i=0}^N c_i x^i\) for \(N\) opp til \(20\). Plott de forskjellige polynomene på intervallet \([-2.5, 2.5]\)
- Finn treningsfeilen, altså \(\sum_{i=0}^n (p(x_i)-y_i)^2\), for hver polynomgrad N, og plott feilen som funksjon av N.
Starthjelp
= np.loadtxt("data/hemmelig_funksjon.txt")
data = data[:,0]
x = data[:,1]
y for i in range(1,15):
= np.polyfit(x, y, i) p
Løsning
1,2,1)
plt.subplot("o")
plt.plot(x,y,
= 5
N = np.zeros(N+1)
training_errors for i in range(N+1):
= np.polyfit(x, y, i)
p = np.linspace(-2.5, 2.5, 200)
xv 1,2,1)
plt.subplot(=get_color_value(i, 0, N, cmap="jet"), label=f"{i}")
plt.plot(xv, np.polyval(p, xv), c= np.mean((np.polyval(p, x)-y)**2)
training_errors[i]
plt.legend()1,2,2)
plt.subplot(
plt.plot(training_errors)"Grad av polynom")
plt.xlabel("Treningsfeil")
plt.ylabel( plt.tight_layout()
Øke N
- Hva kan vi lære om hvilken N vi bør velge av dette?
Validerings-trenings-splitt 50/50
- Vi deler datasettet i to deler, hvorfor det?
=(8,6))
plt.figure(figsize= np.linspace(-2.5, 2.5, 12)
x1 = np.linspace(-2.5, 2.5, 8)
x2 = f(x1, Cs) + np.random.normal(0, sigma, 12)
y1 = f(x2, Cs) + np.random.normal(0, sigma, 8)
y2 = np.polyfit(x1, y1, 10)
p "o", label="Trening")
plt.plot(x1, y1, "o", label="Validering")
plt.plot(x2, y2, = np.linspace(-2.5, 2.5, 100)
x3 = np.polyval(p, x3)
y3 ":", c="k", label="Modell")
plt.plot(x3, y3,-55, 20])
plt.ylim([ plt.legend()
- Avdekke overtilpasning
- Vurdere modellkvalitet
- Velge modell
Implementasjonsforslag for datasplitting
= np.random.permutation(len(x))
indices = np.array_split(indices, 2)
idx_train, idx_validate = x[idx_train]
x_train = y[idx_train]
y_train = x[idx_validate]
x_validate = y[idx_validate] y_validate
Vi deler opp datasettet i treningssett og valideringssett for å kunne evaluere modellen på data som den ikke har sett under trening. Treningssettet brukes til å trene modellen, mens valideringssettet brukes til å bestemmehvor høye grads polynomer vi kan ta med i modellen uten å få overtilpasning.
- Print indeksene for treningsdata og valideringsdata.
- Verifiser (med programmering) at indeksene i treningsdata og valideringsdata er forskjellige.
- Plott treningsdata og valideringsdata i forskjellige farger for å verifisere at de er tilfeldig, og ikke systematisk, trukket!
Løsning
import matplotlib.pyplot as plt
"o", label="treningsdata")
plt.plot(x_train, y_train, "o", label="testdata")
plt.plot(x_validate, y_validate, plt.legend()
Test-train-split 50/50
- Nå ønsker vi å sammenligne gjennomsnittlig kvadrert feil for treningsdata og valideringsdata ved forskjellige valg av høyeste mulige grad av polynom, for eksempel om vi setter høyeste grad til 20 har vi: \[p(x) = \sum_{i=0}^{20} c_i x^i\]
- Skriv opp hvordan vi konseptuelt kan gjøre dette
Implementasjon
(her skal vi gå igjennom koden linje for linje! Rop om jeg ikke gjør det)
1,2,1)
plt.subplot("o")
plt.plot(x,y,
= 15
N = np.zeros(N+1)
training_errors = np.zeros(N+1)
validation_errors for i in range(N+1):
= np.polyfit(x_train, y_train, i)
p = np.linspace(-2.5, 2.5, 200)
xv 1,2,1)
plt.subplot(=get_color_value(i, 0, N, cmap="jet"), label=f"{i}")
plt.plot(xv, np.polyval(p, xv), c= np.mean((np.polyval(p, x_train)-y_train)**2)
training_errors[i] = np.mean((np.polyval(p, x_validate)-y_validate)**2)
validation_errors[i]
1,2,2)
plt.subplot(="training error")
plt.plot(training_errors, label="test error")
plt.plot(validation_errors, label"Grad av polynom")
plt.xlabel("Treningsfeil")
plt.ylabel(
plt.legend() plt.tight_layout()
- Hva lærte vi nå om hvilken polynomgrad vi bør bruke?
Foreløpig oppsummering
- Vi har funnet ut at et polynom av grad mellom 3 og 8 bør kunne funke. Kan vi gjøre det bedre?
k-fold kryssvalidering!
Fold | Partisjon 1 | Partisjon 2 | Partisjon 3 | Partisjon 4 | Partisjon 5 |
---|---|---|---|---|---|
1 | 🟥 Validering | 🟦 Trening | 🟦 Trening | 🟦 Trening | 🟦 Trening |
2 | 🟦 Trening | 🟥 Validering | 🟦 Trening | 🟦 Trening | 🟦 Trening |
3 | 🟦 Trening | 🟦 Trening | 🟥 Validering | 🟦 Trening | 🟦 Trening |
4 | 🟦 Trening | 🟦 Trening | 🟦 Trening | 🟥 Validering | 🟦 Trening |
5 | 🟦 Trening | 🟦 Trening | 🟦 Trening | 🟦 Trening | 🟥 Validering |
Dere skal nå dele opp datasettet i 5 partisjoner, og verifiser med et plott at det har gått bra.
Beskriv først med ord hva som skal til for å dele opp i 5 partisjoner.
Dette er slik vi delte i treningsdata og valideringsdata tidligere:
= np.random.permutation(len(x))
indices = np.array_split(indices, 2)
idx_train, idx_validate = x[idx_train]
x_train = y[idx_train]
y_train = x[idx_validate]
x_validate = y[idx_validate] y_validate
Løsning
0)
np.random.seed(= np.random.permutation(len(x))
indices = np.array_split(indices, 5)
partitions
= ["r", "g", "b", "c", "m"]
colors =(8, 4))
plt.figure(figsizefor i, partition in enumerate(partitions):
"o", label=f"Partisjon {i+1}", color=colors[i])
plt.plot(x[partition], y[partition], plt.legend()
Dele opp i 5 folds
Det er kanskje ikke helt åpenbart, men det å dele opp i 5 folds er ikke det samme som å dele opp i 5 partisjoner.
Del opp i 5 partisjoner, og visualier disse partisjonene.
(Live-programmere løsning)
Tilpasse polynom til hver fold
= plt.subplots(2, 3, figsize=(9, 6), sharey=True)
fig, axs = axs.flatten()
axs
for i, partition in enumerate(partitions):
= np.setdiff1d(indices, partition)
train_idx = np.polyfit(x[train_idx], y[train_idx], 4)
p "o", color="lightgray", label="Treningsdata")
axs[i].plot(x[train_idx], y[train_idx], "o", color="blue", label="Valideringsdata")
axs[i].plot(x[partition], y[partition], = np.linspace(-2.5, 2.5, 100)
xv = np.polyval(p, xv)
yv "k")
axs[i].plot(xv, yv, 5].plot(xv, yv)
axs[f"Fold {i+1}")
axs[i].set_title(
axs[i].legend() plt.tight_layout()
Hvilke data er det linja er tilpasset til i grafene over her. Er det de grå eller de blå? Hva er fold og hva er partisjoner her?
I denne tilpasningen har jeg brukt et polynom av grad 4. Hva skal vi gjøre for å beregne mean squared error for den et polynom av grad 4 med 5-fold kryssvalidering? Beskriv på figuren.
Tilfellet \(i_\mathrm{max}=4\)
= 0
mean_squared_error for i, partition in enumerate(partitions):
= np.setdiff1d(indices, partition)
train_idx = np.polyfit(x[train_idx], y[train_idx], 4)
p += np.mean((np.polyval(p, x[partition])-y[partition])**2)
mean_squared_error /= len(partitions)
mean_squared_error print(mean_squared_error)
70.96934537774104
Snitt av alle folds, for mange \(i_\mathrm{max}\)
Vi ønsker nå å bruke k-fold kryssvalidering til å sammenligne hvor bra modellene blir med forskjellige valg av \(i_\mathrm{max}\).
Snitt av alle folds, for mange \(i_\mathrm{max}\)
= np.array_split(indices, 5)
folds
= 15
i_max = np.zeros(15)
mse_values
for i, fold in enumerate(folds):
= np.setdiff1d(indices, fold)
train_idx for j in range(i_max):
= np.polyfit(x[train_idx], y[train_idx], j)
p = np.mean((np.polyval(p, x[fold])-y[fold])**2)
mse += mse
mse_values[j] /= len(folds)
mse_values
plt.plot(mse_values)0, 10], [49, 49], "--", c="k") plt.plot([
Den stiplede linja avslører den ekte variansen til det stokastiske leddet \(\varepsilon\) fra datatrekningen i starten av timen.
Peker mot \(i_\mathrm{max} = 5?\)
[-3.39655030e+00 2.82703972e+01 8.67920082e+00 -3.31733925e+01
-1.86241023e+01 1.52155891e+01 1.09424134e+01 -1.25976127e+00
-3.59182687e+00 -9.53165305e-01 7.60116699e-01 2.49178868e-01
-9.66010793e-02 -1.72214555e-02 5.26860717e-03]
Sammenligning av \(\{c_i\}\)
Fasit: [0, 20, -8, -10.7, 1.2, 1.6, -0.15, -0.05]
Beste modell: [ 1.15123423 -0.15289326 -9.51306655 -4.00555715 18.74401723 -2.10654956]
Beste modell når vi kjenner $i_{max}$: [ -0.03446598 -0.13178075 1.50576443 0.99185812 -10.53909373
-6.43603876 19.46976614 -1.369465 ]
Merk at selv med 100 punkter fikk vi ganske dårlige estimater på \(c\)-verdiene. Om disse verdiene er viktige å vite nøyaktig for å karakterisere et eller annet system eller fenomen, så må vi være klar over at selv om modellen ser ut til å prestere bra, så trenger ikke alt å være presist. Vi kom nesten helt ned på samme variasjon fra modellen til dataene som det som lå i selve trekkingen av data. Men allikevel er avviket på enkeltkoeffisientene stort.
Måter å gjøre ting enklere på
I stedet for np.array_split
manuelt, kan vi kjøre
from sklearn.model_selection import train_test_split
= train_test_split(x, y, test_size=0.5, random_state=0)
x_train, x_test, y_train, y_test
"o", label="train")
plt.plot(x_train, y_train, "o", label="validation")
plt.plot(x_test, y_test, plt.legend()
Måter å gjøre ting enklere på
For kryssvalidering kan vi i stedet for np.array_split
kjøre sklearn.model_selection.KFold
.
from sklearn.model_selection import KFold
= KFold(n_splits=5)
kf for train_index, validate_index in kf.split(range(len(x))):
"o") plt.plot(x[validate_index], y[validate_index],
- Men obs!
Måter å gjøre ting enklere på
Må passe på at autometodene ikke lurer oss! Her måtte vi oppgi at dataene skulle stokkes, siden de var sortert etter x-aksen fra før.
from sklearn.model_selection import KFold
= KFold(n_splits=5, shuffle=True)
kf for train_index, validate_index in kf.split(range(len(x))):
"o") plt.plot(x[validate_index], y[validate_index],
Kryssvalidering
Vi bruker kryssvalidering for å
- Hindre overtilpasning. Om testfeilen er mye større enn treningsfeilen har vi typisk overtilpasset
- Utnytte data godt. Kryssvalidering gir et godt estimat på hva som er usikkerheten når vi trener på alle dataene våre.
Hvorfor k-fold, og ikke leave one out kryssvalidering?
Eksempel på k-fold kryssvalidering med k=5:
Fold 1: Train on [2, 3, 4, 5], Test on [1]
Fold 2: Train on [1, 3, 4, 5], Test on [2]
Fold 3: Train on [1, 2, 4, 5], Test on [3]
Fold 4: Train on [1, 2, 3, 5], Test on [4]
Fold 5: Train on [1, 2, 3, 4], Test on [5]