Importância relativa variável para impulsionar

33

Estou procurando uma explicação de como a importância da variável relativa é calculada nas árvores com gradiente de aumento que não é excessivamente geral / simplista como:

As medidas são baseadas no número de vezes que uma variável é selecionada para divisão, ponderada pela melhoria ao quadrado do modelo como resultado de cada divisão e calculada a média de todas as árvores . [ Elith et al. 2008, Um guia de trabalho para aumentar as árvores de regressão ]

E isso é menos abstrato do que:

Euj2^(T)=t=1J-1Eut2^1(vt=j)

Sempre que o somatório é sobre o não-terminal nodos do J árvore de nós -terminal t , v t é a variável de divisão associada com o nó t , e ^ i 2 t é a melhoria empírica correspondente em erro quadrado como um resultado da divisão, definida como i 2 ( R l , R r ) = w l w rtJTvttEut2^, onde ¯ y l , ¯ y r estão à esquerda e meios de resposta filha direita, respectivamente, ewl,wrsão as somas correspondentes dos pesos. Eu2(Reu,Rr)=WeuWrWeu+Wr(yeu¯-yr¯)2yeu¯,yr¯Weu,Wr[Friedman 2001, aproximação de função gananciosa: uma máquina de aumento de gradiente]

Finalmente, não achei os Elementos de Aprendizagem Estatística (Hastie et al. 2008) uma leitura muito útil aqui, pois a seção relevante (10.13.1 página 367) tem um gosto muito semelhante à segunda referência acima (que pode ser explicada pelo fato de Friedman ser co-autor do livro).

PS: Eu sei que medidas de importância variável são dadas pelo summary.gbm no pacote gbm R. Tentei explorar o código-fonte, mas não consigo encontrar onde o cálculo real ocorre.

Brownie aponta: Gostaria de saber como obter essas parcelas em R.

Antoine
fonte
Acabei de adicionar uma nova resposta para a pergunta relacionada sobre como traçar importância variável classe que pode ser útil stackoverflow.com/a/51952918/3277050
see24

Respostas:

55

Vou usar o código sklearn , pois geralmente é muito mais limpo que o Rcódigo.

Aqui está a implementação da propriedade feature_importances do GradientBoostingClassifier (removi algumas linhas de código que atrapalham o material conceitual)

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

Isso é muito fácil de entender. self.estimators_é uma matriz que contém as árvores individuais no booster, portanto, o loop for está iterando sobre as árvores individuais. Há um hickup com o

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

isso é cuidar do caso de resposta não binária. Aqui, ajustamos várias árvores em cada estágio de uma maneira um contra todos. É conceitualmente mais simples se concentrar no caso binário, em que a soma tem um somatório, e isso é justo tree.feature_importances_. Portanto, no caso binário, podemos reescrever tudo isso como

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

Assim, em palavras, resuma as importâncias das árvores individuais e divida pelo número total de árvores . Resta ver como calcular as importâncias do recurso para uma única árvore.

O cálculo da importância de uma árvore é implementado no nível do cython , mas ainda pode ser seguido. Aqui está uma versão limpa do código

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

Isso é bem simples. Iterar através dos nós da árvore. Desde que você não esteja em um nó folha, calcule a redução ponderada na pureza do nó a partir da divisão nesse nó e atribua-a ao recurso que foi dividido em

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)

Em seguida, quando terminar, divida tudo pelo peso total dos dados (na maioria dos casos, o número de observações)

importances /= nodes[0].weighted_n_node_samples

Vale lembrar que o impureza é um nome comum para a métrica usar ao determinar qual divisão fazer ao cultivar uma árvore. Sob essa luz, estamos simplesmente resumindo o quanto a divisão em cada recurso nos permitiu reduzir a impureza em todas as divisões na árvore.

No contexto do aumento de gradiente, essas árvores são sempre árvores de regressão (minimizam o erro ao quadrado com avidez) ajustadas ao gradiente da função de perda.

Matthew Drury
fonte
Muito obrigado por esta resposta muito detalhada. Deixe-me algum tempo para analisá-lo cuidadosamente antes de aceitá-lo.
Antoine
4
Embora pareça que vários critérios de impureza possam ser usados, o índice de Gini não foi o critério usado por Friedman. Como mencionado na minha pergunta e na linha 878 do seu terceiro link, Friedman usou o critério da impureza do erro quadrado médio com pontuação de melhoria . Se você pudesse atualizar esta seção de sua resposta, seria ótimo. E sim, você está certo, parece que os pesos são realmente o número de observações.
Antoine
3
ou talvez sua resposta seja ainda melhor, mantendo ambas as partes sobre o índice de Gini e o critério original de Friedman, enfatizando que a primeira é usada para classificação e a segunda para regressão?
Antoine
Antoine, obrigado por esta atualização. É realmente útil saber que o erro quadrático médio é o critério de melhoria usado para as árvores de regressão. Não era óbvio como isso seria usado para a classificação. No entanto, mesmo no aumento de gradiente para classificação, acho que ainda estão sendo usadas árvores de regressão, em oposição às árvores de classificação. Pelo menos em python, a análise de regressão está sendo feita sobre o erro atual em cada estágio de aumento.
Bastante Nerdy
Vocês estão corretos sobre as árvores de regressão.
Matthew Drury