基于Python实现的决策树模型. |9 Y9 a9 a3 |
, L& s4 G5 q" {; @2 \ [3 A
决策树模型 7 ?- j' X/ }+ j! h目录" q6 q8 Q2 x' {, D( }
人工智能第五次实验报告 1 6 x5 L! W7 J2 N+ Y1 o决策树模型 1 : B: I2 n0 d1 G8 f* m8 l5 a; K6 s一 、问题背景 1 0 d& {1 s: I2 {/ _% ]1.1 监督学习简介 15 P- M5 m# E: L
1.2 决策树简介 1 P1 \; t. G3 v( c* o7 [) {2 R) m
二 、程序说明 3( S8 h, \" h. ~
2.1 数据载入 36 J, s# P. x4 _; \ i4 l" E f1 K
2.2 功能函数 3 - Q* D2 n+ F5 i" n& h2.3 决策树模型 4 # E$ v0 P, a4 B( A3 c2 r( l三 、程序测试 56 b Y+ y' c2 E
3.1 数据集说明 50 { U5 s5 p" H. A- p! T1 @- o
3.2 决策树生成和测试 6 % m; X) J0 _8 `! \4 w3.3 学习曲线评估算法精度 7 4 w5 V, { u# m5 |/ D6 ^四 、实验总结 8' o6 Q, h+ X4 `* D2 V& Z
附 录 - 程序代码 84 u" a" B6 w0 i* |" @
一 、问题背景: Z, e L, G; H7 g) [( F
1.1监督学习简介 ! Y+ v" n; }1 f3 K机器学习的形式包括无监督学习,强化学习,监督学习和半监督学习;学习任务有分类、聚类和回 归等。8 L0 e" j+ L' ?; w# {6 a; @
监督学习通过观察“输入—输出”对,学习从输入到输出的映射函数。分类监督学习的训练集为标记 数据,本文转载自http://www.biyezuopin.vip/onews.asp?id=16720每一条数据有对应的”标签“,根据标签可以将数据集分为若干个类别。分类监督学习经训练集生 成一个学习模型,可以用来预测一条新数据的标签。% F& v; x: _- c: S' W& P6 m* ?
常见的监督学习模型有决策树、KNN算法、朴素贝叶斯和随机森林等。 3 V6 N& ]# |; g! q0 W: ~1.2决策树简介 8 e5 Y$ ~7 K. i2 S% W9 |决策树归纳是一类简单的机器学习形式,它表示为一个函数,以属性值向量作为输入,返回一个决策。 / l$ i6 Q4 C; i, i A0 O决策树的组成 * z0 O6 b5 P& A& h9 }决策树由内节点上的属性值测试、分支上的属性值和叶子节点上的输出值组成。 / {; ]. v- t. Q) D: H7 h . B% t" H' A% p, C" N9 oimport numpy as np( G) U }% m' l9 t1 I5 P: U4 F; i
from matplotlib import pyplot as plt) i# Z* b3 ~/ e0 U
from math import log 9 M# `: v' ]/ c/ B2 c. g9 fimport pandas as pd * I' p. P* H0 s& limport pydotplus as pdp1 ?& X" b7 M- Y2 _! Q, [: @$ Q* A& M( g
0 O4 C, m# J& }1 G5 }- @7 f
""" ) W: d( X* }5 i8 b19335286 郑有为" \& I4 M- f) K$ t. }. B) P
人工智能作业 - 实现ID3决策树 1 t3 K* r! M9 K"""6 m" Q s' r1 p8 _8 T
) E8 Z: t2 v4 V0 x0 _# b4 b( S- @2 xnonce = 0 # 用来给节点一个全局ID 0 }$ s' r' F+ S' p( e! F/ jcolor_i = 00 G4 ^- d8 R/ q5 [ g
# 绘图时节点可选的颜色, 非叶子节点是蓝色的, 叶子节点根据分类被赋予不同的颜色! l' e+ z! c6 R, G5 ]/ Y! u
color_set = ["#AAFFDD", "#DDAAFF", "#DDFFAA", "#FFAADD", "#FFDDAA"] % N5 w: g% s! t# w; E$ ]4 B! s+ ]. [7 R0 Y. S' K
# 载入汽车数据, 判断顾客要不要买 2 [- G& E7 Z, H2 m! S+ u7 ]0 y! Eclass load_car:( b* B& ]/ Q* x4 s( q) W
# 在表格中,最后一列是分类结果 " ~9 K k8 Y+ e( j # feature_names: 属性名列表 % }. a- I* {* A # target_names: 标签(分类)名 i" |' P- L O4 ~8 Z5 ^ # data: 属性数据矩阵, 每行是一个数据, 每个数据是每个属性的对应值的列表( H+ U4 t. ~ }0 g5 U' V6 y/ d* H
# target: 目标分类值列表 # Y( \/ [; j: ?$ g- }8 _8 G" c8 { def __init__(self): - B' U/ c' o6 z5 b! r/ h0 s df = pd.read_csv('../dataset/car/car_train.csv') 0 _, B, m: J9 M$ ^8 i labels = df.columns.values X' E; x# C% A/ U1 s data_array = np.array(df[1:]) & Q9 H! T8 _ b: w6 v self.feature_names = labels[0:-1] - I; g; i" J, f6 }1 p+ ?) y self.target_names = labels[-1] 7 i; ?6 Y2 [: ]+ G self.data = data_array[0:,0:-1]& u$ e* n% `$ a% d: B
self.target = data_array[0:,-1] + s1 `$ Z+ f4 R" Z* X) w: |* D" y0 D3 g. p) r
# 载入蘑菇数据, 鉴别蘑菇是否有毒 # b4 ?* p3 P8 f. [class load_mushroom:# j- `- \& x! b z8 ~
# 在表格中, 第一列是分类结果: e 可食用; p 有毒.' t/ e8 W0 S" S9 X' O% E& ^
# feature_names: 属性名列表0 `6 \( U- q* m6 e2 H0 D* N
# target_names: 标签(分类)名9 C+ K5 Y" n% j; m
# data: 属性数据矩阵, 每行是一个数据, 每个数据是每个属性的对应值的列表 F$ z; R8 _& n% o- n6 ~
# target: 目标分类值列表 7 {2 Z4 X7 ]2 |2 z. ` def __init__(self): 3 @3 F' ^- U$ N# {: y5 J6 W! n7 l df = pd.read_csv('../dataset/mushroom/agaricus-lepiota.data') * p }; \# ^$ u# ] data_array = np.array(df) + E3 [$ A' {( `0 N0 w labels = ["edible/poisonous", "cap-shape", "cap-surface", "cap-color", "bruises", "odor", "gill-attachment",3 }6 i- J: v9 s1 A
"gill-spacing", "gill-size", "gill-color", "stalk-shape", "stalk-root", "stalk-surface-above-ring", ! z0 \2 h R8 c: ]. N! B) a* a "stalk-surface-below-ring", "stalk-color-above-ring", "stalk-color-below-ring", h+ r" u6 o+ i5 u) J1 _2 m/ }7 | "veil-type", "veil-color", "ring-number", "ring-type", "spore-print-color", "population", "habitat"]2 f4 s4 E; `# ~* v7 W, E
self.feature_names = labels[1:] - X6 X* f+ {$ u9 ]+ N self.target_names = labels[0] / a6 M* ?+ U# }: y self.data = data_array[0:,1:]; u$ ^0 _) K( P' X! B
self.target = data_array[0:,0] ( |3 d) k5 P0 z$ L, [ c! b0 u$ C 4 P7 H/ v' r1 t& {) k" F5 F# 创建一个临时的子数据集, 在划分测试集和训练集时使用6 ]" A" U+ \: G6 m" D
class new_dataset:8 @- B+ k% b/ K3 C, Q ]$ }
# feature_names: 属性名列表, O9 F+ x `# @( X$ H& M# x, W
# target_names: 标签(分类)名& B M" e6 W3 m; D1 l4 k. ?
# data: 属性数据矩阵, 每行是一个数据, 每个数据是每个属性的对应值的列表: U% o9 `0 P* U; Y5 p9 c
# target: 目标分类值列表$ C: W3 S% g4 ^* N
def __init__(self, f_n, t_n, d, t): . I7 h I _ Z- l& c self.feature_names = f_n" P4 G9 X6 @" h3 g1 J: h
self.target_names = t_n z6 J8 m0 d5 \
self.data = d 3 f R( _' y6 l( c+ i1 V# ? self.target = t* j9 f4 E" {. }, E P. i
5 u+ F- \; X. G! A: e0 N# 计算熵, 熵的数学公式为: $H(V) = - \sum_{k} P(v_k) \log_2 P(v_k)$ ) R+ d, j. i, @; L8 x) d, A# 其中 P(v_k) 是随机变量 V 具有值 V_k 的概率 , R6 R' L* s! @6 Y# target: 分类结果的列表, return: 信息熵 ! I3 y# J) I- O& C- Z7 e+ Xdef get_h(target):% J4 i% E- p) R( l; u+ I U0 | T
target_count = {}9 Z6 A# H# S) Z- D/ n
for i in range(len(target)): # D5 N" f- [3 O0 L# g7 b; I label = target ( @9 y* x2 x O if label not in target_count.keys():: \# L" `- `& p* M8 N! _+ z" j8 d
target_count[label] = 1.0- g" \7 k4 H1 \. }) h; T n
else: ' U2 R3 k" t, b& \6 a target_count[label] += 1.0 4 \& S% o t6 l9 r j2 s6 ?' C% g h = 0.0 - _ D* ], Z w4 j for k in target_count: F; F. d' w) l" \% f" S. q8 t
p = target_count[k] / len(target) 9 q6 M, K% N9 A" \0 z/ y* E h -= p * log(p, 2) ; K/ E6 a1 ^: |3 }) m9 B$ C return h& E1 o$ s. X& X0 U4 x7 |/ C D3 N
' L4 `! R$ R$ S- u; N5 V- p+ M# K# 取数据子集, 选择条件是原数据集中的属性 feature_name 值是否等于 feature_value4 l N0 c2 e3 ^$ j7 m
# 注: 选择后会从数据子集中删去 feature_name 属性对应的一列 9 a* G. \3 r8 Z, c- [& m wdef get_subset(dataset, feature_name, feature_value):( v+ @4 W( Y3 D
sub_data = []2 Z) Y" ?- ?% V9 |8 Z8 j" C3 P
sub_target = []- \9 g3 h# f' t+ q4 \- g% l
f_index = -1 ; m$ h+ |# G# X* a/ O for i in range(len(dataset.feature_names)):. U b0 K6 G' L5 D$ E% M2 v
if dataset.feature_names == feature_name:! O& w, ^4 m& q% A
f_index = i 8 h, N0 _0 z( c; @ break ) }& I9 i7 T: U4 S7 X8 [6 v, I* D. _/ y/ e1 H; O0 w9 M, }% W: u
for i in range(len(dataset.data)): $ j. F$ ?, O& D: \) M1 a7 i if dataset.data[f_index] == feature_value: 2 y/ ~# o! y3 m. J l = list(dataset.data[:f_index]): A) l2 [+ Z8 |6 M, J/ Q/ v
l.extend(dataset.data[f_index+1:])5 b2 }+ f/ f2 c& b. o m0 Y/ g3 z
sub_data.append(l)) }& p' m/ _5 C
sub_target.append(dataset.target) - N3 u; V, a: t+ k) g# i I# ~ x; M6 X5 X. N5 k* P: h
sub_feature_names = list(dataset.feature_names[:f_index]) 6 f3 \/ _0 f! S8 E% w$ S sub_feature_names.extend(dataset.feature_names[f_index+1:])* ~! Q: a c8 Z) t( {% g
return new_dataset(sub_feature_names, dataset.target_names, sub_data, sub_target) ; ~, d: U7 ~9 |: Q* y7 R ! w# T7 T* D* X3 Z: o+ f' w# 寻找并返回信息收益最大的属性划分 $ e+ S" { z: j. b2 m" f# 信息收益值划分该数据集前后的熵减. L/ T) N$ w4 e( g, b+ A
# 计算公式为: Gain(A) = get_h(ori_target) - sum(|sub_target| / |ori_target| * get_h(sub_target))$ 1 v! x$ S8 R a2 u; j! e* edef best_spilt(dataset):" n: T; R+ T/ j( R
9 n1 n# C s; s( Z' o" i' X
base_h = get_h(dataset.target) ; R5 d H% m5 P1 Q$ t) ^7 }6 U best_gain = 0.0 , E0 |6 P3 z4 ?. y( e' z best_feature = None ) k) Q8 d7 C9 |! `9 i& s# g+ t# Q% w for i in range(len(dataset.feature_names)): - P- K& z. u+ h9 p! } feature_range = [] 6 T7 w+ [* d" J& E8 O for j in range(len(dataset.data)): 5 p7 H% ^- u7 y+ \2 ]5 S if dataset.data[j] not in feature_range:: s# X1 [4 }7 `5 ~3 r! V
feature_range.append(dataset.data[j]) 0 ?2 H, z; n ^; e7 \4 M1 U+ x( X* r2 n5 O, n, G2 Y
spilt_h = 0.0 ( u o$ N6 `7 v' b7 N& f3 Z for feature_value in feature_range:/ j( c3 D |" a2 z
subset = get_subset(dataset, dataset.feature_names, feature_value) ( T" u0 o0 T- m( D2 E a spilt_h += len(subset.target) / len(dataset.target) * get_h(subset.target)3 M5 u x' G, m4 P
& h$ ]) b0 U& j% c$ ~: w4 ~: W
if best_gain <= base_h - spilt_h:1 }" A# H. F3 y% K) l& T3 s
best_gain = base_h - spilt_h/ t$ Q$ ~( U& `$ {! N
best_feature = dataset.feature_names 4 [4 v& F: P& C/ ]& i! `; m3 l) o3 d% z/ r4 R# U- S
return best_feature 7 m5 t4 Y* F" l9 [; P5 g4 N4 p% h5 `' A) v+ ^$ D3 g
# 返回数据集中一个数据最可能的标签& i' B5 M# G, b# a
def vote_most(dataset):. @& D3 D9 l' T n0 ?1 t" d0 h- [
target_range = {}+ O/ V9 ]$ y, i1 K' t: X
best_target = None: V7 c9 o! F% |. I
best_vote = 0# L( ]# K! X/ q* k/ Q( P. o
0 z( V" ? C& ?- o, F for t in dataset.target: * C; e. D" T# \" S2 h if t not in target_range.keys():/ K2 m. ?4 i. U v
target_range[t] = 1 2 j8 g$ b H+ J2 i# s, _ else: : ?' {: J: Q! f$ |& N4 W3 F target_range[t] += 1 9 W9 \ I6 z# v/ B; ^$ w3 X% Y9 w2 ]9 B/ K" [' e
for t in target_range.keys():# V- O% n* U* o9 I" S
if target_range[t] > best_vote: : v& h. j6 L8 O6 F; V best_vote = target_range[t] - p( k7 o7 g3 Q; Y best_target = t8 ]) u2 J+ k0 A
. E, u5 R% d0 {' S8 e8 r$ ~- z
return best_target) B3 W+ _. D- n4 ]8 E' F8 C
7 `7 R/ Z9 J* s8 g# 返回测试的正确率; H. y. Z' f7 r% ?1 ^
# predict_result: 预测标签列表, target_result: 实际标签列表2 ~3 O9 `# |6 r" }5 T, m
def accuracy_rate(predict_result, target_result): + B- E: A- g3 \5 W6 c# g+ d # print("Predict Result: ", predict_result) 5 }3 a" ?) O* ^* z `& c # print("Target Result: ", target_result) 1 t5 j! S J% A2 Z0 X% N accuracy_score = 0( w8 W9 ^1 c4 l! c5 F
for i in range(len(predict_result)): 2 O& ]- B$ l5 N if predict_result == target_result:/ D3 X# Z, n( I7 k% Y9 m. I/ Z
accuracy_score += 1; w f) o$ V7 L
return accuracy_score / len(predict_result) # E% D& ]- W! O* ^9 d% c0 x- k2 A2 u% ^, [0 F3 W3 }4 d t- R5 B" l8 U
# 决策树的节点结构 % Y" Q' v, ?% G) P- ^class dt_node: ( [* d p& j, b4 y: J 6 r+ Y4 M1 \2 `: t: n# b2 @* d def __init__(self, content, is_leaf=False, parent=None):' T: W, ]9 d# Z5 D5 }
global nonce 6 }1 o6 `$ t; o+ U4 |7 o0 K" p( f self.id = nonce # 为节点赋予一个全局ID, 目的是方便画图 ) ?* W3 n5 S* L nonce += 1, z- e! L& P* T; W6 M1 [; V% |2 `2 J# x
self.feature_name = None! P# t' d" b2 P
self.target_value = None5 z" I" q; Y! b; O! |$ J- A
self.vote_most = None # 记录当前节点最可能的标签 5 R" e3 x( p' G. S x! k' @* o1 x if not is_leaf: ( D' V* A* |! i9 @: f* z/ z self.feature_name = content # 非叶子节点的属性名. g# K4 e& o9 n* ^
else:# p- L5 M2 {* }* K+ @$ k5 |
self.target_value = content # 叶子节点的标签7 ~1 V6 D* ]0 N/ D# g% R. o& S6 A& P
( }' M7 l' U) I* R+ a j( e; | b/ J0 ~ self.parent = parent: u% S: D4 f& N
self.child = {} # 以当前节点的属性对应的属性值作为键值: a0 I( b8 h1 `& x& i
{4 U& Y J6 [, H& G
# 决策树模型6 O' r2 h9 t5 h: T3 K
class dt_tree: 9 f* B/ b }+ X! |8 l! O* D: } 9 K% F1 Y3 R1 ^1 h6 k def __init__(self): : c/ }: `( \. s self.tree = None # 决策树的根节点0 O7 ]! g1 s& @* |2 N- P, A' j# h
self.map_str = """ " F2 t. j z5 i: B digraph demo{ . _4 F- x" a. q6 Z9 U node [shape=box, style="rounded", color="black", fontname="Microsoft YaHei"]; . W. q% {; n# u" G7 n edge [fontname="Microsoft YaHei"]; M2 k2 K" d1 s: q& C7 n3 n% H4 c& ^ """ # 用于作图: pydotplus 格式的树图生成代码结构0 E: p- W- p( _! V
self.color_dir = {} # 用于作图: 叶子节点可选颜色, 以标签值为键值 0 U2 h( E6 l9 Z' ^2 U/ `7 I8 Q1 i( K }# f( b: {+ Y& k/ B# s" V # 训练模型, train_set: 训练集$ [7 x( l" {( _- y( R
def fit(self, train_set):" q, M; J, U5 r' v c. o
9 M6 h8 C1 v0 \ if len(train_set.target) <= 0: # 如果测试集数据为空, 则返回空节点, 结束递归8 _6 d& [8 i$ U( l$ V
return None 8 R. P: B @) T* @# Y( f3 Q 6 @4 I5 f% d- @ target_all_same = True+ G$ O+ \1 H; }
for i in train_set.target: " O8 M% x5 Q3 { if i != train_set.target[0]: 4 _4 C4 K8 E) t( H/ o! Q target_all_same = False+ b' U; l, u' a% W% P' `6 O' h
break # i2 x# s. i* }9 B; h* s0 I n# x9 W: @3 x6 s( h, z
if target_all_same: # 如果测试集数据中所有数据的标签相同, 则构造叶子节点, 结束递归 ) i% e/ U) {; y* x& q. w1 Z* Y node = dt_node(train_set.target[0], is_leaf=True)5 j5 R$ |. B5 d: _
if self.tree == None: # 如果根节点为空,则让该节点成为根节点* ?6 t* a$ b: X4 ?
self.tree = node& {$ c/ t0 G# R# L. h