- .gitignore
- 01.01 Python 基础.ipynb
- 01.02 Python 进阶.ipynb
- 01.03 机器学习常用的包.ipynb
- 01.04 Keras MNIST Playground.ipynb
- 02.01 基于搜索的问题求解(学生版).ipynb
- 02.01 基于搜索的问题求解.ipynb
- 02.02 决策树(学生版).ipynb
- 02.02 决策树.ipynb
- 02.03 回归分析(学生版).ipynb
- 02.03 回归分析.ipynb
- 02.04 贝叶斯分析(学生版).ipynb
- 02.04 贝叶斯分析.ipynb
- 02.05 神经网络学习(学生版).ipynb
- 02.05 神经网络学习.ipynb
- _overview.md
- data_sample.png
- decision_tree_1.mp4
- essay1_ch.txt
- essay1_en.txt
- essay2_ch.txt
- essay2_en.txt
- essay3_ch.txt
- essay3_en.txt
- foo.csv
- iris.csv
- mnist.npz
- model.h5
- model.png
- nn_media1.mp4
- nn_media2.mp4
- nn_media3.mp4
- nn_media4.mp4
- nn_media5.mp4
- nn_media6.mp4
- search-Copy1.py
- search.py
- Untitled.ipynb
02.02 决策树(学生版).ipynb @master — view markup · raw · history · blame
2.2 决策树¶
决策树是一种通过树形结构进行分类的方法,使用层层推理来实现最终的分类。
决策树由下面几种元素构成:
- 根节点:最顶层的分类条件。
- 决策节点(中间节点):中间分类条件。
- 叶子节点:代表标签类别。
上面的说法过于抽象,下面来看一个实际的例子。构建一棵结构简单的决策树,用于预测贷款用户是否具有偿还贷款的能力。
贷款用户主要具备三个属性:是否拥有房产,是否结婚,平均月收入。
每一个内部节点都表示一个属性条件判断,叶子节点表示贷款用户是否具有偿还能力。
首先判断贷款用户是否拥有房产,如果用户拥有房产,则说明该用户具有偿还贷款的能力;否则需要判断该用户是否结婚,如果已经结婚则具有偿还贷款的能力;否则需要判断该用户的收入大小,如果该用户月收入小于 4K 元,则该用户不具有偿还贷款的能力,否则该用户是具有偿还能力的。
想一想
决策树的流程是什么?
在有一个贷款用户A,其情况是月收入 3K、已经结婚、没有房产,那么他是否具有偿还贷款的能力呢?
上图中我们为什么要用“是否拥有房产”作根节点呢?可不可以用“是否结婚”和“平均月收入”做根节点呢?
2.2.1 决策树分类概念¶
数据: 游乐场经营者提供天气情况(如晴、雨、多云)、温度高低、湿度大小、风力强弱等气象特点以及游客当天是否前往游乐场。
目标: 预测游客是否来游乐场游玩。
序号 | 天气 | 温度(℃) | 湿度 | 是否有风 | 是(1)否(0)前往游乐场 |
---|---|---|---|---|---|
1 | 晴 | 29 | 85 | 否 | 0 |
2 | 晴 | 26 | 88 | 是 | 0 |
3 | 多云 | 28 | 78 | 否 | 1 |
4 | 雨 | 21 | 96 | 否 | 1 |
5 | 雨 | 20 | 80 | 否 | 1 |
6 | 雨 | 18 | 70 | 是 | 0 |
7 | 多云 | 18 | 65 | 是 | 1 |
8 | 晴 | 22 | 90 | 否 | 0 |
9 | 晴 | 21 | 68 | 否 | 1 |
10 | 雨 | 24 | 80 | 否 | 1 |
11 | 晴 | 24 | 63 | 是 | 1 |
12 | 多云 | 22 | 90 | 是 | 1 |
13 | 多云 | 27 | 75 | 否 | 1 |
14 | 雨 | 21 | 80 | 是 | 0 |
根据上表,绘制如图所示的决策树:
根节点是天气状况,具有 雨、 多云 和 晴 三种属性取值。
- 多云: 样本子集是 { 3, 7, 12, 13 } ,仅有“前往游乐场游玩”一个类别,即肯定去游乐场。
- 晴: 样本子集是 { 1, 2, 8, 9, 11 }
- 湿度大于 75:样本子集为 { 1, 2, 8 },不前往游乐场。
- 湿度不大于 75:样本子集 { 9, 11 },前往游乐场。
- 雨:样本子集为 { 4, 5, 6, 10, 14 }
- 有风:样本子集 { 6, 14 },不去游乐场。
- 无风:样本子集 { 4, 5, 10 },前往游乐场。
想一想
通过上面的例子,你观察到构建决策树的过程是哪几步?
下面,我们创建数据并进行一些预处理
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
import math
from math import log
import warnings
warnings.filterwarnings("ignore")
# 原始数据
datasets = [
['晴', 29, 85, '否', '0'],
['晴', 26, 88, '是', '0'],
['多云', 28, 78, '否', '1'],
['雨', 21, 96, '否', '1'],
['雨', 20, 80, '否', '1'],
['雨', 18, 70, '是', '0'],
['多云', 18, 65, '是', '1'],
['晴', 22, 90, '否', '0'],
['晴', 21, 68, '否', '1'],
['雨', 24, 80, '否', '1'],
['晴', 24, 63, '是', '1'],
['多云', 22, 90, '是', '1'],
['多云', 27, 75, '否', '1'],
['雨', 21, 80, '是', '0']
]
# 数据的列名
labels = ['天气', '温度', '湿度', '是否有风', '是否前往游乐场']
# 将湿度大小分为大于 75 和小于等于 75 这两个属性值,
# 将温度大小分为大于 26 和小于等于 26 这两个属性值
for i in range(len(datasets)):
if datasets[i][2] > 75:
datasets[i][2] = '>75'
else:
datasets[i][2] = '<=75'
if datasets[i][1] > 26:
datasets[i][1] = '>26'
else:
datasets[i][1] = '<=26'
# 构建 dataframe 并查看数据
df = pd.DataFrame(datasets, columns=labels)
df
2.2.2 构建决策树¶
想一想
信息熵和信息增益分别是什么?
假设有 $K$ 个信息,其组成了集合样本 $D$ ,记第 $k$ 个信息发生的概率为 $p_k(1≤k≤K)$。
这 $K$ 个信息的信息熵该如何计算?
所有 $p_k$ 累加起来的和是多少?
动手练
根据公式编写代码计算信息熵
def calc_entropy(total_num, count_dict):
"""
计算信息熵
:param total_num: 总样本数
:param count_dict: 每类样本及其对应数目的字典
:return: 信息熵
"""
# todo 使用公式计算信息熵
ent =
# 返回信息熵,精确到小数点后 4 位
return round(ent, 4)
现在用信息熵来构建决策树。数据中 14 个样本分为 “游客来游乐场 (9 个样本)” 和 “游客不来游乐场( 5 个样本)” 两个类别,即 K = 2。
记 “游客来游乐场” 和 “游客不来游乐场” 的概率分别为 $p_1$ 和 $p_2$ ,显然 $p_1=\frac{9}{14}$,$p_1=\frac{5}{14}$,则这 14 个样本所蕴含的信息熵:
$$E(D)=-\sum_{k=1}^{2}p_{k}log_{2}{p_k}=-(\frac{9}{14}×log_{2}{\frac{9}{14}}+\frac{5}{14}×log_{2}{\frac{5}{14}})=0.940$$我们可以用下面这种方式对 DataFrame 的数据按条件进行筛选。
# 例如:按 是否前往游乐场 == 0 进行筛选
df[df['是否前往游乐场']=='0']
使用上面的方法,可以得到计算信息熵所需的总样本数,以及每类样本及其对应数目的字典,然后计算信息熵。
# 总样本数
total_num = df.shape[0]
# 每类样本及其对应数目的字典
count_dict = {'前往': df[df['是否前往游乐场']=='1'].shape[0], '不前往': df[df['是否前往游乐场']=='1'].shape[1]}
# 计算信息熵
entropy = calc_entropy(total_num, count_dict)
entropy
计算天气状况所对应的信息熵:
天气状况的三个属性记为 $a_0=“晴”$ ,$a_1=“多云”$ ,$a_2=“雨”$ ,
属性取值为 $a_i$ 对应分支节点所包含子样本集记为 $D_i$ ,该子样本集包含样本数量记为 $|D_i|$ 。
天气属性取值$a_i$ | “晴” | “多云” | “雨” | ||
---|---|---|---|---|---|
对应样本数$ | D_i | $ | 5 | 4 | 5 |
正负样本数量 | (2+,3-) | (4+,0-) | (3+,2-) |
计算天气状况每个属性值的信息熵:
$“晴”:E(D_0)=-(\frac{2}{5}×log_{2}{\frac{2}{5}}+\frac{3}{5}×log_{2}{\frac{3}{5}})=0.971$
$“多云”:E(D_1)=-(\frac{4}{4}×log_{2}{\frac{4}{4}})=0$
$“雨”:E(D_2)=-(\frac{3}{5}×log_{2}{\frac{3}{5}}+\frac{2}{5}×log_{2}{\frac{2}{5}})=0.971$
现在,我们来编写代码进行完成上面的计算。
首先,我们可以使用下面的写法,对 Dataframe 进行多个条件的筛选。
# 筛选出 天气为晴并且去游乐场的样本数据
df[(df['天气']=='晴') & (df['是否前往游乐场']=='1')]
# 天气为晴的总天数
total_num_sun = df[df['天气']=='晴'].shape[0]
# 天气为晴时,去游乐场和不去游乐场的人数
count_dict_sun = {'前往':df[(df['天气']=='晴') & (df['是否前往游乐场']=='1')].shape[0],
'不前往':df[(df['天气']=='晴') & (df['是否前往游乐场']=='0')].shape[0]}
print(count_dict_sun)
# 计算天气-晴 的信息熵
ent_sun = calc_entropy(total_num_sun, count_dict_sun)
print('天气-晴 的信息熵为:%s' % ent_sun)
# 天气为多云的总天数
total_num_cloud = df[df['天气']=='多云'].shape[0]
# 天气为多云时,去游乐场和不去游乐场的人数
count_dict_cloud = {'前往':df[(df['天气']=='多云') & (df['是否前往游乐场']=='1')].shape[0],
'不前往':df[(df['天气']=='多云') & (df['是否前往游乐场']=='0')].shape[0]}
print(count_dict_cloud)
# 计算天气-多云 的信息熵
ent_cloud = calc_entropy(total_num_cloud, count_dict_cloud)
print('天气-多云 的信息熵为:%s' % ent_cloud)
# 天气为雨的总天数
total_num_rain = df[df['天气']=='雨'].shape[0]
# 天气为雨时,去游乐场和不去游乐场的人数
count_dict_rain = {'前往':df[(df['天气']=='雨') & (df['是否前往游乐场']=='1')].shape[0],
'不前往':df[(df['天气']=='雨') & (df['是否前往游乐场']=='0')].shape[0]}
print(count_dict_rain)
# 计算天气-雨 的信息熵
ent_rain = calc_entropy(total_num_rain, count_dict_rain)
print('天气-雨 的信息熵为:%s' % ent_rain)
计算天气状况的信息增益:
$$Gain(D,A)=E(D)-\sum_{i}^{n}\frac{|D_i|}{D}E(D)$$
其中,$A=“天气状况”$。于是天气状况这一气象特点的信息增益为: $$Gain(D,天气)=0.940-(\frac{5}{14}×0.971+\frac{4}{14}×0+\frac{5}{14}×0.971)=0.246$$
同理可以计算温度高低、湿度大小、风力强弱三个气象特点的信息增益。
通常情况下,某个分支的信息增益越大,则该分支对样本集划分所获得的“纯度”越大,信息不确定性减少的程度越大。
动手练
使用上面的公式计算信息增益。
# todo 计算按天气状况分割的信息增益
gain =
gain
扩展内容¶
基尼指数
除了使用信息增益以外,我们也可以使用基尼指数来构建决策树。
分类问题中,假设有 $K$ 个类,样本点属于第 $k$ 类的概率为 $p_{k}$,则概率分布的基尼指数定义为:
$$\operatorname{Gini}(p)=\sum_{k=1}^{K} p_{k}\left(1-p_{k}\right)=1-\sum_{k=1}^{K} p_{k}^{2}$$对于给定的样本集合 $D$,其基尼指数为
$$\operatorname{Gini}(D)=1-\sum_{k=1}^{K}\left(\frac{\left|C_{k}\right|}{|D|}\right)^{2}$$这里,$C_{k}$ 是 $D$ 中属于第 $k$ 类的样本子集,$K$ 是类的个数。
如果样本集合 $D$ 根据特征 $A$ 是否取某一可能值 $a$ 被分割为 $D_{1}$ 和 $D_{2}$ 两部分,即
$$D_{1}=\{(x, y) \in D | A(x)=a\}, \quad D_{2}=D-D_{1}$$则在特征 $A$ 的条件下,集合 $D$ 的基尼指数定义为
$$\operatorname{Gini}(D, A)=\frac{\left|D_{1}\right|}{|D|} \operatorname{Gini}\left(D_{1}\right)+\frac{\left|D_{2}\right|}{|D|} \operatorname{Gini}\left(D_{2}\right)$$基尼指数 $Gini(D)$ 表示集合 $D$ 的不确定性,基尼指数 $Gini(D, A)$ 表示经过分割后集合 $D$ 的不确定性。基尼指数值越大,样本集合的不确定性也就越大,这一点与信息熵相似。
想一想:
对于二分类问题,若样本点属于第 1 个类的概率是 $p$,则概率分布的基尼指数是多少?
思考与练习¶
- 分别将天气状况、温度高低、湿度大小、风力强弱作为分支点来构建决策树,查看信息增益。
# todo 以温度 26 为温度高低的分界线,计算信息增益
# todo 以湿度 75 为湿度大小的分界线,计算信息增益
# todo 以有风无风划分,计算信息增益
- 每朵鸢尾花有萼片长度、萼片宽度、花瓣长度、花瓣宽度四个特征。现在需要根据这四个特征将鸢尾花分为杂色鸢尾花、维吉尼亚鸢尾和山鸢尾三类,试构造决策树进行分类。
序号 | 萼片长度 | 萼片宽度 | 花瓣长度 | 花瓣宽度 | 种类 |
---|---|---|---|---|---|
1 | 5.0 | 2.0 | 3.5 | 1.0 | 杂色鸢尾 |
2 | 6.0 | 2.2 | 5.0 | 1.5 | 维吉尼亚鸢尾 |
3 | 6.0 | 2.2 | 4.0 | 1.0 | 杂色鸢尾 |
4 | 6.2 | 2.2 | 4.5 | 1.5 | 杂色鸢尾 |
5 | 4.5 | 2.3 | 1.3 | 0.3 | 山鸢尾 |
观察上表中的五笔数据,我们可以看到 杂色鸢尾 和 维吉尼亚鸢尾 的花瓣宽度明显大于山鸢尾,所以可以通过判断花瓣宽度是否大于 0.7,来将山鸢尾区从其他两种鸢尾中区分出来。
同时,杂色鸢尾 和 维吉尼亚鸢尾 的花瓣长度明显大于山鸢尾,所以也可以通过判断花瓣长度是否大于 2.4,来将山鸢尾区从其他两种鸢尾中区分出来。
然后我们观察到 维吉尼亚鸢尾的花瓣长度明显大于杂色鸢尾,所以可以通过判断花瓣长度是否大于 4.75,来将杂色鸢尾和维吉尼亚鸢尾区分出来。
实际上是否如此呢?你能否想到其他的切分方式?
上面的表格只是 Iris 数据集的一小部分,完整的数据集包含 150 个数据样本,分为 3 类,每类 50 个数据,每个数据包含 4 个属性。即花萼长度,花萼宽度,花瓣长度,花瓣宽度4个属性。
我们使用 sklearn 工具包来构建决策树模型,先导入数据集。
from sklearn.datasets import load_iris
# 加载数据集
iris = load_iris()
# 查看 label
print(list(iris.target_names))
# 查看 feature
print(iris.feature_names)
setosa 是山鸢尾,versicolor是杂色鸢尾,virginica是维吉尼亚鸢尾。
sepal length, sepal width,petal length,petal width 分别是萼片长度,萼片宽度,花瓣长度,花瓣宽度。
然后进行训练集和测试集的切分。
from sklearn.model_selection import train_test_split
# 载入数据
X, y = load_iris(return_X_y=True)
# 切分训练集合测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=42)
接下来,我们在训练集数据上训练决策树模型。
from sklearn import tree
from sklearn.tree import DecisionTreeClassifier
# 初始化模型,可以调整 max_depth 来观察模型的表现
# 也可以调整 criterion 为 gini 来使用 gini 指数构建决策树
clf = tree.DecisionTreeClassifier(criterion='entropy', max_depth=2)
# 训练模型
clf = clf.fit(X_train, y_train)
我们可以使用 graphviz 包来展示构建好的决策树。
import graphviz
feature_names = ['萼片长度','萼片宽度','花瓣长度','花瓣宽度']
target_names = ['山鸢尾', '杂色鸢尾', '维吉尼亚鸢尾']
# 可视化生成的决策树
dot_data = tree.export_graphviz(clf, out_file=None,
feature_names=feature_names,
class_names=target_names,
filled=True, rounded=True,
special_characters=True)
graph = graphviz.Source(dot_data)
graph
我们看模型在测试集上的表现。
from sklearn.metrics import accuracy_score
y_test_predict = clf.predict(X_test)
accuracy_score(y_test,y_test_predict)
首先定义一个方法来辅助读取文件的内容。
def read_file(path):
"""
读取某文件的内容
:param path: 文件的路径
:return: 文件的内容
"""
contents = ""
with open(path) as f:
# 读取每一行的内容
for line in f.readlines():
contents += line
return contents
使用上面定义的方法读取英文短文及其对应的中文短文。
# 读取英文短文
en_essay = read_file('essay3_en.txt')
# 读取中文短文
ch_essay = read_file('essay3_ch.txt')
处理文本,统计单词出现的概率,并计算信息熵。
from collections import Counter
import re
def cal_essay_entropy(essay, split_by=None):
"""
计算文章的信息熵
:param essay: 文章内容
:param split_by: 切分方式,对于中文文章,不需传入,按字符切分,
对于英文文章,需传入空格字符来进行切分
:return: 文章的信息熵
"""
# 把英文全部转为小写
essay = essay.lower()
# 去除标点符号
essay = re.sub(
"[\f+\n+\r+\t+\v+\?\.\!\/_,$%^*(+\"\']+|[+——!,。?、~@#《》¥%……&*()]", "",
essay)
# print(essay)
# 把文本分割为词
if split_by:
word_list = essay.split(split_by)
else:
word_list = list(essay)
# 统计总的单词数
word_number = len(word_list)
print('此文章共有 %s 个单词' % word_number)
# 得到每个单词出现的次数
word_counter = Counter(word_list)
# print('每个单词出现的次数为:%s' % word_counter)
# 使用信息熵公式计算信息熵
ent = -sum([(p / word_number) * log(p / word_number, 2) for p in
word_counter.values()])
print('信息熵为:%.2f' % ent)
return ent
ent = cal_essay_entropy(ch_essay)
ent = cal_essay_entropy(en_essay, split_by = ' ')
问题 1: 你在上面的试验中观察到了什么?请在下面写下你观察到的现象,并尝试分析其原因。
答案 1:(在此处填写你的答案。)