Как извлечь правила принятия решений из дерева решений scikit-learn?

157

Могу ли я извлечь базовые правила принятия решений (или «пути принятия решений») из обученного дерева в дереве решений в виде текстового списка?

Что-то вроде:

if A>0.4 then if B<0.2 then if C>0.8 then class='X'

Спасибо за вашу помощь.

Дрор Хилман
источник
Вы когда-нибудь находили ответ на эту проблему? Я должен экспортировать правила дерева решений в формате шага данных SAS, который почти точно соответствует списку.
Zelazny7
1
Вы можете использовать пакет sklearn-porter для экспорта и переноса деревьев решений (также случайных лесов и расширенных деревьев) в C, Java, JavaScript и другие.
Дарий
Вы можете проверить эту ссылку
kdnuggets.com/2017/05/…..oghesh agrawal

Ответы:

139

Я считаю, что этот ответ является более правильным, чем другие ответы здесь:

from sklearn.tree import _tree

def tree_to_code(tree, feature_names):
    tree_ = tree.tree_
    feature_name = [
        feature_names[i] if i != _tree.TREE_UNDEFINED else "undefined!"
        for i in tree_.feature
    ]
    print "def tree({}):".format(", ".join(feature_names))

    def recurse(node, depth):
        indent = "  " * depth
        if tree_.feature[node] != _tree.TREE_UNDEFINED:
            name = feature_name[node]
            threshold = tree_.threshold[node]
            print "{}if {} <= {}:".format(indent, name, threshold)
            recurse(tree_.children_left[node], depth + 1)
            print "{}else:  # if {} > {}".format(indent, name, threshold)
            recurse(tree_.children_right[node], depth + 1)
        else:
            print "{}return {}".format(indent, tree_.value[node])

    recurse(0, 1)

Это распечатывает допустимую функцию Python. Вот пример выходных данных для дерева, которое пытается вернуть свой ввод, число от 0 до 10.

def tree(f0):
  if f0 <= 6.0:
    if f0 <= 1.5:
      return [[ 0.]]
    else:  # if f0 > 1.5
      if f0 <= 4.5:
        if f0 <= 3.5:
          return [[ 3.]]
        else:  # if f0 > 3.5
          return [[ 4.]]
      else:  # if f0 > 4.5
        return [[ 5.]]
  else:  # if f0 > 6.0
    if f0 <= 8.5:
      if f0 <= 7.5:
        return [[ 7.]]
      else:  # if f0 > 7.5
        return [[ 8.]]
    else:  # if f0 > 8.5
      return [[ 9.]]

Вот некоторые камни преткновения, которые я вижу в других ответах:

  1. Использование, tree_.threshold == -2чтобы решить, является ли узел листом, не является хорошей идеей. Что если это реальный узел принятия решения с порогом -2? Вместо этого вы должны посмотреть на tree.featureили tree.children_*.
  2. Строка features = [feature_names[i] for i in tree_.feature]падает с моей версией sklearn, потому что некоторые значения tree.tree_.featureравны -2 (специально для конечных узлов).
  3. Нет необходимости иметь несколько операторов if в рекурсивной функции, достаточно одного.
paulkernfeld
источник
1
Этот код прекрасно работает для меня. Тем не менее, у меня есть 500+ имен_функций, поэтому выходной код для человека почти невозможен для понимания. Есть ли способ разрешить мне вводить только те функции, которые мне интересны, в функцию?
user3768495
1
Я согласен с предыдущим комментарием. IIUC, print "{}return {}".format(indent, tree_.value[node])следует изменить print "{}return {}".format(indent, np.argmax(tree_.value[node][0]))на функцию, возвращающую индекс класса.
soupault
1
@paulkernfeld Ах да, я вижу, что вы можете повторить цикл RandomForestClassifier.estimators_, но я не смог понять, как объединить результаты оценок.
Натан Ллойд
6
Я не мог заставить это работать в Python 3, биты _tree, кажется, не будут работать когда-либо, и TREE_UNDEFINED не был определен. Эта ссылка помогла мне. Несмотря на то, что экспортированный код напрямую не запускается в python, он похож на c и довольно легко переводится на другие языки: web.archive.org/web/20171005203850/http://www.kdnuggets.com/…
Джосия
1
@Josiah, добавьте () к операторам печати, чтобы он работал в python3. например print "bla"=>print("bla")
Нир
48

