Jak działa las losowy (random forest)?

miroslawmamczur.pl 4 miesięcy temu

– Tato! gwałtownie do stołu! Jak zawsze na Ciebie czekamy – przybiegła z pretensjami Otylka.

– Już Skarbie. Jeszcze sekundka. Sprawdzę tylko, czy już działa poprawnie mój las losowy.

– Ale tatusiu, przecież nie masz tutaj żadnych drzew.

– Oj mam choćby setki Skarbie. Ale w komputerze – zobaczyłem niezrozumienie w oczach młodszej córeczki, więc spróbowałem jeszcze raz. – Wyobraź sobie, iż gotowanie to szukanie tajemniczego przepisu na pyszne ciasto. W tym celu zatrudniłem 100 kucharzy, z których każdy próbuje zrobić to ciasto najlepiej jak potrafi. A na koniec pytamy każdego z nich, jakie ciasto zrobił. Potem łączymy wszystkie pomysły, aby uzyskać najlepszy przepis.

– To brzmi jak magiczne gotowanie!

– Dokładnie! Las losowy to trochę jak takie magiczne gotowanie w świecie komputerów. Każde drzewo wnosi swój unikalny smak do końcowego dania.

Las losowy, znany również jako random forest, to potężny algorytm uczenia maszynowego, który cieszy się ogromną popularnością ze względu na swoją skuteczność i szerokie zastosowanie. Dzięki swojej elastyczności i prostocie las losowy pozostaje jednym z najbardziej wszechstronnych narzędzi w arsenale uczenia maszynowego. W tym wpisie poznamy tajniki jego działania i rozgryziemy, dlaczego jest tak dobry i skuteczny!

Uwaga! jeżeli nie wiesz, jak tworzone są drzewa decyzyjne, to polecam najpierw zapoznać się z poprzednim wpisem [LINK].

Nasz przykład

Wróćmy do przykładu z poprzedniego wpisu o drzewach decyzyjnych. Naszym zadaniem była klasyfikacja klientów banku, czy dana osoba spłaci kredyt, czy będzie miała problemy z oddaniem pieniędzy z odsetkami.

Algorytm lasu losowego działa w bardzo prosty sposób i przebiega dwuetapowo.

Krok 1. Budujemy zespół N drzew decyzyjnych, tworząc tzw. las losowy. dla wszystkich drzewa decyzyjnego wybieramy losowo X punktów danych ze zbioru uczącego i Y cech. Dla tak przygotowanego zbioru tworzymy niezależne drzewo decyzyjne.

I tutaj od razu mamy odpowiedź na pytanie: “Dlaczego las losowy jest losowy”? Las nazywamy losowym, ponieważ losowość występuje na dwóch etapach.

Po pierwsze każde drzewo dostaje losową liczbę obserwacji ze zwracaniem (boostraping).

Po drugie, każde drzewo ma ten sam zbiór wejściowych cech, ale ostatecznie wybierany jest inny wylosowany podzbiór cech.

Krok 2. Dokonujmy prognozowania dla wszystkich drzewa zbudowanego w pierwszym etapie, a ostateczny wynik (w przypadku klasyfikacji) jest rozpatrywany na podstawie głosowania większościowego. W przypadku regresji możemy wziąć na przykład przewidywaną średnią wartość ze wszystkich drzew.

I to wszystko! A wiesz, iż ideą do takiego podejścia jest tzw. mądrość tłumu?

Mądrość tłumu (ang. the wisdom of the crowd)

Mądrość tłumu to zjawisko, w którym kolektywne opinie lub decyzje wielu osób lub modeli są często trafniejsze niż opinia jednostki działającej samodzielnie.

Najbardziej znanym przykładem mądrości tłumu jest prawdopodobnie konkurs przeprowadzony w 1906 roku na targu bydła w Plymouth. W konkursie wzięło udział osiemset osób (zarówno starzy farmerzy, jak i rodziny z dziećmi), które miały za zadanie oszacować wagę zabitego wołu po oprawieniu.

Francis Galton, znany statystyk, zebrał wszystkie odpowiedzi i był zdumiony, kiedy odkrył, iż średnia wartość tych oszacowań była zaskakująco bliska rzeczywistej wadze zwierzęcia, odbiegając zaledwie o jeden procent!

