Использование scikit для определения вклада каждой функции в предсказание определенного класса

Я использую классификатор дополнительных деревьев scikit:

model = ExtraTreesClassifier(n_estimators=10000, n_jobs=-1, random_state=0)

Как только модель будет установлена ​​и использована для прогнозирования классов, я хотел бы узнать вклад каждой функции в конкретное предсказание класса. Как мне это сделать в scikit учиться? Возможно ли это с дополнительным классификатором деревьев или мне нужно использовать какую-либо другую модель?

Ответ 1

Я проработал аналогичную проблему некоторое время назад и разместил тот же вопрос о Cross Validated. Короткий ответ, что в sklearn нет реализации, которая делает все, что вы хотите.

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

Здесь супер простая реализация, которая берет datamatrix X, список прогнозов Y и массив признаков, а также выводит JSON, описывающий важность каждой функции для каждого класса.

def class_feature_importance(X, Y, feature_importances):
    N, M = X.shape
    X = scale(X)

    out = {}
    for c in set(Y):
        out[c] = dict(
            zip(range(N), np.mean(X[Y==c, :], axis=0)*feature_importances)
        )

    return out

Пример:

import numpy as np
import json
from sklearn.preprocessing import scale

X = np.array([[ 2,  2,  2,  0,  3, -1],
              [ 2,  1,  2, -1,  2,  1],
              [ 0, -3,  0,  1, -2,  0],
              [-1, -1,  1,  1, -1, -1],
              [-1,  0,  0,  2, -3,  1],
              [ 2,  2,  2,  0,  3,  0]], dtype=float)

Y = np.array([0, 0, 1, 1, 1, 0])
feature_importances = np.array([0.1, 0.2, 0.3, 0.2, 0.1, 0.1])
#feature_importances = model._feature_importances

result = class_feature_importance(X, Y, feature_importances)

print json.dumps(result,indent=4)

{
    "0": {
        "0": 0.097014250014533204, 
        "1": 0.16932975630904751, 
        "2": 0.27854300726557774, 
        "3": -0.17407765595569782, 
        "4": 0.0961523947640823, 
        "5": 0.0
    }, 
    "1": {
        "0": -0.097014250014533177, 
        "1": -0.16932975630904754, 
        "2": -0.27854300726557779, 
        "3": 0.17407765595569782, 
        "4": -0.0961523947640823, 
        "5": 0.0
    }
}

Первый уровень ключей в result - это метки классов, а второй уровень ключей - индексы столбцов, т.е. функциональные индексы. Напомним, что большие абсолютные значения соответствуют важности, а знак указывает, насколько важны небольшие (возможно, отрицательные) или большие значения.

Ответ 2

Это изменено из docs

from sklearn import datasets
from sklearn.ensemble import ExtraTreesClassifier

iris = datasets.load_iris()  #sample data
X, y = iris.data, iris.target

model = ExtraTreesClassifier(n_estimators=10000, n_jobs=-1, random_state=0)
model.fit_transform(X,y) # fit the dataset to your model

Я думаю, что feature_importances_ - это то, что вы ищете:

In [13]: model.feature_importances_
Out[13]: array([ 0.09523045,  0.05767901,  0.40150422,  0.44558631])

EDIT

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

from sklearn import datasets
from sklearn.cross_validation import train_test_split
from sklearn.tree import DecisionTreeClassifier

from treeinterpreter import treeinterpreter as ti

iris = datasets.load_iris()  #sample data
X, y = iris.data, iris.target
#split into training and test 
X_train, X_test, y_train, y_test = train_test_split( 
    X, y, test_size=0.33, random_state=0)

# fit the model on the training set
model = DecisionTreeClassifier(random_state=0)
model.fit(X_train,y_train)

Я просто перебираю каждый образец в X_test для иллюстративных целей, это почти точно имитирует сообщение в блоге выше:

for test_sample in range(len(X_test)):
    prediction, bias, contributions = ti.predict(model, X_test[test_sample].reshape(1,4))
    print "Class Prediction", prediction
    print "Bias (trainset prior)", bias

    # now extract contributions for each instance
    for c, feature in zip(contributions[0], iris.feature_names):
        print feature, c

    print '\n'

Первая итерация цикла дает:

Class Prediction [[ 0.  0.  1.]]
Bias (trainset prior) [[ 0.34  0.31  0.35]]
sepal length (cm) [ 0.  0.  0.]
sepal width (cm) [ 0.  0.  0.]
petal length (cm) [ 0.         -0.43939394  0.43939394]
petal width (cm) [-0.34        0.12939394  0.21060606]

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

Ответ 3

Документ "Почему я должен доверять вам?" : Объяснение прогнозов любого классификатора было отправлено через 9 дней после этого вопроса, предоставив алгоритм для общего решения этой проблемы!: -)

Короче говоря, он называется LIME для "локальных интерпретируемых моделей - агностические объяснения" и работает, устанавливая более простую локальную модель вокруг предсказания, которое вы хотите понять.