Я создал свою собственную функцию для извлечения правил из деревьев решений, созданных sklearn:

import pandas as pd
import numpy as np
from sklearn.tree import DecisionTreeClassifier

# dummy data:
df = pd.DataFrame({'col1':[0,1,2,3],'col2':[3,4,5,6],'dv':[0,1,0,1]})

# create decision tree
dt = DecisionTreeClassifier(max_depth=5, min_samples_leaf=1)
dt.fit(df.ix[:,:2], df.dv)

Эта функция сначала начинается с узлов (обозначается -1 в дочерних массивах), а затем рекурсивно находит родителей. Я называю это «родословной» узла. Попутно я беру значения, которые мне нужны для создания логики SAS if / then / else:

def get_lineage(tree, feature_names):
     left      = tree.tree_.children_left
     right     = tree.tree_.children_right
     threshold = tree.tree_.threshold
     features  = [feature_names[i] for i in tree.tree_.feature]

     # get ids of child nodes
     idx = np.argwhere(left == -1)[:,0]     

     def recurse(left, right, child, lineage=None):          
          if lineage is None:
               lineage = [child]
          if child in left:
               parent = np.where(left == child)[0].item()
               split = 'l'
          else:
               parent = np.where(right == child)[0].item()
               split = 'r'

          lineage.append((parent, split, threshold[parent], features[parent]))

          if parent == 0:
               lineage.reverse()
               return lineage
          else:
               return recurse(left, right, parent, lineage)

     for child in idx:
          for node in recurse(left, right, child):
               print node

Наборы кортежей ниже содержат все, что мне нужно для создания операторов SAS if / then / else. Мне не нравится использовать doблоки в SAS, поэтому я создаю логику, описывающую весь путь узла. Единственное целое число после кортежей - это идентификатор терминального узла в пути. Все предыдущие кортежи объединяются, чтобы создать этот узел.

In [1]: get_lineage(dt, df.columns)
(0, 'l', 0.5, 'col1')
1
(0, 'r', 0.5, 'col1')
(2, 'l', 4.5, 'col2')
3
(0, 'r', 0.5, 'col1')
(2, 'r', 4.5, 'col2')
(4, 'l', 2.5, 'col1')
5
(0, 'r', 0.5, 'col1')
(2, 'r', 4.5, 'col2')
(4, 'r', 2.5, 'col1')
6

GraphViz вывод дерева примеров

Zelazny7
источник
является ли этот тип дерева правильным, потому что col1 снова приходит, один col1 <= 0.50000 и один col1 <= 2.5000, если да, это любой тип рекурсии, который используется в библиотеке
jayant singh
правая ветвь будет иметь записи между (0.5, 2.5]. Деревья сделаны с рекурсивным разбиением. Ничто не мешает выбрать переменную несколько раз.
Zelazny7
хорошо, вы можете объяснить рекурсивную часть, что происходит xactly, потому что я использовал его в своем коде, и похожий результат виден
jayant singh
38

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

def get_code(tree, feature_names):
        left      = tree.tree_.children_left
        right     = tree.tree_.children_right
        threshold = tree.tree_.threshold
        features  = [feature_names[i] for i in tree.tree_.feature]
        value = tree.tree_.value

        def recurse(left, right, threshold, features, node):
                if (threshold[node] != -2):
                        print "if ( " + features[node] + " <= " + str(threshold[node]) + " ) {"
                        if left[node] != -1:
                                recurse (left, right, threshold, features,left[node])
                        print "} else {"
                        if right[node] != -1:
                                recurse (left, right, threshold, features,right[node])
                        print "}"
                else:
                        print "return " + str(value[node])

        recurse(left, right, threshold, features, 0)

если ты позвонишь get_code(dt, df.columns) к тому же примеру, вы получите:

