一、决策树的定义
决策树又称为判定树,是数据挖掘技术中的一种重要的分类与回归方法,它是一种以树结构形式来表达的预测分析模型。其每个非叶节点表示一个特征属性上的测试,每个分支代表这个特征属性在某个值域上的输出,而每个叶节点存放一个类别。
一般,一棵决策树包含一个根节点,若干个内部结点和若干个叶结点。
叶结点对应于决策结果,其他每个结点对应于一个属性测试。每个结点包含的样本集合根据属性测试的结果划分到子结点中,根结点包含样本全集,从根结点到每个叶结点的路径对应了一个判定的测试序列。决策树学习的目的是产生一棵泛化能力强,即处理未见示例强的决策树。
使用决策树进行决策的过程就是从根节点开始,测试待分类项中相应的特征属性,并按照其值选择输出分支,直到到达叶子节点,将叶子节点存放的类别作为决策结果。
二、决策树的构造
算法核心思想
决策树通过递归地将数据集划分为更纯的子集来构建树结构。每次划分选择能够带来最大信息增益的特征,直到满足停止条件(如所有样本属于同一类别或没有更多特征可用)。
决策树的关键在于如何选择最优的划分属性,对于二分类而言,就是尽量使得划分的样本属于同一类别,即“纯度”最高的属性。对于度量特征的纯度,需要用信息熵。
信息熵
信息熵的定义:当前的样本集D中第k类样本所占的比例为,K为类别的总数。
则样本集的信息熵为
对于Ent(D)的值来说,Ent(D)的值越小,D的纯度越高
三、代码分析
1. 计算熵
def calculate_entropy(data):
# 计算数据集的熵
total_count = len(data) # 计算数据集的总数
class_counts = np.unique(data[:, -1], return_counts=True)[1] # 计算数据集中每个类别的数量
entropy = 0 # 初始化熵为0
for count in class_counts:
probability = count / total_count # 计算每个类别的概率
entropy -= probability * np.log2(probability) # 计算熵
return entropy # 返回熵
2. 计算信息增益
def calculate_info_gain(data, feature_index):
# 计算数据集的熵
total_entropy = calculate_entropy(data)
# 获取特征列的唯一值
unique_values = np.unique(data[:, feature_index])
# 初始化加权熵
weighted_entropy = 0
# 遍历特征列的唯一值
for value in unique_values:
# 获取特征列等于当前值的子数据集
sub_data = data[data[:, feature_index] == value]
# 计算子数据集的熵
sub_entropy = calculate_entropy(sub_data)
# 计算加权熵
weighted_entropy += (len(sub_data) / len(data)) * sub_entropy
# 计算信息增益
info_gain = total_entropy - weighted_entropy
# 返回信息增益
return info_gain
3. 选择最佳特征
def choose_best_feature(data):
# 获取特征数量
num_features = data.shape[1] - 1
# 初始化最佳特征为None
best_feature = None
# 初始化最大信息增益为0
max_info_gain = 0
# 遍历每个特征
for i in range(num_features):
# 计算当前特征的信息增益
info_gain = calculate_info_gain(data, i)
# 如果当前特征的信息增益大于最大信息增益
if info_gain > max_info_gain:
# 更新最大信息增益
max_info_gain = info_gain
# 更新最佳特征
best_feature = i
# 返回最佳特征
return best_feature
4. 多数表决
# 定义一个函数,用于找出数据集中出现次数最多的类别
def majority_class(data):
# 使用numpy的unique函数,找出数据集中最后一列的唯一值,并返回其出现次数
class_counts = np.unique(data[:, -1], return_counts=True)[1]
# 找出出现次数最多的类别的索引
max_count_index = np.argmax(class_counts)
# 返回出现次数最多的类别
return np.unique(data[:, -1])[max_count_index]
5. 构建决策树
def create_decision_tree(data):
# 如果数据集中所有样本的标签都相同,则返回该标签
if len(np.unique(data[:, -1])) == 1:
return data[0, -1]
# 如果数据集中只有一个特征,则返回该特征中出现次数最多的标签
if data.shape[1] == 1:
return majority_class(data)
# 选择最佳特征
best_feature = choose_best_feature(data)
# 创建决策树
tree = {best_feature: {}}
# 获取最佳特征的所有取值
unique_values = np.unique(data[:, best_feature])
# 遍历最佳特征的所有取值
for value in unique_values:
# 获取最佳特征取值为value的子数据集
sub_data = data[data[:, best_feature] == value]
# 删除最佳特征
sub_data = np.delete(sub_data, best_feature, axis=1)
# 递归调用create_decision_tree函数,生成子树
tree[best_feature][value] = create_decision_tree(sub_data)
# 返回决策树
return tree
四、实验小结
理论与实践的印证 决策树的构造过程(如信息熵、信息增益的计算)在理论上清晰易懂,但实际编码时发现细节处理尤为重要。例如,递归终止条件的设定、特征划分时的数据拆分逻辑,都需要严谨的边界条件判断,否则容易陷入无限循环或得到错误结果。
信息增益的局限性 实验中通过信息增益选择最优特征,但实际应用中可能遇到高基数特征的干扰,这类特征的信息增益往往偏高但泛化能力差。这让我意识到,可能需要引入增益率或基尼系数等替代指标来优化特征选择。
过拟合问题的思考 实验中构建的决策树会一直分裂直到纯度达到100%,但这样生成的树容易过拟合。可以加入预剪枝或后剪枝策略,提升模型的泛化能力。