วิธีการแยกกฎการตัดสินใจจากต้นไม้ตัดสินใจเรียนรู้?


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 และอื่น ๆ
Darius

คุณสามารถตรวจสอบลิงค์
yogesh

คำตอบ:


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.featuretree.children_*
  2. สายfeatures = [feature_names[i] for i in tree_.feature]ขัดข้องกับรุ่น sklearn ของฉันเนื่องจากค่าบางค่าtree.tree_.featureเป็น -2 (โดยเฉพาะสำหรับโหนดใบไม้)
  3. ไม่จำเป็นต้องมีหลายคำสั่งถ้าในฟังก์ชันเวียนเกิดเพียงคำสั่งเดียว

1
รหัสนี้ใช้งานได้ดีสำหรับฉัน อย่างไรก็ตามฉันมีคุณสมบัติ 500+ ชื่อดังนั้นรหัสเอาท์พุทจึงแทบเป็นไปไม่ได้ที่มนุษย์จะเข้าใจ มีวิธีให้ฉันป้อนเฉพาะคุณสมบัติ _ ชื่อฉันอยากรู้เกี่ยวกับฟังก์ชั่นหรือไม่?
user3768495

1
ฉันเห็นด้วยกับความคิดเห็นก่อนหน้า IIUC print "{}return {}".format(indent, tree_.value[node])ควรเปลี่ยนเป็นเพื่อprint "{}return {}".format(indent, np.argmax(tree_.value[node][0]))ให้ฟังก์ชันส่งคืนดัชนีคลาส
soupault

1
@ พอลเคอร์เฟลด์อาใช่ฉันเห็นว่าคุณสามารถวนซ้ำRandomForestClassifier.estimators_แต่ฉันไม่สามารถหาวิธีรวมผลลัพธ์ของตัวประมาณ
นาธานลอยด์

6
ฉันไม่สามารถใช้งานได้ใน python 3 บิต _tree ไม่เหมือนที่เคยทำงานและไม่ได้กำหนด TREE_UNDEFINED ลิงค์นี้ช่วยฉัน แม้ว่ารหัสที่ส่งออกจะไม่สามารถเรียกใช้งานได้โดยตรงใน python แต่ c-like และแปลภาษาอื่น ๆ ได้ค่อนข้างง่าย: web.archive.org/web/20171005203850/http://www.kdnuggets.com/…
Josiah

1
@Josiah, เพิ่ม () ในคำสั่ง print เพื่อให้ทำงานได้ใน python3 เช่น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

ชุดของ tuples ด้านล่างมีทุกสิ่งที่ฉันต้องการในการสร้าง SAS if / then / else statement ฉันไม่ชอบการใช้doบล็อกใน SAS ซึ่งเป็นเหตุผลที่ฉันสร้างตรรกะอธิบายเส้นทางทั้งหมดของโหนด จำนวนเต็มเดียวหลังจาก tuples คือ 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 ถ้าใช่นี่คือการเรียกซ้ำประเภทใด ๆ ที่ใช้ในห้องสมุด
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.]]
}
}
}

1
คุณสามารถบอกได้ว่าอะไร [[1. 0. ]] ในคำสั่ง return หมายความว่าในผลลัพธ์ข้างต้น ฉันไม่ใช่คน Python แต่ทำงานในสิ่งเดียวกัน มันจะดีสำหรับฉันถ้าคุณโปรดพิสูจน์รายละเอียดบางอย่างเพื่อมันจะง่ายขึ้นสำหรับฉัน
Subhradip Bose

1
@ user3156186 หมายความว่ามีหนึ่งวัตถุในคลาส '0' และวัตถุเป็นศูนย์ในคลาส '1'
Daniele

1
@Daniele คุณรู้วิธีการสั่งคลาสไหม? ฉันคาดเดาตัวอักษรและตัวเลข แต่ฉันไม่พบคำยืนยันใด ๆ
IanS