if ( col1 <= 0.5 ) {
return [[ 1.  0.]]
} else {
if ( col2 <= 4.5 ) {
return [[ 0.  1.]]
} else {
if ( col1 <= 2.5 ) {
return [[ 1.  0.]]
} else {
return [[ 0.  1.]]
}
}
}
Daniele
источник
1
Можете ли вы сказать, что именно [[1. 0.]] в выражении return означает в вышеприведенном выводе. Я не парень из Python, но работаю над тем же. Поэтому мне будет хорошо, если вы докажете некоторые детали, чтобы мне было легче.
Субрадип Бозе
1
@ user3156186 Это означает, что есть один объект в классе '0' и ноль объектов в классе '1'
Даниэль
1
@Daniele, вы знаете, как классы упорядочены? Я бы предположил буквенно-цифровой, но я нигде не нашел подтверждения.
Янв
Спасибо! Для сценария пограничного случая, где пороговое значение фактически равно -2, нам может потребоваться изменить (threshold[node] != -2)его ( left[node] != -1)(аналогично методу ниже для получения идентификаторов дочерних узлов)
tlingf
@Daniele, есть идеи, как заставить вашу функцию "get_code" "возвращать" значение, а не "печатать" его, потому что мне нужно отправить его в другую функцию?
RoyaumeIX
17

Scikit learn представил восхитительный новый метод, названный export_textв версии 0.21 (май 2019 г.) для извлечения правил из дерева. Документация здесь . Больше нет необходимости создавать пользовательские функции.

После того, как вы подобрали свою модель, вам просто нужно две строки кода. Во-первых, импорт export_text:

from sklearn.tree.export import export_text

Во-вторых, создайте объект, который будет содержать ваши правила. Чтобы правила выглядели более читабельными, используйте feature_namesаргумент и передайте список имен ваших объектов. Например, если ваша модель вызывается, modelа ваши функции называются в кадре данных X_train, вы можете создать объект с именем tree_rules:

tree_rules = export_text(model, feature_names=list(X_train))

Затем просто распечатайте или сохраните tree_rules. Ваш вывод будет выглядеть так:

|--- Age <= 0.63
|   |--- EstimatedSalary <= 0.61
|   |   |--- Age <= -0.16
|   |   |   |--- class: 0
|   |   |--- Age >  -0.16
|   |   |   |--- EstimatedSalary <= -0.06
|   |   |   |   |--- class: 0
|   |   |   |--- EstimatedSalary >  -0.06
|   |   |   |   |--- EstimatedSalary <= 0.40
|   |   |   |   |   |--- EstimatedSalary <= 0.03
|   |   |   |   |   |   |--- class: 1
Айзерман
источник
14

Существует новый DecisionTreeClassifierметод, decision_pathв 0.18.0 релизе. Разработчики предоставляют обширный (хорошо задокументированный) обзор .

Первый раздел кода в пошаговом руководстве, который печатает древовидную структуру, выглядит нормально. Однако я изменил код во втором разделе, чтобы опросить один образец. Мои изменения обозначены# <--

Редактировать Изменения, отмеченные # <--в приведенном ниже коде, с тех пор были обновлены в пошаговой ссылке после того, как ошибки были указаны в запросах на получение доступа # 8653 и # 10951 . Теперь намного легче следовать за ним.

sample_id = 0
node_index = node_indicator.indices[node_indicator.indptr[sample_id]:
                                    node_indicator.indptr[sample_id + 1]]

print('Rules used to predict sample %s: ' % sample_id)
for node_id in node_index:

    if leave_id[sample_id] == node_id:  # <-- changed != to ==
        #continue # <-- comment out
        print("leaf node {} reached, no decision here".format(leave_id[sample_id])) # <--

    else: # < -- added else to iterate through decision nodes
        if (X_test[sample_id, feature[node_id]] <= threshold[node_id]):
            threshold_sign = "<="
        else:
            threshold_sign = ">"

        print("decision id node %s : (X[%s, %s] (= %s) %s %s)"
              % (node_id,
                 sample_id,
                 feature[node_id],
                 X_test[sample_id, feature[node_id]], # <-- changed i to sample_id
                 threshold_sign,
                 threshold[node_id]))

Rules used to predict sample 0: 
decision id node 0 : (X[0, 3] (= 2.4) > 0.800000011921)
decision id node 2 : (X[0, 2] (= 5.1) > 4.94999980927)
leaf node 4 reached, no decision here

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

Kevin
источник
ты мой друг легенда! Есть идеи, как построить дерево решений для этого конкретного образца? большая помощь приветствуется
1
Спасибо Виктор, вероятно, лучше задать этот вопрос как отдельный вопрос, так как требования к графику могут быть специфическими для потребностей пользователя. Вы, вероятно, получите хороший ответ, если дадите представление о том, как должен выглядеть вывод.
Кевин
эй Кевин, я создал вопрос stackoverflow.com/questions/48888893/...
Не могли бы вы взглянуть на: stackoverflow.com/questions/52654280/…
Александр Червов
Можете ли вы объяснить часть под названием node_index, не получая эту часть. Что это делает?
Анинда Санкар Дей
12
from StringIO import StringIO
out = StringIO()
out = tree.export_graphviz(clf, out_file=out)
print out.getvalue()

