从零构建CART决策树:基于西瓜数据集的机器学习入门实战

1次阅读
没有评论

共计 2792 个字符,预计需要花费 7 分钟才能阅读完成。

image.webp

一、为什么选择决策树?

决策树是机器学习中最直观的算法之一,它的最大优势在于模型可解释性强——最终的决策逻辑可以像流程图一样呈现。CART(Classification and Regression Trees)作为经典实现,相比 ID3/C4.5 有以下特点:

从零构建 CART 决策树:基于西瓜数据集的机器学习入门实战

  • 支持连续值和离散值混合处理
  • 使用基尼系数 (Gini Index) 代替信息增益
  • 二叉树结构更节省计算资源
  • 天然支持回归任务

二、准备西瓜数据集

UCI 西瓜数据集是经典的入门练习数据,包含 17 个样本和以下特征:

  1. 离散特征
  2. 色泽(青绿 / 乌黑 / 浅白)
  3. 根蒂(蜷缩 / 稍蜷 / 硬挺)
  4. 敲声(浊响 / 沉闷 / 清脆)

  5. 连续特征

  6. 密度(0- 1 之间的浮点数)
  7. 含糖率(百分比)

  8. 标签:好瓜(是 / 否)

数据预处理步骤

import pandas as pd
from sklearn.preprocessing import LabelEncoder

# 手工构建数据集
watermelon_data = [['青绿', '蜷缩', '浊响', 0.697, 0.460, '是'],
    # ... 其他 16 条数据
]

# 转换为 DataFrame
columns = ['色泽', '根蒂', '敲声', '密度', '含糖率', '好瓜']
df = pd.DataFrame(watermelon_data, columns=columns)

# 离散特征编码
le = LabelEncoder()
for col in ['色泽', '根蒂', '敲声', '好瓜']:
    df[col] = le.fit_transform(df[col])

三、核心算法实现

1. 基尼系数计算

基尼系数反映数据集的「不纯度」,计算公式:

$$Gini(D) = 1 – \sum_{k=1}^{K}(\frac{|C_k|}{|D|})^2$$

Python 实现:

def gini_index(y):
    """计算基尼系数"""
    _, counts = np.unique(y, return_counts=True)
    probabilities = counts / len(y)
    return 1 - np.sum(probabilities ** 2)

2. 特征选择策略

对每个特征计算分割后的加权基尼系数:

  1. 离散特征:遍历所有可能二分组合
  2. 连续特征:先排序后取相邻值中点作为候选分割点
def find_best_split(X, y):
    best_gini = float('inf')
    best_feature = None
    best_value = None

    for feature in range(X.shape[1]):
        # 处理离散特征
        if isinstance(X[0, feature], str):
            # ... 遍历所有可能分割组合
        # 处理连续特征
        else:
            sorted_values = np.sort(np.unique(X[:, feature]))
            split_points = (sorted_values[:-1] + sorted_values[1:]) / 2
            # ... 评估每个分割点

    return best_feature, best_value

3. 递归建树

终止条件:
– 当前节点样本全部属于同一类别
– 没有剩余特征可供分割
– 达到预设的最大深度

class TreeNode:
    def __init__(self, feature=None, value=None, left=None, right=None):
        self.feature = feature  # 分裂特征
        self.value = value      # 分裂值
        self.left = left        # 左子树
        self.right = right      # 右子树

def build_tree(X, y, max_depth=5):
    # 终止条件检查
    if len(np.unique(y)) == 1 or max_depth == 0:
        return TreeNode(value=np.argmax(np.bincount(y)))

    # 寻找最佳分割
    feature, value = find_best_split(X, y)

    # 递归构建子树
    left_idx = X[:, feature] <= value
    left = build_tree(X[left_idx], y[left_idx], max_depth-1)
    right = build_tree(X[~left_idx], y[~left_idx], max_depth-1)

    return TreeNode(feature=feature, value=value, left=left, right=right)

四、可视化与评估

决策树可视化

安装 graphviz 后:

from sklearn.tree import export_graphviz
import graphviz

dot_data = export_graphviz(
    tree_model,
    out_file=None, 
    feature_names=features,
    class_names=['坏瓜', '好瓜'],
    filled=True
)
graph = graphviz.Source(dot_data)
graph.render('watermelon_tree')

模型评估

from sklearn.metrics import accuracy_score, confusion_matrix

# 预测函数
def predict(node, x):
    if node.value is not None:
        return node.value
    if x[node.feature] <= node.value:
        return predict(node.left, x)
    else:
        return predict(node.right, x)

# 评估
preds = [predict(tree, x) for x in X_test]
print("准确率:", accuracy_score(y_test, preds))
print("混淆矩阵:\n", confusion_matrix(y_test, preds))

五、避坑指南

1. 解决过拟合

  • 预剪枝:限制最大深度 / 最小样本数
  • 后剪枝:通过验证集评估剪枝收益

2. 类别不平衡

  • 在基尼计算中引入类别权重
  • 使用过采样 / 欠采样技术

3. 连续值处理

  • 排序后取中点作为候选分割点
  • 避免选择方差过小的分割点

六、扩展思考

  1. 与其他算法对比
  2. ID3 只能处理离散特征
  3. C4.5 通过信息增益比改进特征选择

  4. sklearn 快速实现

    from sklearn.tree import DecisionTreeClassifier
    
    clf = DecisionTreeClassifier(criterion='gini', max_depth=3)
    clf.fit(X_train, y_train)

写在最后

通过这个完整的实现过程,你会发现决策树的魅力在于其直观性——每个判断节点都可以对应到业务逻辑。建议尝试用不同参数训练模型,观察树结构的变化,这对理解算法行为非常有帮助。当熟悉基本原理后,可以进一步探索随机森林等集成方法如何提升单棵决策树的性能。

正文完
 0
评论(没有评论)