scikit-learn 의사 결정 트리에서 의사 결정 규칙을 추출하는 방법은 무엇입니까?


156

의사 결정 트리의 훈련 된 트리에서 텍스트 목록으로 기본 의사 결정 규칙 (또는 '결정 경로')을 추출 할 수 있습니까?

다음과 같은 것 :

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/...
아그라 왈 인 Yogesh

답변:


138

이 답변이 다른 답변보다 더 정확하다고 생각합니다.

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)

유효한 파이썬 함수를 출력합니다. 다음은 입력을 리턴하려는 트리에 대한 예제 출력입니다 (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 문을 가질 필요는 없으며 하나만 있으면됩니다.

1
이 코드는 저에게 효과적입니다. 그러나 500 개 이상의 feature_names가 있으므로 출력 코드는 인간이 거의 이해할 수 없습니다. 함수에 궁금한 feature_names 만 입력 할 수있는 방법이 있습니까?
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_알지만 견적 결과를 결합하는 방법을 알아낼 수 없었습니다.
Nathan Lloyd

6
파이썬 3 에서이 작업을 수행 할 수 없었습니다. _tree 비트는 작동하지 않는 것으로 보이며 TREE_UNDEFINED는 정의되지 않았습니다. 이 링크가 도움이되었습니다. 내 보낸 코드는 파이썬에서 직접 실행할 수 없지만 c와 비슷하고 다른 언어로 번역하기는 매우 쉽습니다. web.archive.org/web/20171005203850/http://www.kdnuggets.com/…
Josiah

1
@Josiah, python3에서 작동하도록 print 문에 ()를 추가하십시오. 예 : print "bla"=>print("bla")
Nir

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로 식별)로 시작한 다음 부모를 재귀 적으로 찾습니다. 나는 이것을 노드의 '계보'라고 부릅니다. 그 과정에서 if / then / else SAS 논리를 작성하는 데 필요한 값을 가져옵니다.

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 문을 작성하는 데 필요한 모든 것이 포함되어 있습니다. doSAS에서 블록을 사용하는 것을 좋아하지 않기 때문에 노드의 전체 경로를 설명하는 논리를 작성합니다. 튜플 뒤의 단일 정수는 경로에서 터미널 노드의 ID입니다. 앞의 모든 튜플이 결합되어 해당 노드를 만듭니다.

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 출력


col1이 다시 오는 중이기 때문에이 유형의 트리가 올바른 것입니다. 하나는 col1 <= 0.50000이고 하나는 col1 <= 2.5000이면 예,이 유형의 재귀 whish가 라이브러리에서 사용
됩니까

오른쪽 지점은 사이에 레코드가 (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.]]
}
}
}

1
return 문에서 정확히 [[1. 0.]]이 위의 출력에서 ​​무엇을 의미하는지 알 수 있습니까? 나는 파이썬 사람이 아니지만 같은 종류의 일을하고 있습니다. 그래서 당신이 나에게 더 쉬울 수 있도록 세부 사항을 증명해 주시면 나에게 좋을 것입니다.
Subhradip Bose가

1
@ user3156186 '0'클래스에 하나의 객체가 있고 '1'클래스에 0 개의 객체가 있음을 의미합니다.
Daniele

1
@Daniele, 수업 순서를 알고 있습니까? 영숫자를 추측하지만 어디에서나 확인을 찾지 못했습니다.
IanS

감사! 임계 값은 실제로 가장자리 케이스 시나리오 -2, 우리는 변경해야 할 수도 있습니다 (threshold[node] != -2)( left[node] != -1)(자식 노드의 ID를 얻기 위해 아래의 방법과 유사)
tlingf

@Daniele, 다른 함수로 보내야하기 때문에 함수 "get_code"를 "반환"하고 값을 "인쇄"하는 방법에 대한 아이디어가 있습니까?
RoyaumeIX

17

Scikit Learn에서는 export_text트리에서 규칙을 추출하기 위해 버전 0.21 (2019 년 5 월)에서 맛있는 새로운 방법을 도입했습니다 . 여기에 문서 . 더 이상 사용자 정의 기능을 만들 필요가 없습니다.

