Skip to content

推荐系统 · 时间衰减公式怎么调参 ​

日期: 2026-07-09 | 作者: sunxin(+ Cursor AI 结对讲解) 关联路线图: 推荐系统建设路线图.md §五 Phase 3 + §六 Phase 4 预计触发: ADR-019(画像聚合的时间衰减,P3 ✅ 已沉淀)/ ADR-020(热榜打分的时间衰减,P4 待沉淀)

TL;DR ​

  • 推荐系统里到处都用"时间衰减"—— 热榜排序、用户画像、新旧内容平衡、消息 TTL —— 本质是告诉系统"越老的信息越不重要"。
  • 业界最常用的公式只有 3 种:Hacker News 幂次衰减 1/(hours+2)^1.5、Reddit 对数衰减、指数衰减 e^(-λt)。三者是"不同直觉"的数学化,选哪个要看你想让内容多久"失去竞争力"。
  • 本项目选幂次衰减 ^1.5:24h 前热度 ≈ 30%、48h 前 ≈ 15%、72h 前 ≈ 8% —— 与"帖子内容的现实半衰期约 1-2 天"匹配。
  • 调参核心是"半衰期"直觉:不要盯着公式看,盯着"多久热度掉一半"这个人话指标 —— 反推公式参数。
  • 不同场景要不同参数:热榜(快衰减)、画像(慢衰减)、消息触达(分档衰减)—— 一套参数打天下是典型错误。

目录 ​


1. 场景:为什么"时间衰减"是推荐系统的心脏 ​

1.1 一个反面例子 ​

假设你做"热榜",用最朴素的公式:

hot_score = like_count + favorite_count × 2 + comment_count × 3

跑一周后你会发现:热榜永远是同几条帖子。原因显而易见 —— 一条 3 天前爆火的帖子累积了 5000 赞,新发的帖子怎么也追不上。

结果:新内容没曝光机会 → 用户觉得"没啥新东西" → 活跃度下降 → 内容生态死循环。

1.2 时间衰减的核心作用 ​

时间衰减做的事:让老内容的分数随时间"自然掉出榜单",给新内容让出位置。

理想效果:

  • 新发布的帖子:起步分数低,但随时间累积互动能爬榜
  • 爬到榜后:靠新增互动追赶时间衰减的下降
  • 一旦互动增速放缓:分数快速下降,被更新的帖子挤下去
  • 3 天/7 天后:即使互动数还在缓慢增加,也已经几乎不再上榜

这就是"热度"的正确物理含义:不是"总互动数",而是"近期互动速率"。时间衰减是它的数学表达。

1.3 场景延伸:时间衰减在推荐系统的 4 大用途 ​

用途说明衰减速度需求
热榜排序让新内容有机会上榜(本笔记主场景)快(半衰期 12-24h)
用户画像用户最近的兴趣比 3 个月前的更重要慢(半衰期 7-14 天)
新老内容平衡推荐流里控制"新内容比例",避免只推老爆款中(半衰期 24-48h)
消息 TTL30 天前的通知自动过期阶跃函数(0/1 硬截断)

关键洞察:同一个项目在不同场景应该用不同的衰减参数,不能一套打天下。

2. 三种主流公式对比 ​

2.1 Hacker News 幂次衰减 ​

score = (P - 1) / (T + 2) ^ α

P: 点赞数 + 1(防止 0 分)
T: 帖子年龄(小时)
α: 衰减指数,Hacker News 用 1.8
+2: 让新帖子(T=0 时)不至于分数无穷大

特点:

  • 前期衰减快(0-6h 内下降明显)
  • 中期趋于平缓(12-48h)
  • 尾部慢(一周后仍有微弱余量)

曲线示意(α=1.5, +2, 权重 P=100):

T=0h    分数=100/(2^1.5)  ≈ 35.4    (100%)
T=1h    分数=100/(3^1.5)  ≈ 19.2    (54%)
T=6h    分数=100/(8^1.5)  ≈ 4.4     (12%)
T=12h   分数=100/(14^1.5) ≈ 1.9     (5.4%)
T=24h   分数=100/(26^1.5) ≈ 0.75    (2.1%)
T=48h   分数=100/(50^1.5) ≈ 0.28    (0.8%)

为什么加 +2:避免新帖 T=0 时分母 = 0,分数飙到无穷大;+2 相当于"起跑时压 2 小时权重",让新帖不至于一发就霸榜。

2.2 Reddit 对数衰减 ​