Вы можете увидеть дерево орграфа. Тогда, clf.tree_.featureи clf.tree_.valueявляются массивом узлов, разделяющим признак и массивом значений узлов соответственно. Вы можете обратиться к более подробной информации из этого источника GitHub .

lennon310
источник
1
Да, я знаю, как нарисовать дерево - но мне нужна более текстовая версия - правила. что-то вроде: orange.biolab.si/docs/latest/reference/rst/…
Дрор Хилман
4

Просто потому, что все были так полезны, я просто добавлю модификацию в прекрасные решения Zelazny7 и Даниэле. Это для Python 2.7, с вкладками, чтобы сделать его более читабельным:

def get_code(tree, feature_names, tabdepth=0):
    left      = tree.tree_.children_left
    right     = tree.tree_.children_right
    threshold = tree.tree_.threshold
    features  = [feature_names[i] for i in tree.tree_.feature]
    value = tree.tree_.value

    def recurse(left, right, threshold, features, node, tabdepth=0):
            if (threshold[node] != -2):
                    print '\t' * tabdepth,
                    print "if ( " + features[node] + " <= " + str(threshold[node]) + " ) {"
                    if left[node] != -1:
                            recurse (left, right, threshold, features,left[node], tabdepth+1)
                    print '\t' * tabdepth,
                    print "} else {"
                    if right[node] != -1:
                            recurse (left, right, threshold, features,right[node], tabdepth+1)
                    print '\t' * tabdepth,
                    print "}"
            else:
                    print '\t' * tabdepth,
                    print "return " + str(value[node])

    recurse(left, right, threshold, features, 0)
Руслан
источник
3

Коды ниже - это мой подход под anaconda python 2.7 плюс имя пакета "pydot-ng" для создания файла PDF с правилами принятия решений. Я надеюсь, что это полезно.

from sklearn import tree

clf = tree.DecisionTreeClassifier(max_leaf_nodes=n)
clf_ = clf.fit(X, data_y)

feature_names = X.columns
class_name = clf_.classes_.astype(int).astype(str)

def output_pdf(clf_, name):
    from sklearn import tree
    from sklearn.externals.six import StringIO
    import pydot_ng as pydot
    dot_data = StringIO()
    tree.export_graphviz(clf_, out_file=dot_data,
                         feature_names=feature_names,
                         class_names=class_name,
                         filled=True, rounded=True,
                         special_characters=True,
                          node_ids=1,)
    graph = pydot.graph_from_dot_data(dot_data.getvalue())
    graph.write_pdf("%s.pdf"%name)

output_pdf(clf_, name='filename%s'%n)

дерево графическое шоу здесь

Тед Чжао
источник
3

Я проходил через это, но мне нужно, чтобы правила были написаны в этом формате

if A>0.4 then if B<0.2 then if C>0.8 then class='X' 

Поэтому я адаптировал ответ @paulkernfeld (спасибо), который вы можете настроить под свои нужды

def tree_to_code(tree, feature_names, Y):
    tree_ = tree.tree_
    feature_name = [
        feature_names[i] if i != _tree.TREE_UNDEFINED else "undefined!"
        for i in tree_.feature
    ]
    pathto=dict()

    global k
    k = 0
    def recurse(node, depth, parent):
        global k
        indent = "  " * depth

        if tree_.feature[node] != _tree.TREE_UNDEFINED:
            name = feature_name[node]
            threshold = tree_.threshold[node]
            s= "{} <= {} ".format( name, threshold, node )
            if node == 0:
                pathto[node]=s
            else:
                pathto[node]=pathto[parent]+' & ' +s

            recurse(tree_.children_left[node], depth + 1, node)
            s="{} > {}".format( name, threshold)
            if node == 0:
                pathto[node]=s
            else:
                pathto[node]=pathto[parent]+' & ' +s
            recurse(tree_.children_right[node], depth + 1, node)
        else:
            k=k+1
            print(k,')',pathto[parent], tree_.value[node])
    recurse(0, 1, 0)
Ала Хам
источник
3

