一、决策树的定义和理解
决策树是一种基于树状结构的机器学习算法,用于解决分类和回归问题。它是一种直观且易于理解的模型,常被用于数据挖掘和预测分析。
决策树通过将数据集逐步划分为不同的子集来进行决策。树的每个内部节点代表一个特征或属性,叶节点代表一个决策结果或类别。从根节点开始,根据样本特征的取值,沿着树的分支向下遍历,最终到达叶节点,得到预测结果。
为了更加好理解我们列举一个实例,
假设我们有一个数据集,包含了一些汽车的属性(如品牌、车型、价格、颜色等),我们想要使用决策树算法来预测这些汽车是否适合买家。
首先,我们需要将数据集划分为训练集和测试集。然后,我们可以使用决策树算法来构建模型。在构建模型时,算法会选择最优的特征来进行划分,并递归地生成一棵决策树。
例如,算法可能会选择“品牌”作为第一个划分特征。如果品牌为“奥迪”,则继续判断车型,如果为“Q5”,则进一步判断价格是否大于10万;如果品牌为“宝马”,则继续判断车型,如果为“X5”,则进一步判断颜色是否为黑色。
通过这样的递归过程,算法可以得出一个针对汽车属性的决策树,用于预测每个汽车是否适合买家。
在预测新的汽车时,我们可以沿着决策树进行遍历,直到到达叶子节点上的类别,即可得到预测结果。最后会得到一个类似于二叉树的决策树(它是基于贪心算法):
二、决策树的构造(原理)
2.1决策树的一般流程
收集数据:收集用于构建决策树的数据集。数据集应该包含特征和对应的类别或目标变量。
特征选择:通过一定的准则选择最佳的特征来划分数据集。常用的特征选择准则包括基尼不纯度、熵或信息增益等。
构建树:根据选择的特征,将数据集逐步划分为不同的子集,构建决策树的过程。通常采用递归的方式从根节点开始构建树。
剪枝:为了防止过拟合,可以对已构建的决策树进行剪枝操作。剪枝可以通过预剪枝或后剪枝来实现。
预测:使用构建好的决策树对新的样本进行预测。从根节点开始,根据样本的特征值沿着树的分支进行遍历,最终到达叶节点,得到预测结果。
评估模型:对构建好的决策树进行评估,评估指标可以包括准确率、召回率、F1值等。
可视化:可以将构建好的决策树可视化,以便更好地理解和解释模型。
2.2信息熵和信息增益
信息熵(Entropy)和信息增益(Information Gain)是决策树算法中常用的特征选择准则,用于衡量分裂数据集的效果。
(1)定义:信息熵是指在给定样本集合的条件下,随机变量不确定性的度量,通常用公式表示为:
其中,pi表示第i个类别在样本中出现的概率,n表示类别数目。信息熵的值越大,表示随机变量的不确定性越高。
(2)定义:在决策树算法中,我们希望通过选择最佳的特征来最大化信息增益。信息增益表示利用某一特征对样本进行划分所得到的信息增益,通常用公式表示为:
信息增益的值越大,表示使用特征进行划分后所获得的信息增益越大。
下面通过一个例子来更好地理解:
我们通过上数据来确定:
2.3基尼值和基尼指数
基尼值和基尼指数是衡量样本集合纯度的指标,常用于决策树算法中的特征选择准则。
(1)定义:基尼值表示从样本集合中随机抽取两个样本,其类别标签不一致的概率。对于一个具有nn个类别的样本集合,基尼值可以通过以下公式计算:
(2)定义:基尼指数是使用某一特征对样本进行划分后,衡量划分结果纯度的指标。基尼指数可以通过以下公式计算:
下图很好展示基尼系数随着概率的变化:
下面通过一个案例来展示:
有工作 有房子 信誉 贷款结果
2.4 剪枝处理
决策树的剪枝是为了防止过度拟合,提高模型的泛化能力。剪枝可以通过两种方式实现:预剪枝和后剪枝。
预剪枝:在构建决策树时,在每个节点处对当前节点进行判断,如果当前节点不满足一定的条件,则停止分裂并将该节点标记为叶子节点。常用的预剪枝条件包括设定一个最小样本数、限制树的最大深度等。
后剪枝:在构建完整棵决策树之后,从下往上逐层考虑是否需要剪枝,判断是否剪枝的依据是对验证集的准确率是否有所提升。如果剪枝后准确率有所提升,则进行剪枝操作;否则保留原来的节点。
具体的后剪枝算法可以采用C4.5算法中的REP方法,该方法的步骤如下:
1.把原始数据集分成训练集和验证集。
2.构建完整的决策树。
3.自下而上地对各内部节点进行考虑,如果剪枝后验证集的错误率不降反升,则不进行剪枝操作;否则进行剪枝操作。
4.重复步骤3,直到无法提高验证集的准确率为止。
三、决策树的生成(实现)
3.1计算给定数据集的基尼指数
def calc_gini_index(data_set):
"""
计算给定数据集的基尼指数
:param data_set: 数据集
:return: 基尼指数
"""
num_entries = len(data_set)
label_counts = {}
for feat_vec in data_set:
current_label = feat_vec[-1]
label_counts[current_label] = label_counts.get(current_label, 0) + 1
gini_index = 1.0
for key in label_counts:
prob = float(label_counts[key]) / num_entries
gini_index -= prob ** 2
return gini_index
3.2划分数据集
def split_data_set(data_set, axis, value):
"""
划分数据集
:param data_set: 待划分的数据集
:param axis: 划分数据集的特征的索引
:param value: 特征的值
:return: 划分后的子集
"""
ret_data_set = []
for feat_vec in data_set:
if feat_vec[axis] == value:
reduced_feat_vec = np.concatenate((feat_vec[:axis], feat_vec[axis+1:]))
ret_data_set.append(reduced_feat_vec)
return ret_data_set
3.3根据基尼指数选择划分方式
def choose_best_feature_to_split(data_set):
"""
选择最好的数据集划分方式
:param data_set: 数据集
:return best_feature: 最优特征的索引
best_gini_index: 最小基尼指数
"""
num_features = len(data_set[0]) - 1
best_gini_index = float('inf')
best_feature = -1
for i in range(num_features):
feat_list = [example[i] for example in data_set]
unique_vals = set(feat_list)
new_gini_index = 0.0
for value in unique_vals:
sub_data_set = split_data_set(data_set, i, value)
prob = len(sub_data_set) / float(len(data_set))
new_gini_index += prob * calc_gini_index(sub_data_set)
if new_gini_index < best_gini_index:
best_gini_index = new_gini_index
best_feature = i
return best_feature, best_gini_index
3.4统计类别出现的次数
def majority_cnt(class_list):
"""
统计类别出现的次数
:param class_list: 类别列表
:return sorted_class_count[0][0]: 出现次数最多的类别
"""
class_count = {}
for vote in class_list:
class_count[vote] = class_count.get(vote, 0) + 1
sorted_class_count = sorted(class_count.items(), key=lambda x: x[1], reverse=True)
return sorted_class_count[0][0]
3.5创建决策树并使用决策树分类
def create_tree(data_set, labels):
"""
创建决策树
:param data_set: 数据集
:param labels: 特征标签
:return my_tree: 决策树
"""
class_list = [example[-1] for example in data_set]
# 如果类别完全相同则停止继续划分
if class_list.count(class_list[0]) == len(class_list):
return class_list[0]
# 遍历完所有特征时返回出现次数最多的类别
if len(data_set[0]) == 1:
return majority_cnt(class_list)
best_feat, _ = choose_best_feature_to_split(data_set)
best_feat_label = labels[best_feat]
my_tree = {best_feat_label: {}}
del(labels[best_feat])
feat_values = [example[best_feat] for example in data_set]
unique_vals = set(feat_values)
for value in unique_vals:
sub_labels = labels[:]
my_tree[best_feat_label][value] = create_tree(split_data_set(data_set, best_feat, value), sub_labels)
return my_tree
def classify(input_tree, feat_labels, test_vec):
"""
使用决策树分类
:param input_tree: 已经生成的决策树
:param feat_labels: 特征标签
:param test_vec: 测试数据列表,顺序对应特征标签
:return class_label: 分类结果
"""
first_str = list(input_tree.keys())[0]
second_dict = input_tree[first_str]
feat_index = feat_labels.index(first_str)
key = test_vec[feat_index]
value_of_feat = second_dict.get(key, None)
if isinstance(value_of_feat, dict):
class_label = classify(value_of_feat, feat_labels, test_vec)
else:
class_label = value_of_feat
return class_label
3.6递归添加决策树的节点和边
def add_tree_nodes(dot_data, tree, labels, parent_node=None, edge_label=None):
"""
递归添加决策树节点和边
:param dot_data: GraphViz对象
:param tree: 决策树
:param labels: 特征标签
:param parent_node: 父节点名称
:param edge_label: 边标签
"""
first_str = list(tree.keys())[0]
if parent_node is not None:
dot_data.node(parent_node, first_str)
for key in tree[first_str]:
if isinstance(tree[first_str][key], dict):
if edge_label is not None:
dot_data.edge(parent_node, key, label=edge_label)
edge_label = None
else:
dot_data.edge(parent_node, key)
add_tree_nodes(dot_data, tree[first_str][key], labels, parent_node=key)
else:
if edge_label is not None:
dot_data.node(key, tree[first_str][key])
dot_data.edge(parent_node, key, label=edge_label)
else:
dot_data.node(key, tree[first_str][key])
dot_data.edge(parent_node, key)
3.7用图像将决策树输出
def export_tree_as_graph(tree, labels):
"""
将决策树输出为图像
:param tree: 决策树
:param labels: 特征标签
"""
dot_data = graphviz.Digraph()
add_tree_nodes(dot_data, tree, labels)
dot_data.format = 'png'
dot_data.render('decision_tree')
3.8引用数据集输出结果
# 加载鸢尾花数据集
iris = load_iris()
X, y = iris.data, iris.target
# 构建决策树模型
clf = DecisionTreeClassifier()
clf.fit(X, y)
# 绘制决策树
plt.figure(figsize=(10, 6))
plot_tree(clf, feature_names=iris.feature_names, class_names=iris.target_names.tolist(), filled=True)
plt.show()
3.9实验输出结果
四、决策树算法的优劣
决策树算法具有以下优点:
直观易懂:决策树的模型可视化简单明了,易于解释和理解。
适用于各种类型的数据:决策树可以处理离散型和连续型特征,也能够处理多分类和回归问题。
处理特征交互和缺失值:决策树可以捕捉特征之间的交互关系,并且能够处理缺失值的情况。
然而,决策树算法也存在一些局限性:
容易过拟合:决策树倾向于过度匹配训练数据,导致在新数据上表现不佳。剪枝技术可以一定程度上缓解这个问题。
对输入数据的变化敏感:即使输入数据的微小变化,也可能导致完全不同的决策树结构。
难以处理类别数较多的特征:当特征具有大量类别时,决策树可能会产生过于复杂的模型。
决策树算法在实际应用中被广泛使用,特别适用于具有可解释性要求的场景,例如金融风控、医学诊断、推荐系统等。
五、小结和实现过程中遇到问题的解决方案
决策树算法是一种常用的机器学习算法,具有易于理解和解释、能够处理离散和连续特征、能够处理多分类问题等优点。在实验中,我总结了一些关于决策树实现的要点和经验。
1.数据预处理:在进行决策树实验之前,需要对原始数据进行预处理。包括数据清洗、缺失值处理、特征选择等步骤。这些步骤可以提高决策树模型的性能和稳定性。
2.特征选择:决策树算法对特征选择非常敏感。选择合适的特征可以提高模型的准确性和泛化能力。常用的特征选择方法包括信息增益、基尼指数和方差等。需要根据具体问题选择合适的特征选择方法。
3.剪枝处理:决策树容易过拟合,剪枝处理是防止过拟合的重要手段。可以使用预剪枝或后剪枝来提高决策树模型的泛化能力。预剪枝通过限制分裂条件来控制决策树的复杂度,后剪枝通过剪枝操作来简化已经生成的决策树结构。
4.参数调优:决策树模型有一些参数需要进行调优,例如树的深度、节点分裂的最小样本数等。通过交叉验证等方法,调整这些参数可以提高模型的性能。
5.可视化展示:决策树算法生成的模型具有可解释性,可以将生成的决策树可视化展示。这有助于了解决策树的结构和规则,并对模型的结果进行解释。
在实验中报错:
后面查资料得知在调用plot_tree函数时,class_names参数应该是一个列表或者为None。而代码中传入的是一个数组。
解决这个问题,可以将class_names参数改为一个列表形式,即class_names=iris.target_names.tolist()。
后面解决这个问题