score = log10(max(|P - N|, 1)) × sign(P - N) + seconds_since / 45000

P: 顶数
N: 踩数
sign: 顶多则 +1,踩多则 -1
seconds_since: 帖子发布后秒数
45000: ≈ 12.5 小时,用作时间权重

特点:

  • 顶踩用对数抑制"刷票"(10 万顶 vs 1 万顶只差 log10(10) = 1 分)
  • 时间是线性加分而非衰减 —— 每 12.5 小时加 1 分
  • 结果:新帖天然有优势(时间分持续累加),老帖必须靠更多顶超越

为什么 Reddit 采用:适合"讨论区"型内容 —— 观点被讨论几天是常态,一个好帖子 3 天后还有价值,用时间线性加分而不是衰减更符合场景。

为什么本项目不选:小红书/朋友圈型内容的"新鲜度"重要性远高于讨论型内容,Reddit 公式会让老爆款帖霸榜。

2.3 指数衰减(牛顿冷却) ​

score = P × e^(-λ × T)

P: 原始热度(互动加权和)
λ: 衰减率(大 → 掉得快;小 → 掉得慢)
T: 帖子年龄(小时)

特点:

  • 每单位时间衰减固定比例(严格半衰期)
  • 数学最"纯粹"(对应牛顿冷却定律:温度差消散)
  • 参数 λ 的直觉:半衰期 = ln(2) / λ ≈ 0.693 / λ

例:λ = 0.05 → 半衰期 ≈ 14 小时

为什么本项目不选:指数衰减尾部消失太快,一周前的老帖分数几乎为 0,无法保留"经典好帖"的长尾曝光机会;幂次衰减在这方面更宽容。

2.4 三者曲线对比 ​

假设新帖 P = 100,画 168 小时(7 天)内的分数曲线:

分数(相对 T=0)
100% ┤●
     │ ●
 80% ┤  ●● 幂次 α=1.5
     │    ●●●
 60% ┤       ●●●●
     │           ●●●●
 40% ┤              ●●●●●●
     │                    ●●●●●●●●
 20% ┤                             ●●●●●●●●●●●●●●●
     │                                            ●●●●●●●●●●●●●●
  0% ┼──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──▶
     0  12 24 36 48 60 72 84 96 108 120 132 144 156 168 小时


指数 λ=0.05:
100% ┤●
     │ ● 
 80% ┤  ●●
     │    ●●
 60% ┤      ●●
     │        ●●●
 40% ┤          ●●●
     │             ●●●●
 20% ┤                ●●●●●
     │                    ●●●●●●●●
  0% ┼──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──▶ 
     (72h 后几乎归零)


Reddit(线性时间加分,无衰减):
     ⬆持续增长(假设互动数不变,只算时间维度)

结论:

  • 幂次衰减:曲线软着陆,前期快落后期慢,符合"新鲜感"直觉
  • 指数衰减:曲线硬着陆,短时间热点后完全冷却,适合"极速新闻"
  • Reddit 线性:反向鼓励新内容,适合讨论型

社交内容平台几乎都选幂次衰减(HN / Reddit-except-Reddit / 微博 / Instagram / 抖音等),因为"新鲜感 + 长尾并存"是社交内容的典型形态。

3. 幂次衰减 1/(t+c)^α 的调参方法 ​

3.1 三个参数各干嘛 ​

score = interaction_weight_sum / (T + c) ^ α
参数作用常见取值直觉
interaction_weight_sum互动加权和(分子)因项目而异(见 §4)越大越"热"
c起步保护常数1-4越大越保护"新帖不至于霸榜"
α衰减指数(分母幂)0.8-2.0核心调参 —— 越大衰减越快

3.2 用"半衰期"反推 α ​

半衰期:一个分数掉到原始 50% 所需的时间。

对幂次衰减 1/(t+c)^α:

掉到 50% ⇔  1/(t_half + c)^α = 0.5 × 1/c^α
        ⇔  (t_half + c)^α = 2 × c^α
        ⇔  t_half = c × (2^(1/α) - 1)

c=2 时的半衰期表:

α半衰期(小时)24h 剩余72h 剩余
0.83.7 h18%8.6%
1.02.0 h7.7%2.7%
1.5 ← 本项目1.2 h2.1%0.4%
1.8 (HN)1.0 h1.0%0.15%
2.00.83 h0.66%0.075%

注意:这里"半衰期"是纯衰减因子的半衰期。实际热榜里帖子分数下降更慢,因为分子(互动数)在持续增加。

