Source de chaux: https://github.com/marcotcr/Lime
source d'interpréteur d'arbre: interprète d'arbre
J'essaie de comprendre comment le DecisionTree
a fait ses prédictions en utilisant Lime et treeinterpreter. Bien que les deux prétendent qu'ils sont capables d'interpréter l'arbre de décision dans leur description. Il semble que les deux interprètent le même DecisionTree
de différentes manières. Autrement dit, l'ordre de contribution des fonctionnalités . Comment est-ce possible? si les deux regardent la même chose et essaient de décrire le même événement mais attribuent une importance dans l'ordre des différences.
À qui devons-nous faire confiance? Surtout là où la fonctionnalité supérieure importe dans la prédiction.
Le code de l'arbre
import sklearn
import sklearn.datasets
import sklearn.ensemble
import numpy as np
import Lime
import Lime.lime_tabular
from __future__ import print_function
np.random.seed(1)
from treeinterpreter import treeinterpreter as ti
from sklearn.tree import DecisionTreeClassifier
iris = sklearn.datasets.load_iris()
dt = DecisionTreeClassifier(random_state=42)
dt.fit(iris.data, iris.target)
n = 100
instances =iris.data[n].reshape(1,-1)
prediction, biases, contributions = ti.predict(dt, instances)
for i in range(len(instances)):
print ("prediction:",prediction)
print ("-"*20)
print ("Feature contributions:")
print ("-"*20)
for c, feature in sorted(Zip(contributions[i],
iris.feature_names),
key=lambda x: ~abs(x[0].any())):
print (feature, c)
Le code pour la chaux
import sklearn
import sklearn.datasets
import sklearn.ensemble
import numpy as np
import Lime
import Lime.lime_tabular
from __future__ import print_function
np.random.seed(1)
from sklearn.tree import DecisionTreeClassifier
iris = sklearn.datasets.load_iris()
dt = DecisionTreeClassifier(random_state=42)
dt.fit(iris.data, iris.target)
explainer = Lime.lime_tabular.LimeTabularExplainer(iris.data, feature_names=iris.feature_names,
class_names=iris.target_names,
discretize_continuous=False)
n = 100
exp = explainer.explain_instance(iris.data[n], dt.predict_proba, num_features=4, top_labels=2)
exp.show_in_notebook(show_table=True, predict_proba= True , show_predicted_value = True , show_all=False)
Regardons d'abord la sortie de l'arborescence .
donc un il a correctement dit qu'il s'agissait d'un virginica . Cependant, en accordant
1) largeur des pétales (cm) puis longueur des pétales (cm)
Regardons maintenant la sortie de Lime
Oui, cela dit que l'algorithme a prédit virginica mais en regardant comment il a fait cette classification, nous voyons clairement ce qui suit
1) longueur des pétales (cm)> largeur des pétales (cm) en citron vert au lieu de la longueur des pétales (cm) <largeur des pétales (cm) comme indiqué dans l'arbre
2) où la largeur et la longueur du sépale étaient prédites à zéro, les revendications de chaux d'une certaine valeur, comme le montrent les images téléchargées
Que se passe-t-il ici ?
Le problème s'aggrave lorsque les fonctionnalités sont supérieures à 1000, où chaque chiffre compte pour prendre une décision.
Lime : Une courte explication de son fonctionnement, tirée de leur page github :
Intuitivement, une explication est une approximation linéaire locale du comportement du modèle. Bien que le modèle puisse être très complexe à l'échelle mondiale, il est plus facile de l'approcher au voisinage d'une instance particulière. Tout en traitant le modèle comme une boîte noire, nous perturbons l'instance que nous voulons expliquer et apprenons un modèle linéaire clairsemé autour de lui, comme explication. La figure ci-dessous illustre l'intuition de cette procédure. La fonction de décision du modèle est représentée par le fond bleu/rose et est clairement non linéaire. La croix rouge vif est l'instance expliquée (appelons-la X). Nous échantillonnons des instances autour de X et les pondérons en fonction de leur proximité avec X (le poids est ici indiqué par la taille). Nous apprenons ensuite un modèle linéaire (ligne pointillée) qui se rapproche bien du modèle au voisinage de X, mais pas nécessairement globalement.
Il y a des informations beaucoup plus détaillées dans divers liens sur la page github.
treeinterpreter : Une explication de son fonctionnement est disponible sur http://blog.datadive.net/interpreting-random-forests/ (ceci est pour la régression; un exemple de classification, qui fonctionne de manière très similaire, peut être trouvé ici ).
En bref: supposons que nous ayons un nœud qui compare la fonction F
à une certaine valeur et fractionne les instances en fonction de cela. Supposons que 50% de toutes les instances atteignant ce nœud appartiennent à la classe C
. Supposons que nous ayons une nouvelle instance, et qu'elle finisse par être affectée à l'enfant gauche de ce nœud, où maintenant 80% de toutes les instances appartiennent à la classe C
. Ensuite, la contribution de la fonction F
pour cette décision est calculée comme 0.8 - 0.5 = 0.3
(plus des termes supplémentaires s'il y a plus de nœuds le long du chemin vers leaf qui utilisent également la fonction F
).
Comparaison: La chose importante à noter est que la chaux est une méthode indépendante du modèle (non spécifique aux arbres de décision/RF), qui est basée sur le linéaire local approximation. Treeinterpreter, d'autre part, fonctionne spécifiquement d'une manière similaire à l'arbre de décision lui-même, et examine vraiment quelles fonctionnalités sont réellement utilisées dans les comparaisons par l'algorithme. Ils font donc fondamentalement des choses très différentes. Lime dit "une fonctionnalité est importante si nous la remuons un peu et cela se traduit par une prédiction différente". Treeinterpreter dit "une fonctionnalité est importante si elle est comparée à un seuil dans l'un de nos nœuds et cela nous a amenés à prendre une scission qui a radicalement changé notre prédiction".
Il est difficile de répondre définitivement. Ils sont probablement tous les deux utiles à leur manière. Intuitivement, vous pourriez être enclin à vous pencher vers un interpréteur d'arbre à première vue, car il a été spécialement créé pour les arbres de décision. Cependant, considérez l'exemple suivant:
F <= 50
, allez à gauche, sinon allez à droite.Ce type de configuration est possible si la majorité des instances vont à gauche, seulement à droite. Supposons maintenant que nous ayons une instance avec F = 49
qui a été assigné à la gauche et finalement assigné à la classe 1. Treeinterpreter ne se souciera pas que F
était vraiment proche de se retrouver de l'autre côté de l'équation dans le nœud racine, et n'assigne qu'une faible contribution de 0.48 - 0.50 = -0.02
. Lime remarquera que changer F
juste un peu changerait complètement les chances.
Laquelle a raison? Ce n'est pas vraiment clair. On pourrait dire que F
était vraiment important car s'il n'avait été que légèrement différent, la prédiction serait différente (alors Lime gagne). Vous pouvez également affirmer que F
n'a pas contribué à notre prédiction finale car nous ne nous sommes presque pas rapprochés d'une décision après avoir inspecté sa valeur, et nous avons encore dû étudier de nombreuses autres fonctionnalités par la suite. Ensuite, treeinterpreter gagne.
Pour avoir une meilleure idée ici, il peut également être utile de tracer l'arbre de décision appris lui-même. Ensuite, vous pouvez suivre manuellement son chemin de décision et décider quelles fonctionnalités vous semblent importantes et/ou voir si vous pouvez comprendre pourquoi Lime et treeinterpreter disent ce qu'ils disent.