Site icon Mirosław Mamczur

Czym jest wzmocnienie gradientowe i dlaczego ma super moce?

– Tatusiu, co robiłeś dzisiaj w pracy? – zapytała Jagódka kiedy odbierałem ją wraz z Otylką z przedszkola.

–  Dzisiaj z moimi kolegami i koleżankami z pracy sprawdzałem, czy nowe dane, które pozyskaliśmy, pomogą nam ulepszyć aktualne modele budowane techniką wzmocnienia gradientowego, czyli tak zwaną metodą gradient boosting’u.

– Co Ty tatusiu do mnie mówisz? – skrzywiła się słysząc takie dziwne sformułowanie pierwszy raz. – A co to jest ten gafent bumpstink?

– Pamiętasz jak ostatnio byliśmy w restauracji i mamusia zamówiła zupę kasztanową? – Jagódka pokiwała głową i oblizała się. Nie wiem, czy to na myśl o tej zupie, czy o naleśnikach, które zamawia prawie wszędzie.

– Była tak przepyszna, że zdecydowaliśmy się ugotować taką samą w domku. To była ta zupka, którą gotowaliśmy w ostatnią sobotę. Najpierw rozpoczęliśmy od czegoś prostego. Na początku ugotowaliśmy warzywka i kasztany w słonej wodzie. Po spróbowaniu okazało się, że nasza zupa ma całkiem inny smak niż ta w restauracji i w sumie w ogóle nie była dobra. Była za mało doprawiona, taka…hmm.. bez smaku. Chcieliśmy to poprawić, więc dodaliśmy troszkę pieprzu, takie większe czarne kulki zwane zielem angielskim i uschnięte listki z drzewa laurowego. Zupka miała już lepszy smak, ale była zbyt płynna. Wobec czego dodaliśmy więcej kasztanów i ziemniaków, a po ich ugotowaniu tym głośnym urządzeniem zmiksowaliśmy zupkę. Dalej nam coś nie pasowało więc dodaliśmy dla smaku odrobinę suszonych śliwek. Troszkę za mało, ale Otylcia nam po cichu połowę wyjadła. I chyba ktoś jej w tym pomógł. Jagoda zaczęła się śmiać, bo przypomniała sobie jak podkradała z siostrą suszone owoce. Po tych poprawkach zupka zaczęła nabierać ciekawszego smaku, podobnego do tej z restauracji. Rozpoczęliśmy od czegoś prostego, a każdy kolejny nasz krok sprawiał, że zupka stawała się coraz lepsza. Na tym polega ta metoda.

Nie wiem, czy zrozumiała samą metodę. Natomiast mam nadzieję, że zapamięta, że nawet jak kiedyś popełni jakiś błąd, to często można go naprawić.

Skąd nazwa wzmocnienie gradientowe (ang. Gradient Boosting)?

Samo sformułowanie Gradient Boosting (wzmocnienie gradientowe) brzmi bardzo tajemniczo. Zatem rozszyfrujmy skąd taka nazwa.

Czym jest gradient?

Gradient – pole wektorowe wskazujące kierunki najszybszych wzrostów wartości danego pola skalarnego.

Definicja z portalu wikipedia

Aby zwizualizować to zagadnienie wyobraź sobie, że w jednym garnku gotujesz zupę dla 300 osób. Aby zupa się zmieściła garnek musi być olbrzymi. We wnętrzu tak wielkiego naczynia temperatura rozkłada się nierównomiernie – gdzieś będzie cieplej a gdzieś zimniej. Na szczęście masz pod ręką 🙂 funkcję T(x,y,z), która mówi o temperaturze w garnku. Zmienne x, y, z to konkretny punkt w przestrzeni, w którym ta funkcja zwraca mam temperaturę. Wówczas gradient funkcji T wskazywałby nam kierunek, gdzie temperatura jest najmniejsza.

Z tego powodu gradient będzie wykorzystywany do wskazywania kierunku, w którym nasz model ma się poprawiać. Ale największym zagrożeniem jest to, że możemy wpaść w minimum lokalne.

A czym jest Boosting?

Boosting to tak zwane przyśpieszenie. Polega na tym, że w każdym kolejnym kroku chcemy poprawić wcześniejsze błędy.

W pierwszym kroku budujemy proste drzewo decyzyjne. Następnie, mając informacje o działaniu tego drzewa, budujemy kolejne drzewko, które próbuje naprawić błąd tego wcześniejszego. I tak dalej. Uruchamiamy kolejne iteracje działające w taki sam sposób, tzn. próbując naprawiać wszystkie wcześniejsze decyzje. A korzystając z gradientu wiemy, w którym kierunku należy to robić. To jest idea przyśpieszonego uczenia.

