Pielęgnowanie drzew decyzyjnych

Podobnie jak wszystkie modele nadzorowanych systemów uczących się, drzewa decyzyjne są trenowane w taki sposób, aby jak najlepiej wyjaśniać zestaw przykładów treningowych. Optymalne wytrenowanie schematu decyzyjnego to problem trudny w rozwiązaniu w czasie wielomianowym. Dlatego trening jest zwykle przeprowadzany za pomocą heurystyki – łatwego w utworzeniu algorytmu uczenia się, który daje drzewo decyzyjne nieoptymalne, ale zbliżone do optymalnego.

Większość algorytmów używanych do trenowania drzew decyzyjnych działa zgodnie ze strategią żarłocznego dziel i podbijaj. Algorytm zaczyna od utworzenia pojedynczego węzła (węzła głównego) i rekursywnie oraz żarłocznie rozbudowuje drzewo decyzyjne.

W każdym węźle wszystkie możliwe warunki są oceniane i przypisywane. Algorytm wybiera „najlepszy” stan, czyli stan o najwyższym wyniku. Na razie wystarczy wiedzieć, że wynik to dane, które są skorelowane z zadaniem, a warunki są wybierane w celu maksymalizacji tych danych.

Na przykład w zbiorze danych Palmer Penguins (używanym w przypadku przykładów kodu w dalszej części tego kursu) większość pingwinów Adelijskiego i Tundrowego ma dziób dłuższy niż 16 mm, a pingwiny białolica mają krótsze dzioby. Dlatego warunek bill_length_mm ≥ 16 zapewnia niemal idealne prognozy dotyczące pingwinów śnieżnych, ale nie pozwala odróżnić pingwinów Adeli i pingwinów białobrewych. Algorytm prawdopodobnie wybierze ten warunek.

Jeden warunek prowadzący do 2 węzłów wyjściowych. Warunek to „bill_length_mm >= 16”.
Jeśli tak, liść należy do pingwina białobrzeżnego lub maskowego.  Jeśli nie, liść jest „Gentoo”.

Rysunek 7. Pierwszy krok w hodowaniu drzewa.

 

Następnie algorytm powtarza się rekurencyjnie i niezależnie na obu podrzędnych węzłach. Jeśli nie zostanie znaleziony żaden warunek, węzeł stanie się liściem. Prognoza na poziomie liścia jest określana jako najbardziej reprezentatywna wartość etykiety w przypadku przykładów.

Algorytm działa w ten sposób:

def train_decision_tree(training_examples):
  root = create_root() # Create a decision tree with a single empty root.
  grow_tree(root, training_examples) # Grow the root node.
  return root

def grow_tree(node, examples):
  condition = find_best_condition(examples) # Find the best condition.

  if condition is None:
    # No satisfying conditions were found, therefore the grow of the branch stops.
    set_leaf_prediction(node, examples)
    return

  # Create two childrens for the node.
  positive_child, negative_child = split_node(node, condition)

  # List the training examples used by each children.
  negative_examples = [example for example in examples if not condition(example)]
  positive_examples = [example for example in examples if condition(example)]

  # Continue the growth of the children.
  grow_tree(negative_child, negative_examples)
  grow_tree(positive_child, positive_examples)

Przyjrzyjmy się dokładniej etapom trenowania konkretnego schematu decyzyjnego.

Krok 1. Utwórz element rdzeniowy:

Węzeł ze znakiem zapytania.

Krok 2. Rozwiń węzeł 1. Wykryto warunek „x1 ≥ 1”. Tworzone są 2 węzły podrzędne:

Węzeł główny prowadzący do 2 niezdefiniowanych węzłów.

Krok 3. Rozwiń węzeł 2. Nie znaleziono warunków spełniających kryteria. Zmień węzeł na liść:

Węzeł główny prowadzący do 2 niezdefiniowanych węzłów.

Krok 4. Rozwiń węzeł 3. Wykryto warunek „x2 ≥ 0,5”. Tworzone są 2 węzły podrzędne.

Węzeł główny, warunek i 3 liście.

Istnieją też inne metody rozwijania drzewek decyzji. Popularną alternatywą jest globalna optymalizacja węzłów zamiast stosowania strategii „dziel i rządź”.

Kod YDF
W YDF drzewa decyzyjne są domyślnie rozrastrzane za pomocą algorytmu „łapczywego dziel i podbijaj” opisanego powyżej. Możesz też włączyć globalny wzrost za pomocą parametru growing_strategy="BEST_FIRST_GLOBAL". Aby dowiedzieć się więcej, zapoznaj się z dokumentacją.

W zależności od liczby i typu danych wejściowych liczba możliwych warunków dla danego węzła może być ogromna, a na ogół nieskończona. Na przykład przy założeniu warunku progowego $\mathrm{feature}_i \geq t$ kombinacja wszystkich możliwych wartości progowych dla $t \in \mathbb{R}$ jest nieskończona.

Procedura odpowiedzialna za znajdowanie najlepszego warunku nosi nazwę splitter. Ponieważ musi testować wiele możliwych warunków, splittery są wąskim gardłem podczas trenowania drzewa decyzyjnego.

Wynik maksymalizowany przez splitter zależy od zadania. Przykład:

Istnieje wiele algorytmów splitterów, z różnym poziomem obsługi:

  • Typy cech, np. liczbowe, kategorialne, tekstowe
  • zadanie, np. klasyfikacja binarna, klasyfikacja wieloklasowa, regresja;
  • Typ warunku, np. warunek progowy, warunek w zestawie, warunek ukośny
  • kryteria regularyzacji, np. rozdzielacze dokładne lub przybliżone w przypadku warunków progowych;

Dodatkowo istnieją równoważne warianty rozdzielaczy z różnymi kompromisami dotyczącymi wykorzystania pamięci, procesora, rozkładu obliczeń itp.

Kod YDF
W YDF rozdzielacze są wybierane domyślnie na podstawie automatycznie wykrytego (lub ręcznie określonego) typu cechy, hiperparametrów i szacowanej szybkości każdego rozdzielacza (podczas trenowania YDF uruchamia mały model, aby przewidzieć szybkość każdego potencjalnego rozdzielacza).