☰
决策树与随机森林——非参数模型的直觉
2026/10/7 20:30:00 网站建设 项目流程
个人主页: > for_ever_love__ <
其他栏目: > 我想学python了 <

其他栏目: > iOS项目总结大全 <

其他栏目: > iOS UI <

文章目录

  • 决策树与随机森林——非参数模型的直觉
    • 一、决策树的核心问题:先问哪个问题?
      • 1.1 纯度指标
      • 1.2 手写一次最优切分
    • 二、从零实现一棵决策树
    • 三、可视化决策树(sklearn)
    • 四、过拟合:决策树的天性问题
    • 五、随机森林:三个臭皮匠
      • 袋外误差 OOB(免费验证集)
    • 六、决策树 vs 神经网络:什么时候用谁
    • 七、XGBoost / LightGBM 简介(Boosting 一族)
    • 八、常见坑与注意事项
    • 九、本篇小结

决策树与随机森林——非参数模型的直觉

承上:上一篇《逻辑回归与 Softmax》走的是「假设形式 + 拟合参数」的路线。

本篇:本篇换一条完全不同的思路做预测:不问公式,只问一系列是非题(选修视野)。

启下:下一篇《模型评估与过拟合》系统解决「怎么判断模型是真的好」。

学完这一节,你能动手做:

  1. 用基尼/熵从零手写一棵决策树,并打印成人能读懂的规则
  2. 用随机森林的 Bagging 降低方差,让结果更稳定
  3. 判断表格数据该用树模型还是神经网络,理解大模型为何不走这条路

前面三篇都是"参数模型":假设数据服从某种形式(直线、Sigmoid),然后拟合出参数。

这一篇换个完全不同的思路——决策树:不问"数据符合什么公式",而是问一系列是非题。

【月收入 > 1万?】 / \ 是 否 【有房?】 【工作年限>3?】 / \ / \ 批准 拒绝 批准 拒绝

它简单、可解释、不需要标准化,而且是XGBoost / LightGBM这些 Kaggle 神器的基础。学完你会明白:为什么在表格数据上树模型常常吊打神经网络,以及为什么大模型最终还是选了神经网络。

一、决策树的核心问题:先问哪个问题?

树要学的是两件事:

  1. 在哪个特征上切一刀?(选特征)
  2. 切在哪里?(选阈值)

判断"切得好不好"的标准是:切完后,两堆数据是不是更"纯"了。

1.1 纯度指标

importnumpyasnpfromcollectionsimportCounterdefgini(labels):"""基尼不纯度:随机抽两个样本,类别不同的概率。0=最纯"""n=len(labels)ifn==0:return0.0counts=np.bincount(labels)probs=counts/nreturn1.0-np.sum(probs**2)defentropy(labels):"""信息熵"""n=len(labels)ifn==0:return0.0counts=np.bincount(labels)probs=counts[counts>0]/nreturn-np.sum(probs*np.log2(probs))# 三种情况对比print("全同一类:",gini([0,0,0,0]),entropy([0,0,0,0]))# 0, 0 → 最纯print("混合一半:",gini([0,0,1,1]),entropy([0,0,1,1]))# 0.5, 1.0 → 最不纯print("9:1 :",round(gini([0]*9+[1]),3),round(entropy([0]*9+[1]),3))
指标公式特点
基尼 Gini1 - Σp²计算快,sklearn 默认
信息熵 Entropy-Σ p·log₂p对纯度变化更敏感
分类误差1 - max(p)不敏感,几乎不用

信息增益= 切分前的纯度 − 切分后的加权纯度。增益越大,这一刀切得越值。

1.2 手写一次最优切分

defbest_split(X,y):"""暴力搜索:遍历每个特征、每个候选阈值,找加权基尼最小的切分"""best={"gain":-1}base_gini=gini(y)n,d=X.shapeforfeatinrange(d):thresholds=np.unique(X[:,feat])fortinthresholds:left_mask=X[:,feat]<=t right_mask=~left_maskifleft_mask.sum()==0orright_mask.sum()==0:continue# 加权基尼wl=left_mask.sum()/n wr=right_mask.sum()/n weighted=wl*gini(y[left_mask])+wr*gini(y[right_mask])gain=base_gini-weightedifgain>best["gain"]:best={"gain":gain,"feat":feat,"thr":t}returnbest# 造个简单数据测试np.random.seed(0)X_demo=np.random.rand(60,2)*10y_demo=(X_demo[:,0]+2*X_demo[:,1]>12).astype(int)sp=best_split(X_demo,y_demo)print(f"最优切分: 特征{sp['feat']}<={sp['thr']:.3f}, 信息增益={sp['gain']:.4f}")

这就是决策树训练的全部核心——暴力搜索所有可能的切分点,选增益最大的。

二、从零实现一棵决策树