Что еще, они сделали реализацию python (https://github.com/marcotcr/lime) с довольно подробными примерами того, как использовать его с sklearn. Например, этот один относится к двухклассной случайной проблеме леса по текстовым данным и этот является непрерывным и категориальным. Все они могут быть найдены через README на github.

У авторов был очень продуктивный год в 2016 году в отношении этого поля, поэтому, если вам нравится читать статьи, здесь стартер:

Ответ 4

До сих пор я проверял eli5 и treeinterpreter (оба были упомянуты ранее), и я думаю, что eli5 будет наиболее полезным, потому что я думаю, что у вас больше вариантов и более общий и обновленный.

Тем не менее через некоторое время я применяю eli5 для конкретного случая, и я не смог получить отрицательные вклады для ExtraTreesClassifier, исследуя немного больше Я понял, что получаю значение или вес, как показано здесь здесь. Поскольку меня больше интересовало что-то вроде вклада, как упоминалось в названии этого вопроса, я понимаю, что какая-то особенность может иметь отрицательный эффект, но при измерении важности знак не важен, поэтому функция с положительными эффектами и негативами объединяется.

Потому что меня очень интересовал знак, который я сделал следующим образом: 1) получать взносы для всех случаев 2) провести все результаты, чтобы иметь возможность различать их. Нет очень элегантного решения, возможно, там что-то лучше, я отправляю его здесь, если это поможет.

Я воспроизвожу то же самое, что предыдущий пост.

from sklearn import datasets
from sklearn.cross_validation import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import  (ExtraTreesClassifier, RandomForestClassifier, 
                              AdaBoostClassifier, GradientBoostingClassifier)
import eli5


iris = datasets.load_iris()  #sample data
X, y = iris.data, iris.target
#split into training and test 
X_train, X_test, y_train, y_test = train_test_split( 
    X, y, test_size=0.33, random_state=0)

# fit the model on the training set
#model = DecisionTreeClassifier(random_state=0)
model = ExtraTreesClassifier(n_estimators= 100)

model.fit(X_train,y_train)


aux1 = eli5.sklearn.explain_prediction.explain_prediction_tree_classifier(model,X[0], top=X.shape[1])

aux1

Выход введите описание изображения здесь

Предыдущие результаты работают в одном случае, я хочу запустить все и создать среднее значение:

Вот как выглядит datrame с результатами:

aux1 = eli5.sklearn.explain_prediction.explain_prediction_tree_classifier(model,X[0], top=X.shape[0])
aux1 = eli5.format_as_dataframe(aux1)
# aux1.index = aux1['feature']
# del aux1['target']
aux


target  feature weight  value
0   0   <BIAS>  0.340000    1.0
1   0   x3  0.285764    0.2
2   0   x2  0.267080    1.4
3   0   x1  0.058208    3.5
4   0   x0  0.048949    5.1
5   1   <BIAS>  0.310000    1.0
6   1   x0  -0.004606   5.1
7   1   x1  -0.048211   3.5
8   1   x2  -0.111974   1.4
9   1   x3  -0.145209   0.2
10  2   <BIAS>  0.350000    1.0
11  2   x1  -0.009997   3.5
12  2   x0  -0.044343   5.1
13  2   x3  -0.140554   0.2
14  2   x2  -0.155106   1.4

Итак, я создаю функцию для объединения предыдущих типов таблиц:

def concat_average_dfs(aux2,aux3):
    # Putting the same index together
#     I use the try because I want to use this function recursive and 
#     I could potentially introduce dataframe with those indexes. This
#     is not the best way.
    try:
        aux2.set_index(['feature', 'target'],inplace = True)
    except:
        pass
    try:
        aux3.set_index(['feature', 'target'],inplace = True)
    except:
        pass
    # Concatenating and creating the meand
    aux = pd.DataFrame(pd.concat([aux2['weight'],aux3['weight']]).groupby(level = [0,1]).mean())
    # Return in order
    #return aux.sort_values(['weight'],ascending = [False],inplace = True)
    return aux
aux2 = aux1.copy(deep=True)
aux3 = aux1.copy(deep=True)

concat_average_dfs(aux3,aux2)

введите описание изображения здесь

Итак, теперь мне нужно использовать только предыдущую функцию со всеми желаемыми примерами. Я возьму все население не только на тренировочный набор. Проверьте средний эффект во всех реальных случаях

for i in range(X.shape[0]):


    aux1 = eli5.sklearn.explain_prediction.explain_prediction_tree_classifier(model,X\[i\], top=X.shape\[0\])
    aux1 = eli5.format_as_dataframe(aux1)

    if 'aux_total'  in locals() and 'aux_total' in  globals():
        aux_total = concat_average_dfs(aux1,aux_total)
    else:
        aux_total = aux1

В результате:

введите описание изображения здесь

Лас-таблица показывает средние эффекты каждой функции для всей моей реальной популяции.

Компакт-ноутбук в мой github.