Tak działa mądrość tłumu! Poprzez zebranie szacunków od różnych osób i wykorzystanie ich do obliczenia przeciętnej wartości, jesteśmy bardziej skłonni zbliżyć się do rzeczywistej odpowiedzi. To właśnie dzięki kombinacji różnych perspektyw i doświadczeń możemy dokonać mądrych decyzji.

Teraz tylko zamieńmy setki osób na setki drzew decyzyjnych i otrzymujemy las.

Historia lasu losowego

Warto również wspomnieć o twórcach i naukowcach, którzy stworzyli tak wspaniały algorytm.

Początki idei lasu losowego sięgają lat 90. XX wieku, kiedy to Tin Kam Ho przyczynił się do stworzenia podstaw tej techniki. Ho w 1995 roku zaproponował „random subspace method„, który wykorzystywał losowo wybrane podzbiory cech w celu tworzenia różnych drzew decyzyjnych.

Na wczesny rozwój koncepcji lasów losowych miała również wpływ praca [LINK] Amita i Gemana, którzy wprowadzili ideę przeszukiwania losowego podzbioru dostępnych decyzji podczas podziału węzła w kontekście uprawy pojedynczego drzewa.

Natomiast w tej chwili znany kształt algorytmu rozwinął Leo Breiman, który zaproponował koncepcję komitetu drzew decyzyjnych, które miałyby współpracować ze sobą, aby uzyskać lepsze wyniki niż pojedyncze drzewo decyzyjne.

Las losowy – zalety

Las losowy to potężne narzędzie w analizie danych, które ma wiele zalet:

  1. Odporność na nadmierne dopasowanie: Dzięki temu, iż las losowy tworzony jest z wielu drzew decyzyjnych na podstawie losowych podzbiorów danych, jest mniej podatny na nadmierne dopasowanie niż pojedyncze drzewa decyzyjne. To oznacza, iż może lepiej radzić sobie z ogólnymi danymi i unikać przetrenowania.
  2. Zdolność do pracy z dużymi zestawami danych: Pomimo tego, iż proces tworzenia losowego lasu może być nieco wolniejszy niż pojedyncze drzewo decyzyjne, przez cały czas jest w stanie efektywnie obsługiwać duże zestawy danych dzięki równoległemu przetwarzaniu.
  3. Stabilność wyników: Ponieważ las losowy opiera się na uśrednianiu wyników z wielu drzew decyzyjnych, jego wyniki są bardziej stabilne i mniej podatne na zmiany w danych treningowych.
  4. Wykrywanie istotności cech: Las losowy może dostarczyć informacji na temat tego, które cechy są istotne dla prognozowania, co jest przydatne w analizie i interpretacji danych.
  5. Łatwość w użyciu: Las losowy jest stosunkowo łatwy w użyciu i nie wymaga zbyt wielu zaawansowanych ustawień parametrów, co czyni go narzędziem dostępnym dla osób o różnym poziomie doświadczenia w analizie danych.

Las losowy – wady

Oczywiście nie ma idealnych algorytmów. Chociaż las losowy ma wiele zalet, to posiada także pewne wady:

  1. Wolniejsze przetwarzanie: Głównym ograniczeniem lasu losowego jest to, iż duża liczba drzew może sprawić, iż algorytm będzie zbyt wolny i nieskuteczny w przypadku przewidywań w czasie rzeczywistym. Drzewa decyzyjne wymagają większej mocy obliczeniowej w porównaniu z pojedynczym drzewem decyzyjnym czy algorytmem regresji liniowej (regresja) bądź logistycznej (klasyfikacja). W większości zastosowań w świecie rzeczywistym algorytm lasu losowego jest wystarczająco szybki, ale z pewnością mogą zaistnieć sytuacje, w których ważna jest wydajność w czasie wykonywania i preferowane byłoby inne podejście.
  2. Trudność w interpretacji: Wyniki uzyskane z lasu losowego mogą być trudniejsze do interpretacji niż wyniki pojedynczego drzewa decyzyjnego. Ze względu na to, iż las losowy składa się z wielu drzew, zrozumienie, jakie cechy są istotne dla prognozowania, może być bardziej skomplikowane.

Kod w python