换个更直观的问法:"我希望一条 24h 前的帖子,除非新增互动比新帖多多少倍,才能保持在同榜位置?"

  • α=1.0:24h 前的帖子需要新帖 13 倍的互动才能追上(1/0.077 ≈ 13)
  • α=1.5:48 倍
  • α=1.8:100 倍
  • α=2.0:150 倍

本项目选 α=1.5 的理由:

  • 一条 24h 前的帖子如果能吸引 50 倍于新帖的互动(比如爆款),仍值得让它保留 —— α=1.5 满足
  • 一条 24h 前的帖子如果没有 50 倍热度,就该给新帖让位 —— α=1.5 会让它掉出榜
  • 相比 HN 的 α=1.8(要求 100 倍),α=1.5 对老爆款更宽容,符合"生活分享类"内容的调性(微博/小红书都用 1.4-1.6 之间)

3.3 参数选择流程(面向不同产品阶段) ​

  1. 确定你想要的半衰期(用人话:多久后老内容基本失去竞争力)

    • 突发新闻/直播:1-3 小时
    • 热榜微博/微信朋友圈:12-24 小时(本项目)
    • 长内容/深度好文:1-3 天
    • 短视频(抖音/快手):3-6 小时
  2. 反推 α(用 §3.2 表格 or 公式)

  3. 确定 c(起步保护):

    • c=0:新帖直接爆发,可能一发就霸榜
    • c=2:新帖起步压 2 小时权重,前 2h 与已有 2h 帖打平
    • c=4:更保守,避免"新帖靠僵尸粉一波流"
  4. 确定互动权重(下面 §4 项目落地部分详解)

  5. 上线一周后 A/B:对比几个 α 值下的"用户平均停留时间"、"人均点赞数"、"新用户次日留存",选最优

3.4 常见错误 ​

  • ❌ α 太大(如 3.0 甚至 5.0):几乎没有"热榜"效果,全站几乎只推刚发的帖子
  • ❌ α 太小(如 0.3):老爆款永远不掉,热榜死气沉沉
  • ❌ c=0:新帖分数无穷大(一定要 c ≥ 1)
  • ❌ 忘记规范化:不同类目的互动数量级差异大(美食帖平均 100 赞、政治帖 3 赞),必须按类目归一化再打分

4. 项目里的具体落地 ​

4.1 P4 热榜打分公式(对应路线图 §六 §6.2.2 ③) ​

hot_score = (
    like_count × 2 +
    favorite_count × 3 +
    comment_count × 2 +
    view_count × 0.5
) / (hours_since_publish + 2) ^ 1.5

关键设计:

  • c = 2:新帖起步压 2 小时权重
  • α = 1.5:24h 前的帖子需要 ~50 倍互动才能保持同榜位置
  • 互动权重:fav > comment ≈ like > view
    • fav 是"愿意保存",最强意图 → 3
    • comment、like 是"表态",中等意图 → 2
    • view 是"看过",弱意图 → 0.5
    • 不加 share(分享带社交性但主要看用户圈子而非内容质量)

Redis ZSet 存储:

Key:    hot_posts:global
Type:   ZSet
Score:  hot_score (float)
Member: postId

刷新方式:HotPostRankingRefreshTask 每 5 min 全量重算 + AFTER_COMMIT 事件驱动增量

为什么 c=2 而不是 HN 的 c=2:跟 HN 一样,因为经验值证明这个值最平衡(c=1 太激进、c=4 太保守)。

为什么 α=1.5 而不是 HN 的 1.8:本项目定位"生活分享 + 关注流",不是"新闻聚合",用户对"48 小时前的好内容"仍有兴趣,衰减稍缓一点更合适。

4.2 P3 用户画像的时间衰减(对应路线图 §五 §5.2.2) ​

score(user, category) = SUM over action in last_30d {
    if action.target_category == category:
        weight(action.action_type) / (days_since(action.action_time) + 1) ^ 0.5
}

关键设计(跟热榜完全不同的参数):

  • T 用天,不用小时(画像半衰期以天为单位)
  • α = 0.5(比热榜的 1.5 缓和得多,因为兴趣变化速度远慢于热度衰减)
  • c = 1(防 T=0 时分母 0)

半衰期表(α=0.5, c=1):

天数 T权重保留率
01.0100%
70.3535%
140.2626%
300.1818%

