第一次听说决策树能挑西瓜时,我正蹲在水果摊前纠结。老板拍着胸脯说"包甜",结果回家切开是个白瓤。这事让我意识到,光靠人眼判断西瓜品质确实不靠谱。后来接触机器学习才发现,早在上世纪80年代,科学家就用决策树算法解决这类分类问题了。
决策树就像我们日常做决定的过程。比如判断西瓜好坏,你会先看纹理是否清晰?再敲敲听声音?每个判断节点都像树的分叉,最终到达"买"或"不买"的结论。这种白盒模型最吸引人的是它的可解释性——不像神经网络像个黑箱,决策树的每个判断步骤都清晰可见。
三种经典算法各有所长:ID3用信息增益找最佳划分属性,简单直接但容易过拟合;C4.5引入增益率改进这个问题;CART则采用基尼指数,还能处理回归任务。有次我用ID3给实验室挑西瓜,结果模型死磕"触感"特征,因为训练集里硬滑的瓜都甜。直到加入更多样本,改用C4.5才解决这个尴尬。
记得初学信息熵时,我总想象成"意外程度"。一箱全是好瓜(纯度100%),熵值为0;好坏参半时,熵值最高。数学表达很简单:
python复制import numpy as np
def entropy(p):
return -p * np.log2(p) - (1-p) * np.log2(1-p)
但实际计算要考虑多分类情况。有次处理包含3种品质的西瓜数据,忘记修改公式导致结果完全错误。教训是:基础公式要活学活用。
ID3的信息增益有个致命弱点——会偏爱取值多的属性。试想用"编号"当特征,每个样本编号唯一,信息增益最大但毫无意义。这就引出C4.5的增益率:
code复制增益率 = 信息增益 / 固有值(intrinsic value)
固有值就像属性的"身份证号",取值越多值越大。不过增益率又可能偏爱取值少的属性,所以C4.5采用折中方案:先选信息增益高于平均的属性,再从中挑增益率最高的。
CART采用的基尼指数计算更简单:
python复制def gini(p):
return 1 - p**2 - (1-p)**2
它表示随机抽两个样本,类别不一致的概率。有次比赛我发现,在小数据集上Gini计算比熵快30%,这对实时系统很关键。但要注意:Gini对类别分布变化更敏感。
经典的西瓜数据集2.0包含17个样本,6个特征:
处理时容易踩的坑:
建议先用pandas做探索性分析:
python复制import pandas as pd
data = pd.read_csv('watermelon.csv')
print(data.describe())
print(data['色泽'].value_counts())
递归建树的关键点在于终止条件:
这里分享个调试技巧:在递归函数开头打印缩进和当前特征,像这样:
python复制def create_tree(data, labels, depth=0):
print(' '*depth + f'当前特征: {labels[0]}')
# ...其余代码
遇到深度爆炸时,可以立即发现是哪条路径出了问题。
用matplotlib画树时,要注意:
plt.rcParams['font.sans-serif'] = ['SimHei']plotTree.xOff等参数调整arrow_args字典控制我曾花两小时调试节点重叠问题,最后发现是plotTree.totalW计算有误。可视化虽费时,但对理解模型行为帮助巨大。
相比ID3,C4.5主要增加:
在sklearn中只需改个参数:
python复制from sklearn.tree import DecisionTreeClassifier
clf = DecisionTreeClassifier(criterion='entropy') # 相当于C4.5
但要注意:sklearn的"entropy"实现并不完全等同C4.5,它没有增益率计算。如需严格实现,得自己重写splitter参数。
CART的独特优势:
一个重要细节:sklearn的CART默认使用加权基尼指数。比如处理类别不平衡数据时,可以设置class_weight='balanced'。
可视化时推荐graphviz:
python复制import graphviz
dot_data = tree.export_graphviz(clf, out_file=None,
feature_names=features,
class_names=['坏瓜','好瓜'],
filled=True)
graph = graphviz.Source(dot_data)
graph.render('watermelon') # 保存为PDF
在相同训练集上测试:
但要注意:小数据集上5%的波动可能只是随机误差。更可靠的验证方法是k折交叉验证:
python复制from sklearn.model_selection import cross_val_score
scores = cross_val_score(clf, X, y, cv=5)
三个最影响效果的参数:
max_depth:我通常从3开始尝试min_samples_split:防止过拟合的利器ccp_alpha:用于代价复杂度剪枝网格搜索示例:
python复制from sklearn.model_selection import GridSearchCV
params = {'max_depth': range(3,8),
'min_samples_split': range(2,10)}
grid = GridSearchCV(estimator=clf, param_grid=params, cv=5)
grid.fit(X_train, y_train)
遇到过的典型问题:
解决方案:
有次比赛用决策树总卡在89%准确率,后来加入"纹理清晰且脐部凹陷"这样的组合特征,直接提升到93%。这提醒我们:算法再高级,也离不开对业务的理解。