Zacznijmy standardowo od zaimportowania wymaganych bibliotek:

from sklearn.datasets import make_classification from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import roc_auc_score import matplotlib.pyplot as plt import time

oraz wygenerowania zbioru danych (szczegóły funkcji `make_classification` w poprzednim wpisie)

# Create a balanced random dataset X, y = make_classification(n_samples=10000, n_features=50, n_classes=2, weights=[0.5, 0.5], random_state=2024) # Division into training and test set X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, stratify=y, random_state=2024) print(f'X_train: {X_train.shape}; X_test: {X_test.shape}; ') print(f'y_train: (1){y_train.sum()}; y_test: (1){y_test.sum()}; ')

Zbudujmy nasz pierwszy model lasu losowego na domyślnych parametrach:

# Create an instance of RandomForestClassifier rf_model = RandomForestClassifier(random_state=2024) # Train the model using your training data rf_model.fit(X_train, y_train) # Make predictions on test data predictions = rf_model.predict(X_test) # Calculating AUC on test data y_pred_proba = rf_model.predict_proba(X_test)[:, 1] auc = round(roc_auc_score(y_test, y_pred_proba),3) print(f"AUC: {auc}")

Moc naszego modelu wynosi według metryki AUC: 0.972. To bardzo dobry wynik na domyślnych parametrach:

# default parameters rf_model.get_params()

Las losowy – hiperparametry

Hiperparametry w lesie losowym służą albo do zwiększenia mocy predykcyjnej modelu, albo do przyspieszenia modelu. Zapoznajmy się z głównymi hiperparametrami lasu losowego, które warto znać.

Stwórzmy najpierw dwie funkcje pomocnicze. Pierwszą do trenowania modelu, która będzie zwracać metrykę AUC i czas:

def rf_calc(params): """ Calculates the AUC score and time taken by a RandomForestClassifier model. """ start_time = time.time() # Build RandomForestClassifier based on imported parameters model = RandomForestClassifier(**params) model.fit(X_train, y_train) # Predict probabilities for the positive class y_pred_proba = model.predict_proba(X_test)[:, 1] # Calculate AUC score auc = round(roc_auc_score(y_test, y_pred_proba), 3) # Calculate the time taken t = round(time.time() - start_time, 2) # return metric auc and time return auc, t

a drugą do rysowania wyników, abyśmy mogli lepiej zrozumieć działanie hiperparametrów.

def chart_for_param_dict(d, param_name, min_auc_lim=0.9): """ Creates a plot showing AUC values and corresponding training times for different parameter values. """ # Extracting data from dictionary and sorting by keys param_keys = sorted(list(d.keys())) auc_values = [d[depth]['auc'] for depth in param_keys] time_values = [d[depth]['time'] for depth in param_keys] # Creating the plot with specified figure size fig, ax1 = plt.subplots(figsize=(10, 5)) # Plotting AUC values against the range of indices bars = ax1.bar(range(len(param_keys)), auc_values, color='gray', label='AUC') ax1.set_xlabel('Index') ax1.set_ylabel('AUC') ax1.set_xticks(range(len(param_keys))) ax1.set_xticklabels(param_keys) ax1.tick_params('y') ax1.set_ylim(min_auc_lim, 1.01) # Creating secondary y-axis for time values ax2 = ax1.twinx() ax2.plot(range(len(param_keys)), time_values, color='crimson', label='Time', marker='o') ax2.set_ylabel('Time (sec)', color='crimson') ax2.tick_params('y', colors='crimson') # Adding legend lines1, labels1 = ax1.get_legend_handles_labels() lines2, labels2 = ax2.get_legend_handles_labels() lines = lines1 + lines2 labels = labels1 + labels2 ax1.legend(lines, labels, loc='upper left') # Adding title plt.title(f'AUC and Time for {param_name} parameter') # Adding values on top of each bar for bar, value in zip(bars, auc_values): ax1.text(bar.get_x() + bar.get_width() / 2, bar.get_height() + 0.001, f'{value:.3f}', ha='center', va='bottom', rotation=45, fontsize=8) # Display the plot plt.show()

n_estimators

Parametr n_estimators w przypadku lasów losowych kontroluje liczbę drzew decyzyjnych, które są tworzone w lesie. Im większa wartość tego parametru, tym większa liczba drzew w lesie. Warto pamiętać, iż każde drzewo w lesie losowym działa niezależnie od siebie i generuje swoje własne przewidywania.

