一、决策树概述
1.1概述
决策树是一种非参数的监督学习方法,通过对训练集数据学习,挖掘一定规则用于对新的数据集进行预测,通俗来说,是if-then决策集合。目的是使样本尽可能属于同一类别,分类更准确,通过递归选择最优特征对数据集进行分割,使每个子集都有一个最优分类过程。通过特征选择,选择最佳特征,将数据集分割成正确分类的子集。
1.2组成
决策树的主要组成包括:
根节点(Root Node):决策树的起始节点,代表整个数据集。
内部节点(Internal Nodes):非叶子节点,表示一个特征属性及其对应的判断条件。
叶节点(Leaf Nodes):最终的输出节点,表示一个类别或者数值。
分裂条件(Splitting Criteria):决定在每个内部节点如何分裂数据集的条件,常见的包括基尼系数、信息增益、信息增益率等。
决策规则(Decision Rules):由决策树的结构和节点组成,用于对新样本进行分类或回归预测。
剪枝(Pruning):一种防止过拟合的技术,通过删除一些不必要的节点来简化树结构。
特征重要性(Feature Importance):衡量每个特征对模型预测的贡献程度。
深度(Depth):决策树的层数,表示树的复杂度和泛化能力。
1.3流程
决策树的一般流程
(1)收集数据:可以使用任何方法
(2)准备数据:树构造算法只适用于标称型数据,因此数值型数据必须离散化
(3)分析数据:可以使用任何方法,构造树完成之后,我们应该检查图形是否符合预期
(4)训练算法:构造树的数据结构
(5)测试算法:使用经验树计算错误率
(6)使用算法:此步骤可以适用于任何监督学习算法,而使用决策树可以更好地理解数据的内在含义
二、决策树的构造算法
2.1 ID3(Iterative Dichotomiser 3)
信息增益的计算基于信息熵(Entropy)的概念,信息熵定义为信息的期望值,信息熵越大,则表明数据集的混乱程度越大。
信息熵: H(D)=−∑ni−1p(xi)log2p(xi)
信息增益 = 信息熵 - 条件熵:G(D,A)=H(D)−H(D|A)
条件熵:H(D|A)=∑ni=1|Di||D|H(Di)
2.2 C4.5(信息增益率)
C4.5算法最大的特点是克服了ID3对特征数目的偏重这一缺点,引入信息增益率来作为分类标准。
信息增益率=信息增益/特征本身的熵:
信息增益率对可取值较少的特征有所偏好(分母越小,整体越大),因此C4.5并不是直接用增益率最大的特征进行划分,而是使用一个启发式方法:先从候选划分特征中找到信息增益高于平均值的特征,再从中选择增益率最高的。
2.3基尼指数(CART)
决策树中的基尼指数是用来衡量节点的纯度或不纯度的指标之一。在构建决策树时,基尼指数通常用于选择最佳的划分特征,以便将数据集划分为纯度更高的子集。
基尼指数计算公式:Gini(D)=1−∑nk=1(pk)^2
基尼指数的取值范围在0到1之间,数值越低表示节点的纯度越高,即样本的类别分布越趋于一致;而数值越高则表示节点的不纯度越高,即样本的类别分布越杂乱。
三、决策树代码实战
对下面给出的数据构造出决策树
对下面给出的数据构造出决策树
编号 色泽 根蒂 敲声 纹理 脐部 触感 好瓜
0 青绿 蜷缩 浊响 清晰 凹陷 硬滑 是
1 乌黑 蜷缩 沉闷 清晰 凹陷 硬滑 是
2 乌黑 蜷缩 浊响 清晰 凹陷 硬滑 是
3 青绿 蜷缩 沉闷 清晰 凹陷 硬滑 是
4 浅白 蜷缩 浊响 清晰 凹陷 硬滑 是
5 青绿 稍蜷 浊响 清晰 稍凹 软粘 是
6 乌黑 稍蜷 浊响 稍糊 稍凹 软粘 是
7 乌黑 稍蜷 浊响 清晰 稍凹 硬滑 是
8 乌黑 稍蜷 沉闷 稍糊 稍凹 硬滑 否
9 青绿 硬挺 清脆 清晰 平坦 软粘 否
10 浅白 硬挺 清脆 模糊 平坦 硬滑 否
11 浅白 蜷缩 浊响 模糊 平坦 软粘 否
12 青绿 稍蜷 浊响 稍糊 凹陷 硬滑 否
13 浅白 稍蜷 沉闷 稍糊 凹陷 硬滑 否
14 乌黑 稍蜷 浊响 清晰 稍凹 软粘 否
15 浅白 蜷缩 浊响 模糊 平坦 硬滑 否
16 青绿 蜷缩 沉闷 稍糊 稍凹 硬滑 否
该数据集包含17个样本,每个样本有6个特征:
色泽:表示西瓜的外皮颜色,包括青绿、乌黑、浅白。
根蒂:表示西瓜的根部状况,有蜷缩、稍蜷、硬挺三种。
敲声:敲击西瓜时发出的声音,有浊响和沉闷两种。
纹理:西瓜表面的纹理,分为清晰和模糊两种。
脐部:西瓜脐部的形状,有凹陷和稍凹两种。
触感:摸到西瓜表面的感觉,有软粘和硬滑两种。
数据集如下:
- 色泽:0表示青绿,1表示乌黑,2表示浅白。
- 根蒂:0表示蜷缩,1表示稍蜷,2表示硬挺。
- 敲声:0表示浊响,1表示沉闷。
- 纹理:0表示清晰,1表示模糊。
- 脐部:0表示凹陷,1表示稍凹,2表示平坦。
- 触感:0表示软粘,1表示硬滑。
data = [[0, 0, 0, 0, 0, 0],
[1, 0, 1, 0, 0, 0],
[1, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0],
[2, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 1, 1],
[1, 1, 0, 1, 1, 1],
[1, 1, 0, 0, 1, 0],
[1, 1, 1, 1, 1, 0],
[0, 2, 2, 0, 2, 1],
[2, 2, 2, 2, 2, 0],
[2, 0, 0, 2, 2, 1],
[0, 1, 0, 1, 0, 0],
[2, 1, 1, 1, 0, 0],
[1, 1, 0, 0, 1, 1],
[2, 0, 0, 2, 2, 0],
[0, 0, 1, 1, 1, 0]]
3.1 使用ID3构造决策树
def entropy(data):
# 计算数据集的总体熵
labels = [row[-1] for row in data]
label_counts = {}
for label in labels:
if label not in label_counts:
label_counts[label] = 0
label_counts[label] += 1
entropy = 0.0
for count in label_counts.values():
prob = count / len(data)
entropy -= prob * math.log2(prob)
return entropy
def split_data(data, column, value):
# 根据特征列的值将数据集分割成子集
sub_data = []
for row in data:
if row[column] == value:
sub_row = row[:column] + row[column +1:]
sub_data.append(sub_row)
return sub_data
def information_gain(data, base_entropy, column):
# 计算特征列的信息增益
unique_values = set([row[column] for row in data])
new_entropy = 0.0
for value in unique_values:
sub_data = split_data(data, column, value)
prob = len(sub_data) / len(data)
new_entropy += prob * entropy(sub_data)
information_gain = base_entropy - new_entropy
return information_gain
def choose_best_feature(data):
# 选择信息增益最大的特征作为划分特征
num_features = len(data[0]) - 1
base_entropy = entropy(data)
best_information_gain = 0.0
best_feature = -1
for i in range(num_features):
info_gain = information_gain(data, base_entropy, i)
if info_gain > best_information_gain:
best_information_gain = info_gain
best_feature = i
return best_feature
def create_tree(data, labels):
# 递归构造决策树
class_list = [row[-1] for row in data]
if class_list.count(class_list[0]) == len(class_list):
# 如果所有样本属于同一类别,则返回该类别
return class_list[0]
if len(data[0]) == 1:
# 如果所有特征已经使用完毕,则返回样本数最多的类别
label_counts = {}
for label in class_list:
if label not in label_counts:
label_counts[label] = 0
label_counts[label] += 1
sorted_label_counts = sorted(label_counts.items(), key=lambda x: x[1], reverse=True)
return sorted_label_counts[0][0]
best_feature = choose_best_feature(data)
best_feature_label = labels[best_feature]
tree = {best_feature_label: {}}
del(labels[best_feature])
feature_values = [row[best_feature] for row in data]
unique_values = set(feature_values)
for value in unique_values:
sub_labels = labels[:]
tree[best_feature_label][value] = create_tree(split_data(data, best_feature, value), sub_labels)
return tree
# 构造决策树
labels = ['色泽', '根蒂', '敲声', '纹理', '脐部', '触感']
tree = create_tree(data=Data, labels=labels)
print(tree)
运行结果为:{'脐部': {0: 0, 1: {'敲声': {0: {'色泽': {0: 1, 1: {'纹理': {0: {'触感': {0: 0, 1: 1}}, 1: 1}}}}, 1: 0}}, 2: {'色泽': {0: 1, 2: {'根蒂': {0: {'触感': {0: 0, 1: 1}}, 2: 0}}}}}}
3.2使用C4.5构造决策树
def entropy(data):
# 计算数据集的总体熵
labels = [row[-1] for row in data]
label_counts = {}
for label in labels:
if label not in label_counts:
label_counts[label] = 0
label_counts[label] += 1
entropy = 0.0
for count in label_counts.values():
prob = count / len(data)
entropy -= prob * math.log2(prob)
return entropy
def split_data(data, column, value):
# 根据特征列的值将数据集分割成子集
sub_data = []
for row in data:
if row[column] == value:
sub_row = row[:column] + row[column + 1:]
sub_data.append(sub_row)
return sub_data
def information_gain_ratio(data, base_entropy, column):
# 计算特征列的信息增益比
unique_values = set([row[column] for row in data])
new_entropy = 0.0
split_info = 0.0
for value in unique_values:
sub_data = split_data(data, column, value)
prob = len(sub_data) / len(data)
new_entropy += prob * entropy(sub_data)
split_info -= prob * math.log2(prob)
if split_info == 0:
return 0 # 避免出现除以零的情况
information_gain_ratio = (base_entropy - new_entropy) / split_info
return information_gain_ratio
def choose_best_feature(data):
# 选择信息增益比最大的特征作为划分特征
num_features = len(data[0]) - 1
base_entropy = entropy(data)
best_information_gain_ratio = 0.0
best_feature = -1
for i in range(num_features):
info_gain_ratio = information_gain_ratio(data, base_entropy, i)
if info_gain_ratio > best_information_gain_ratio:
best_information_gain_ratio = info_gain_ratio
best_feature = i
return best_feature
def create_tree(data, labels):
# 递归构造决策树
class_list = [row[-1] for row in data]
if class_list.count(class_list[0]) == len(class_list):
# 如果所有样本属于同一类别,则返回该类别
return class_list[0]
if len(data[0]) == 1:
# 如果所有特征已经使用完毕,则返回样本数最多的类别
label_counts = {}
for label in class_list:
if label not in label_counts:
label_counts[label] = 0
label_counts[label] += 1
sorted_label_counts = sorted(label_counts.items(), key=lambda x: x[1], reverse=True)
return sorted_label_counts[0][0]
best_feature_index = choose_best_feature(data)
best_feature_label = labels[best_feature_index]
decision_tree = {best_feature_label: {}}
del (labels[best_feature_index])
feature_values = [row[best_feature_index] for row in data]
unique_values = set(feature_values)
for value in unique_values:
sub_labels = labels[:]
decision_tree[best_feature_label][value] = create_tree(split_data(data, best_feature_index, value), sub_labels)
return decision_tree
# 测试
labels = ['色泽', '根蒂', '敲声', '纹理', '脐部', '触感']
tree = create_tree(Data, labels)
print(tree)
运行结果:{'脐部': {0: 0, 1: {'敲声': {0: {'色泽': {0: 1, 1: {'纹理': {0: {'触感': {0: 0, 1: 1}}, 1: 1}}}}, 1: 0}}, 2: {'色泽': {0: 1, 2: {'根蒂': {0: {'触感': {0: 0, 1: 1}}, 2: 0}}}}}}
四、总结
4.1 优点
速度快
准确度高
可处理连续字段和种类字段
无需领域知识和参数假设
适合高维数据
4.2 缺点
容易过拟合
忽略相关性
各类别样本数量不一致
特征选择偏向于取值较多的特征
综上所述,决策树算法是一种简单而有效的机器学习算法,但在实际应用中需要注意过拟合、数据准备和特征选择等问题。