Względne zmienne znaczenie dla wzmocnienia

33

Szukam wyjaśnienia, w jaki sposób względna ważność zmiennych jest obliczana w drzewach wspomaganych gradientem, które nie jest zbyt ogólne / uproszczone, takie jak:

Miary są oparte na liczbie wyborów zmiennej do podziału, ważone przez podniesienie kwadratu do modelu w wyniku każdego podziału i uśredniane dla wszystkich drzew . [ Elith i in. 2008, Roboczy przewodnik po ulepszonych drzewach regresji ]

A to mniej abstrakcyjne niż:

jajot2)^(T.)=t=1jot-1jat2)^1(vt=jot)

Gdzie sumowanie się nieterminalowi węzły o -końcową węzeł drzewa , jest zmienny podział związany z węzłem i jest odpowiedni empiryczny poprawa kwadrat błędu w wyniku podziału, zdefiniowanego jako , gdzie oznaczają odpowiednio lewą i prawą odpowiedź córki, a są odpowiednimi sumami wag. J T v t t ^ i 2 t i 2 ( R l , R r ) = w l w rtjotT.vttjat2)^ja2)(Rl,Rr)=wlwrwl+wr(yl¯-yr¯)2)yl¯,yr¯wl,wr[ Friedman 2001, Przybliżenie funkcji chciwości: maszyna zwiększająca gradient ]

Wreszcie nie uważam, że elementy uczenia statystycznego (Hastie i in. 2008) są bardzo pomocne, ponieważ odpowiedni rozdział (10.13.1 strona 367) smakuje bardzo podobnie do drugiego odnośnika powyżej (który można wyjaśnić przez fakt, że Friedman jest współautorem książki).

PS: Wiem, że miary względnej zmiennej ważności są podane w pliku summary.gbm w pakiecie gbm R. Próbowałem zbadać kod źródłowy, ale nie mogę znaleźć miejsca, w którym odbywa się obliczenie.

Punkty Brownie: Zastanawiam się, jak zdobyć te działki w R.

Antoine
źródło
Właśnie dodałem nową odpowiedź do połączonego pytania o to, jak wykreślić znaczenie zmiennej według klasy, co może być pomocne stackoverflow.com/a/51952918/3277050
patrz 24

Odpowiedzi:

55

Użyję sklearn kodu, jak to jest na ogół znacznie czystsze niż w Rkodzie.

Oto implementacja właściwości feature_importances GradientBoostingClassifier (usunąłem kilka wierszy kodu, które przeszkadzają w tworzeniu koncepcji)

def feature_importances_(self):
    total_sum = np.zeros((self.n_features, ), dtype=np.float64)
    for stage in self.estimators_:
        stage_sum = sum(tree.feature_importances_
                        for tree in stage) / len(stage)
        total_sum += stage_sum

    importances = total_sum / len(self.estimators_)
    return importances

Jest to dość łatwe do zrozumienia. self.estimators_to tablica zawierająca pojedyncze drzewa w booster, więc pętla for iteruje po poszczególnych drzewach. Jest jeden problem z

stage_sum = sum(tree.feature_importances_
                for tree in stage) / len(stage)

zajmuje się to przypadkiem odpowiedzi niebinarnej. Tutaj dopasowujemy wiele drzew na każdym etapie w sposób jeden do wszystkich. Najprostszym koncepcyjnie jest skupienie się na przypadku binarnym, w którym suma ma jedno podsumowanie, i to jest sprawiedliwe tree.feature_importances_. Więc w przypadku binarnym możemy przepisać to wszystko jako

def feature_importances_(self):
    total_sum = np.zeros((self.n_features, ), dtype=np.float64)
    for tree in self.estimators_:
        total_sum += tree.feature_importances_ 
    importances = total_sum / len(self.estimators_)
    return importances

Innymi słowy, podsumuj ważność cech poszczególnych drzew, a następnie podziel przez całkowitą liczbę drzew . Pozostaje sprawdzić, jak obliczyć ważność operacji dla pojedynczego drzewa.

Obliczanie ważności drzewa jest realizowane na poziomie cytonu , ale nadal jest możliwe. Oto oczyszczona wersja kodu

cpdef compute_feature_importances(self, normalize=True):
    """Computes the importance of each feature (aka variable)."""

    while node != end_node:
        if node.left_child != _TREE_LEAF:
            # ... and node.right_child != _TREE_LEAF:
            left = &nodes[node.left_child]
            right = &nodes[node.right_child]

            importance_data[node.feature] += (
                node.weighted_n_node_samples * node.impurity -
                left.weighted_n_node_samples * left.impurity -
                right.weighted_n_node_samples * right.impurity)
        node += 1

    importances /= nodes[0].weighted_n_node_samples

    return importances

To jest całkiem proste. Iteruj przez węzły drzewa. Dopóki nie znajdujesz się w węźle liścia, oblicz ważoną redukcję czystości węzła na podstawie podziału w tym węźle i przypisz go do funkcji, która została podzielona na

importance_data[node.feature] += (
    node.weighted_n_node_samples * node.impurity -
    left.weighted_n_node_samples * left.impurity -
    right.weighted_n_node_samples * right.impurity)

Następnie, po zakończeniu, podziel to wszystko przez całkowitą wagę danych (w większości przypadków liczbę obserwacji)

importances /= nodes[0].weighted_n_node_samples

Warto przypomnieć, że nieczystość to powszechna nazwa metryki, którą należy stosować przy określaniu podziału, jaki należy wykonać podczas uprawy drzewa. W tym świetle podsumowujemy, jak bardzo podział na każdą cechę pozwolił nam zmniejszyć zanieczyszczenie wszystkich podziałów w drzewie.

W kontekście zwiększania gradientu drzewa te są zawsze drzewami regresji (chciwie minimalizują błąd kwadratu) dopasowującymi się do gradientu funkcji straty.

Matthew Drury
źródło
Bardzo dziękuję za tę bardzo szczegółową odpowiedź. Daj mi trochę czasu, aby dokładnie go przejrzeć, zanim zaakceptuję.
Antoine,
4
Chociaż wydaje się, że można zastosować różne kryteria zanieczyszczenia, indeks Giniego nie był kryterium zastosowanym przez Friedmana. Jak wspomniano w moim pytaniu i wierszu 878 trzeciego linku, Friedman zastosował kryterium nieczystości błędu średniej kwadratowej z wynikiem poprawy . Gdybyś mógł zaktualizować tę sekcję swojej odpowiedzi, byłoby świetnie. I tak, masz rację, wydaje się, że wagi są rzeczywiście liczbą obserwacji.
Antoine,
3
a może poprawiłoby to twoją odpowiedź, aby zachować zarówno części dotyczące indeksu Giniego, jak i pierwotnego kryterium Friedmana, podkreślając, że pierwsza służy do klasyfikacji, a druga do regresji?
Antoine,
Antoine, dzięki za tę aktualizację. Naprawdę pomocna jest wiedza, że ​​średni błąd kwadratu to kryteria poprawy zastosowane dla drzew regresji. Nie było oczywiste, w jaki sposób zostanie to wykorzystane do klasyfikacji. Jednak nawet przy zwiększaniu gradientu do klasyfikacji uważam, że drzewa regresji są nadal używane, w przeciwieństwie do drzew klasyfikacji. Przynajmniej w pythonie analiza regresji jest przeprowadzana na bieżącym błędzie na każdym etapie wzmocnienia.
Dość Nerdy,
Macie rację co do drzew regresji.
Matthew Drury