/ r8 j; O- a: x. y决策树模型; k: h; ^% @7 ^3 E) u. N
目录5 v' E$ V& |! z$ \% {; Y
人工智能第五次实验报告 1 2 \0 v) }7 c) q决策树模型 1 5 n. J3 |4 d0 ~ t" r一 、问题背景 19 ?' r& H: _4 `* I) j
1.1 监督学习简介 1# Q( E4 h# g4 u
1.2 决策树简介 1 N$ ?' v1 h: }二 、程序说明 3) G; j: ]0 a& }* C: ]5 S
2.1 数据载入 3 ) F7 D; {- E% {1 M2.2 功能函数 3, H9 d( P: M' k! F5 z! L
2.3 决策树模型 4 3 ~& [: d- W+ w Z& X. v ]8 @三 、程序测试 5: d9 r9 g7 c6 l* }
3.1 数据集说明 5 % E% E1 i3 \3 c1 q2 U3.2 决策树生成和测试 6 8 L- g8 ` k) v3.3 学习曲线评估算法精度 7 . @% x$ x; x* S! `2 `' m, u9 X四 、实验总结 8 3 |" ]* Z/ D2 x/ U附 录 - 程序代码 8 4 S% F, T- O( M9 T O一 、问题背景. Q5 l# X) |0 n5 Z. X
1.1监督学习简介6 o) Z: U6 V1 x! W' ]) W1 J7 @
机器学习的形式包括无监督学习,强化学习,监督学习和半监督学习;学习任务有分类、聚类和回 归等。 0 z& C: h& p- @0 H! x监督学习通过观察“输入—输出”对,学习从输入到输出的映射函数。分类监督学习的训练集为标记 数据,本文转载自http://www.biyezuopin.vip/onews.asp?id=16720每一条数据有对应的”标签“,根据标签可以将数据集分为若干个类别。分类监督学习经训练集生 成一个学习模型,可以用来预测一条新数据的标签。) Z7 D, W! A( f. G' _9 E
常见的监督学习模型有决策树、KNN算法、朴素贝叶斯和随机森林等。' E( Q) z" C8 W- w
1.2决策树简介 & ]. O! L& F6 G( Q3 _9 K决策树归纳是一类简单的机器学习形式,它表示为一个函数,以属性值向量作为输入,返回一个决策。 $ Y2 n3 |! l+ U: j( S& R( W4 [7 O决策树的组成3 I0 ?# C9 b+ y" K" \# M
决策树由内节点上的属性值测试、分支上的属性值和叶子节点上的输出值组成。 ' N1 n; Z+ ]: d. ^) q! C1 o$ E8 r2 D$ q
import numpy as np' z( F! [% F4 P( H
from matplotlib import pyplot as plt4 G' `; c. q s; [* T% c
from math import log( ^7 c6 e% O' m4 I
import pandas as pd : X( p! [6 @6 s; ~import pydotplus as pdp+ |0 N0 [ ~- s! L0 \% J5 n7 f
; n' U9 {- S8 o" ?"""8 e& B/ U. H* I8 x8 F8 `
19335286 郑有为 4 x+ Z' E' x, u( \; ?6 M8 K; J9 v) B人工智能作业 - 实现ID3决策树1 n A( [' c# B6 y
""" 5 ^- u4 F0 `# u' R# Q @/ C- d! t/ D: z8 P/ d! F1 a
nonce = 0 # 用来给节点一个全局ID; c6 K) [1 l/ E; ^0 \+ ~) V
color_i = 0 H! a0 A7 O0 u, S z0 n
# 绘图时节点可选的颜色, 非叶子节点是蓝色的, 叶子节点根据分类被赋予不同的颜色 5 h1 ~9 m7 s+ T: ^7 Y% Kcolor_set = ["#AAFFDD", "#DDAAFF", "#DDFFAA", "#FFAADD", "#FFDDAA"] % F. y/ R; W! M) u; X; [+ e$ H* E$ s2 R7 j4 [+ y: K
# 载入汽车数据, 判断顾客要不要买 + k I. y8 J. ^: j: r& F% P& X: W- Oclass load_car: $ W3 { o/ J/ n" `2 R8 }8 Y # 在表格中,最后一列是分类结果 0 C5 [8 {3 Z. ]$ {8 Y9 }5 h # feature_names: 属性名列表* e. `$ g& A; G
# target_names: 标签(分类)名) p5 E$ Y2 G7 _) ] p Y o: l
# data: 属性数据矩阵, 每行是一个数据, 每个数据是每个属性的对应值的列表 8 z& s3 {* s% A # target: 目标分类值列表 - W' T2 D0 U* B4 X1 v& N def __init__(self): ) d. `5 W+ ]0 T8 F" ^ df = pd.read_csv('../dataset/car/car_train.csv') 8 L: b$ n; m& E: i, b( _! x labels = df.columns.values# v3 ?- v' `; m
data_array = np.array(df[1:]) 2 P4 h/ z! j$ G6 Y self.feature_names = labels[0:-1]( _ v- n% o/ Z
self.target_names = labels[-1] 9 b' m; P" h9 `, B! o1 D+ m& P: \ Q self.data = data_array[0:,0:-1] 6 P% o! {2 t, q9 S0 o- | c( `. ` self.target = data_array[0:,-1] % G% X4 S- o. y) I4 l; f! E9 |, T" c) y% l* {
# 载入蘑菇数据, 鉴别蘑菇是否有毒 , U. ^+ [$ k* A" J; \$ _: c) i' Q9 g- wclass load_mushroom:/ V+ z6 B% \$ q
# 在表格中, 第一列是分类结果: e 可食用; p 有毒. 1 s' R9 {, P d+ x6 ` # feature_names: 属性名列表7 w5 ^! S* r. e' X4 k
# target_names: 标签(分类)名 9 d1 U h% T. t; R( l # data: 属性数据矩阵, 每行是一个数据, 每个数据是每个属性的对应值的列表' F# y, n1 a! Y0 _& T" |
# target: 目标分类值列表 * K9 R5 G% M ]1 Q# Z def __init__(self):% y7 J0 x3 g& n9 E) K
df = pd.read_csv('../dataset/mushroom/agaricus-lepiota.data')- I: Q/ r7 i% e4 M/ N
data_array = np.array(df) 8 P3 r7 u' e' g* Q* C$ R labels = ["edible/poisonous", "cap-shape", "cap-surface", "cap-color", "bruises", "odor", "gill-attachment", . E7 G+ V1 i$ P "gill-spacing", "gill-size", "gill-color", "stalk-shape", "stalk-root", "stalk-surface-above-ring", # U% R$ s5 E4 e$ H3 n* G! T "stalk-surface-below-ring", "stalk-color-above-ring", "stalk-color-below-ring", # g. B* z$ U8 `: k3 v6 g "veil-type", "veil-color", "ring-number", "ring-type", "spore-print-color", "population", "habitat"] ; X, T, F9 D: |: F: {$ o! k+ b self.feature_names = labels[1:] ) |* V) L" ?1 j$ K3 ]. ^& }: L self.target_names = labels[0] " d$ @9 O4 F) Z7 y self.data = data_array[0:,1:]/ T; m, Z$ N u/ @& a5 v! s7 @ N
self.target = data_array[0:,0] 0 {8 w" E, R$ Z; m# m8 S" `! ^9 D# O/ V/ T. b7 D% @: y2 g# D) m
# 创建一个临时的子数据集, 在划分测试集和训练集时使用 0 s5 h" l8 Z! b- c! d* _class new_dataset: $ s* i, Z4 h' }; a # feature_names: 属性名列表 1 H4 [% C" f' ~2 Q0 x # target_names: 标签(分类)名 # \# F% v# K0 s R3 H # data: 属性数据矩阵, 每行是一个数据, 每个数据是每个属性的对应值的列表7 W; ~; @/ p ~5 w/ ^1 g" y
# target: 目标分类值列表 " Y! b9 _, H7 T+ c7 L def __init__(self, f_n, t_n, d, t): & s0 E5 B! M+ P2 B" L4 R self.feature_names = f_n( E4 c( {* k. `& H* _2 p
self.target_names = t_n. z7 o# q% c. @; N' k }# c- ?4 p
self.data = d# p" e4 G5 n/ d1 B+ Y2 [
self.target = t$ N2 C8 n ], v
" H) A1 i( N( ]& P( Q# s7 t8 p
# 计算熵, 熵的数学公式为: $H(V) = - \sum_{k} P(v_k) \log_2 P(v_k)$ * d) {5 k& `7 p6 P# _: M# 其中 P(v_k) 是随机变量 V 具有值 V_k 的概率3 M# {& {" k2 [. | t! K" c+ k
# target: 分类结果的列表, return: 信息熵* C- S6 u$ \. }! }/ \+ U% H/ K
def get_h(target):/ A# Z+ ~1 J; g
target_count = {}( n# L) Y8 n3 L+ U! N: R6 G1 x4 E
for i in range(len(target)): # c$ X3 Y$ S6 q0 \ label = target 2 O3 S6 m' B o( ]# @1 t% b if label not in target_count.keys(): # U) [' u: @" M% P4 n target_count[label] = 1.0 4 D9 I/ I3 G2 h7 Y else: 8 U3 N/ K9 C$ W6 a target_count[label] += 1.0% j" Y- @+ m) W# t. g5 {3 \
h = 0.0 / H- n, K6 G5 P% X; ?" M* D for k in target_count: . y6 v1 m: A% W2 Z+ C p = target_count[k] / len(target)8 C8 j. ]& i8 ]
h -= p * log(p, 2) Z d& V5 r$ Q0 f, Z) L4 g
return h ! O4 C5 m% w( _$ S1 G ' f8 Y" b. [6 \. a$ |# 取数据子集, 选择条件是原数据集中的属性 feature_name 值是否等于 feature_value ! }! ]2 g; i1 C- T, g2 @* O# 注: 选择后会从数据子集中删去 feature_name 属性对应的一列( [! X- q7 V0 n: `1 G
def get_subset(dataset, feature_name, feature_value): 3 @' k5 X! \ ]. s5 q/ ~- R* R; T sub_data = [] * c! u" c- X, h p; D sub_target = [] " q J) _ C- n8 d f_index = -1! C ^! Z* q8 S5 a0 I" q$ S9 p
for i in range(len(dataset.feature_names)):: D9 w( [' N3 x; p
if dataset.feature_names == feature_name:# I" K5 A6 r& @" B/ d
f_index = i3 P8 g' N/ y: g- w
break 0 `' U% v4 \+ C/ |. ]' {3 X0 t ' M/ K# v0 T8 z8 l' V9 Z for i in range(len(dataset.data)):$ f6 J4 R$ p6 c# w
if dataset.data[f_index] == feature_value:/ j* N5 L! _& h4 M E% ~+ ]1 V! s
l = list(dataset.data[:f_index]) 4 n6 [- N6 p' F4 [ N( W l.extend(dataset.data[f_index+1:]) " ` ?. c! ]$ R% R sub_data.append(l)7 U+ u0 v3 W- @- p
sub_target.append(dataset.target) _6 J8 r1 h( A# p+ R, q- W Y. v/ O3 [
sub_feature_names = list(dataset.feature_names[:f_index])' K* |6 x# k3 k0 S# K& l: R
sub_feature_names.extend(dataset.feature_names[f_index+1:])- b# N0 u6 o% R9 ^. \
return new_dataset(sub_feature_names, dataset.target_names, sub_data, sub_target) ! ]! O$ j; t; F. t " C& |1 d3 I y# H& y" b$ i6 g) S# 寻找并返回信息收益最大的属性划分1 @% ~4 n; `$ S- ?$ p# U) f
# 信息收益值划分该数据集前后的熵减) C, z j d# j( p
# 计算公式为: Gain(A) = get_h(ori_target) - sum(|sub_target| / |ori_target| * get_h(sub_target))$ - ^* ]% a% |* ~: @* N- B' tdef best_spilt(dataset): 5 A" L" b( k2 s4 a) d: x& S5 A& \1 }% [
base_h = get_h(dataset.target)5 D ?0 ~% Q, @$ d0 e
best_gain = 0.07 s! q! N: }5 e( i5 s& Q
best_feature = None " ]1 T/ S# c8 B$ H$ P! {1 ~; Z for i in range(len(dataset.feature_names)):5 B+ L2 i9 U$ v/ J
feature_range = [] B* U! G8 r/ s. Z4 V! W X for j in range(len(dataset.data)): ; [0 A2 p4 c* } if dataset.data[j] not in feature_range:0 G# ]# L. _6 I$ M
feature_range.append(dataset.data[j]) 4 k# a4 Q5 r' i( j' T- M& _+ T ) \- y- _5 q9 n- R' s3 N( | spilt_h = 0.0. q/ Z, N+ C7 n) k5 @; B- a& d
for feature_value in feature_range: , n" ?# e8 a( e9 w% [6 O* ?1 R+ L subset = get_subset(dataset, dataset.feature_names, feature_value)* m3 q2 |9 T* u: Z1 u' V& C0 v' ]( d1 u
spilt_h += len(subset.target) / len(dataset.target) * get_h(subset.target) 5 [: Y0 U+ ?/ F( x6 i0 L+ [. }% l7 w9 S- {8 H& C
if best_gain <= base_h - spilt_h:; h# n0 }5 W$ c0 w3 S4 u) n7 n
best_gain = base_h - spilt_h( F3 t1 V' _) e; p) T+ T- ~; h3 v
best_feature = dataset.feature_names& m; R* n1 c5 Y5 f
+ X% j; f3 o P8 A n
return best_feature1 S. i5 k+ W0 O
( K6 m' F4 x& t0 B3 i% q8 U
# 返回数据集中一个数据最可能的标签2 T' ]1 ]* w2 N5 ]. n( h& }
def vote_most(dataset): " `' q H5 W5 v$ S4 D" M target_range = {} 0 C2 \' i+ W" y8 B& E* p best_target = None & T5 g+ A( D# [ best_vote = 0' N a$ P1 D' J
) }. ?" k) }; X" W! q! `! b
for t in dataset.target:' g3 w4 n" g" z+ ?; M0 a1 O9 z
if t not in target_range.keys():5 K1 j' P& {' \
target_range[t] = 1 ! V0 b+ f5 D* r$ Q1 O8 l2 K( N- v' ] else:* C% V1 L: v8 ?' p/ H1 d" E
target_range[t] += 1 : {' p, N8 q& |0 z8 @. q4 ]5 ^. k( W3 [3 A6 r I" m1 a( S
for t in target_range.keys():) H& v/ a' t- D5 l( \) V0 W; u
if target_range[t] > best_vote: 5 j9 m0 u) p# A2 W; W' H best_vote = target_range[t]( |6 k7 |* ^" F7 `
best_target = t! O* C) \" b, ?0 W8 K+ Z
6 V; `$ S) r9 K8 { return best_target 1 Q( ~; U* A8 @% l2 D8 x % r( }+ d/ f: ^2 c" i, b- t# 返回测试的正确率* e3 C: x1 `) E7 J; Q
# predict_result: 预测标签列表, target_result: 实际标签列表2 V, ~* R" W+ Q' Y4 Z! y! M& i
def accuracy_rate(predict_result, target_result): & I* N' ^& s. O+ O # print("Predict Result: ", predict_result)! r( R* d5 s) `: ^1 a5 K2 Z
# print("Target Result: ", target_result) ' C& ]3 @) |: y) U, g$ {/ ?9 N/ Y U accuracy_score = 0 9 Z! n6 h' _0 ]) ^ for i in range(len(predict_result)): * f9 d9 w( {* o0 o# a8 ?& E if predict_result == target_result:* ]( x, y0 h: [. B+ |
accuracy_score += 19 S/ D+ u% X+ Y: U
return accuracy_score / len(predict_result)7 X" P- @% @ B3 \- E
( S4 \) k0 [7 ~) Q
# 决策树的节点结构 9 M/ G# [" E! S7 E/ xclass dt_node: 4 z! @# G$ K- }! m; f " X( e% }% o- E; I def __init__(self, content, is_leaf=False, parent=None):/ }4 X" N" T8 Z9 v' U! Z
global nonce : L! x7 R' E2 L; X7 u self.id = nonce # 为节点赋予一个全局ID, 目的是方便画图* H7 |. j8 G$ y8 k
nonce += 1) b1 b$ D& I1 [- w
self.feature_name = None) n1 x8 ^" y4 T& w' H" [
self.target_value = None ' C p& @: j/ D3 v" g- O: q# { self.vote_most = None # 记录当前节点最可能的标签 8 E4 M v. |) l2 K if not is_leaf: / }1 k9 d9 p4 l- \% Q) ?$ H3 o self.feature_name = content # 非叶子节点的属性名( e/ a. j; M" w. F& `: k
else: 8 Z/ [3 W2 H2 g9 n2 I4 T0 j' c% T2 ` self.target_value = content # 叶子节点的标签3 [" G9 r% x: O7 d3 U
2 a' v- g3 ~! R! w2 V" A& T" ^
self.parent = parent 0 v& b/ i6 C$ m; a Z* q. C self.child = {} # 以当前节点的属性对应的属性值作为键值% g& t' m% w1 I9 O. `* k: F
1 a0 _1 U$ c0 W9 {
# 决策树模型3 V0 s, m P& i5 X* n
class dt_tree: 6 Q$ U( @4 Y# p & f" K1 X8 v& H' V5 x. W def __init__(self): 9 z0 y! u; Y B# w2 C( H self.tree = None # 决策树的根节点3 `: r. g9 T$ {# o$ U4 _
self.map_str = """ 5 s/ ~: n# J! i2 Z: h# [# ~ digraph demo{0 M6 P! E/ K5 H* v( d0 i
node [shape=box, style="rounded", color="black", fontname="Microsoft YaHei"]; V& z O0 @8 G# p; r6 G; `
edge [fontname="Microsoft YaHei"];6 P9 k, h% K4 v$ X0 H( }
""" # 用于作图: pydotplus 格式的树图生成代码结构 3 u, e! k1 K( G1 s* C self.color_dir = {} # 用于作图: 叶子节点可选颜色, 以标签值为键值3 K5 e. e, G. ^- E
6 s7 _" j/ H' V x9 \9 ^( |' { # 训练模型, train_set: 训练集 / F5 E6 O* R: |4 [. I2 v/ G! O, C3 P def fit(self, train_set): # n6 m5 c+ `* {. X* C9 F# m/ `& d @( m" H3 e ]6 t* r4 \' y0 e
if len(train_set.target) <= 0: # 如果测试集数据为空, 则返回空节点, 结束递归 9 c3 C+ y8 q/ C1 t+ \8 Q; \ return None z Q- e8 V7 y( r7 y
+ I+ D; ^' W9 I6 ?! ^. u target_all_same = True, V u" E( t, U+ ] |% N$ t
for i in train_set.target: . X5 }# t& s0 I& x1 } if i != train_set.target[0]: . g$ Q; Y# V$ V target_all_same = False6 O4 g1 q% x" t8 b6 ], h
break* D8 ~9 V S- x) T' E
6 \4 G) u1 n! Q" u8 | if target_all_same: # 如果测试集数据中所有数据的标签相同, 则构造叶子节点, 结束递归 9 B. K9 R L( Y5 s node = dt_node(train_set.target[0], is_leaf=True); A% v# v" C$ b/ }7 Q" [# M: b2 C. E
if self.tree == None: # 如果根节点为空,则让该节点成为根节点 ! [* |( e S8 B# L/ s- h' M- a d. m self.tree = node) m' E3 i, A) k& l2 v" n0 a h. q
2 J# |- @4 ^3 X$ p # 用于作图, 更新 map_str 内容, 为树图增加一个内容为标签值的叶子节点 y8 v1 q" P3 ~- q% F, i node_content = "标签:" + str(node.target_value)" ~4 R! f/ w1 p8 {0 f6 {4 v
self.map_str += "id" + str(node.id) + "[label=\"" + node_content + "\", fillcolor=\"" + self.color_dir[node.target_value] + "\", style=filled]\n"( k5 i! m/ O4 V- [4 g, ?6 J9 e, L
/ F* A: ^1 z. A U( C
return node + Y4 y" w4 Y* s elif len(train_set.feature_names) == 0: # 如果测试集待考虑属性为空, 则构造叶子节点, 结束递归 & l* T0 W! [2 X6 Z' ^ node = dt_node(vote_most(train_set), is_leaf=True) # 这里让叶子结点的标签为概率上最可能的标签 - D* g. V# m6 l2 E if self.tree == None: # 如果根节点为空,则让该节点成为根节点4 x2 t5 Q) W: K3 g
self.color_dir[vote_most(train_set)] = color_set[0] 3 u( r# u( g" T self.tree = node9 k# `/ p6 U6 b p
3 ^# \1 x0 x H2 m+ w+ j" Z
# 用于作图, 更新 map_str 内容, 为树图增加一个内容为标签值的叶子节点 $ `' P* V* p8 U8 W; ^8 s node_content = "标签:" + str(node.target_value)% d; \2 v$ R0 b
self.map_str += "id" + str(node.id) + "[label=\"" + node_content + "\", fillcolor=\"" + self.color_dir[node.target_value] + "\", style=filled]\n"& f4 @/ c, D0 `" T9 r% y
. J9 W- S6 p- x( ]% ~ return node0 ^. }$ E- f# ?1 W
else: # 普通情况, 构建一个内容为属性的非叶子节点 # p; |0 c5 F0 R5 O6 w# P& q best_feature = best_spilt(train_set) # 寻找最优划分属性, 作为该结点的值 + S* A; s$ @$ h6 e. `) V5 h best_feature_index = -1 6 J- L6 K' z8 u for i in range(len(train_set.feature_names)): " d* i7 [1 t+ m' A5 [9 l: A if train_set.feature_names == best_feature: . E$ B8 n$ i H4 d best_feature_index = i ; w' I: C3 g5 S" o0 n, N break 6 ~- H4 P, A1 O& v$ Z! r, O8 y2 _5 @1 Q1 o' ~6 L
node = dt_node(best_feature) $ ~1 e1 b& m0 n2 l j1 i4 P# |. k node.vote_most = vote_most(train_set). Y# B3 i# o4 Q7 B$ v+ z$ U! V
if self.tree == None: # 如果根节点为空,则让该节点成为根节点 - b' a0 Z0 N' E self.tree = node 2 r/ u# U; c$ K# l # 用于作图, 初始化叶子节点可选颜色 0 D$ \/ c/ }8 x& d for i in range(len(train_set.target)):9 i% j1 s5 K; J% [0 o
if train_set.target not in self.color_dir:/ q8 g" k, I" n/ R& b* H* l
global color_i" \2 T5 W9 X% N% X
self.color_dir[train_set.target] = color_set[color_i] # i' E- Z3 k4 E% l1 @8 w" q2 k color_i += 14 w; _ }# ~* s+ p: [6 ~' O; h
color_i %= len(color_set)& S9 u4 `" p7 ~
% J0 Q/ i7 i, M3 ?
feature_range = [] # 获取该属性出现在数据集中的可选属性值9 f. }! w+ `0 _' [0 `$ O/ @
for t in train_set.data: 8 n1 F0 {0 m0 n t4 Y5 L if t[best_feature_index] not in feature_range: 5 v% H7 u' ~) P: h* x feature_range.append(t[best_feature_index])7 y& i9 J6 I' j% w9 ^% X" y8 ~1 ~: h
: U' R: X8 z, m/ r2 V5 Y" s
# 用于做图, 创建一个内容为属性的非叶子节点 - j# u. d% N8 g5 t6 C node_content = "属性:" + node.feature_name ) E( e! [2 [4 g/ d( u& [ self.map_str += "id" + str(node.id) + "[label=\"" + node_content + "\", fillcolor=\"#AADDFF\", style=filled]\n" - V" R! a& z. w. J! Q: Q$ B$ h V ?& E, @7 Y* Q
for feature_value in feature_range:5 U0 S$ X2 ]* p' w! h+ o
subset = get_subset(train_set, best_feature, feature_value) # 获取每一个子集2 l' X8 d- t4 L1 ?; v' D$ n+ G2 d
node.child[feature_value] = self.fit(subset) # 递归调用 fit 函数生成子节点+ s) s$ b& r2 I4 u
if node.child[feature_value] == None: 6 r2 F! G3 k7 }4 ^ # 如果创建的子节点为空, 则创建一个叶子节点作为其子节点, 其中标签值为概率上最可能的标签: _$ ]/ u" g$ q/ H1 b4 @/ L
node.child[feature_value] = dt_node(vote_most(train_set), is_leaf=True)) |, O& l5 Y$ u
node.child[feature_value].parent = node & O; j, _9 |4 Q6 R 0 M5 {* N, c/ @) g, _ # 用于做图, 创建当前节点到所有子节点的连线8 D- r3 ~$ @5 i8 b' x
self.map_str += "id" + str(node.id) + " -> " + "id" + str(node.child[feature_value].id) + "[label=\"" + str(feature_value) + "\"]\n" , T% r, \ L/ q A' T+ N+ N8 l1 d- V; }1 _8 h& y
# print("Rest Festure: ", train_set.feature_names) ; V$ c4 \4 c7 L. g # print("Best Feature: ", best_feature_index, best_feature, "Feature Range: ", feature_range)7 m; N* f, o' A# a
# for feature_value in feature_range:6 v y! R" \/ ]: a# ?
# print("Child[", feature_value, "]: ", node.child[feature_value].feature_name, node.child[feature_value].target_value)$ g4 R6 A: [( ^" }) N5 a2 @4 p% S
return node$ s2 N% [( H5 D1 p/ i: }
4 _, D& o" a4 |3 P( i
# 测试模型, 对测试集 test_set 进行预测" y1 H3 M0 k) L9 \* R5 p
def predict(self, test_set):4 [8 d1 R# o; |+ |; _7 N8 Y0 e
test_result = []/ t1 J1 F' H# f/ I8 M4 g/ Y- N
for test in test_set.data: ) C0 Y1 C8 O: e. ~$ y7 {) V node = self.tree # 从根节点一只往下找, 知道到达叶子节点 $ q) F# Z1 u% I+ P, G while node.target_value == None:, L: D9 O# T/ {8 d. i
feature_name_index = -1 ) Y& k% y/ w9 z* ] for i in range(len(test_set.feature_names)):) F& g' j. ?5 y( v6 l! t Q
if test_set.feature_names == node.feature_name:: S+ N m! Q% |8 c8 z
feature_name_index = i2 y4 N+ j# h+ U" J4 h
break 3 Y, k3 }' O* d3 z- [! v P R% L if test[feature_name_index] not in node.child.keys(): ' X% W! ?$ m; H) o2 E" | break / f* ~6 u+ o9 @/ N6 J: e4 a else:- D5 c# N7 X% I
node = node.child[test[feature_name_index]] / H$ B& c' p0 Y3 a" g( Y5 \ : E3 H4 {* S7 v" ~& s* t f if node.target_value == None: 7 R- Z9 O( e$ `- A9 Y test_result.append(node.vote_most)4 ?$ ?1 I) k+ Z/ X( b: v
else: # 如果没有到达叶子节点, 则取最后到达节点概率上最可能的标签为目标值+ d0 B! w( i1 ^! l" D" ^5 X
test_result.append(node.target_value) 4 p l/ m: k* N" g; h/ ]% h6 Q7 h( u! k/ w. W
return test_result) ]; S7 A% T! I* `0 _3 Q% S' d% a
3 F5 Z$ ^2 K8 X8 W; Z3 C
# 输出树, 生成图片, path: 图片的位置 # I/ o$ L$ u9 Q) l def show_tree(self, path="demo.png"): ^2 i" P( \1 q5 u
map = self.map_str + "}"$ }8 e4 k% k9 u
print(map)% e7 w* b& a5 H# ~1 e
graph = pdp.graph_from_dot_data(map) 6 C/ u2 N4 |" g, C a* `1 f graph.write_png(path); q7 y% J' F9 E! M; k4 H! R+ }
% E* A* u& _% v! \' ?3 e
# 学习曲线评估算法精度 dataset: 数据练集, label: 纵轴的标签, interval: 测试规模递增的间隔+ T* F( E0 F/ o: \3 y2 ]
def incremental_train_scale_test(dataset, label, interval=1): $ G1 g6 m' X+ z4 H/ b$ [! t& s7 C" l c = dataset3 r/ I6 d+ c# m
r = range(5, len(c.data) - 1, interval)/ a. S6 ? K/ k' F2 j5 d: o
rates = []/ e, p) ?1 I; u$ q
for train_num in r:$ @" D( u% k. ]" ^
print(train_num) + O, v7 f! k& c: I, {% I' g train_set = new_dataset(c.feature_names, c.target_names, c.data[:train_num], c.target[:train_num])$ B' J; w) `1 k K; c
test_set = new_dataset(c.feature_names, c.target_names, c.data[train_num:], c.target[train_num:]) % ]* ~( @2 Z$ T! J+ S dt = dt_tree()3 @/ J6 Z# _/ H/ T4 G L
dt.fit(train_set) : ^% f Z6 N: b5 d! D rates.append(accuracy_rate(dt.predict(test_set), list(test_set.target)))( @# Y% F0 m! X5 B1 y( W
Y* \; z+ [7 N! H& H1 z
print(rates)0 [0 x% C3 \$ l- S+ A$ `/ H
plt.plot(r, rates); j$ P' f* S O( Y! x+ I1 H9 `
plt.ylabel(label) & b1 n) h+ p1 d+ @. X plt.show() C2 ~9 H K. k8 T- W' r$ T1 S( D, n+ o4 T
if __name__ == '__main__':) a$ B Y; v |, Y8 {
; K$ u* J' A7 r
c = load_car() # 载入汽车数据集' Y; D$ v5 k6 B/ W d
# c = load_mushroom() # 载入蘑菇数据集, O+ f( l& O3 ~! ?+ a
train_num = 1000 # 训练集规模(剩下的数据就放到测试集): z/ m/ W9 o8 R$ N. m; w E
train_set = new_dataset(c.feature_names, c.target_names, c.data[:train_num], c.target[:train_num]) 9 m7 n. C4 m ^, d2 D test_set = new_dataset(c.feature_names, c.target_names, c.data[train_num:], c.target[train_num:])* i- w1 q. @! { V$ s( u' Q
7 ^' D2 N0 |; c0 }8 K) x+ m dt = dt_tree() # 初始化决策树模型 . o2 i3 Q1 t# n0 p' C dt.fit(train_set) # 训练$ C. l6 T) w/ S% t
dt.show_tree("../image/demo.png") # 输出决策树图片 - M; ?4 i* \2 r# U. }; n print(accuracy_rate(dt.predict(test_set), list(test_set.target))) # 进行测试, 并计算准确率吧# U" j7 A4 C& ?8 @' s, S
+ P" k/ G6 u' c) G8 g: ~ # incremental_train_scale_test(load_car(), "car")8 ?/ Z$ d1 @3 x3 R' h* |6 c; l n
# incremental_train_scale_test(load_mushroom(), "mushroom", interval=20)6 S- }5 ]- K0 T! s5 T! [: v) S, r% [
; j' ^. F7 @; R9 G7 T4 p
: j5 c C% X) g3 ^- G
1 Y! O0 q7 y4 m" ]$ }1 Y6 q
1* f" l- R+ t7 g8 f' r0 L
21 S# g8 o5 t) Y( u/ C7 R0 m
3 # v5 Y) H6 M. [- Z m D* i48 Y0 q6 u' e5 S& y% T: j
5% P+ }- G5 [) ]9 L1 p0 I
6# \" M$ w0 @/ L/ q1 V8 M
71 B+ r, ~. p/ r
8 - |; m- O1 e4 j0 t, ?/ [. U$ I9 . |' s* s, k* n, L! Y10 * N# r/ y( t- x114 a. D+ G9 c& K7 V
12; S9 n `# {" {( k' M* F/ L! K
13' W8 Q, Y. y" ]% q; z
14 h9 i8 B7 B( B; Y155 M2 V+ ^! C: M3 ~& q
16' z2 d$ `- M+ G# L
17 `" i0 n. X5 M) g, [
18 0 l) q7 d! L4 N4 l. y5 y196 @6 F. X0 A( j5 G1 g4 }
20 q: r* G9 S9 G# M; Z$ {
210 R m( q) |) [; t
22 & q- [+ c+ ^9 }7 q236 h5 r1 p/ W$ _+ g& O
245 |- c7 M6 O. m1 {
25 3 z; B* {, g+ I2 {' d f7 z26 " I Q7 Z8 ^# C27. } y2 A& D6 \) U8 o
28 6 h& Y! E' \7 @) G29( ^5 o: P/ H/ _; b- }
30 ) W! n! y/ d' l7 x31 8 j; @/ R- r/ n5 G+ D1 Z- ~1 [1 N32 3 p) A! B' N4 B8 j33 * R* g3 u% O: p$ q6 G, T1 P4 |34( R( [7 |1 x$ _- s" i- r
35' l! ]9 N0 P! z- g+ x
36 . }, y0 F& U. N. q# S u1 T5 S378 g$ ^$ S7 E0 x* `, v
38 * V% {6 A7 H! m/ T4 K6 Y- w39 ( P* f# h; V( r) D! \- w40) k' U; Z9 k8 F9 K' b5 _. r, C
410 O3 W4 v& O5 g# U
424 L3 L8 u" Z- W9 c
43 / {9 Y, _ ]# X- U" }4 l44( _$ I9 G1 c! S! a. M) D8 Z2 ?
45 p- ]7 A Y; S3 }% }
468 [# ~1 G* R5 [: ?0 t# L# y% I( q
47; H8 |) i+ ]7 e' H
48 1 `9 v* K$ `7 X3 Z1 a' J9 F49 ' \. f9 `4 y, E- l# V3 j: \506 G! R" f4 Q0 J
517 L! r2 ~0 q0 f
52 ! o! j2 {$ U$ e- s6 j5 B53 # }+ {: ^8 ^% R& `1 E5 |2 d" H ~54% x* e( a* n$ f8 D4 J6 n8 F+ O
55$ B$ q& i/ v+ @" _2 E& U$ W; c) C
56 / q, ^# g! q, c9 f+ d6 |57; q- W. d- g1 g. ?4 p% c
58 ' S# h& ?% K: W) }7 [59' |# k! t8 r: E6 L; j! n. G
60 2 N1 y4 z6 W- L. C% Z61 4 {! k/ q9 }( f7 ?2 C62 I: M$ Y: H2 v6 x8 w" n. K9 L637 k7 l' [5 b8 I, ~2 N
645 i" O* y; Y7 {% f+ _
65; i9 M. s; ]4 A; C
66 3 ~* P* u. ^5 w67 6 W, T5 i0 k' @3 @, m( ?" X1 i688 E9 h! C. X. l8 A% R4 Q9 B
69 4 ~6 D. |! L; A% E3 F8 j( G: C% H' |70 0 X' I* }2 I' O- d3 A2 K71 6 w$ V; T) S m( b" z7 o720 }( s$ w4 Q6 s5 C8 ^/ U
73. |3 H/ T8 c7 e% W$ ?7 P: d/ k. O+ \
74' u. n: ^/ L! u6 e O9 G, P
75 ( K) q2 U1 E0 E76! P+ x" t) {' V6 v9 c- _9 E. U
77 / v2 D& U/ B% n1 q# u9 d78& g8 g. B& }% T3 |7 T
79* [0 a1 F; p1 w) \
80- p" L G# {6 C
811 A+ {& l! w: _- c3 A# ~( p6 E
82 ; ^5 Z0 @* `7 V0 W) v4 L% U83% B/ N; }' S% A. u' _! j
84) N- l5 j7 h; E2 w p
85 : b1 [ k. X. `9 p6 @& V86 + |6 R" t. W, C6 B87 ! n7 E1 w0 l6 c# \88- u% }+ n* ~" K! e7 G5 P
89) }7 p5 V$ z c: c7 ^
90 ( ~! [+ z/ [$ U913 W: O. }" b' w, @& R7 H
923 f7 ]* I1 K- A& i' U
93 9 R' l0 ] i* v/ M* v94* r# X* ]: a6 t
95* ~3 [; X! g5 m6 i$ m$ i# ^& |
96; K$ \) S, x1 {
97 0 p; k B3 U4 |8 Q# f& Q98 : t( a6 q) i, d6 O+ D+ n99 0 o6 W5 M2 B* R! v0 @4 G% Y+ y A100 / F9 \: P, n# p4 t: q/ k/ G101 4 Y4 H4 i+ e7 N* M8 p% G' c, B102 . c) D3 ^. \- l; M2 b: k& h103 6 l8 R. M0 o. F ~1 l( p104 9 y9 N% u/ w, n; y" B' p105, I5 u$ m! t( _+ D- E, H/ n6 ~/ M
106 ' ?2 G% j. i( U4 g: o107& a% V0 W$ S' K& v- O9 k
108 a# q. Y2 M7 b# t. q, H k0 v109 R# h( T2 o( f, M( p9 G7 x$ t110 2 y% t$ T+ N' ^9 ~. k8 n( s111 1 U. d9 c6 Q: _1 H7 c112 ; A S1 ?2 R7 Y7 d113 * y& i, g; k ~0 [7 ]# R7 M0 i114 E4 B" Z3 c% e# U' h
115 ! U4 C g. z, l+ y' m116 $ c* B. R2 N; u117 % s& ~' _% {. X0 p118. `) n$ ^3 W: S3 u4 M
119 ! ^; _$ o9 H# J- m# X4 ]0 V* M120, @; K9 U" @, h
121# n# T0 @, J# S! Y1 b! X% Q8 h
1225 ?2 f- m1 h5 K2 w0 @$ l/ H) b
123 : U. O; Z+ Y! ^. U& s0 N2 y, x% z1240 u0 K+ d( c8 E0 H. O/ H8 P
125 ; `, Y' N9 ^6 w: H5 Z126 ) ^2 ]( S8 S& g" H6 q5 G' ?9 r3 E. `127 ( W+ O/ ], \0 ~3 F- C128 ) r( J3 u3 a+ q- W" K129$ {- H0 ^ i8 J: z
130 ( y6 u! O$ l% a% E- L8 p, I131 2 u* Q$ |$ b" F* n. y4 x7 v132 ( a, M7 Q/ S6 e9 \7 x1333 c) A4 H) L1 k: p9 G+ \" i4 ^
134 3 c! e& h3 j/ j( z0 `6 ~135 4 |3 ^# ?, [# p1361 Q" H& p9 y0 G/ y* c
137% N+ B6 |9 I7 C" b2 O4 q- N
138 8 c4 n0 z+ T. [* r3 t139 ! T: v8 G7 g9 ~- {# S5 W6 K) B+ ]8 f0 |' W140: n' n# v$ j- V: b
141 3 ?3 Q, q8 l2 z# C4 |142 2 \, M0 N: i2 ^6 e9 W1 z) @6 [- U }143 ( _: R6 g1 r" ~& T m0 J$ g144 Q! E: S7 G! d0 Q, z1458 D$ a. R% J3 `5 M% o
1464 {/ {) g! L# i
147 @5 D, N; T' y
148 ! T" t. x! k7 ~; ^' w149 ! v" Y; B! J, J) `( v3 ]3 o150 % i1 D6 r" G& B, z* i( @, x% ]8 o1514 ~# h4 B6 ^0 D, u
1520 B! y# e+ `4 ]
1534 ^* A) n9 l0 g/ H9 t0 b5 l
1548 ^" ?8 o* ^0 Y9 `
155 3 S: J7 M. y. x* V% H3 z* \156& Q' f9 B* w5 h/ t, a
157! x" h+ k) h9 @" l; }# s
1589 H9 `, X" l7 n/ |
159 L9 C2 b) V. U
1605 P& ]. O! q$ J7 s
1616 ~& I/ ~$ Z! r1 x
162 , l' v- |. K5 \9 ^163 % U: P8 H( @+ j1 P f164) R P3 e l8 l, |# |% P" [
165 ! U5 r0 s H+ @3 X166, y0 \2 l: A& _5 z
167) D6 I. }) O+ e2 _! v' f9 U; Z
168 , J$ K. z# _# L8 F8 X3 C2 ~9 K# b169! K4 S) |- u5 ~: I
170 # L" {- ~7 p6 z J7 X1716 K; ^$ ?! k8 U- Z5 T: J
172 ; w7 v2 u, W `$ L3 l% O173 . B) D- |2 M5 Z/ g* @) T+ w174+ R' |/ }7 z9 M, f8 _
175" [/ e! T) A- f. M1 F5 S% V3 p' x: w" y
176. I# x1 A& L, D
177 ; ]& J u- t1 B8 U7 {2 Y178 / _9 z. o4 g2 t; ~. P: O# v179 ( m1 H. R* X" ^; S180 8 X5 [6 L3 Z' W. D6 U; ^181 ( m* o; i! { G# L. K% C& y182 . |. v* M/ T6 E$ d. {+ B1839 _) ?7 s% l- `2 }" W0 Q. c8 A- G
184' D% a& Z2 P w9 S( M7 c- g) u
185 $ k! g5 i% D) L6 S+ k1 s* s186% e# D* h- w9 [" S* D( ~
187 ' s% K5 w. d1 b188 2 x2 X% v. Q0 o& D; ?, m: ]9 \% [$ n. b189 5 B- ?, p0 d. q1 x, v/ \/ K190 , J/ m- R9 W7 S191 ! f ]0 w+ ]; _* t* T5 { W192, m% y+ x$ p0 ^ Q% S9 v: I9 q
193. j/ G, u" Y) P; C7 O7 Z/ y& h% J3 H
194# y% c9 h$ m6 |) Z E6 B
195' H# y+ b2 `: A" `/ G9 m6 n
196. q. K/ e# a2 H" [% M; f
197, \3 @ v" |" u! o# X/ ?
198 9 a7 M# F# p3 @- L6 V0 E/ {1998 P: {+ ~0 d. ^% z1 v
2005 n2 Z$ ^7 Y$ A3 A
201" Z4 u- o& g% z" _7 U5 W1 ^
202 # I0 |" F6 J9 L" e203 . ]1 F* S2 M$ Y; y. ~204 : E. D2 U6 ?, k6 A$ f, @205 3 A- K+ D# x& o2 D4 _206. T+ ^ ^+ }# I6 D Y3 L& m
207 l F9 X j1 Y: e- b# H
208 % r# r T4 U# T% W. |2095 V; h: m: L# H. |5 n: f/ M" ], V) s8 ~
210, d9 i4 v0 h" O" r9 D& x3 H: S! ^
211 $ U4 t0 Z! o- J9 k& u; L2128 K- g+ P3 Z- H6 K
213$ A. F+ A8 @5 x6 m
214 5 a: m# C4 M- X' f! i0 [- P$ R215 + w0 I3 W2 h6 x/ V p216 9 M6 j% U; [9 p7 x217 7 y) |, ]1 S2 X$ a) n! Z. N( T. f7 C2186 P4 X& [+ B: z4 K, _
219 - T# a* z' ?3 L220 2 I- T! b1 q2 m: q0 G2216 F0 c# _; ~' p5 z
222& z% r0 U- o" b, w P
223* G1 `( p9 r. W
224 ' a; _8 n( t, X) P/ i# S225 w {1 _" Q1 C! `0 _
2260 M; H8 d% P) R1 [5 q' f
227, h$ y9 S9 M7 X8 \" n- Q1 h+ R8 D
228$ Z4 h* s3 \0 f y) n% e# t: F5 o
229& l) y' z1 i- A. T8 ?
230 4 D# g4 @8 _$ P- ^+ M231( d5 l3 u+ |. Q
232 ) q0 k; I9 r) y3 u0 l233: I, D& q6 e4 ~: a% c" x
234) Y& `. t# c/ K$ ]
235 / y' B5 y- E% ` [, k! F1 q236 4 G: S6 Z- \6 L/ T237- K! K. v- A! N* X/ D/ P/ s
238$ ?/ Y- u, \7 s( i" b4 l2 Y' v
2396 f4 D+ f c$ P* a3 i: y
240 2 } }. W6 x) g2 X1 s241% f( H( l! q @* E/ |, R
242 $ X' Y; X( R! c! E# }243( e: t2 ^9 U8 ^ J1 Q1 g% |+ g1 I
244! R, ]; P( L; {: \5 d
245 ; @; R! B3 P. Q; c. b( l# i- Y246: ?3 |: A% {6 |- A, E7 n
247 1 w6 X, Z; Q. m" m7 ~9 j# W248 $ c' ]/ J' A7 \' B0 w; ~249& @2 h1 I8 O: X+ N$ f/ I; c
250 2 r7 C! r, Q. q/ }1 f251 ' i- y) ?, I" S, s& k252 9 a* N3 z; C5 t6 N2539 S3 b+ U, M/ y5 H3 i; ^+ x
254- c. F1 k* O( x5 o" W. J
255 5 p1 o0 e- T; Y- H+ \" n256* T( G4 i% S& s2 }
2576 a) f# q! x; z/ \# T, @! S3 L( m
258 ' r) G6 T4 Z9 ^2 m259 + o, q7 i- `5 j& \; y4 b260 & ` C: Z$ Y, A6 Y7 @261, V8 i4 O" ]4 V4 k9 G% Z
262 4 r* t+ p. r8 W7 s2 V# e263 ]4 H8 B5 d" G' x) N7 S264 0 w; h( E, ^6 d# H6 a, P265& K7 T7 w; c/ S# r4 `. Y
266; E: W8 M7 Y! M4 n {
267 1 a" K8 | g) Y" w Y268 ! J& B- A/ W+ X$ V5 A# F269# X6 q+ N8 L! U$ C8 B
270 5 J6 j& I1 W" ]) i6 d271( v3 ~0 k, i# M* F1 L/ [$ q
272 3 s+ E! I' v0 F% F, O9 {1 t273 , E% o. _- m& K! J274/ M2 V2 P9 C2 q u7 Y0 e
275/ v, T: e k. j" [
276/ c$ `+ O. T* z7 f; T b# J
277 + Z; {7 }, L! t" l& {( l278 2 T1 p- ], U( l" B8 [( ^ M279 6 U& x+ U1 D+ n7 }( d, g280 2 ?8 u J, K' t+ b281 & n& s0 n" e/ Z/ m" |282 : z' [7 O1 S _ D; r283 8 A: `: ^3 U; c; S- N# K. y1 E7 a2845 w$ l* U" Q# H' H1 C' ^$ }
285: F: d; D6 i: ^& m- a& F6 p" G
286 0 h3 Y( v% v& Q% O2 T287' J& o( a1 j" `# c5 p3 S
288& F& I# d+ m3 x- J6 O6 a
289 4 C5 @5 y/ m# B# R$ i& C0 S t( R" `2901 c" K& A( L; r& y
2919 o- A n- x5 Y) ~7 `8 m
292& L3 i. n) A5 e4 H2 g1 r
293 4 {# Q# _. O; [3 P/ I6 f294 3 k( V& x# a# ?' w# I, \9 a( c2951 O4 r( S4 r. f& X
296 ! X9 |! |" N; I9 X* h: ^0 ]( r2975 E$ M" K3 b( Y' V5 d9 D$ g! s
298 F! R8 q9 V: ^8 W5 a4 Q3 {299* D o8 t9 F- k8 c
300" t3 ] H* d# ~3 i% u
301 " f2 @' e6 r2 e: P* l, l302 5 r, L; c4 E) q0 `- i0 y* D) u303* X0 L$ X- v5 A1 U: O: W7 ]
304 3 m/ V9 L6 Q: ^/ i- Q305# L& X7 b K7 U
306* J9 |* Q/ k- c/ |6 i" N2 b. o
307 * E+ ]) E5 b3 y308 " ~8 p, m/ y$ b, }; H! } b309 2 Z u9 n! M, E; q310' e4 q/ [3 t0 h1 O* Z7 x& }, m. }. ]
311# f' [& B# S" [
312 9 g# t& r7 v, r7 a3132 M1 U$ ~( |- O8 @0 Z
314 8 d5 Y) `8 r1 m x315 4 T2 ]/ l% u2 H C; S4 r316' o* w4 W2 h% H; U. E$ L
317. O# v/ w. P9 s" C" j, ]# \4 O+ v4 P( V
318% U" C! H- ^, ]
319 4 v8 m9 b4 n# J' L3207 o: R2 s7 K/ g" A9 A3 V% L
321 - J; F/ }) l/ q- p" g i; |322 N. V0 d4 V+ o7 @323& ~7 s7 d3 w2 {
324 ) k7 ^/ i6 d; F; G% r( g325* R: U9 _) O5 H9 T+ W9 M
326 7 h" J; H6 Q+ I! X9 G327 ! a# V) ]' c; I) @' M' X1 G328 / E0 B W) U# Q+ F' ~329 7 R( A# v4 A7 K0 v* s! ]1 x8 G; T" _330" n, n$ K3 [& I x9 c7 ?
331+ k* f. f! y+ @" `
% ]9 Y: K# F7 d3 o* m$ Z
5 ^( W$ L6 b' h' y1 n' E9 p
% Q0 k, A8 I3 g! H + v4 V% A I6 |: l$ G ; P/ l, m h4 A% P/ L# o8 q0 o+ d7 _' \- j& N4 R
+ o. y; C0 O7 |/ B
2 `) x7 N5 J+ b' i: A
% G+ t9 f. ~6 L. h$ w$ J( m
' n" q3 m3 D. p5 J
% H) b" q' Q2 `4 C 8 B( e J. y$ b6 O, g& t————————————————3 n: k7 m! X8 ^* i; J
版权声明:本文为CSDN博主「biyezuopin」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。( c! n& o- b; E# L
原文链接:https://blog.csdn.net/sheziqiong/article/details/1268032423 M( V5 K! a) ]6 G0 Z: Y9 v7 X
! f& f$ m2 T$ s3 K1 P
1 p$ t6 E% \+ W* v