Вот способ перевести все дерево в одно (не обязательно слишком удобочитаемое) выражение python с помощью библиотеки SKompiler :

from skompiler import skompile
skompile(dtree.predict).to('python/code')
KT.
источник
3

Это основано на ответе @paulkernfeld. Если у вас есть фрейм данных X с вашими функциями и целевой фрейм данных y с вашими резонансами, и вы хотите получить представление о том, какое значение y заканчивается в каком узле (а также ant для его построения соответственно), вы можете сделать следующее:

    def tree_to_code(tree, feature_names):
        from sklearn.tree import _tree
        codelines = []
        codelines.append('def get_cat(X_tmp):\n')
        codelines.append('   catout = []\n')
        codelines.append('   for codelines in range(0,X_tmp.shape[0]):\n')
        codelines.append('      Xin = X_tmp.iloc[codelines]\n')
        tree_ = tree.tree_
        feature_name = [
            feature_names[i] if i != _tree.TREE_UNDEFINED else "undefined!"
            for i in tree_.feature
        ]
        #print "def tree({}):".format(", ".join(feature_names))

        def recurse(node, depth):
            indent = "      " * depth
            if tree_.feature[node] != _tree.TREE_UNDEFINED:
                name = feature_name[node]
                threshold = tree_.threshold[node]
                codelines.append ('{}if Xin["{}"] <= {}:\n'.format(indent, name, threshold))
                recurse(tree_.children_left[node], depth + 1)
                codelines.append( '{}else:  # if Xin["{}"] > {}\n'.format(indent, name, threshold))
                recurse(tree_.children_right[node], depth + 1)
            else:
                codelines.append( '{}mycat = {}\n'.format(indent, node))

        recurse(0, 1)
        codelines.append('      catout.append(mycat)\n')
        codelines.append('   return pd.DataFrame(catout,index=X_tmp.index,columns=["category"])\n')
        codelines.append('node_ids = get_cat(X)\n')
        return codelines
    mycode = tree_to_code(clf,X.columns.values)

    # now execute the function and obtain the dataframe with all nodes
    exec(''.join(mycode))
    node_ids = [int(x[0]) for x in node_ids.values]
    node_ids2 = pd.DataFrame(node_ids)

    print('make plot')
    import matplotlib.cm as cm
    colors = cm.rainbow(np.linspace(0, 1, 1+max( list(set(node_ids)))))
    #plt.figure(figsize=cm2inch(24, 21))
    for i in list(set(node_ids)):
        plt.plot(y[node_ids2.values==i],'o',color=colors[i], label=str(i))  
    mytitle = ['y colored by node']
    plt.title(mytitle ,fontsize=14)
    plt.xlabel('my xlabel')
    plt.ylabel(tagname)
    plt.xticks(rotation=70)       
    plt.legend(loc='upper center', bbox_to_anchor=(0.5, 1.00), shadow=True, ncol=9)
    plt.tight_layout()
    plt.show()
    plt.close 

не самая элегантная версия, но она делает свою работу ...

подкова
источник
1
Это хороший подход, когда вы хотите вернуть строки кода, а не просто печатать их.
Хаджар Хомаюни
3

Это код, который вам нужен

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

import numpy as np
from sklearn.tree import _tree

def tree_to_code(tree, feature_names):
    tree_ = tree.tree_
    feature_name = [feature_names[i] 
                    if i != _tree.TREE_UNDEFINED else "undefined!" 
                    for i in tree_.feature]
    print("def tree({}):".format(", ".join(feature_names)))

    def recurse(node, depth):
        indent = "    " * depth
        if tree_.feature[node] != _tree.TREE_UNDEFINED:
            name = feature_name[node]
            threshold = tree_.threshold[node]
            print("{}if {} <= {}:".format(indent, name, threshold))
            recurse(tree_.children_left[node], depth + 1)
            print("{}else:  # if {} > {}".format(indent, name, threshold))
            recurse(tree_.children_right[node], depth + 1)
        else:
            print("{}return {}".format(indent, np.argmax(tree_.value[node])))

    recurse(0, 1)
Кэмерон Соренсен
источник
2

Вот функция, печатающая правила дерева решений scikit-learn под python 3 и со смещениями для условных блоков, чтобы сделать структуру более читабельной:

