Analiza błędów¶
Gdzie i dlaczego nasze modele się mylą?
Wnioski płyną zarówno z residuów modelu regresji, jak i z pomyłek klasyfikatora.
import numpy as np
import pandas as pd
import plotly.express as px
reg = pd.read_parquet('../data/reg_predictions.parquet')
clf = pd.read_parquet('../data/clf_predictions.parquet')
reg['residual'] = reg['actual_rel'] - reg['pred_rel']
print('reg:', reg.shape, '| clf:', clf.shape)
reg: (10477, 9) | clf: (918, 14)
Regresja - rozkład residuów¶
Analizujemy zapisane w notebooku 03 predykcje HistGradientBoosting - modelu, który tam posłużył do diagnostyki jako reprezentant podejścia nieliniowego (zwycięzca benchmarku, LinearRegression, miał MAE niższe o ~0.04 s).
Residuum to różnica między wartością rzeczywistą a przewidzianą (dla tempa względnego). Dobry model daje symetryczną chmurę wokół zera, bez systematycznego przesunięcia (bias).
mae = reg['residual'].abs().mean()
bias = reg['residual'].mean()
print(f'MAE = {mae:.3f} s | bias = {bias:+.3f} s')
fig = px.histogram(reg, x='residual', nbins=80,
title=f'Rozkład residuów (MAE={mae:.3f}s, bias={bias:+.3f}s)',
labels={'residual': 'residuum [s]'})
fig.add_vline(x=0, line_dash='dash')
fig.show()
MAE = 0.627 s | bias = +0.044 s
Histogram pokazuje rozkład residuów - na osi poziomej jest residuum (różnica rzeczywiste - przewidziane dla tempa względnego, w sekundach), a wysokość słupka to liczba okrążeń wpadających w dany przedział. Przerywana pionowa linia w zerze wyznacza punkt, wokół którego rozkład residuów dobrego modelu powinien być symetryczny.
Widzimy dwie rzeczy. Po pierwsze, MAE = 0.627 s - model myli się średnio o ponad sześć dziesiątych sekundy, co zgadza się z wynikiem HistGradientBoosting w benchmarku z notebooka 03 (0.627 s; zwycięski model liniowy miał 0.585 s).
Po drugie, co ważniejsze diagnostycznie, bias = +0.044 s jest bliski zera. Jest to średnia residuów: gdyby była wyraźnie dodatnia, model systematycznie zaniżałby tempo, gdyby ujemna - zawyżałby. Tutaj jest bliska zeru, więc model nie ma systematycznego przesunięcia.
Sam kształt rozkładu jest jednowierzchołkowy i skupiony wokół zera, ale z zauważalnymi ogonami w obie strony - to właśnie pojedyncze okrążenia z mocno chybioną predykcją, którym przyjrzymy się niżej.
Czy został jakiś wzorzec? Residua a wiek opony¶
Gdyby błąd rósł wraz z wiekiem opony, oznaczałoby to, że model nie uchwycił w pełni degradacji. Chcemy zobaczyć chmurę wokół zera (bez wyraźnego trendu).
fig = px.scatter(reg, x='TyreLife', y='residual', color='Compound', opacity=0.3,
title='Residua vs wiek opony (chcemy chmurę wokół 0, bez trendu)',
labels={'TyreLife': 'wiek opony [okr.]', 'residual': 'residuum [s]'})
fig.add_hline(y=0, line_dash='dash')
fig.show()
corr = reg['TyreLife'].corr(reg['residual'])
print(f'korelacja TyreLife - residuum: {corr:+.3f}')
wiek = pd.cut(reg['TyreLife'], [0, 5, 10, 20, 30, 100])
reg.groupby(wiek, observed=True)['residual'].agg(mediana='median', srednia='mean', n='size').round(3)
korelacja TyreLife - residuum: -0.116
| mediana | srednia | n | |
|---|---|---|---|
| TyreLife | |||
| (0, 5] | 0.116 | 0.143 | 1659 |
| (5, 10] | 0.167 | 0.192 | 2253 |
| (10, 20] | -0.007 | 0.007 | 3645 |
| (20, 30] | -0.049 | -0.066 | 2040 |
| (30, 100] | -0.111 | -0.115 | 880 |
Każdy punkt to jedno okrążenie testowe: na osi poziomej wiek opony (TyreLife, liczba okrążeń przejechanych na danym komplecie), na pionowej residuum, a kolor oznacza mieszankę. Przerywana linia w zerze to poziom bezbłędnej predykcji.
Czy błąd rośnie wraz z wiekiem opony? Gdyby chmura odchylała się ku górze lub ku dołowi przy starszych oponach, oznaczałoby to, że model nie domknął opisu degradacji i zostawił w residuach wzorzec związany ze zużyciem gumy.
Widzimy jednak chmurę rozłożoną zasadniczo symetrycznie wokół zera na całej szerokości osi, bez wyraźnego nachylenia. Potwierdza to policzona powyżej korelacja TyreLife z residuum: -0.116, czyli bliska zeru - trend jest słaby (tłumaczy około 1% wariancji residuów), choć przy dziesięciu tysiącach okrążeń statystycznie wykrywalny. Tak właśnie chcieliśmy: model uchwycił degradację opony na tyle, że w residuach został z niej tylko nikły ślad.
Tabela powyżej pokazuje ten delikatny resztkowy wzorzec: okrążenia na świeżych oponach są lekko niedoszacowane (mediana residuum +0.12 do +0.17 s dla wieku do dziesięciu okrążeń - model przewiduje odrobinę za szybkie tempo), a na bardzo starych - lekko przeszacowane (mediana -0.11 s powyżej trzydziestu okrążeń). To jednak drobiazg - przy korelacji rzędu 0.12 praktycznie bez znaczenia. Obserwacja symetrii jest więc słuszna: to, co zostaje w residuach, to w przeważającej części losowy rozrzut, a nie pominięta degradacja.
by_comp = (reg.assign(abs_err=reg['residual'].abs())
.groupby('Compound')['abs_err'].agg(['mean', 'count']).reset_index())
fig = px.bar(by_comp, x='Compound', y='mean',
title='Średni błąd bezwzględny per mieszanka opon',
labels={'mean': 'MAE [s]', 'Compound': 'mieszanka'})
fig.show()
by_comp
| Compound | mean | count | |
|---|---|---|---|
| 0 | HARD | 0.615374 | 5728 |
| 1 | MEDIUM | 0.620592 | 3624 |
| 2 | SOFT | 0.709311 | 1125 |
Słupki pokazują średni błąd bezwzględny modelu z podziałem na mieszanki opon - wysokość słupka to MAE w sekundach dla okrążeń przejechanych na danej gumie. Tabela pod wykresem dokłada liczbę okrążeń każdego typu.
Widzimy wyraźną różnicę: na oponie miękkiej model myli się najmocniej (SOFT MAE = 0.709 s), znacznie bardziej niż na twardej (HARD 0.615 s) i średniej (MEDIUM 0.621 s). Wytłumaczenie kryje się w kolumnie count.
Okrążeń na SOFT jest tylko 1125, podczas gdy na HARD aż 5728, a na MEDIUM 3624. Miękka mieszanka oznacza mniej danych, z których model może się uczyć, a do tego jest używana inaczej: w krótkich, agresywnych stintach (przejazdach na jednym komplecie opon), w których szybciej się degraduje i tempo zmienia się gwałtowniej z okrążenia na okrążenie.
Mniej przykładów i większa wewnętrzna zmienność składają się na trudniejszy do przewidzenia, a więc obarczony większym błędem segment danych.
To pierwsza wskazówka, dokąd kierować dalszą pracę nad modelem.
Najgorzej przewidziane okrążenia¶
Teraz przejrzymy najgorsze przewidywania modelu.
worst = reg.reindex(reg['residual'].abs().sort_values(ascending=False).index).head(15)
worst[['EventName', 'Compound', 'TyreLife', 'LapNumber',
'actual_rel', 'pred_rel', 'residual']].round(2)
| EventName | Compound | TyreLife | LapNumber | actual_rel | pred_rel | residual | |
|---|---|---|---|---|---|---|---|
| 679 | Azerbaijan Grand Prix | HARD | 3.0 | 14.0 | 3.35 | -0.25 | 3.60 |
| 4766 | Australian Grand Prix | HARD | 2.0 | 43.0 | 1.81 | -1.58 | 3.39 |
| 6245 | Japanese Grand Prix | HARD | 7.0 | 39.0 | 2.89 | -0.47 | 3.37 |
| 2605 | Spanish Grand Prix | MEDIUM | 9.0 | 48.0 | 1.73 | -1.64 | 3.36 |
| 5513 | Australian Grand Prix | HARD | 6.0 | 42.0 | 2.57 | -0.78 | 3.35 |
| 8353 | Singapore Grand Prix | HARD | 31.0 | 59.0 | 2.15 | -1.16 | 3.31 |
| 8787 | Singapore Grand Prix | SOFT | 8.0 | 54.0 | 1.95 | -1.27 | 3.22 |
| 8737 | Singapore Grand Prix | SOFT | 19.0 | 56.0 | 1.89 | -1.29 | 3.18 |
| 10347 | Qatar Grand Prix | HARD | 9.0 | 43.0 | 1.71 | -1.37 | 3.08 |
| 6105 | Japanese Grand Prix | SOFT | 18.0 | 52.0 | 2.49 | -0.57 | 3.06 |
| 1801 | Miami Grand Prix | HARD | 35.0 | 40.0 | 2.14 | -0.90 | 3.04 |
| 719 | Azerbaijan Grand Prix | HARD | 3.0 | 9.0 | 2.60 | -0.43 | 3.03 |
| 5559 | Australian Grand Prix | HARD | 30.0 | 38.0 | 2.59 | -0.41 | 3.00 |
| 10280 | Qatar Grand Prix | HARD | 21.0 | 56.0 | -3.59 | -0.59 | -3.00 |
| 4528 | Dutch Grand Prix | SOFT | 2.0 | 11.0 | 2.57 | -0.39 | 2.96 |
Tabela zestawia piętnaście okrążeń, na których model pomylił się najbardziej (posortowane według wartości bezwzględnej residuum). Dla każdego widzimy tor, mieszankę, wiek opony, numer okrążenia oraz tempo rzeczywiste, przewidziane i samo residuum.
Niemal wszystkie te okrążenia były w rzeczywistości wolne (actual_rel od +1.7 do +3.4 s powyżej mediany wyścigu).
Model konsekwentnie typował tempo bliskie zeru lub nawet ujemne (pred_rel od -0.25 do -1.64 s).
Widać tu zachowanie odnotowane już w notebooku 03 (wykres przewidziane/rzeczywiste). Nie znajdując przyczyny skrajnie wolnego okrążenia, model zgaduje tempo zbliżone do przeciętnego. Analogiczne zachowanie występuje dla okrążenia z Qatar Grand Prix o residuum -3.00, na którym tempo było wyjątkowo szybkie (-3.59 s), a model spodziewał się przeciętnego.
Powyższe dane łączy to, że są skrajnymi przypadkami wywołanymi czynnikami spoza naszych cech - ruchem na torze, chwilową sytuacją wyścigową, pozostałościami po neutralizacji (wstrzymaniu wyścigu). Nie ma w danych sygnału, który pozwoliłby je przewidzieć - trafiają niestety na samą górę listy błędów.
# wnioski regresji LICZONE z danych
worst_track = reg.assign(ae=reg.residual.abs()).groupby('EventName')['ae'].mean().idxmax()
worst_comp = by_comp.loc[by_comp['mean'].idxmax(), 'Compound']
tail = (reg['residual'].abs() > 3 * reg['residual'].abs().median()).mean()
print('Tor z największym średnim błędem:', worst_track)
print('Mieszanka z największym błędem: ', worst_comp)
print(f'Odsetek okrążeń z błędem >3x mediany (ogon): {tail:.1%}')
Tor z największym średnim błędem: Qatar Grand Prix Mieszanka z największym błędem: SOFT Odsetek okrążeń z błędem >3x mediany (ogon): 6.3%
Torem o największym średnim błędzie jest Qatar Grand Prix, najtrudniejszą mieszanką SOFT, a odsetek okrążeń z błędem przekraczającym trzykrotność mediany wynosi 6.3%.
Oznacza to, że na każde sto okrążeń około sześciu jest przewidzianych wyraźnie gorzej niż typowe.
Model więc radzi sobie dobrze z większością okrążeń, a jego kłopoty koncentrują się na niewielkiej, ale rozpoznawalnej grupie: trudny tor (Qatar), nietrwała mieszanka (SOFT) i nieliczne skrajne przypadki, których przyczyn nie ma w cechach.
Granica jakości modelu leży więc nie w przeciętnym okrążeniu, lecz w tym wąskim ogonie sytuacji nietypowych.
Klasyfikacja - gdzie model się myli¶
Głównym tematem są pomyłki bliskie granicy. Chodzi o kierowców z czwartego czy piątego miejsca, których model typował na podium, i odwrotnie.
clf['err_type'] = np.select(
[(clf.podium == 1) & (clf.pred_logit == 1),
(clf.podium == 0) & (clf.pred_logit == 0),
(clf.podium == 0) & (clf.pred_logit == 1),
(clf.podium == 1) & (clf.pred_logit == 0)],
['TP (trafione podium)', 'TN (trafione nie-podium)',
'FP (typował podium, nie było)', 'FN (przegapił podium)'],
default='?')
clf['err_type'].value_counts()
err_type TN (trafione nie-podium) 624 FP (typował podium, nie było) 156 TP (trafione podium) 123 FN (przegapił podium) 15 Name: count, dtype: int64
Klasyfikacja - z czego składają się pomyłki¶
Powyżej (tym razem w formie tabeli) ponownie pojawia się macierz pomyłek z notebooka 04.
TP(true positive) to trafione podium,TN(true negative) to trafione nie-podium,FP(false positive) to sytuacja, gdy model typował podium, którego nie było,FN(false negative) to przegapione podium.
TN = 624, TP = 123, a wśród błędów FP = 156 znacznie przewyższa FN = 15.
Oznacza to, że model woli zgłosić fałszywy alarm (typować podium, którego nie było) niż przegapić prawdziwe podium.
Jak już wspomniano w notebooku 04, to skutek class_weight='balanced'.
Przy zaledwie ~15% podiów w danych ta opcja każe modelowi traktować rzadką klasę poważnie, dlatego model chętnie wskazuje kandydatów na podium.
Widać to w dwóch metrykach.
- Recall (czułość, czyli jaka część prawdziwych podiów została wyłapana) wynosi
123 / (123 + 15) = 0.89- model przegapia bardzo niewiele podiów. - Precision (precyzja, czyli jaka część typowanych podiów okazała się trafna) to tylko
123 / (123 + 156) = 0.44- mniej niż połowa typowań się sprawdza.
miss = clf[clf.podium != clf.pred_logit].sort_values('proba_logit', ascending=False)
miss[['EventName', 'Driver', 'Team', 'GridPosition', 'Position',
'proba_logit', 'podium', 'err_type']].round(3).head(20)
| EventName | Driver | Team | GridPosition | Position | proba_logit | podium | err_type | |
|---|---|---|---|---|---|---|---|---|
| 642 | Austrian Grand Prix | VER | Red Bull Racing | 1.0 | 5.0 | 0.964 | 0 | FP (typował podium, nie było) |
| 704 | Belgian Grand Prix | PER | Red Bull Racing | 2.0 | 7.0 | 0.959 | 0 | FP (typował podium, nie było) |
| 497 | Australian Grand Prix | VER | Red Bull Racing | 1.0 | 19.0 | 0.954 | 0 | FP (typował podium, nie było) |
| 823 | Mexico City Grand Prix | VER | Red Bull Racing | 2.0 | 6.0 | 0.953 | 0 | FP (typował podium, nie było) |
| 682 | Hungarian Grand Prix | VER | Red Bull Racing | 3.0 | 5.0 | 0.939 | 0 | FP (typował podium, nie było) |
| 903 | Abu Dhabi Grand Prix | VER | Red Bull Racing | 4.0 | 6.0 | 0.919 | 0 | FP (typował podium, nie było) |
| 774 | Azerbaijan Grand Prix | PER | Red Bull Racing | 4.0 | 17.0 | 0.910 | 0 | FP (typował podium, nie było) |
| 723 | Dutch Grand Prix | PER | Red Bull Racing | 5.0 | 6.0 | 0.904 | 0 | FP (typował podium, nie było) |
| 862 | Las Vegas Grand Prix | VER | Red Bull Racing | 5.0 | 5.0 | 0.900 | 0 | FP (typował podium, nie było) |
| 317 | Japanese Grand Prix | PER | Red Bull Racing | 5.0 | 19.0 | 0.895 | 0 | FP (typował podium, nie było) |
| 541 | Miami Grand Prix | PER | Red Bull Racing | 4.0 | 4.0 | 0.888 | 0 | FP (typował podium, nie było) |
| 358 | United States Grand Prix | LEC | Ferrari | 1.0 | 20.0 | 0.888 | 0 | FP (typował podium, nie było) |
| 843 | São Paulo Grand Prix | NOR | McLaren | 1.0 | 6.0 | 0.886 | 0 | FP (typował podium, nie było) |
| 801 | United States Grand Prix | NOR | McLaren | 1.0 | 4.0 | 0.886 | 0 | FP (typował podium, nie było) |
| 378 | Mexico City Grand Prix | PER | Red Bull Racing | 5.0 | 20.0 | 0.878 | 0 | FP (typował podium, nie było) |
| 762 | Azerbaijan Grand Prix | VER | Red Bull Racing | 6.0 | 5.0 | 0.871 | 0 | FP (typował podium, nie było) |
| 124 | Spanish Grand Prix | SAI | Ferrari | 2.0 | 5.0 | 0.862 | 0 | FP (typował podium, nie było) |
| 721 | Dutch Grand Prix | PIA | McLaren | 3.0 | 4.0 | 0.858 | 0 | FP (typował podium, nie było) |
| 907 | Abu Dhabi Grand Prix | PIA | McLaren | 2.0 | 10.0 | 0.855 | 0 | FP (typował podium, nie było) |
| 424 | Abu Dhabi Grand Prix | PIA | McLaren | 3.0 | 6.0 | 0.855 | 0 | FP (typował podium, nie było) |
Tabela pokazuje pomyłki modelu posortowane malejąco według przypisanego prawdopodobieństwa podium.
Każdy wiersz to przypadek, w którym predykcja rozminęła się z rzeczywistością - widzimy tor, kierowcę, zespół, pozycję startową i końcową, prawdopodobieństwo oraz typ błędu.
Cała czołówka to fałszywe alarmy (FP) z bardzo wysokim prawdopodobieństwem (proba_logit od 0.86 do 0.96).
Łączy je jeden schemat: to znani kierowcy wiodących zespołów (Red Bull Racing, Ferrari, McLaren), którzy startowali z przodu (GridPosition od 1 do 6).
Część z nich skończyła tuż za podium (Position 4-7).
Kilka nazwisk widnieje jednak przy miejscach 17, 19 czy 20 - to wyścigi, w których stało się coś całkowicie nieprzewidywalnego (awaria, kolizja, kara).
Wspólny mianownik jest więc taki, że na podstawie cech przedwyścigowych wyglądali na pewnych kandydatów na podium i model słusznie dał im wysokie prawdopodobieństwo. Niestety, sam przebieg wyścigu - od utraty jednego, dwóch miejsc po pełną awarię - przekreślił tę prognozę.
Model nie postawił tu na przypadkowych kierowców - wskazał właściwych faworytów, a o wyniku zadecydowały zdarzenia z przebiegu wyścigu, których w cechach nie ma.
Kalibracja - czy prawdopodobieństwa coś znaczą?¶
Dobra kalibracja odznacza się tym, że wśród przypadków z przewidzianym prawdopodobieństwem ~0.7 faktycznie około 70% kończy się podium.
Na poniższym wykresie punkty powinny układać się blisko przekątnej.
clf['bin'] = pd.cut(clf['proba_logit'], bins=np.linspace(0, 1, 11))
cal = (clf.groupby('bin', observed=True)
.agg(srednie_P=('proba_logit', 'mean'),
udzial_podiow=('podium', 'mean'),
n=('podium', 'size')).dropna().reset_index())
fig = px.scatter(cal, x='srednie_P', y='udzial_podiow', size='n',
title='Krzywa kalibracji (regresja logistyczna)',
labels={'srednie_P': 'średnie P(podium)', 'udzial_podiow': 'realny udział podiów'})
fig.add_shape(type='line', x0=0, y0=0, x1=1, y1=1, line=dict(dash='dash', color='red'))
fig.show()
ece = (cal['n'] / cal['n'].sum() * (cal['srednie_P'] - cal['udzial_podiow']).abs()).sum()
print(f'ECE = {ece:.3f} | średnie P(podium) = {clf.proba_logit.mean():.3f} | realny odsetek podiów = {clf.podium.mean():.3f}')
cal.round(3)
ECE = 0.153 | średnie P(podium) = 0.304 | realny odsetek podiów = 0.150
| bin | srednie_P | udzial_podiow | n | |
|---|---|---|---|---|
| 0 | (0.0, 0.1] | 0.027 | 0.007 | 449 |
| 1 | (0.1, 0.2] | 0.142 | 0.051 | 59 |
| 2 | (0.2, 0.3] | 0.255 | 0.041 | 49 |
| 3 | (0.3, 0.4] | 0.346 | 0.024 | 42 |
| 4 | (0.4, 0.5] | 0.450 | 0.150 | 40 |
| 5 | (0.5, 0.6] | 0.551 | 0.156 | 45 |
| 6 | (0.6, 0.7] | 0.648 | 0.238 | 42 |
| 7 | (0.7, 0.8] | 0.749 | 0.352 | 71 |
| 8 | (0.8, 0.9] | 0.854 | 0.568 | 74 |
| 9 | (0.9, 1.0] | 0.952 | 0.830 | 47 |
Wykres bada kalibrację - czy prawdopodobieństwa zwracane przez model można czytać dosłownie.
Prawdopodobieństwa dzielimy na dziesięć koszyków - dla każdego na osi poziomej jest średnie P(podium), a na pionowej udział podiów wśród tych przypadków. Wielkość punktu to liczność koszyka. Przerywana przekątna to kalibracja idealna: wśród przypadków z przewidzianym prawdopodobieństwem ~0.7 faktycznie około 70% powinno kończyć na podium.
Punkty leżą wyraźnie poniżej przekątnej - model systematycznie przeszacowuje prawdopodobieństwa. Gdy przewiduje 0.55, podium zdarza się w rzeczywistości tylko w ~16% przypadków; przy 0.75 - w ~35%.
ECE (expected calibration error), czyli błąd kalibracji uśredniony po koszykach z wagą równą ich liczebności, wynosi 0.153 - ten sam wynik, który dla surowego modelu balanced dał eksperyment na końcu notebooka 04 (tam podany z czterema miejscami po przecinku: 0.1535). Średnie deklarowane P(podium) wynosi 0.304 przy realnym odsetku podiów 0.150: model obiecuje podium mniej więcej dwa razy częściej, niż faktycznie się zdarza. class_weight='balanced' sztucznie zawyża prawdopodobieństwa klasy mniejszościowej, by ją wyłapać. Skutkiem ubocznym jest przesadna pewność modelu. Wniosek praktyczny: tych prawdopodobieństw nie wolno czytać dosłownie jako "tyle procent szans".
Jest jednak druga strona. Krzywa rośnie niemal monotonicznie - wyższemu P(podium) odpowiada realnie wyższa szansa. Jedyny wyjątek to dwa małe koszyki 0.2-0.4 (po ~40-50 przypadków), w których odsetek podiów lekko spada; przy tak małych licznościach to raczej szum niż wzorzec. Model więc dobrze szereguje kandydatów (wyższa wycena to lepszy kandydat), tylko źle wycenia bezwzględne prawdopodobieństwo.
Mamy dobrą dyskryminację (ROC-AUC = 0.92) przy słabej kalibracji. Najliczniejszy koszyk to 0-0.1 (449 przypadków, niemal połowa danych, realny udział podiów ~0.01), w którym ocena jest trafna - model z dużą pewnością odsiewa przypadki bez nadziei na podium.
# najwieksza niespodzianka: podium z dalekiej pozycji startowej
upset = clf[(clf.podium == 1)].sort_values('GridPosition', ascending=False)
print('Podia z najdalszych pozycji startowych (model słusznie je przegapia):')
print(upset[['EventName', 'Driver', 'GridPosition', 'Position', 'proba_logit']].head(5).to_string(index=False))
Podia z najdalszych pozycji startowych (model słusznie je przegapia):
EventName Driver GridPosition Position proba_logit
Abu Dhabi Grand Prix LEC 19.0 3.0 0.006586
São Paulo Grand Prix VER 17.0 1.0 0.107751
Saudi Arabian Grand Prix VER 15.0 2.0 0.175216
Austrian Grand Prix PER 15.0 3.0 0.266306
São Paulo Grand Prix GAS 13.0 3.0 0.084385
Ostatnia tabela zestawia prawdziwe niespodzianki: podia zdobyte z najdalszych pozycji startowych. To skrajny test dla modelu - czy potrafi przyznać, że pewnych wyników nie da się przewidzieć z cech przedwyścigowych.
LECzP19naP3z prawdopodobieństwem zaledwie0.007,VERzP17na zwycięstwo zproba 0.108,VERzP15naP2(0.175).
To wyścigi, w których o wszystkim zadecydowały czynniki działające w trakcie rywalizacji. Model słusznie dawał tym kierowcom bardzo niskie prawdopodobieństwa, bo na starcie nic nie zapowiadało podium.
Wnioski końcowe¶
Regresja (tempo okrążenia)
- Po zmianie celu na tempo względne model działa poprawnie. Największe błędy zostają na okrążeniach o nietypowych warunkach, które przetrwały filtry - na przykład ruch na torze czy pozostałości po neutralizacji.
- Wielkość błędu zależy od mieszanki opon - twarda i miękka mają różny rozrzut degradacji.
Klasyfikacja (podium)
- Model dobrze wychwytuje regułę "start z przodu zwiększa szansę na podium", a myli się tam, gdzie F1 bywa nieprzewidywalna: w deszczu, przy samochodzie bezpieczeństwa, awariach i odważnych strategiach.
- Te pomyłki to naturalna granica przewidywalności na podstawie samych cech przedwyścigowych - nie da się ich usunąć bez danych z przebiegu wyścigu, a te byłyby wyciekiem.
- Model
balanceddobrze szereguje kandydatów (ROC-AUC = 0.92), ale jego prawdopodobieństwa są przeszacowane (ECE = 0.153, średnio0.30wobec realnych0.15). Jak pokazał eksperyment na końcu notebooka 04, wagi klas praktycznie nie zmieniają kolejności kandydatów (ROC-AUC0.920-0.922), tylko punkt pracy przy progu0.5, a kalibracja (Platta lub izotoniczna) usuwa przeszacowanie niemal bez zmiany kolejności (ROC-AUCstałe,PR-AUClekko spada). Do szeregowania wystarcza dyskryminacja; kalibracja staje się potrzebna dopiero wtedy, gdy prawdopodobieństwa mają być czytane dosłownie.
Wniosek metodyczny
- Dwie porażki z wcześniejszych notebooków ($R^2 < 0$ oraz
quali_gap_sw całości puste) były pouczające - pokazały, że wynik zależy bardziej od postawienia zadania i jakości danych niż od wyboru algorytmu. - Benchmark z pomiarem czasu pokazał, że przy małych zbiorach prostsze modele bywają zarazem lepsze i tańsze.