小白一句话
监督模型在这套体系里就干一件事:拿一个用户"评分日及之前"的特征,算出一个数字——他未来 7 天有多大概率还会回来领。这件事在机器学习里叫二分类,算出来的那个数字就是概率。
监督模型在这一章的位置
第2章讲过,本项目有两个模型,行为分类(第9–11章,无监督)管"他是什么风格",活跃度预测(本章起,有监督)管"他会不会回来"。
第6章把样本长什么样定下来了:一行 = (uid, 评分日) 快照,左边是特征、右边是标签 active_7d(未来 7 天是否至少再来 1 次)。第7章把左边那 10 维特征讲清楚了。第8章把"特征只能看左边、标签只能看右边"这条线焊死。
到了本章,左边的特征和右边的标签都齐了,该让模型学"从特征到标签"的映射了。我们用示例数据生成的训练表来做这件事:162 行样本,其中 134 行未来 7 天回来了(label=1),28 行没回来(label=0)。
这 134:28 的不均衡不是偶然——大部分常来的用户确实还会再来,真正"凉了"的是少数。这个不均衡后面第14章(样本权重)和第19章(评估指标)都要专门对付,先记一笔。
二分类,但模型给的是概率
标签 active_7d 本身是 0 或 1。但直接让模型硬猜 0/1 太浪费——我们更想要一个连续的概率:0.9 表示"九成会回来",0.1 表示"基本凉了"。
原因很实际:概率比 0/1 好用。后面可以把它乘 100 当成 0–100 分的活跃度(第16章校准后会细讲),也可以按概率高低给运营排个队,而不是直接二分。所以这一章的任务,是学一个函数 f(特征) → 0~1 之间的概率。
决策树:模型其实在问一连串"是否"
要理解后面那个模型,先得懂它最基础的零件——决策树。它的工作方式特别像人做判断:不断问"这个条件成立吗",顺着答案往下走,走到叶子节点给个结论。
用示例训练表训一棵只许问 3 层问题的浅树,它自己学出的规则长这样:

