Użyję sklearn kodu, jak to jest na ogół znacznie czystsze niż w R
kodzie.
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.