监督学习
这一课你将能做到
- 用线性回归给题目估难度分,并解释每个系数的业务含义
- 用逻辑回归、决策树、随机森林预测用户是否掌握知识点,对比三者差异
- 说清随机森林(bagging)和 XGBoost(boosting)的原理区别,知道什么时候上哪个
为什么要学
监督学习是工业界用得最多的一类模型:你有「特征 + 答案」,让模型学出映射。本站两个核心场景全是监督学习——
- 回归:根据全站正确率、平均用时、平均尝试次数,给题目打一个 0-100 的难度分;
- 分类:根据用户的作答表现,预测「这个知识点掌握了没有」。
这一课一次过五个模型:线性回归 → 逻辑回归 → 决策树 → 随机森林 → XGBoost。它们不是并列的五个工具,而是一条「从简单到复杂、从可解释到高精度」的演进线,面试最爱沿这条线追问。
先建立图像
| 模型 | 一句话 | 强项 | 弱项 |
|---|---|---|---|
| 线性回归 | 加权求和拟合数值 | 可解释、快 | 只能学线性关系 |
| 逻辑回归 | 线性 + Sigmoid 出概率 | 概率输出、稳定 | 同上 |
| 决策树 | 一连串 if-else | 自动学交互、可读 | 深了就过拟合 |
| 随机森林 | 很多树投票(bagging) | 稳、准、几乎免调参 | 不如单树好解释 |
| XGBoost | 一棵接一棵纠错(boosting) | 表格数据精度天花板 | 超参数多、易过拟合 |
跟着做
步骤 1:造数据——题目难度(回归)
import numpy as np
from sklearn.model_selection import train_test_split
rng = np.random.default_rng(42)
n = 300
accuracy = rng.uniform(0.2, 0.95, n) # 全站正确率
avg_seconds = rng.uniform(30, 240, n) # 平均用时
avg_attempts = rng.uniform(1, 5, n) # 平均尝试次数
# 人为造一个「真实规律」+ 噪声:正确率高、用时短、尝试少 → 越简单(分数越低)
difficulty = 100 - 60*accuracy - 0.1*avg_seconds - 5*avg_attempts + rng.normal(0, 3, n)
X = np.column_stack([accuracy, avg_seconds, avg_attempts])
X_tr, X_te, y_tr, y_te = train_test_split(X, difficulty, test_size=0.2, random_state=42)
步骤 2:线性回归拟合并解释系数
from sklearn.linear_model import LinearRegression
reg = LinearRegression().fit(X_tr, y_tr)
print("R²:", reg.score(X_te, y_te).round(3)) # 通常 > 0.95
print("系数:", reg.coef_.round(2)) # ≈ [-60, -0.1, -5],和真实规律几乎一致
print("截距:", reg.intercept_.round(1)) # ≈ 100
系数读法:全站正确率每提高 0.1,难度分约降 6;平均用时每多 10 秒,难度分约降 1。模型把隐藏在数据里的规则「称」出来了——这是面试里「怎么给题目估难度」的完整答案雏形。
步骤 3:分类——预测用户是否掌握知识点
n = 600
X_user = np.column_stack([
rng.uniform(0, 1, n), # 个人正确率
rng.uniform(30, 240, n), # 平均用时
rng.uniform(1, 8, n), # 做题次数
])
mastery = ((X_user[:, 0] > 0.55) & (X_user[:, 2] > 3)).astype(int) # 造标签的「真实规则」
Xu_tr, Xu_te, yu_tr, yu_te = train_test_split(X_user, mastery, test_size=0.25,
random_state=42, stratify=mastery)
步骤 4:逻辑回归 → 决策树 → 随机森林 三连
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
models = {
"逻辑回归": LogisticRegression(max_iter=1000),
"决策树": DecisionTreeClassifier(max_depth=3, random_state=42),
"随机森林": RandomForestClassifier(n_estimators=100, random_state=42),
}
for name, m in models.items():
m.fit(Xu_tr, yu_tr)
print(f"{name}: test acc = {m.score(Xu_te, yu_te):.3f}")
会发现决策树和随机森林几乎完美找到规则(正确率>0.55 且 次数>3 是「轴平行」的边界,正是树的强项),逻辑回归略逊。换一个非线性边界的数据集,差距会更明显。
步骤 5:XGBoost 压轴
pip install xgboost
from xgboost import XGBClassifier
xgb = XGBClassifier(n_estimators=200, max_depth=4, learning_rate=0.1,
eval_metric="logloss", random_state=42)
xgb.fit(Xu_tr, yu_tr)
print(f"XGBoost: test acc = {xgb.score(Xu_te, yu_te):.3f}")
print("特征重要性:", dict(zip(["正确率", "用时", "次数"],
xgb.feature_importances_.round(3))))
表格类数据上,XGBoost/LightGBM 至今仍是精度天花板。特征重要性还能反哺业务:做题次数权重最高,说明「推题要优先拉练习量」。
讲清楚要点
- 线性回归 = 最小化均方误差:找一条直线让所有点到它的竖直距离平方和最小。线性模型可解释、可推系数,但学不了弯的边界。
- 逻辑回归 = 线性模型套 Sigmoid:线性输出压到 (0,1) 变概率,损失是对数损失。名字叫回归,干的是分类——面试经典陷阱题。
- 决策树自己学规则:按特征逐层分裂(基尼/熵最大化的切点),能自动捕捉「且」「或」组合;不限深度必然把训练集背下来,必须限深或剪枝。
- 随机森林 = bagging 降方差:几百棵树各自看随机子样本+随机子特征,最后投票。单棵树的偶然性被平均掉,又准又稳。
- XGBoost = boosting 降偏差:每棵新树专门拟合前面所有树的残差,一步步纠错。和随机森林的区别是「并行一群专家投票」对「接力串行纠错」,这是面试最高频对比题。
你可能会懵的地方
- 为什么树模型不用标准化? 树只关心「特征 x 大于阈值 t 吗」,比较只在单特征内部进行,量纲不影响分裂结果。逻辑回归、KNN、K-Means、神经网络都要标准化,因为它们对特征尺度敏感。
- random_state 是干嘛的? 固定随机种子(切分、初始化、子采样),保证别人复现你的代码得到同样结果。生产训练可以不固定,但实验对比必须固定。
- 特征重要性高 = 这个特征是原因吗? 不是。重要性只说明「模型大量用它做预测」,可能与标签是相关而非因果。得出业务结论要配合 A/B 实验或因果分析,别把相关当因果。
- 小数据集上应该无脑上 XGBoost 吗? 几百条样本时逻辑回归/浅树往往更稳,XGBoost 参数多容易过拟合。经验:先跑逻辑回归当基线,再上树模型对比,涨幅明显才收下复杂度。
- 线性回归能做分类吗? 不行也不该。它输出无界,压不进 (0,1),对离群样本还特别敏感。分类用逻辑回归起步,这是约定俗成的工程常识。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
| 用 LinearRegression 做分类 | 输出无界、对离群敏感 | 分类用 LogisticRegression |
| 决策树不限 max_depth | 深到每个叶子只剩 1 条样本 = 背题 | max_depth 3~8 起步,配合交叉验证调 |
| 用未标准化特征的系数比大小 | 尺度不同无法比较 | 标准化后再看系数,或直接看置换重要性 |
| 随机森林不设 random_state 就对比实验 | 结果波动无法归因 | 固定种子,多次运行取均值 |
| 数据几百条就上 XGBoost 狂调参 | 复杂度收益为负 | 先建线性基线,有稳定提升才升级模型 |
卡点急救
pip install xgboost失败:Windows 优先用 pip 装 wheel;Python 3.11+ 需 xgboost ≥ 1.7。实在装不上,用 sklearn 自带的HistGradientBoostingClassifier,效果同级。- LogisticRegression 报
ConvergenceWarning:特征没标准化或迭代不够。先 StandardScaler,再max_iter=1000。 - 树的可视化缺 Graphviz:不折腾安装,改用
sklearn.tree.export_text(tree, feature_names=[...])直接打印文本版规则。
面试常问
- 随机森林和 XGBoost 的区别? 随机森林是 bagging:并行训练多棵独立树(随机样本+随机特征),投票平均,主要降方差;XGBoost 是 boosting:串行训练,每棵树拟合前面模型的残差,主要降偏差,通常精度更高但对超参更敏感、更易过拟合。
- 逻辑回归为什么叫「回归」却在做分类? 它回归的是「对数几率」(log-odds)——线性组合 + Sigmoid 把结果压成概率,再用阈值切分类别。本质是广义线性模型,输出是概率,因此天然支持按业务调阈值。
- 决策树怎么选择分裂特征? 每次分裂遍历候选特征和切点,选让子节点「纯度」提升最大的:分类用基尼系数或信息增益(熵),回归用方差减少。sklearn 默认基尼。
自测三问
- 随机森林「随机」在哪两处?这对效果有什么影响?
- 逻辑回归输出的 0.7 是什么含义?怎么变成分类结果?
- 想给题目自动估难度分,该用回归还是分类?用什么特征?
小结一句话
五个模型一条线:线性可解释、树会学规则、森林稳、XGBoost 准——从基线到上限逐级升级,每一步用评估说话。
延伸
- scikit-learn 监督学习指南
- 下一课:ml-unsupervised 无监督学习——没有标签时,K-Means 聚类和 PCA 降维怎么用。