为什么画像用 α=0.5 而热榜用 α=1.5:兴趣是长期属性,一个月前的兴趣仍有 18% 参考价值;热度是即时属性,一个月前的热点基本无关。同一个数学工具(幂次衰减),不同场景配不同参数。

4.3 消息 TTL(用阶跃函数而非衰减) ​

expiresAt = createdTime + 30 天
isExpired() = LocalDateTime.now().isAfter(expiresAt)

为什么消息不用衰减:消息是"要么显示、要么不显示"的二元态,没有"半可见"这种中间态。硬截断即可。

4.4 参数集中管理建议 ​

Phase 4 完成时建议把所有时间衰减参数收敛到 RecommendConfig 配置类:

java
@ConfigurationProperties(prefix = "recommend.decay")
public class DecayConfig {
    // 热榜
    private double hotAlpha = 1.5;
    private double hotBase = 2.0;
    private Map<String, Double> hotInteractionWeights = Map.of(
        "like", 2.0, "favorite", 3.0, "comment", 2.0, "view", 0.5
    );
    
    // 画像
    private double profileAlpha = 0.5;
    private double profileBase = 1.0;
    private Map<String, Double> profileInteractionWeights = Map.of(
        "view", 1.0, "comment", 2.0, "like", 3.0, "share", 4.0, "favorite", 5.0
    );
}

好处:

  • A/B 调参不改代码(配置中心热更新)
  • 未来上机器学习模型时,这些配置项就是模型初始化的基线特征权重

5. 教学收获 ​

5.1 "调参"不是玄学 —— 有明确的推导路径 ​

新手容易觉得"这些参数怎么定的?看起来很玄学"。实际推导流程:

业务目标("我希望老帖多久失去竞争力")
      ↓
数学化("半衰期 = X 小时")
      ↓
反推参数(用 §3.2 公式或表格)
      ↓
上线 A/B(用实际指标验证)
      ↓
调整(如果发现新帖曝光太多/太少)

关键:先想业务目标,再挑数学工具,最后调参数。反过来(先看到别人用了什么参数就抄)容易走偏。

5.2 "同一个工具在不同场景要不同参数" 是通用启示 ​

热榜 α=1.5 vs 画像 α=0.5:同一个数学工具,参数差 3 倍,因为业务本质不同。

延伸案例:

  • 缓存 TTL:热数据 30 秒 vs 冷数据 30 分钟(同样是 TTL,差 60 倍)
  • 重试间隔:网络抖动 100ms 指数退避 vs 支付回调 5 min 线性重试(同样是重试,策略完全不同)
  • 超时时间:内网 RPC 100ms vs 外网 API 5s(同样是超时,差 50 倍)

教训:看到"同一个概念"不要以为"同一个参数"。参数是场景的函数,不是概念的函数。

5.3 数学公式是业务直觉的"翻译" ​

看到 1/(t+2)^1.5 这个公式,正确的思考不是"1.5 是什么魔法数",而是翻译回业务:

  • 1/...^1.5 = 越老越弱
  • (t+2) = 保护新帖不至于起点无穷大
  • 1.5 = 衰减速度中等(12h 掉一半)

训练自己的能力:看到任何公式,能立刻用一句人话讲清楚它在业务上的含义。做不到就是没真正理解。

6. 附录 ​

6.1 术语表 ​

术语说明
时间衰减让老信息权重随时间自动下降的机制
半衰期分数掉到 50% 所需的时间
幂次衰减分母是"时间的幂次"(本项目采用)
指数衰减分母是"e 的时间次方"
起步保护常数幂次公式里的 +c,防止新帖分数爆炸

6.2 延伸阅读 ​

  • Hacker News Ranking Algorithm 源码:GitHub - HackerNews Firebase API 或搜"Y Combinator news.arc"
  • Reddit Ranking Algorithm 分析:搜"Reddit hot ranking algorithm derivation"
  • Nathan Marz 讲座《The Idealized Streaming System》—— 深入讲流处理里的时间语义
  • 《数学之美》(吴军) 第 8 章 "布尔代数和搜索引擎的索引" —— 相似的"数学工具翻译业务" 的思维训练

6.3 关联决策 ​

  • ADR-020(P4 待沉淀):本项目 P4 热榜采用幂次衰减 1/(t+2)^1.5 + 5 类互动加权
  • ADR-019(P3 ✅ 已沉淀):本项目 P3 画像采用幂次衰减 1/(d+1)^0.5 + 30 天窗口

Powered by VitePress