Metoda ta trochę przypomina ideę metodologii Agile, gdzie praca odbywa się w sprintach i po każdym przebiegu staramy się ulepszać proces.

Mam nadzieję, że mój Agile Coach Piotrek nie będzie się wstydził za mnie za powyższą wizualizację, na której chciałem pokazać moje rozumienie idei Agile.

A jak to działa dokładniej?

Bardziej matematycznie można byłoby to podsumować, że po każdym przebiegu próbujemy dopasować nasz predyktor do błędu resztkowego popełnionego przez poprzedni predyktor.

Mam nadzieję, że poniższa wizualizacja bardziej Wam to wyjaśni.

Krok 0 – tutaj mamy przedstawiony nasz zbiór uczący. Poniższe wartości (kropki) będziemy starali się przewidywać.

Krok I – budujemy pierwsze proste drzewo decyzyjne. Prognozę drzewa nanosimy na wykres zieloną kreską.

Krok II – naprawiamy błędy wcześniejszego drzewa, czyli wyliczamy reszty i dla nich budujemy kolejne drzewo. Reszty wyliczamy poprzez odjęcie od wartości rzeczywistej wartości przewidywanej.

Teraz możemy zobaczyć jak nasz predyktor (suma predyktorów h1 i h2) po dwóch krokach się polepszył.

Widać, że coraz lepiej odwzorowuje wartość do przewidywania, mimo że były dopiero dwa kroki. Potwórzmy ten cykl jeszcze raz

Krok III – naprawiamy błędy wcześniejszych dwóch drzew. Poniżej wykres reszt oraz kolejny predyktor.

Teraz możemy zobaczyć, jak nasz predyktor (suma predultora h1, h2 i h3) polepszył się po trzech krokach.

Porównajmy jeszcze kolejne predyktory, aby zobaczyć jak się w tym konkretnym przykładzie dopasowywał.

Mam nadzieję, że teraz jest już w miarę jasne, co stoi za tą matematyką. Gdybyś chciał dokładniej poznać matematyczne zależności, to pod tym linkiem znajdziesz świetny artykuł napisany na Analytics Vidhya.

Jakie są rodzaje implementacji?

Wzmocnienie gradientowe pierwszy raz zostało zaprezentowane w Arcing the Edge w 1997, natomiast w ostatnim dziesięcioleciu zostało udoskonalone. Istnieje wiele różnych rodzajów implementacji.

Z doświadczenia wiem, że wybierając którąkolwiek z pierwszych dwóch metod będziecie mieć bardzo zbliżone wyniki. Jeśli zatem nie walczycie o miejsca po przecinku (np. konkurs na Kaggle), to nie będzie dużej różnicy.

Jakie są podstawowe hiperparametry, z których korzystam?

Osobiście mam największe doświadczenie z implementacją XGB, zatem przybliżę Wam pokrótce hiperparametry dla tej metody wzmocnienia gradietowego.

Główne:

Do walki z przeuczeniem:

Kolejne dwa to regularyzacje (również pomagają w walce z overfittingiem czyli przeuczeniem).

Można myśleć, że im większa alpha, tym mniej charakterystyk jest branych pod uwagę i wyłapywane są bardziej istotne. Stopniowo odrzuca współliniowe atrybuty i pozostawia zbiór najbardziej istotnych.

Intuicyjnie im większe lambda, tym wagi będą bliższe zeru.

Więcej o regularyzacji możecie zobaczyć na filmiku profesora Aleksandra Ihlera.

Pozostałe parametry

Są jeszcze pozostałe parametry, które zostaną dobrane. Warto mieć świadomość, że można je modyfikować:

W razie potrzeby istnieje możliwość zdefiniowania własnych metryk sukcesu lub użycia wbudowanych.

Jak napisać kod w Python?

Wczytanie bibliotek:

import numpy as np
import pandas as pd
import xgboost as xgb 

from sklearn.metrics import roc_auc_score, accuracy_score # wczytanie metryk sukcesu
from sklearn.model_selection import train_test_split

pd.options.display.max_columns = 250

Abyśmy mogli zobaczyć, jak działa gradient boosting proponuję pobrać dane z Kaggle z konkursu Santander (link do przykładowych danych). Są one już na tyle oczyszczone, że nie ma potrzeby robić żadnej obróbki. Zatem będą idealne do szybkiej implementacji gradient boosting’u. Pobierz jedynie zbiór train.csv, który podzielimy na zbiór do budowy modelu oraz dwa walidacyjne.