param_name = 'n_estimators' results = {} for param in [1, 5, 10, 20, 30, 40, 50, 75, 100, 150, 200, 250, 300, 400, 500, 750, 1000]: # change params params = { param_name:param, 'random_state': 2024, } # build model and save auc & time auc, t = rf_calc(params) results[param] = {'auc': auc, 'time': t} print(f'For {param_name}:{param} auc={auc} in {t} sec.') # Create chart :) chart_for_param_dict(results, param_name, min_auc_lim=0.8)

Większa liczba drzew może prowadzić do lepszych i bardziej stabilnych predykcji. Jednak zwiększenie tej wartości może również zwiększyć czas trenowania modelu.

W naszym przypadku parametr n_estimators =150 wydaje się najlepszy pod względem balansu pomiędzy mocą modelu a czasem liczenia. Aby zyskać 0.001 AUC musielibyśmy zwiększyć liczbę drzew dwukrotnie do 300.

max_depth

Parametr max_depth określa maksymalną głębokość każdego drzewa decyzyjnego w lesie. Głębokość drzewa oznacza, jak wiele podziałów (warstw) może wystąpić przed osiągnięciem liści, które są ostatecznymi węzłami decyzyjnymi.

param_name = 'max_depth' results = {} for param in range(1, 21): # change params params = { param_name:param, 'n_estimators': 150, 'random_state': 2024, } # build model and save auc & time auc, t = rf_calc(params) results[param] = {'auc': auc, 'time': t} print(f'For {param_name}:{param} auc={auc} in {t} sec.') # Create chart :) chart_for_param_dict(results, param_name)

Im większa wartość max_depth, tym bardziej skomplikowane mogą być drzewa decyzyjne, co pozwala na bardziej precyzyjne dopasowanie do danych treningowych. Jednakże, jeżeli drzewo stanie się zbyt głębokie, może to prowadzić do zapamiętywania szczegółów danych treningowych i pogorszenia ogólnej umiejętności uogólniania na nowe dane.

Ja zawsze staram się dobierać taką głębokość, gdzie kolejne poziomy głębokości nie wpływają znacząco na moc modelu. Dzięki temu model jest prostszy, szybszy i łatwiejszy w wytłumaczeniu.

W naszym przypadku wybieram max_depth = 6.

Jeszcze zwróćcie proszę uwagę, iż dla tego zróżnicowanego zbioru danych, choćby zwiększając maksymalną głębokość drzewa do 20(*) to model dzięki losowaniu i tak się zbytnio nie przetrenuje.

Zatem jeżeli boicie się przetrenowania, to używajcie lasu losowego 😉

(*) 2^20 = 1.048.576 – jest to maksymalna ilość liści w drzewie z 20 rozgałęzieniami (czyli głębokość drzewa)

min_samples_split

Parametr min_samples_split kontroluje minimalną liczbę próbek wymaganą do podziału węzła wewnętrznego. jeżeli liczba dostępnych próbek w węźle jest mniejsza niż ustalona wartość min_samples_split, to drzewo nie będzie już dzielić się na mniejsze gałęzie w tym miejscu. To pomaga kontrolować, jak daleko drzewo idzie w dopasowywaniu się do szczegółów danych treningowych, co może pomóc w uniknięciu zbytniego dopasowania.

param_name = 'min_samples_split' results = {} for param in [2, 3, 4, 5, 10, 20, 50, 100, 200, 500, 750, 1000, 2000]: # change params params = { param_name:param, 'n_estimators': 150, 'max_depth': 6, 'random_state': 2024, } # build model and save auc & time auc, t = rf_calc(params) results[param] = {'auc': auc, 'time': t} print(f'For {param_name}:{param} auc={auc} in {t} sec.') # Create chart :) chart_for_param_dict(results, param_name)

Widzimy, jak wraz ze wzrostem parametru zmniejsza się czas, ponieważ mamy mniej możliwości do przeliczenia.

min_samples_leaf