读图:从根节点开始,满足节点条件走「是」分支、否则走「否」分支;落到叶子节点即得到预测类别——绿色「会回来」(class 1)、红色「不会回来」(class 0)。
你会发现它第一句话就问"特征期总领取次数 ≤ 3.5 吗"——这不是人写死的,是树从数据里自己挑的最能分开"回不回"的那个问题。沿着这棵树走,一个用户最终落到某个叶子,叶子给出的就是"这批人里有多大比例回来了",那就是他的预测概率雏形。
树靠"不纯度"挑问题
树不是瞎猜先问哪个。它心里有一把尺子叫不纯度(基尼不纯度),衡量"这一堆人里标签有多乱":如果一堆人全回来了(全 1)或者全凉了(全 0),不纯度是 0,最干净;如果一半回一半没回,最乱,不纯度最高。树的做法是——对每个特征、每个可能的切分点都试一刀,挑那一刀能让切完之后两边的不纯度加权和降得最多的。
拿根节点试算一下(162 行,134 回 / 28 凉,基准率 0.827):
- 根节点不纯度 = 0.2859。
- 第一刀 f_total_claims ≤ 3.5:左支 46 行(25 回,不纯度 0.4962)、右支 116 行(109 回,不纯度 0.1134);加权不纯度 = 0.2221,不纯度下降 0.0638。
- 对照一刀"按行号奇偶乱分"(和特征无关):加权不纯度 0.2847,只降 0.0012,几乎没分干净。
差距就在"下降多少"——树自动选下降最多的那刀,所以第一句话问的是领取次数,不是某个没用的特征。这套"试所有可能、选降最多的"就是决策树的学习,也是为什么它第一刀和 GBDT 认为最重要的特征一致(都是总领取次数)。
叶子的值为什么是概率
每个叶子聚了一群人。树不硬说"这群都会回来 / 都不会回来",而是报这群人里实际回来的比例。比如某叶子拢了 100 人、80 人未来 7 天回来了,就输出 0.8——意思是"这拨人八成会回来",而不是铁板钉钉。这个比例是训练数据里数出来的经验概率,是后面概率预测的雏形(第16章会讲它还得校准)。
深度:简单规则 vs 死记硬背
这棵浅树只许问 3 层。为什么不让它一直问下去?因为树越深,能把每个叶子分到"只剩一种标签"——等于把训练表背下来了。背下来的规则对新用户反而失灵(过拟合):训练时它认得 U0009,换个人就不灵了。深度就是个旋钮,浅一点是"看得见的简单规则",深一点是"死记硬背"。本项目用浅树当零件、再用 GBDT 把很多棵浅树叠起来,既抓得住规律又不至于背死。
决策树的好处是能直接读规则,坏处是单棵太死板:问的问题稍微变一点,整棵树就换样,而且单棵树抓不住"多个特征合起来才说明问题"的情况——这正是下一节 GBDT 要补的。
GBDT:把很多棵"不太准"的树叠起来
本项目用的主模型是一类叫 GBDT(梯度提升树) 的方法——同样是监督模型,也可以选神经网络,为什么最后选了树,本章最后一节会说清楚。它的想法很朴素:
先种一棵简单的树,它肯定看不太准;再看它错在哪儿,种第二棵专门补这些错;再看剩下的错,种第三棵……最后把所有这些树的判断加起来,就是最终预测。
每一棵新树都在纠正前一堆树没搞定的部分(专业地说,是拟合前面模型的残差)。单棵树弱,叠几十上百棵就强了,而且能捕捉特征之间拧在一起的关系——比如"领得多且趋势没掉"才高概率回来,这种组合单棵树很难一次问清。
残差:新树到底在补什么
“补错"用大白话讲就是:模型先把所有人统一猜成"平均水平”——示例里 134 / 162 的人都回来了,所以开局每个人都被估成 0.827(八成会回来)。对 U0009 这种实际没回来的人,开局就猜高了,差了 0.827 − 0 = 0.827,这个"差"就是残差(分类里它严格说是损失函数对当前预测的负梯度,但直觉上就是"猜的和真相差多少")。
第二棵树不预测"回不回",而是专门去学这个差值 0.827——它努力把 U0009 这类人的分往下拉。第三棵再学"拉完之后还差多少",继续补。所以 GBDT 不是在造 100 个独立的分类器,而是在反复追问"我现在还差哪儿没猜对",一层层把错削小。
看叠加怎么纠错:概率随树棵数收敛
把 GBDT(默认 100 棵、学习率 0.1)在示例训练表上跑起来,盯着 U0036(真回来)和 U0009(真没回)两个老熟人,看它们的概率怎么随树一棵棵加上去而变化:
| 1 棵 | 0.844 | 0.836 |
| 5 棵 | 0.881 | 0.824 |
| 10 棵 | 0.920 | 0.756 |
| 30 棵 | 0.968 | 0.580 |
| 50 棵 | 0.979 | 0.437 |
| 100 棵 | 0.992 | 0.219 |
开头两棵几乎没把他俩分开(都贴在 0.83 附近,因为模型还只会"瞎猜平均")。往后每加一棵,U0036 被稳稳往上推到 0.99、U0009 被一点点拽到 0.22——这就是"叠加纠错"长什么样:不是一步到位,是几十棵小树一点点把每个人挪到该去的地方。最终 0.99 / 0.22 正好就是下一节抽查用的两个数。
这里有个值得盯的细节:第 1 棵小树是一次性去拟合所有人的残差,本身是个折中——U0009 在第一个切分上看着和活跃人群没两样(领取次数高),所以第 1 棵反而把他往上带了零点几(0.836);得等后面的树看到他在"掉",才把他一路拉回 0.22。光看"领取次数"这种历史活跃特征,起初根本分不出他和真活跃的人——树要叠起来才看得清。
学习率:为什么是一百小步而不是几大步
看上面这张表,每加一棵,概率只挪一点点(比如 U0009 从 0.836 到 0.824 才动一丢丢)。GBDT 故意这样:每棵树的修正都先乘一个很小的数(学习率,默认 0.1)再叠加,叫收缩(shrinkage)。好处是任何一棵树都掀不起大浪,得靠很多棵一起把预测推到位,模型更稳、不容易被个别怪样本带偏。所以 GBDT 的套路是"很多棵浅树 + 小步快跑",而不是"一棵深树一步登天"——这也和上面决策树"别长太深"是一个道理。
写成式子就是这个加法的样子:
最终分数 = 开局平均 + 0.1×树₁ + 0.1×树₂ + … + 0.1×树₁₀₀
概率 = 把这个分数塞进 S 形函数(sigmoid)压到 0~1 之间
用同一张训练表训一个 GBDT,看它认为哪些特征最影响"回不回":
f_total_claims 0.250 特征期总领取次数(最主要的信号)
f_age 0.173 龄:第一次出现距今多久
f_active_rate 0.149 活跃率:来过的天数占观测期的比例
f_trend 0.084 趋势:近 7 天比前 7 天多还是少
f_recency 0.083 新近度:多久没来了
f_entropy 0.069 偏好熵:任务偏好的分散程度
f_days_total 0.069 全期活跃天数
f_longest_streak 0.057 最长连续活跃天数
f_task_types 0.045 任务种类数
f_top_share 0.022 主任务占比
"特征期总领取次数"排第一,和浅树第一句话问的它一致——说明这件事模型自己反复确认:领得越多,越可能接着来。这和直觉对得上,也让人放心模型没在乱学。
拿两个老熟人验证一下预测对不对:
- U0036(几乎天天来、啥都领):GBDT 给的概率 0.99,真实标签 1(确实回来了)。
- U0009(历史活跃但快凉,第2、3、6、8章多次出现的反例):概率 0.22,真实标签 0(这次真没回来)。
U0009 这个例子把监督和无监督的分工说得很透:行为上他依然是"高频多任务"那一堆(无监督把他和 U0036 分到类似的群),但光看"历史活跃"会误判他会回来——监督模型吃了趋势、新近度这些特征,读出"他在掉",给了低概率。这正是第2章说的"行为像 ≠ 命运一样"。
超参数从哪来:早停、网格搜索、防过拟合旋钮
前面用的 GBDT 是"100 棵、深 3、学习率 0.1"。这三个数不是拍脑袋——但得坦白:示例只有 105 行训练样本,调参能捞的收益很小,这一节教的是"怎么调"的流程,不是"调完一定变强"。
早停的直觉:树越多越好吗
把树的棵数从 10 加到 200,看训练和测试两边的分数:
| 10 | 0.998 | 0.854 | 0.741 |
| 30 | 0.999 | 0.861 | 0.740 |
| 60 | 1.000 | 0.903 | 0.785 |
| 100 | 1.000 | 0.827 | 0.689 |
| 150 | 1.000 | 0.850 | 0.722 |
| 200 | 1.000 | 0.844 | 0.701 |
训练分数从 10 棵起就贴着 1——树背训练数据的能力太强,再加棵树它也"记得住"。测试分数不一样:60 棵附近最高(0.903),往后开始晃(0.827、0.850、0.844)。训练一路降、测试涨到某处就停甚至往回掉,就是"再加树只是在背数据"的信号,该停了——这就是早停的直觉。
一个小提醒要摆明:示例的测试曲线自己就在晃(60 棵 0.903,100 棵 0.827,150 棵又回 0.850),因为 57 人的测试集噪声大。所以早停不能盯单点,要看验证集的整体趋势——这正是第 8 章"三切分"里验证集存在的意义之一。
网格搜索:换参数组合试
超参数不会自己跳出来,最朴素的办法是网格搜索——几个参数各取几个值,组合起来都跑一遍。示例上试了深度 × 学习率:
| depth=2, lr=0.1 | 0.815 | 0.694 |
| depth=3, lr=0.1(默认) | 0.827 | 0.689 |
| depth=5, lr=0.1 | 0.832 | 0.690 |
| depth=3, lr=0.05 | 0.898 | 0.786 |
| depth=5, lr=0.05 | 0.828 | 0.694 |
depth=3、lr=0.05 看着最好(0.898)。但有条纪律得说在前面:真实流程是"训练 → 验证 → 测试"三层,参数用验证集选,测试集只碰一次。上面这张表直接拿测试集挑参数,是演示用的小作弊——真实项目这么干,等于把测试集也泄给了调参过程,最后报出来的 0.898 会虚高。而且 0.898 和默认 0.827 的差距,在 57 人测试集上同样是噪声级别——参数可以调,但要认命:样本就这么大,能捞的就这么多。
防过拟合旋钮:还有几个开关
除了棵数和深度,GBDT 还有几个"别让模型太自由"的开关:
- subsample:每棵树只随机看一部分行再分裂(0.8 = 看 80%)。树之间看到的数据不一样、更独立,平均起来更稳。示例上 subsample=0.8 的测试 PR-AUC 0.863,比 1.0 的 0.827 略好。
- min_samples_leaf:叶子最少装几个人。太细的叶子(比如一个叶子只装 1 个人)等于在背个体,不让它长。这就是"深度:简单规则 vs 死记硬背"那节的另一种拧法——不限制深度、限制叶子大小,效果类似。
一句话:默认参数能跑,调参是锦上添花;调参的流程比参数本身重要——早停看验证集趋势,选参用验证集,测试集最后碰一次。
另一种主流:神经网络在做什么
GBDT 走的是"问一连串是非题"的路子,那当下更常被提起的神经网络走的是另一条。
想象一排小开关(叫神经元):每个开关把接到的几个数各自乘一个权重、加起来,再过一道把结果压到 0~1 之间的函数,吐出一个新数。把很多这样的开关排成一层,一层接一层——第一层接原始特征,后面每层拿前一层的输出当输入,最后一层吐出预测概率。这就是多层感知机(MLP),最基础的那种神经网络:
# 结构 10 → 32 → 16 → 1:10 个特征进,中间两层各 32、16 个神经元,输出 1 个概率
MLPClassifier(hidden_layer_sizes=(32, 16))
那"学习"学的是什么?就是这些权重。训练时先把样本喂进去,顺着网络算出一个预测(前向传播),看看和真相差多少;再从后往前,把每个权重要为这个误差担多少责任算出来,各自往"让误差变小"的方向挪一点(反向传播配梯度下降)。喂一批挪一点、再喂一批再挪一点,成千上万轮下来,权重就落在了一个总体误差不大的位置。
和树模型对比着看更清楚:
- 树是比大小、走分支——“领取次数 ≤ 3.5 吗”,非此即彼,规则看得见。
- 网络是加权求和——每个特征贡献多少由一个权重说了算,几百个权重拧在一起,很难讲清"它到底看中了什么"。
神经网络真正厉害的地方,是能自己学"特征该怎么表达"。给它一张图片,不用人先告诉它边缘在哪、眼睛在哪,前几层自己就学会描边,后面几层慢慢拼出形状。图像、语音、文字这种原始信号,人很难手工总结出好特征,正好交给它。
代价也实在:
- 参数多。上面那个不起眼的 10 → 32 → 16 → 1,待学权重就有 897 个。参数越多,要喂的样本就越多。
- 吃预处理。权重乘的是特征原值,所以得先缩放到差不多的范围(标准化),否则量纲大的一家独大。树靠比大小,不吃这一套。
- 不好解释。897 个权重各是多少,说不出个所以然,只能讲"它觉得这个人分数高"。
第15章会拿本项目的示例数据真跑一遍神经网络,和 GBDT 比分数、比稳定性。这里先有个概念,接着看为什么本项目最后选了树。
为什么是 GBDT,不是深度学习
新手常有个疑问:现在不是都在用深度学习吗,怎么这项目还用树?
因为咱们的数据是表格型的——一行一个用户、一列一个特征,特征之间没像素那样的空间/序列结构。这类数据上,树模型(GBDT 及其变体)长期是实用首选,原因很实在:
- 量纲不敏感:天数(十几)、次数(几十)、偏好熵(0~几点几)混在一起,树靠"比大小"分割,不用像神经网络那样先标准化(第9章标准化是为了聚类距离,树模型本身不需要)。
- 缺失值友好:某个特征空了(比如冷启动用户的部分特征),树可以走"没这个值"的分支,不像神经网络要求每列都填满。
- 小样本更稳:示例才 162 行,树模型换次训练分数几乎不动;神经网络在这个量级也能跑出像样的分数,但换一次初始化结果就变(第15章会拿两组实测数字比给你看)。
- 能解释:上面的特征重要性、决策规则,都是看得见的,出了问题能查。
深度学习在图像、文本、语音这种"原始信号"上厉害,在咱们这种已经整理好的小表格上属于杀鸡用牛刀,还更难解释。所以这一章先把树模型吃透,足够应付本项目的监督主模型。第15章会把神经网络也拉进来跑一遍——不是比谁分数高,是比谁更让人放心。
集成不只有一条路:bagging、boosting 与 stacking
GBDT 是"很多棵树串行补残差",这条集成路线叫 boosting。但"把很多模型合起来"不止这一种玩法,还有两条:
- bagging:很多棵树并行独立训练(每棵随机看部分行、部分特征),结果投票或平均,代表是随机森林。单个模型波动大,一平均波动就小了——它管的是减方差。
- stacking:把不同种类的模型拼起来(比如随机森林 + GBDT),预测合在一起再学一层。它们犯的错不一样,取长补短。
同一份数据(105 行训练 / 57 行测试),三条路线各跑一遍:
| 随机森林 | bagging:并行独立树投票 | 0.923 | 0.806 |
| GBDT | boosting:串行补残差 | 0.827 | 0.689 |
| 简单融合 | stacking:两个概率平均 | 0.923 | 0.802 |
单看这一行,随机森林和简单融合明显更高(0.923 vs 0.827)。先别急着下"该换随机森林"的结论——换 4 个种子再看:
| 随机森林 | 0.762 ~ 0.806 | 0.044 |
| GBDT | 0.686 ~ 0.690 | 0.005 |
随机森林 4 个种子全都比 GBDT 高(最差 0.762 > GBDT 最好 0.690),但跨度是 GBDT 的将近九倍——它在这份数据上确实占优,只是也更抖。那前面几章为什么一路用 GBDT?因为选它靠的不是"单点分数最高":特征重要性、缺失值友好、规则看得见,这些工程与解释性上的优势,在这份"特征少、要解释、样本小"的数据上更顺手。选模型看的是综合取舍,不是一张表里最高的那个数。
真实项目怎么挑路线,看数据的性格:
- 特征多而杂、容易过拟合 → bagging(平均天然抗噪);
- 特征少而精、要可解释 → boosting(GBDT 一条路走到底);
- 要冲极限分数、工程上养得起 → stacking(把前两种都拿来拼)。
本项目是"特征少、要解释、样本小",所以走了 boosting——这个选择在第 15 章被神经网络挑战过一次,这一节再被随机森林挑战一次,结论都一样:分数不是唯一的尺子。
它和双模型的关系
到这儿,第2章画的"双模型"就完整了:
- 行为分类(无监督):吃左边特征 → 吐"他属于哪类行为风格",不碰标签。
- 活跃度预测(监督,本章):吃左边特征 + 右边标签 → 学"未来 7 天回来的概率"。
两个模型共享同一份特征底座(第7章那套),但回答不同的问题,谁也不替谁。下一章(第13章)先给训练样本做一遍"准入体检"——样本分布、有没有被少数高频用户主导、训练和测试的分布对不对得上,体检不过就白训;第14章讲标签怎么变成损失、样本不均衡和同用户多样本该怎么加权;第16章讲概率为什么要校准;第17章把"训练 → 评估 → 每日评分"三步串起来跑通。
动手
本章配套脚本 附件/05_活跃度预测篇/activity_train_table.py(从明细生成训练表)、附件/05_活跃度预测篇/activity_gbdt_intro.py(决策树 + GBDT 演示)、附件/05_活跃度预测篇/activity_ch12_principle.py(基尼不纯度挑问题 + GBDT 逐阶段概率)、附件/05_活跃度预测篇/activity_hyperparam.py(早停曲线 + 网格搜索 + 防过拟合旋钮)、附件/05_活跃度预测篇/activity_ensemble.py(bagging/boosting/stacking 三条路线对比)。用到的 numpy、scikit-learn 已在第4章 附件/00_公共/requirements.txt 中列出,环境搭建见第4章。
网硕互联帮助中心



评论前必须登录!
注册