classNode:"""树的节点:要么是内部节点(有切分条件),要么是叶子(有预测值)"""def__init__(self,feat=None,thr=None,left=None,right=None,value=None):self.feat,self.thr,self.left,self.right,self.value=feat,thr,left,right,valuedefis_leaf(self):returnself.valueisnotNoneclassDecisionTree:def__init__(self,max_depth=5,min_samples_split=2):self.max_depth,self.min_samples_split=max_depth,min_samples_split self.root=Nonedeffit(self,X,y):self.root=self._build(X,y,depth=0)returnselfdef_build(self,X,y,depth):# 停止条件 1:纯度已足够(全同类)iflen(np.unique(y))==1:returnNode(value=y[0])# 停止条件 2:到达最大深度或样本太少ifdepth>=self.max_depthorlen(y)<self.min_samples_split:returnNode(value=Counter(y).most_common(1)[0][0])sp=best_split(X,y)ifsp["gain"]<=1e-8:# 停止条件 3:切不动了returnNode(value=Counter(y).most_common(1)[0][0])mask=X[:,sp["feat"]]<=sp["thr"]left=self._build(X[mask],y[mask],depth+1)right=self._build(X[~mask],y[~mask],depth+1)returnNode(feat=sp["feat"],thr=sp["thr"],left=left,right=right)def_predict_one(self,x,node):whilenotnode.is_leaf():node=node.leftifx[node.feat]<=node.threlsenode.rightreturnnode.valuedefpredict(self,X):returnnp.array([self._predict_one(x,self.root)forxinX])# 训练并评估fromsklearn.model_selectionimporttrain_test_splitfromsklearn.metricsimportaccuracy_score X_tr,X_te,y_tr,y_te=train_test_split(X_demo,y_demo,test_size=0.25,random_state=1)tree=DecisionTree(max_depth=4).fit(X_tr,y_tr)print("手写决策树 测试 acc:",round(accuracy_score(y_te,tree.predict(X_te)),4))

递归建树的逻辑非常清晰:每次找最优切分 → 分成左右两堆 → 对每堆重复。停止条件有三个(纯度够、深度到、切不动)。

三、可视化决策树(sklearn)

手写是为了理解,实战用 sklearn:

fromsklearn.treeimportDecisionTreeClassifier,export_text,plot_treefromsklearn.datasetsimportload_iris iris=load_iris()X,y=iris.data,iris.target X_tr,X_te,y_tr,y_te=train_test_split(X,y,test_size=0.2,random_state=42,stratify=y)clf=DecisionTreeClassifier(max_depth=3,random_state=42)clf.fit(X_tr,y_tr)print("sklearn 决策树 acc:",round(accuracy_score(y_te,clf.predict(X_te)),4))# 打印成文字规则(超实用,能直接给业务方看)print(export_text(clf,feature_names=list(iris.feature_names)))# 画图importmatplotlib.pyplotasplt plt.figure(figsize=(12,6))plot_tree(clf,feature_names=iris.feature_names,class_names=list(iris.target_names),filled=True,rounded=True,fontsize=9)plt.title("Decision Tree (max_depth=3)")plt.tight_layout();plt.show()

决策树最大的优势:可解释。你能把模型打印成人类能读的 if-else 规则——这在金融风控、医疗诊断等场景是刚需,而神经网络做不到。

特征重要性:

forname,impinsorted(zip(iris.feature_names,clf.feature_importances_),key=lambdat:-t[1]):print(f"{name}:{imp:.3f}")

四、过拟合:决策树的天性问题

不加限制,树会长到"每个叶子一个样本"——完美记住训练集,测试集一塌糊涂。

fordepthin[1,2,3,5,10,None]:m=DecisionTreeClassifier(max_depth=depth,random_state=42).fit(X_tr,y_tr)tr=accuracy_score(y_tr,m.predict(X_tr))te=accuracy_score(y_te,m.predict(X_te))print(f"max_depth={str(depth):>4}训练={tr:.3f}测试={te:.3f}差距={tr-te:+.3f}")

你会清楚看到:深度越大,训练准确率趋近 100%,测试准确率反而不升甚至下降——这就是过拟合的教科书演示。

控制过拟合的关键超参数:

参数作用建议
max_depth树的最大深度3~10,最常用
min_samples_split内部节点再切分所需最小样本数2~20
min_samples_leaf叶子节点最少样本数1~10
max_features每次切分只考虑部分特征随机森林会自动用
ccp_alpha代价复杂度剪枝后剪枝用

五、随机森林:三个臭皮匠

单棵树容易过拟合且不稳定(数据稍微变动,树结构就剧变)。随机森林的思路:训练很多棵树,投票决定结果。

它引入了两重随机性:

  1. 样本随机(Bagging):每棵树用有放回抽样的一批数据训练;
  2. 特征随机:每个切分点只在随机子集里选特征。
fromsklearn.ensembleimportRandomForestClassifier rf=RandomForestClassifier(n_estimators=200,max_depth=6,max_features="sqrt",n_jobs=-1,random_state=42)rf.fit(X_tr,y_tr)print("随机森林 acc:",round(accuracy_score(y_te,rf.predict(X_te)),4))# 单棵树 vs 森林single=DecisionTreeClassifier(max_depth=6,random_state=42).fit(X_tr,y_tr)print("单棵树 acc:",round(accuracy_score(y_te,single.predict(X_te)),4))