def print_decision_tree(tree, feature_names=None, offset_unit='    '):
    '''Plots textual representation of rules of a decision tree
    tree: scikit-learn representation of tree
    feature_names: list of feature names. They are set to f1,f2,f3,... if not specified
    offset_unit: a string of offset of the conditional block'''

    left      = tree.tree_.children_left
    right     = tree.tree_.children_right
    threshold = tree.tree_.threshold
    value = tree.tree_.value
    if feature_names is None:
        features  = ['f%d'%i for i in tree.tree_.feature]
    else:
        features  = [feature_names[i] for i in tree.tree_.feature]        

    def recurse(left, right, threshold, features, node, depth=0):
            offset = offset_unit*depth
            if (threshold[node] != -2):
                    print(offset+"if ( " + features[node] + " <= " + str(threshold[node]) + " ) {")
                    if left[node] != -1:
                            recurse (left, right, threshold, features,left[node],depth+1)
                    print(offset+"} else {")
                    if right[node] != -1:
                            recurse (left, right, threshold, features,right[node],depth+1)
                    print(offset+"}")
            else:
                    print(offset+"return " + str(value[node]))

    recurse(left, right, threshold, features, 0,0)
Apogentus
источник
2

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

def print_decision_tree(tree, feature_names, offset_unit='    '):    
left      = tree.tree_.children_left
right     = tree.tree_.children_right
threshold = tree.tree_.threshold
value = tree.tree_.value
if feature_names is None:
    features  = ['f%d'%i for i in tree.tree_.feature]
else:
    features  = [feature_names[i] for i in tree.tree_.feature]        

def recurse(left, right, threshold, features, node, depth=0):
        offset = offset_unit*depth
        if (threshold[node] != -2):
                print(offset+"if ( " + features[node] + " <= " + str(threshold[node]) + " ) {")
                if left[node] != -1:
                        recurse (left, right, threshold, features,left[node],depth+1)
                print(offset+"} else {")
                if right[node] != -1:
                        recurse (left, right, threshold, features,right[node],depth+1)
                print(offset+"}")
        else:
                #print(offset,value[node]) 

                #To remove values from node
                temp=str(value[node])
                mid=len(temp)//2
                tempx=[]
                tempy=[]
                cnt=0
                for i in temp:
                    if cnt<=mid:
                        tempx.append(i)
                        cnt+=1
                    else:
                        tempy.append(i)
                        cnt+=1
                val_yes=[]
                val_no=[]
                res=[]
                for j in tempx:
                    if j=="[" or j=="]" or j=="." or j==" ":
                        res.append(j)
                    else:
                        val_no.append(j)
                for j in tempy:
                    if j=="[" or j=="]" or j=="." or j==" ":
                        res.append(j)
                    else:
                        val_yes.append(j)
                val_yes = int("".join(map(str, val_yes)))
                val_no = int("".join(map(str, val_no)))

                if val_yes>val_no:
                    print(offset,'\033[1m',"YES")
                    print('\033[0m')
                elif val_no>val_yes:
                    print(offset,'\033[1m',"NO")
                    print('\033[0m')
                else:
                    print(offset,'\033[1m',"Tie")
                    print('\033[0m')

recurse(left, right, threshold, features, 0,0)

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

Амит Раутрей
источник
2

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

Результатом будут последующие CASEпредложения, которые можно скопировать в оператор SQL, напр.

SELECT COALESCE(*CASE WHEN <conditions> THEN > <NodeA>*, > *CASE WHEN <conditions> THEN <NodeB>*, > ....)NodeName,* > FROM <table or view>


import numpy as np

import pickle
feature_names=.............
features  = [feature_names[i] for i in range(len(feature_names))]
clf= pickle.loads(trained_model)
impurity=clf.tree_.impurity
importances = clf.feature_importances_
SqlOut=""

#global Conts
global ContsNode
global Path
#Conts=[]#
ContsNode=[]
Path=[]
global Results
Results=[]