Wczytujemy dane oraz możemy się im przyjrzeć.

df = pd.read_csv('../data_raw/train.csv')
print(df.shape)
df.head()

Możemy sobie przygotować funkcje pomocnicze, które pomogą przygotować wczytanie danych do odpowiednich zborów:

def get_feats(df): #dzieki temu nie będę brał niepotrzebnych kolumn do modelowania
    feats = [f for f in df.columns if f not in ['ID_code','target']]
    return feats

def get_X(df): #do pobierania macierzy X, czyli cech które będę starał się opisywać modelem
    return df[ get_feats(df) ].values

def get_y(df, target_var='target'): #wektor y - informacja o predykcji: w przypadku przykładowych danych to ":target"
    return df[target_var].values

Warto jeszcze sprawdzić przykładowe metryki jak zadziałał nasz model. Zróbmy to na podstawie metryk AUC oraz GINI:

def create_measures(y,y_pred): 
    score_test = roc_auc_score(y, y_pred)
    Gini_index = 2*score_test - 1
        
    d = {'AUC': [round(score_test,4)], 'GINI': [round(Gini_index,4)]}
    d = pd.DataFrame.from_dict(d)
    
    return d

def calculating_metrics(X_train, X_val, X_oot, y_train, y_val, y_oot):
    #prawdopodobieństwa:
    y_pred_test = model.predict_proba(X_train)[:, 1]
    y_pred_test_val = model.predict_proba(X_val)[:, 1]
    y_pred_test_oot = model.predict_proba(X_oot)[:, 1]

    test = create_measures(y_train,y_pred_test)
    val = create_measures(y_val,y_pred_test_val)
    oot = create_measures(y_oot,y_pred_test_oot) 

    measures =  pd.concat([test,val,oot])
    measures.set_index([pd.Index(['TRAIN', 'VAL', 'OOT'])]) 
    return measures

Dokonujemy podziału zbioru wejściowego:

X, y = get_X(df), get_y(df) 
# świadomie chcę mieć dwa niezależne zbiory walidacyjne. Przyda mi się to na przyszłość jak będę robił optymalizację hiperparametrów
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.4, random_state=2019)
X_test, X_val, y_test, y_val = train_test_split(X_test, y_test, test_size=0.5, random_state=2019)

print('TRAIN:',X_train.shape, y_train.shape)
print('TEST:',X_test.shape, y_test.shape)
print('VALIDATION:',X_val.shape, y_val.shape)

i budujemy model na parametrach domyślnych:

model = xgb.XGBClassifier()
model.fit(X_train, y_train)  
measures = calculating_metrics(X_train, X_test, X_val, y_train, y_test, y_val)
measures

Możesz dla siebie pobawić się różnymi parametrami, które opisałem wcześniej i sprawdzić jak wpływają na moc modelu. Poniżej przykład, jak wystarczy odrobinę zmodyfikować parametry, aby uzyskać na próbkach walidacyjnych znaczną poprawę o ponad 7 punktów procentowych wg metryki GINI.

model = xgb.XGBClassifier(max_depth = 5, n_estimators=150, subsample = 0.75)
model.fit(X_train, y_train)  
measures = calculating_metrics(X_train, X_test, X_val, y_train, y_test, y_val)
measures

Chcesz inną implementacje np. LightGBM? Oczywiście to żaden problem. Wystarczy wczytać model (import) oraz podmienić model:

import lightgbm as lgb

model = lgb.LGBMClassifier(max_depth = 5, n_estimators = 150, subsample=0.75)
model.fit(X_train, y_train)  
measures = calculating_metrics(X_train, X_test, X_val, y_train, y_test, y_val)
measures

Zobacz od razu na powyższym przykładzie, że różnica w mocy na podobnych parametrach to zaledwie 0.0019.

Kod dostępny na Github:

https://github.com/MamczurMiroslaw/gradient_boosting_example/blob/master/Gradient_boosting_example.ipynb

Podsumowanie

Mam nadzieję, że w zrozumiały sposób przedstawiłem Wam metodę gradient boosting’u i dostrzegliście jej super moce związane z prostotą i bardzo skutecznym działaniem. Oraz że będziecie z niej korzystać przy kolejnych projektach, jeśli do tej pory nie mieliście takiej okazji.

Życzę najlepszych modeli!

Exit mobile version