: f' A r8 p" Z, V: Z2 x9 ~决策树模型/ \: H+ U# n; Z6 b" k
目录 , H% m4 p. i9 Q人工智能第五次实验报告 1 , f8 D0 y0 c Z9 x决策树模型 1 " q; u6 M5 |0 V/ B/ H; k一 、问题背景 1 + Z3 \6 U0 u* v" S1.1 监督学习简介 1& Z. i8 T- Y% \' D' m5 r
1.2 决策树简介 1 : e) Y8 a! O. n' T f二 、程序说明 3 - E3 K4 O' M7 b; |1 k9 e2.1 数据载入 39 S9 P- X$ x8 a4 D
2.2 功能函数 38 n/ Q' }% M9 y+ a) W
2.3 决策树模型 4 7 h0 y; G7 _, }8 Y7 I三 、程序测试 5 X4 v% Y( l, N4 }$ Z3 O
3.1 数据集说明 5 / {* [; F2 K6 {% B. n& z5 I3.2 决策树生成和测试 6- f a' V- O7 G% C/ K; ^( }
3.3 学习曲线评估算法精度 7% r/ _1 E& C8 n- v
四 、实验总结 8" @7 f8 W4 n( `
附 录 - 程序代码 8 / N5 n" ~5 }1 R3 w. U2 p7 z8 f/ d, o一 、问题背景! C7 t# _4 }! U0 t3 Y, _
1.1监督学习简介 ; o& [* Q) i. ]: W- |/ u机器学习的形式包括无监督学习,强化学习,监督学习和半监督学习;学习任务有分类、聚类和回 归等。+ j8 ^; \- G e) T. N( L
监督学习通过观察“输入—输出”对,学习从输入到输出的映射函数。分类监督学习的训练集为标记 数据,本文转载自http://www.biyezuopin.vip/onews.asp?id=16720每一条数据有对应的”标签“,根据标签可以将数据集分为若干个类别。分类监督学习经训练集生 成一个学习模型,可以用来预测一条新数据的标签。9 i3 p8 ^0 I Y6 r0 W, J) j- l' k' t
常见的监督学习模型有决策树、KNN算法、朴素贝叶斯和随机森林等。 , f- ?1 v' V) A6 A5 H- g t+ m/ Y1.2决策树简介2 G- S" }+ ]* S# `2 f+ B( E
决策树归纳是一类简单的机器学习形式,它表示为一个函数,以属性值向量作为输入,返回一个决策。4 a- N1 t6 G/ ?( ]% J S* @2 K; Z
决策树的组成3 J, Y3 x* u" h" k& X
决策树由内节点上的属性值测试、分支上的属性值和叶子节点上的输出值组成。 + O6 {" U# |. Z0 z8 u. _/ A# X4 H, g
import numpy as np& S: s: ~& A, u* d4 Q: e7 N
from matplotlib import pyplot as plt3 c, R+ u. P4 Q4 Y% ~: \
from math import log) o# B! y, A% b3 f0 d& W
import pandas as pd% D- g/ U. k8 B) D3 \4 r
import pydotplus as pdp ) p1 e! P8 B8 x X. U 4 r; m+ O R3 m# m"""$ W2 q+ ?# Y- P! j: n k
19335286 郑有为 * q7 ]4 C7 }! W% F2 e$ I: F# d% Y人工智能作业 - 实现ID3决策树 % ?1 Q# E. I1 d' s""") E: `; t0 M. O' k5 `
$ `: W6 e# C. D, Q
nonce = 0 # 用来给节点一个全局ID " }$ R. y& L2 G$ Wcolor_i = 0 ; N8 Q, N0 A" l% t7 d# 绘图时节点可选的颜色, 非叶子节点是蓝色的, 叶子节点根据分类被赋予不同的颜色 # I2 o1 }8 Z" qcolor_set = ["#AAFFDD", "#DDAAFF", "#DDFFAA", "#FFAADD", "#FFDDAA"]2 `7 p7 ]/ r1 H9 B E+ k- n
% I5 F% v! e; i M+ P# 载入汽车数据, 判断顾客要不要买5 w! E9 x: m0 p- F& X
class load_car:0 D P# E( U. Z9 k3 d
# 在表格中,最后一列是分类结果 n, v/ i. _5 p8 f7 [4 Q3 @. i+ w# M/ ~
# feature_names: 属性名列表9 U8 ~, d. N$ B5 I
# target_names: 标签(分类)名! w1 b9 b( \ ^# |
# data: 属性数据矩阵, 每行是一个数据, 每个数据是每个属性的对应值的列表0 t3 ^1 c. e9 \/ U# r, i; @% |
# target: 目标分类值列表 $ s* c8 B8 M5 _. A+ u1 v def __init__(self):+ @& O" |4 ^0 N# ^
df = pd.read_csv('../dataset/car/car_train.csv') , i+ }$ N, R. Y- C labels = df.columns.values & Z+ ^# _7 G4 x( |3 L data_array = np.array(df[1:])' M8 @" X7 Y3 v- K3 I6 X+ T9 n* e
self.feature_names = labels[0:-1] : A7 P" E; Q$ e) @ self.target_names = labels[-1]/ t/ Z$ e! q' }+ _7 m
self.data = data_array[0:,0:-1], \/ I/ _4 A( v" r9 B) n/ x6 M
self.target = data_array[0:,-1] % W$ p) ]- L1 a* h9 A& @ / H- ]7 F5 M( {: D' v' p. E# 载入蘑菇数据, 鉴别蘑菇是否有毒5 b% b7 N6 D" v( @( m7 z9 y
class load_mushroom: b) V+ h' y2 x; F& Q9 O
# 在表格中, 第一列是分类结果: e 可食用; p 有毒. ) a4 V3 d& d# f3 } # feature_names: 属性名列表 9 E. }! | }+ w; D3 W # target_names: 标签(分类)名. w5 F* ]: I( P! k3 c4 c, z
# data: 属性数据矩阵, 每行是一个数据, 每个数据是每个属性的对应值的列表 7 ^/ I- T! Z( p" R5 ?# O # target: 目标分类值列表# S; n9 \$ u! N% d% B
def __init__(self): ! V& o- L4 z% x8 R df = pd.read_csv('../dataset/mushroom/agaricus-lepiota.data') 7 ^* [1 _: l( @) d( b o data_array = np.array(df) 8 }4 P& W% G# ]/ g: l7 o labels = ["edible/poisonous", "cap-shape", "cap-surface", "cap-color", "bruises", "odor", "gill-attachment", & U' \$ b# {3 ~8 ^% c! ~' S* J" z" X "gill-spacing", "gill-size", "gill-color", "stalk-shape", "stalk-root", "stalk-surface-above-ring",/ S: A* A% O0 l( J
"stalk-surface-below-ring", "stalk-color-above-ring", "stalk-color-below-ring", ; g: F1 H+ G {$ a l "veil-type", "veil-color", "ring-number", "ring-type", "spore-print-color", "population", "habitat"] ! |# s+ a" K- z) L* q, P. w self.feature_names = labels[1:]/ C$ U( B* G# c! }
self.target_names = labels[0]% M* Q" j) J/ q
self.data = data_array[0:,1:] : W6 K; x8 h7 { self.target = data_array[0:,0]) k% L( w9 J! Q5 b
' ~8 e7 t$ o& C% Z& l
# 创建一个临时的子数据集, 在划分测试集和训练集时使用- E; c) f ~9 o4 J# }
class new_dataset:- B: K; n8 u$ \: J: U7 n' X5 c; C
# feature_names: 属性名列表. {& q& j1 O7 P' I5 ?
# target_names: 标签(分类)名 ( ]0 W, Y, F' ], n* `. }. ^ n # data: 属性数据矩阵, 每行是一个数据, 每个数据是每个属性的对应值的列表3 D. ~+ W7 U1 a* H0 C3 V
# target: 目标分类值列表5 n3 l$ D/ X3 j2 d# ^7 r9 _
def __init__(self, f_n, t_n, d, t):, h2 ]2 b2 y9 }) g
self.feature_names = f_n - C) H0 Q5 L- B1 y8 [4 l" ?& w self.target_names = t_n + Y* \* H* P" @- K% R+ c4 ] self.data = d8 v1 h2 [; }' t' a" B. z; e
self.target = t2 W X1 F; o& ^! m4 O
3 d& [ g1 a `+ M. f5 x9 u# 计算熵, 熵的数学公式为: $H(V) = - \sum_{k} P(v_k) \log_2 P(v_k)$ , t+ W% I" t& x$ B; W/ ~' m% F# 其中 P(v_k) 是随机变量 V 具有值 V_k 的概率 2 i* {& |' O4 E6 i2 G( u- |# target: 分类结果的列表, return: 信息熵2 R8 z+ ^! {5 U+ I6 j) C3 a
def get_h(target): 3 ?" T( \4 A( b0 f$ s' {: \ target_count = {} ' j1 x8 i1 H ~- d j9 r for i in range(len(target)): 9 B) L9 M1 f$ m' B/ @) B label = target & \7 j% ?& z3 n" ~; g- O0 L1 J if label not in target_count.keys():4 i3 s: _9 `; Y. s. V3 h7 z/ O
target_count[label] = 1.0 ; r( B( W# j/ g) q- X- V0 W1 y( s else:+ O- n# k) J) G/ E9 J, v! p
target_count[label] += 1.01 {( c2 f" X7 R1 Y2 o+ a. n0 r+ I4 W
h = 0.0 ! E1 ?: X, i$ o( n7 \ for k in target_count:. F V' t4 C7 E, w7 N+ V
p = target_count[k] / len(target) 9 V3 |- d% q C, l h -= p * log(p, 2) 9 M& U: b0 c0 ? B ~ return h 8 c, C: s4 P% Y, H: ^* z z0 ]5 g' }0 L5 C+ d' S+ Z
# 取数据子集, 选择条件是原数据集中的属性 feature_name 值是否等于 feature_value, W4 x, i1 x# Q/ N$ u
# 注: 选择后会从数据子集中删去 feature_name 属性对应的一列 5 S; ~. b2 O, u5 { tdef get_subset(dataset, feature_name, feature_value):6 M- _4 S: |( e( i( C7 m
sub_data = [] m7 r3 Q9 s0 l+ n7 M; c
sub_target = []9 P+ i* E0 X- y9 ?$ f; p. v
f_index = -1& Z& J! z, x6 L: D# q
for i in range(len(dataset.feature_names)):2 x) u( ?$ A4 c/ i3 w
if dataset.feature_names == feature_name:! B: _% U6 z7 L& h
f_index = i . E2 F6 {& I4 A break$ W+ Y0 L" K0 o+ y
0 {& b2 {& f3 W, F9 v
for i in range(len(dataset.data)):3 B; ?# Y/ D5 ^3 _: X7 [* `
if dataset.data[f_index] == feature_value: + {, k2 L+ N7 ~ l = list(dataset.data[:f_index]) 2 O& }- r1 a" R l.extend(dataset.data[f_index+1:])1 o8 }1 [% n+ q& {
sub_data.append(l) + r* k5 F/ Z+ I, X3 U4 y6 o1 ~- W sub_target.append(dataset.target) ! p: B7 ~% d7 F5 \$ c" e. s- I . a$ K$ l/ I" I sub_feature_names = list(dataset.feature_names[:f_index]) " s/ H: ?9 b, C( V sub_feature_names.extend(dataset.feature_names[f_index+1:])6 x5 U$ H8 e3 H2 Z' m
return new_dataset(sub_feature_names, dataset.target_names, sub_data, sub_target) 0 R8 N+ r7 I! [$ w! X$ \: P1 D/ D . ?* o6 Q* o9 Y$ I3 S5 j# 寻找并返回信息收益最大的属性划分 2 n( L2 j _6 }# Y. {8 C; y# 信息收益值划分该数据集前后的熵减+ h" }" f, ]& ~* G' G$ \* V
# 计算公式为: Gain(A) = get_h(ori_target) - sum(|sub_target| / |ori_target| * get_h(sub_target))$ 6 R7 |; x& A% J: R" Ddef best_spilt(dataset):5 c# p' U* |" |$ |5 O9 X3 l* B
: |. D/ n9 X/ w$ H5 F
base_h = get_h(dataset.target)* n7 s; [* F& }6 A. N4 v3 E
best_gain = 0.08 w& D' g8 t# f1 a
best_feature = None) _2 {. M1 {+ t! X9 K1 Q( g
for i in range(len(dataset.feature_names)): 8 p2 `4 k6 m/ d( U feature_range = [] $ O; U1 ^' y/ U7 k* d% g/ T for j in range(len(dataset.data)): 7 f5 k0 g* {2 k x, G8 L9 K( B/ a0 r0 [. r if dataset.data[j] not in feature_range: , l. q; [( |0 [. B0 J( L' U feature_range.append(dataset.data[j])) i; Y" S8 ]7 d, f: H4 C. B9 [
$ W# p9 @1 `) d: b
spilt_h = 0.0 $ z5 _# E$ A$ k6 F for feature_value in feature_range:# z7 s u9 K( z7 n) D
subset = get_subset(dataset, dataset.feature_names, feature_value)! ]/ m- m: A/ V$ a- ], ` m
spilt_h += len(subset.target) / len(dataset.target) * get_h(subset.target)9 w% w+ T+ L$ h$ x) s# p
/ T' v, }% H- B, f, S- g
if best_gain <= base_h - spilt_h: 4 S0 a: F6 z; C* b best_gain = base_h - spilt_h+ z- q. m! @2 E0 ?7 Y# Z
best_feature = dataset.feature_names) B: }$ a1 I% C3 v5 V; i2 J5 S9 @- E
5 x6 U6 r+ m& `( k l
return best_feature * v+ {9 n/ q k z3 Z, s' F$ ~' ^1 y. F5 b% S9 ^1 @% b
# 返回数据集中一个数据最可能的标签3 x3 t& N7 g# q3 ~
def vote_most(dataset): \6 ~2 d3 Q7 N1 w
target_range = {} " d! k k, `1 n* N best_target = None " Z: |9 I# g6 \& T8 \ best_vote = 0' x) h4 m- n' c% G }
. \" Y, B+ L0 V- O
for t in dataset.target: 0 u( f# P+ a/ c5 z3 U: [3 l if t not in target_range.keys(): & e* g0 }: N$ D0 b8 } target_range[t] = 1 # H+ j5 V' {# o% |' ]+ U4 O+ t else: 0 u7 l4 T5 ^- K% D, e target_range[t] += 11 K y- z( a4 S
/ N: ^) {5 |( [/ F- X v for t in target_range.keys(): . b. x+ g. I8 q9 O3 U if target_range[t] > best_vote: + f7 N7 \4 C M( }$ U best_vote = target_range[t] * X4 c! m* r, R3 S best_target = t $ }. c( c3 U' p ] T/ } 1 Q3 c: \) ~- @ return best_target6 b& B, d) U' R
) t6 L' `; q- M" s0 r# 返回测试的正确率1 _1 T$ B- d% w3 [
# predict_result: 预测标签列表, target_result: 实际标签列表 # ^: C, T. m _! l# g+ A0 d1 P6 Idef accuracy_rate(predict_result, target_result):1 T" F) W3 J& g5 L& b1 d
# print("Predict Result: ", predict_result)' G! u% A' N% u" l( u$ z+ d
# print("Target Result: ", target_result)- a3 s8 t& j$ S' k
accuracy_score = 0 7 s# d/ c7 v3 E4 e for i in range(len(predict_result)):* O# G: b6 B) T* S) n
if predict_result == target_result:) y9 g* [4 N" N
accuracy_score += 1% a# l2 n2 c% V
return accuracy_score / len(predict_result)! y+ D( L2 I j0 }2 ?$ G
* Q/ \9 @$ q( [' {4 B J$ y, V# 决策树的节点结构1 Y9 J3 Z0 J: S; u# |5 m
class dt_node:$ ]( {4 `" ]& I0 Q
1 X" V: e: i8 b- Q% x. V def __init__(self, content, is_leaf=False, parent=None): 3 x1 |; N5 _2 b" A global nonce & U0 `* }" A% m W& k, s$ M self.id = nonce # 为节点赋予一个全局ID, 目的是方便画图& @- k5 I ]3 x+ m6 r
nonce += 1 ! Z! `: c1 F) U7 f. d/ C self.feature_name = None & x3 h% u+ C7 S) }; [) B2 U+ \+ ?0 D self.target_value = None - ` Q, l" ~' t# Z( ] self.vote_most = None # 记录当前节点最可能的标签; a, N$ {" a+ V! L
if not is_leaf: 4 f5 N) _9 O3 p5 S4 z3 B5 v2 w self.feature_name = content # 非叶子节点的属性名' v: ]) y$ ~: g2 q- {, ~2 m
else: $ ^' D; |8 @3 [! j- q self.target_value = content # 叶子节点的标签8 X2 ?" o: B. n
( ^3 D" V' _& n" Z
self.parent = parent " K2 W$ s! K' U4 z+ E$ M8 p ` self.child = {} # 以当前节点的属性对应的属性值作为键值, H/ @3 X. s% _9 c6 J