综合练习
鸢尾花分类端到端 Notebook。
综合练习
这一课你将能做到
- 独立完成一个端到端项目:从 5000 条做题流水预测「用户答对当前题的概率」
- 构建只使用历史信息的时序特征(用户历史正确率、用户-主题正确率),杜绝标签泄漏
- 按时间切分训练/测试集,并用朴素基线证明模型的价值
- 把模型输出变成产品语言:一份薄弱主题推题清单
为什么要学
前面十一课每个主题都练过单点,但真实工作里没有「给好了 X 和 y」这回事。综合练习的目的是把数据构造、防泄漏特征、诚实评估、业务输出串成一条线——这也是面试深挖项目时的考察方式:任何一步说不出为什么,整段经历都会被质疑。
这个项目刻意用本站形态的数据:用户流水、题目难度、掌握度。做完它,你就拥有了一个可以写进简历、可以当面讲 20 分钟的完整案例。
先建立图像
项目流水线(每一步都对应下面的步骤):
模拟做题流水(5000条)
→ 时序特征(只用历史:cumcount / cumsum-shift)
→ 按时间切分(前 80% 训练,后 20% 测试)
→ HistGradientBoosting(原生吃 NaN)
→ 评估(AUC + 朴素基线对照)
→ 产品输出(user-topic 掌握度 < 0.6 → 推题清单)
跟着做
步骤 1:模拟 5000 条做题流水
import numpy as np
import pandas as pd
rng = np.random.default_rng(7)
n_users, n = 300, 5000
users = pd.DataFrame({"user_id": range(n_users),
"ability": rng.normal(0, 1, n_users)})
topics = pd.DataFrame({"topic": ["数组", "哈希表", "二叉树", "动态规划"],
"diff": [-0.3, 0.0, 0.4, 0.9]})
df = pd.DataFrame({
"user_id": rng.integers(0, n_users, n),
"topic": rng.choice(topics.topic, n, p=[0.4, 0.3, 0.2, 0.1]),
"ts": rng.integers(0, 60 * 24 * 3600, n), # 60 天内的时间戳
})
df = df.merge(users, on="user_id").merge(topics, on="topic")
p = 1 / (1 + np.exp(-(df.ability - df.diff))) # 能力-难度 → 答对概率
df["correct"] = rng.binomial(1, p)
df = df.sort_values("ts").reset_index(drop=True) # 一切按时间来
步骤 2:构造「只用历史」的特征(本项目灵魂)
g = df.groupby("user_id")["correct"]
df["user_past_n"] = g.cumcount() # 之前做过几题
df["user_past_acc"] = (g.transform("cumsum") - df["correct"]) \
/ df["user_past_n"].replace(0, np.nan)
g2 = df.groupby(["user_id", "topic"])["correct"]
df["topic_n"] = g2.cumcount()
df["topic_acc"] = (g2.transform("cumsum") - df["correct"]) \
/ df["topic_n"].replace(0, np.nan)
feats = df[["user_past_n", "user_past_acc", "topic_n", "topic_acc"]].astype(float)
label = df["correct"]
cumsum 含当前行,必须减掉 correct 才是「之前的累计」;首题的 n=0,除出 NaN——这个 NaN 必须保留,它是「没有历史」的诚实表达。
步骤 3:按时间切分,不许随机
cut = df["ts"].quantile(0.8)
tr, te = df["ts"] < cut, df["ts"] >= cut
print(f"训练 {tr.sum()} 条,测试 {te.sum()} 条")