为什么森林比单棵树好?方差降低。每棵树的误差是随机的,平均后相互抵消:

# 演示:多棵树投票能显著降低方差np.random.seed(0)n_trees_list=[1,5,20,100,300]forninn_trees_list:accs=[]forseedinrange(10):m=RandomForestClassifier(n_estimators=n,max_depth=6,max_features="sqrt",random_state=seed).fit(X_tr,y_tr)accs.append(accuracy_score(y_te,m.predict(X_te)))print(f"n_estimators={n:>3}平均acc={np.mean(accs):.4f}标准差={np.std(accs):.4f}")

树越多,标准差越小——结果越稳定。

袋外误差 OOB(免费验证集)

rf_oob=RandomForestClassifier(n_estimators=200,oob_score=True,random_state=42)rf_oob.fit(X,y)print("OOB 得分:",round(rf_oob.oob_score,4))

Bagging 时每棵树约 1/3 的样本没被抽到,这些"袋外样本"可以直接当验证集用——不需要单独划分验证集。

六、决策树 vs 神经网络:什么时候用谁

维度树模型(RF/XGBoost)神经网络
表格数据(用户画像、风控)🏆 通常更强一般
非结构化数据(图像/文本/语音)无从下手🏆 唯一选择
数据量小数据也能work需要大量数据
训练速度快(分钟级)慢(小时~天)
可解释性🏆 能打印规则黑盒
特征工程需要人工构造🏆 自动学特征
是否需要标准化❌ 不需要(基于阈值排序)✅ 必须
缺失值🏆 可处理需填充
迁移/预训练不支持🏆 可预训练复用

关键洞察:树模型靠"人工特征 + 阈值切分",无法从原始像素/字符中自动学出层级化的语义表示。而大模型要处理的恰恰是文本这种非结构化数据——这就是为什么大模型必须是神经网络。

但反过来,如果你的任务是"根据用户的 20 个字段预测是否流失",XGBoost 大概率比神经网络又快又好。选对工具比调参重要得多。

七、XGBoost / LightGBM 简介(Boosting 一族)

随机森林是Bagging(并行种树、投票);还有一派叫Boosting(串行种树、每棵纠正前一棵的错误):

try:fromxgboostimportXGBClassifier xgb=XGBClassifier(n_estimators=100,max_depth=4,learning_rate=0.1,random_state=42,eval_metric="logloss")xgb.fit(X_tr,y_tr)print("XGBoost acc:",round(accuracy_score(y_te,xgb.predict(X_te)),4))exceptImportError:print("未安装 xgboost,可运行: pip install xgboost")# sklearn 自带的 GradientBoosting(无需额外安装)fromsklearn.ensembleimportGradientBoostingClassifier gb=GradientBoostingClassifier(n_estimators=100,max_depth=3,random_state=42)gb.fit(X_tr,y_tr)print("GradientBoosting acc:",round(accuracy_score(y_te,gb.predict(X_te)),4))
Bagging(随机森林)Boosting(XGBoost)
训练方式并行,互相独立串行,逐步纠错
降低方差偏差
过拟合风险低需要小心调参
比赛/工业稳🏆 精度通常更高

八、常见坑与注意事项

坑现象解决
树不设 max_depth训练 100%、测试暴跌限制深度或 min_samples_leaf
类别不平衡全预测多数类class_weight="balanced"
独热编码后树变差稀疏特征难切分树模型可直接用原始类别编码
n_estimators太大训练慢、收益递减200~500 通常够
树模型外推无法预测超出训练范围的值回归注意这点
用树处理文本无意义先转 embedding 再喂给别的模型

九、本篇小结

  1. 决策树靠"问是非题"分类,核心是找信息增益最大的切分点(基尼/熵衡量纯度)。
  2. 递归建树的三个停止条件:纯度够、深度到、增益为 0;用max_depth等参数防过拟合。
  3. 随机森林 = Bagging + 特征随机,多树投票降低方差;OOB 可当免费验证集。
  4. 树模型在表格数据上强、可解释、不需标准化;但无法处理文本/图像——这正是大模型必须走神经网络路线的原因。
  5. Boosting(XGBoost/LightGBM)串行纠错降偏差,精度常高于随机森林,是 Kaggle 与工业界的主力。

下一篇模型评估与过拟合:我们会系统解决"怎么判断模型真的好"这个问题——交叉验证、正则化(L1/L2/Dropout)、以及准确率之外的全部指标(精确率、召回率、F1、AUC)。这是面试和工作里最常被考、也最常被做错的一块。

本篇是《大模型开发从 0 到 1》专栏第 20 篇,阶段 3「机器学习基础」第 4 篇。专栏文章按「分类专栏」归类,顺序学习体验最佳。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询