def print_decision_tree(tree, feature_names, offset_unit=''    ''):    
    left      = tree.tree_.children_left
    right     = tree.tree_.children_right
    threshold = tree.tree_.threshold
    value = tree.tree_.value

    if feature_names is None:
        features  = [''f%d''%i for i in tree.tree_.feature]
    else:
        features  = [feature_names[i] for i in tree.tree_.feature]        

    def recurse(left, right, threshold, features, node, depth=0,ParentNode=0,IsElse=0):
        global Conts
        global ContsNode
        global Path
        global Results
        global LeftParents
        LeftParents=[]
        global RightParents
        RightParents=[]
        for i in range(len(left)): # This is just to tell you how to create a list.
            LeftParents.append(-1)
            RightParents.append(-1)
            ContsNode.append("")
            Path.append("")


        for i in range(len(left)): # i is node
            if (left[i]==-1 and right[i]==-1):      
                if LeftParents[i]>=0:
                    if Path[LeftParents[i]]>" ":
                        Path[i]=Path[LeftParents[i]]+" AND " +ContsNode[LeftParents[i]]                                 
                    else:
                        Path[i]=ContsNode[LeftParents[i]]                                   
                if RightParents[i]>=0:
                    if Path[RightParents[i]]>" ":
                        Path[i]=Path[RightParents[i]]+" AND not " +ContsNode[RightParents[i]]                                   
                    else:
                        Path[i]=" not " +ContsNode[RightParents[i]]                     
                Results.append(" case when  " +Path[i]+"  then ''" +"{:4d}".format(i)+ " "+"{:2.2f}".format(impurity[i])+" "+Path[i][0:180]+"''")

            else:       
                if LeftParents[i]>=0:
                    if Path[LeftParents[i]]>" ":
                        Path[i]=Path[LeftParents[i]]+" AND " +ContsNode[LeftParents[i]]                                 
                    else:
                        Path[i]=ContsNode[LeftParents[i]]                                   
                if RightParents[i]>=0:
                    if Path[RightParents[i]]>" ":
                        Path[i]=Path[RightParents[i]]+" AND not " +ContsNode[RightParents[i]]                                   
                    else:
                        Path[i]=" not "+ContsNode[RightParents[i]]                      
                if (left[i]!=-1):
                    LeftParents[left[i]]=i
                if (right[i]!=-1):
                    RightParents[right[i]]=i
                ContsNode[i]=   "( "+ features[i] + " <= " + str(threshold[i])   + " ) "

    recurse(left, right, threshold, features, 0,0,0,0)
print_decision_tree(clf,features)
SqlOut=""
for i in range(len(Results)): 
    SqlOut=SqlOut+Results[i]+ " end,"+chr(13)+chr(10)
фарватер
источник
1

Теперь вы можете использовать export_text.

from sklearn.tree import export_text

r = export_text(loan_tree, feature_names=(list(X_train.columns)))
print(r)

Полный пример из [sklearn] [1]

from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.tree import export_text
iris = load_iris()
X = iris['data']
y = iris['target']
decision_tree = DecisionTreeClassifier(random_state=0, max_depth=2)
decision_tree = decision_tree.fit(X, y)
r = export_text(decision_tree, feature_names=iris['feature_names'])
print(r)
Кевин
источник
0

Изменен код Zelazny7 для извлечения SQL из дерева решений.

# SQL from decision tree

def get_lineage(tree, feature_names):
     left      = tree.tree_.children_left
     right     = tree.tree_.children_right
     threshold = tree.tree_.threshold
     features  = [feature_names[i] for i in tree.tree_.feature]
     le='<='               
     g ='>'
     # get ids of child nodes
     idx = np.argwhere(left == -1)[:,0]     

     def recurse(left, right, child, lineage=None):          
          if lineage is None:
               lineage = [child]
          if child in left:
               parent = np.where(left == child)[0].item()
               split = 'l'
          else:
               parent = np.where(right == child)[0].item()
               split = 'r'
          lineage.append((parent, split, threshold[parent], features[parent]))
          if parent == 0:
               lineage.reverse()
               return lineage
          else:
               return recurse(left, right, parent, lineage)
     print 'case '
     for j,child in enumerate(idx):
        clause=' when '
        for node in recurse(left, right, child):
            if len(str(node))<3:
                continue
            i=node
            if i[1]=='l':  sign=le 
            else: sign=g
            clause=clause+i[3]+sign+str(i[2])+' and '
        clause=clause[:-4]+' then '+str(j)
        print clause
     print 'else 99 end as clusters'
Арслан
источник
0

Видимо, давно кто-то уже решил попробовать добавить следующую функцию в функции экспорта дерева официального scikit (который в основном поддерживает только export_graphviz)