Parametr min_samples_leaf kontroluje minimalną liczbę próbek wymaganą do utworzenia liścia w drzewie decyzyjnym. Można to wyobrazić sobie jako minimalną liczbę przykładów, które muszą być w liściu drzewa. jeżeli liczba próbek w danym węźle spadnie poniżej wartości min_samples_leaf, to nie będziemy już kontynuować dzielenia drzewa w tym kierunku, a ten węzeł będzie traktowany jako liść.

param_name = 'min_samples_leaf' results = {} for param in [1, 2, 3, 4, 5, 10, 20, 50, 100, 200, 500, 750, 1000, 2000]: # change params params = { param_name:param, 'n_estimators': 150, 'max_depth': 6, 'min_samples_split': 20, 'random_state': 2024, } # build model and save auc & time auc, t = rf_calc(params) results[param] = {'auc': auc, 'time': t} print(f'For {param_name}:{param} auc={auc} in {t} sec.') # Create chart :) chart_for_param_dict(results, param_name)

Większa wartość tego parametru może prowadzić do prostszych drzew, co może pomóc w zapobieganiu nadmiernemu dopasowaniu i zwiększeniu umiejętności uogólniania modelu.

Feature Importance

Kolejną wielką zaletą algorytmu lasu losowego jest to, iż bardzo łatwo jest zmierzyć względne znaczenie każdej cechy w przewidywaniu.

Feature Importance w lasach losowych to sposób określania, które cechy mają największy wpływ na predykcję modelu. To pomaga zrozumieć, które informacje są najważniejsze dla modelu. Na przykład, jeżeli model ma przewidywać, czy osoba przeżyje katastrofę, może okazać się, iż wiek lub płeć są kluczowymi czynnikami. To pozwala nam lepiej zrozumieć, jak model działa i jakie cechy są istotne dla naszego problemu.

Feature Importance w bibliotece Scikit-learn dla lasów losowych jest obliczane poprzez pomiar wpływu każdej cechy na poprawę dokładności predykcji. Jest to realizowane poprzez analizę, jak bardzo każda cecha zmniejsza nieczystość węzłów drzewa decyzyjnego w lesie. Im większy spadek nieczystości spowodowany przez daną cechę, tym ważniejsza jest ona dla modelu. Scikit-learn automatycznie oblicza te wartości po przeszkoleniu modelu, skalując je tak, aby suma wszystkich ważności wynosiła jeden. Innymi słowy, im większa ważność cechy, tym bardziej istotna jest ona dla modelu w procesie podejmowania decyzji.

A tutaj, jak wyglądają cechy z naszego przykładu:

import pandas as pd model = RandomForestClassifier( n_estimators=150, max_depth=6, min_samples_split=20, random_state=2024) model.fit(X_train, y_train) # Create a list of feature names if available feature_names = [f"Feature {i}" for i in range(1, X_train.shape[1]+1)] # Calculate Feature Importance feature_importance = model.feature_importances_ # Create a DataFrame to store feature importance data feature_importance_df = pd.DataFrame({'Feature': feature_names, 'Importance': feature_importance}) # Sort the DataFrame by Feature Importance values in descending order feature_importance_df = feature_importance_df.sort_values(by='Importance', ascending=False).reset_index(drop=True) # Add a cumulative importance column feature_importance_df['Cumulative Importance'] = feature_importance_df['Importance'].cumsum() # Display sorted features and their importance - top 10 feature_importance_df.head(10)

Jak widzicie pierwsze 5 z 50 zmiennych zapewnia prawie 96% mocy modelu. Zatem, jeżeli chcielibyśmy upraszczać model, zbudowałbym go tylko na tych 5 zmiennych.

Możecie tę technikę wykorzystać do selekcji zmiennych (ang. feature selection) – o szczegółach możecie przeczytać w tym wpisie.

Podsumowanie

Mam nadzieję, iż przekonałem Wam, iż las losowy jest naprawdę pięknym narzędziem.

Wybór lasu losowego jako algorytmu modelującego jest korzystny na początkowym etapie tworzenia projektu, ze względu na jego prostotę i skuteczność. Zbudowanie nieskutecznego lasu losowego jest trudniejsze niż mogłoby się wydawać, co oznacza, iż choćby podczas eksperymentów uzyskamy sensowne wyniki.

Powodzenia!

Pozdrawiam z całego serducha,

Idź do oryginalnego materiału