ขอบคุณ! สำหรับสถานการณ์ของ edge case ที่ค่า threshold เป็น -2 เราอาจจำเป็นต้องเปลี่ยน(threshold[node] != -2)เป็น ( left[node] != -1)(คล้ายกับวิธีการด้านล่างเพื่อรับ ID ของโหนดชายด์)
tlingf

@Daniele ความคิดใด ๆ ที่จะทำให้ฟังก์ชั่นของคุณ "get_code" "คืนค่า" และไม่ใช่ "พิมพ์" เพราะฉันต้องส่งมันไปยังฟังก์ชั่นอื่นหรือไม่?
RoyaumeIX

17

Scikit เรียนรู้แนะนำวิธีการใหม่ที่อร่อยที่เรียกว่าexport_textในรุ่น 0.21 (พฤษภาคม 2019) เพื่อแยกกฎจากต้นไม้ เอกสารที่นี่เอกสารที่นี่ไม่จำเป็นต้องสร้างฟังก์ชันที่กำหนดเองอีกต่อไป

เมื่อคุณพอดีกับแบบจำลองของคุณคุณต้องมีโค้ดสองบรรทัด ก่อนนำเข้าexport_text:

from sklearn.tree.export import export_text

ประการที่สองสร้างวัตถุที่จะมีกฎของคุณ ในการทำให้กฎดูง่ายขึ้นให้ใช้feature_namesอาร์กิวเมนต์และส่งรายการชื่อคุณลักษณะของคุณ ตัวอย่างเช่นถ้าแบบจำลองของคุณถูกเรียกmodelและคุณสมบัติของคุณถูกตั้งชื่อใน dataframe ชื่อ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/48888893/…

คุณจะกรุณาดู: stackoverflow.com/questions/52654280/ …
Alexander Chervov

คุณช่วยอธิบายส่วนที่เรียกว่า node_index ไม่ได้รับส่วนนั้นได้ไหม มันทำอะไร?
Anindya Sankar Dey

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นี้


1
ใช่ฉันรู้วิธีวาดต้นไม้ - แต่ฉันต้องการเวอร์ชันที่เป็นข้อความมากขึ้น - กฎ สิ่งที่ชอบ: orange.biolab.si/docs/latest/reference/rst/…
Dror Hilman

4

เพียงเพราะทุกคนมีประโยชน์มากฉันจะเพิ่มการแก้ไข Zelazny7 และโซลูชันที่สวยงามของ Daniele อันนี้ใช้สำหรับ 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

รหัสด้านล่างเป็นแนวทางของฉันภายใต้งูใหญ่ 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

นี่คือวิธีในการแปลทรีทั้งหมดเป็นนิพจน์หลามเดียว (ไม่จำเป็นต้องเป็นมนุษย์อ่านได้เกินไป) โดยใช้ไลบรารีSKompiler :

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

3

สิ่งนี้สร้างขึ้นจากคำตอบของ @paulkernfeld หากคุณมี dataframe X พร้อมกับคุณสมบัติและ target dataframe y ที่มี resonses ของคุณและคุณต้องการทราบว่าค่า 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
นี่เป็นวิธีการที่ดีเมื่อคุณต้องการส่งคืนบรรทัดโค้ดแทนที่จะพิมพ์ออกมา
Hajar Homayouni

3

นี่คือรหัสที่คุณต้องการ

ฉันได้แก้ไขโค้ดที่ชอบอันดับต้น ๆ เพื่อเยื้องในไพธูพโน๊ตบุ๊ค jupyter 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 ภายใต้ 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)

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 เรียนรู้อย่างถูกต้องเอกสารsklearn.tree.TreeAPI ซึ่งเป็นโครงสร้างพื้นฐานที่ตีแผ่เป็นแอตทริบิวต์ของมันDecisionTreeClassifiertree_


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.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ชุดเพียง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.