def export_dict(tree, feature_names=None, max_depth=None) :
    """Export a decision tree in dict format.

Вот его полный коммит:

https://github.com/scikit-learn/scikit-learn/blob/79bdc8f711d0af225ed6be9fdb708cea9f98a910/sklearn/tree/export.py

Не совсем уверен, что случилось с этим комментарием. Но вы также можете попробовать использовать эту функцию.

Я думаю, что это требует серьезного запроса на документацию для хороших людей scikit-learn, чтобы правильно документировать sklearn.tree.TreeAPI, который является базовой структурой дерева, которая DecisionTreeClassifierвыставляется как его атрибут tree_.

Арис Конинг
источник
0

Просто используйте функцию из sklearn.tree, как это

from sklearn.tree import export_graphviz
    export_graphviz(tree,
                out_file = "tree.dot",
                feature_names = tree.columns) //or just ["petal length", "petal width"]

А затем найдите в папке вашего проекта файл tree.dot , скопируйте ВСЕ содержимое и вставьте его сюда http://www.webgraphviz.com/ и сгенерируйте свой график :)

chainstair
источник
0

Спасибо за замечательное решение @paulkerfeld. На вершине своего решения, для всех тех , кто хочет иметь упорядоченную версию дерев, просто использовать tree.threshold, tree.children_left, tree.children_right, tree.featureи tree.value. Так как листья не имеют разбиений и, следовательно, не имеют имен элементов и дочерних элементов, их заполнители в tree.featureи tree.children_***являются _tree.TREE_UNDEFINEDи _tree.TREE_LEAF. Каждому разделению присваивается уникальный индекс depth first search.
Обратите внимание, что tree.valueформа[n, 1, 1]

Янки Хуан
источник
0

Вот функция, которая генерирует код Python из дерева решений путем преобразования вывода export_text:

import string
from sklearn.tree import export_text

def export_py_code(tree, feature_names, max_depth=100, spacing=4):
    if spacing < 2:
        raise ValueError('spacing must be > 1')

    # Clean up feature names (for correctness)
    nums = string.digits
    alnums = string.ascii_letters + nums
    clean = lambda s: ''.join(c if c in alnums else '_' for c in s)
    features = [clean(x) for x in feature_names]
    features = ['_'+x if x[0] in nums else x for x in features if x]
    if len(set(features)) != len(feature_names):
        raise ValueError('invalid feature names')

    # First: export tree to text
    res = export_text(tree, feature_names=features, 
                        max_depth=max_depth,
                        decimals=6,
                        spacing=spacing-1)

    # Second: generate Python code from the text
    skip, dash = ' '*spacing, '-'*(spacing-1)
    code = 'def decision_tree({}):\n'.format(', '.join(features))
    for line in repr(tree).split('\n'):
        code += skip + "# " + line + '\n'
    for line in res.split('\n'):
        line = line.rstrip().replace('|',' ')
        if '<' in line or '>' in line:
            line, val = line.rsplit(maxsplit=1)
            line = line.replace(' ' + dash, 'if')
            line = '{} {:g}:'.format(line, float(val))
        else:
            line = line.replace(' {} class:'.format(dash), 'return')
        code += skip + line + '\n'

    return code

Пример использования:

res = export_py_code(tree, feature_names=names, spacing=4)
print (res)

Пример вывода:

def decision_tree(f1, f2, f3):
    # DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=3,
    #                        max_features=None, max_leaf_nodes=None,
    #                        min_impurity_decrease=0.0, min_impurity_split=None,
    #                        min_samples_leaf=1, min_samples_split=2,
    #                        min_weight_fraction_leaf=0.0, presort=False,
    #                        random_state=42, splitter='best')
    if f1 <= 12.5:
        if f2 <= 17.5:
            if f1 <= 10.5:
                return 2
            if f1 > 10.5:
                return 3
        if f2 > 17.5:
            if f2 <= 22.5:
                return 1
            if f2 > 22.5:
                return 1
    if f1 > 12.5:
        if f1 <= 17.5:
            if f3 <= 23.5:
                return 2
            if f3 > 23.5:
                return 3
        if f1 > 17.5:
            if f1 <= 25:
                return 1
            if f1 > 25:
                return 2

Приведенный выше пример генерируется с names = ['f'+str(j+1) for j in range(NUM_FEATURES)].

Одна удобная особенность заключается в том, что он может генерировать файлы меньшего размера с уменьшенным интервалом. Просто установите spacing=2.

Андрей Макуха
источник