모델에 적합하면 두 줄의 코드 만 있으면됩니다. 먼저 수입 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다른 샘플의 결정 경로를 보려면를 변경 하십시오. 개발자에게 이러한 변경 사항에 대해 묻지 않고 예제를 통해 작업 할 때 더 직관적 인 것처럼 보였습니다.


당신은 내 친구가 전설입니다! 특정 샘플에 대한 의사 결정 트리를 구성하는 방법에 대한 아이디어가 있습니까? 많은 도움을 주셔서 감사합니다

1
Victor에게 감사드립니다. 플로팅 요구 사항은 사용자의 요구에 따라 달라질 수 있으므로 별도의 질문으로하는 것이 가장 좋습니다. 출력 결과를 원하는 아이디어를 제공하면 좋은 응답을 얻을 수 있습니다.
케빈


: 당신은 한 번 봐 걸릴 너무 친절 것 stackoverflow.com/questions/52654280/...
알렉산더 Chervov

node_index라는 부분은 설명하지 말고 설명해 주시겠습니까? 무엇을합니까?
Anindya Sankar Dey

12
from StringIO import StringIO
out = StringIO()
out = tree.export_graphviz(clf, out_file=out)
print out.getvalue()

Digraph Tree를 볼 수 있습니다. 그런 다음, clf.tree_.featureclf.tree_.value노드 분할 기능 및 노드 값의 배열의 배열은 각각이다. 이 github 소스 에서 자세한 내용을 참조 할 수 있습니다 .


1
그렇습니다. 나무를 그리는 법을 알고 있지만 규칙이 더 텍스트 버전이 필요합니다. 같은 : orange.biolab.si/docs/latest/reference/rst/...
인 Dror Hilman

4

모두가 매우 도움이 되었기 때문에 Zelazny7과 Daniele의 아름다운 솔루션에 수정 사항을 추가합니다. 이것은 파이썬 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 및 결정 규칙이있는 PDF 파일을 만드는 패키지 이름 "pydot-ng"에 대한 나의 접근 방식입니다. 도움이 되길 바랍니다.

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

다음은 SKompiler 라이브러리를 사용하여 전체 트리를 하나의 (사람이 읽을 수없는) 파이썬 표현식으로 변환하는 방법입니다 .

from skompiler import skompile
skompile(dtree.predict).to('python/code')

3

이것은 @paulkernfeld의 답변을 기반으로합니다. 특징이있는 데이터 프레임 X와 공명이있는 대상 데이터 프레임 y가 있고 어떤 노드에서 어떤 y 값이 끝났는지 (그리고 그에 따라 개미 표시) 아이디어를 얻으려면 다음을 수행 할 수 있습니다.

    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
인쇄하는 대신 코드 행을 반환하려는 경우이 방법이 좋습니다.
Hajar Homayouni

3

이것은 필요한 코드입니다

jupyter 노트북 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

다음은 파이썬 3에서 scikit-learn 의사 결정 트리의 규칙을 인쇄하고 구조를 읽기 쉽도록 조건부 블록에 대한 오프셋을 갖는 함수입니다.

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)

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에서 직접 사용할 수있는 형식으로 결정 규칙을 추출하는 방법입니다. 따라서 데이터를 노드별로 그룹화 할 수 있습니다. (이전 포스터의 접근 방식을 기반으로합니다.)

결과는 CASEsql 문에 복사 할 수 있는 후속 절입니다 (예 : ex.

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

의사 결정 트리에서 SQL을 가져 오도록 Zelazny7의 코드를 수정했습니다.

# 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.Tree되는 기본 트리 구조 인 API 를 올바르게 문서화하기 위해 심각한 문서 요청을 필요로한다고 생각 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/에 붙여 넣은 다음 그래프를 생성하십시오. :)


0

@paulkerfeld의 훌륭한 솔루션에 감사드립니다. 그의 솔루션의 상단에, 나무의 직렬화 된 버전이 원하는 모든 사람들, 그냥 사용을 위해 tree.threshold, tree.children_left, tree.children_right, tree.featuretree.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.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.