Относительная важность переменной для повышения


33

Я ищу объяснение того, как относительная важность переменной вычисляется в деревьях с градиентным усилением, которое не является слишком общим / упрощенным, например:

Измерения основаны на количестве раз, которое переменная была выбрана для расщепления, взвешенной по квадрату улучшения модели в результате каждого расщепления и усредненном по всем деревьям . [ Элит и др. 2008, Рабочее руководство по ускоренным деревьям регрессии. ]

И это менее абстрактно, чем:

Ij2^(T)=t=1J1it2^1(vt=j)

Там , где суммирование ведется по нетерминалу узлы из терминальном узла дерева , является переменным расщепление , связанный с узлом и является соответствующим эмпирическим улучшением в квадрате ошибки в результате разделения, определяемого как , где - левое и правое дочерние средства ответа соответственно, а w_ { l}, w_ {r} - соответствующие суммы весов. J T v t t ^ i 2 t i 2 ( R l , R r ) = w l w rtJTvttit2^i2(Rl,Rr)=wlwrwl+wr(yl¯yr¯)2yl¯,yr¯wl,wr[ Фридман 2001, приближение функции Жадности: машина повышения градиента ]

Наконец, я не нашел Элементы Статистического Обучения (Hastie et al. 2008) очень полезными для прочтения здесь, так как соответствующий раздел (10.13.1 стр. 367) на вкус очень похож на вторую ссылку выше (которая может быть объяснена тем, что Фридман является соавтором книги).

PS: я знаю, что относительные переменные значения важности даны summary.gbm в пакете gbm R. Я пытался исследовать исходный код, но я не могу найти, где происходит фактическое вычисление.

Брауни указывает: мне интересно, как получить эти участки в R.


Я только что добавил новый ответ на связанный вопрос о том, как
изобразить

Ответы:


55

Я буду использовать код sklearn , так как он, как правило, намного чище, чем Rкод.

Вот реализация свойства feature_importances класса GradientBoostingClassifier (я удалил несколько строк кода, которые мешают концептуальным вещам)

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

Это довольно легко понять. self.estimators_это массив, содержащий отдельные деревья в бустере, поэтому цикл for выполняет итерации по отдельным деревьям. Есть один сбой с

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

это заботится о случае недвоичного ответа. Здесь мы вписываем несколько деревьев на каждом этапе по принципу «один против всех». Проще всего концептуально сосредоточиться на двоичном случае, где сумма имеет одно слагаемое, и это справедливо tree.feature_importances_. Таким образом, в двоичном случае, мы можем переписать все это как

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

Итак, на словах, суммируйте значения свойств отдельных деревьев, а затем разделите на общее количество деревьев . Осталось посмотреть, как рассчитать значения объектов для одного дерева.

Вычисление важности дерева реализовано на уровне Cython , но оно все еще можно отслеживать. Вот очищенная версия кода

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

Это довольно просто. Итерация по узлам дерева. Пока вы не находитесь на листовом узле, рассчитайте взвешенное снижение чистоты узла из разделения на этом узле и отнесите его к объекту, который был разделен на

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)

Затем, когда это будет сделано, разделите все это на общий вес данных (в большинстве случаев количество наблюдений)

importances /= nodes[0].weighted_n_node_samples

Стоит напомнить, что примесь - это общее название для метрики, которое используется при определении того, что разделить, чтобы сделать при выращивании дерева. В этом свете мы просто суммируем, насколько расщепление на каждом объекте позволило нам уменьшить примеси во всех расщеплениях в дереве.

В контексте повышения градиента эти деревья всегда являются деревьями регрессии (жадно минимизируют квадратичную ошибку), подходящими для градиента функции потерь.


Большое спасибо за этот очень подробный ответ. Позвольте мне некоторое время тщательно пройти через это, прежде чем я приму это.
Антуан

4
Хотя кажется, что можно использовать различные критерии примесей, индекс Джини не был критерием, используемым Фридманом. Как уже упоминалось в моем вопросе и строке 878 вашей третьей ссылки, Фридман использовал среднеквадратичный критерий примесной ошибки с оценкой улучшения . Если бы вы могли обновить этот раздел своего ответа, это было бы здорово. И да, вы правы, кажется, что вес действительно число наблюдений.
Антуан

3
или, может быть, ваш ответ будет еще лучше, если оставить обе части об индексе Джини и первоначальный критерий Фридмана, подчеркнув, что первое используется для классификации, а второе - для регрессии?
Антуан

Антуан, спасибо за это обновление. Действительно полезно знать, что среднеквадратическая ошибка является критерием улучшения, используемым для деревьев регрессии. Не было очевидно, как это будет использоваться для классификации. Однако даже при повышении градиента для классификации я думаю, что деревья регрессии все еще используются, в отличие от деревьев классификации. По крайней мере, в python регрессионный анализ проводится по текущей ошибке на каждом этапе повышения.
Довольно Nerdy

Вы правы насчет деревьев регрессии.
Мэтью Друри
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.