Files
mindfulness/spec_kit/Personalized Reco/modules/scoring/plan.md
2026-02-02 16:47:37 +08:00

11 KiB
Raw Blame History

Scoring软打分与惩罚项Plan

对应规范:spec_kit/Personalized Reco/modules/scoring/spec.md

规则来源(必须严格对齐):

  • 设计说明文档/個性化推薦算法規則.mdSoft Scoring 公式、V1.2 缺失字段口径、场景默认权重)
  • 设计说明文档/句子文案打分規則.mdrisk_flags 语义与默认值口径)

模块边界参考:spec_kit/Personalized Reco/overview.md(由 reco-engine 串起候选→过滤→打分→重排)


1. 目标与交付物

1.1 目标

  • 实现三种场景统一的软打分函数 final_score(U, Cᵢ),并输出可观测的 breakdown 以便调参与回归测试。
  • 严格实现 V1.2 缺失字段的保守策略(S_need/S_context/S_emotion 的默认值)。
  • 实现 Push 默认启用的不确定性惩罚 P_uncertainty
  • 实现 Widget 情绪区间0.4~0.8)的软降权(不硬过滤,但能明显压低分数)。

1.2 交付物

  • modules/scoring/plan.md:本技术计划(本文件)。
  • 代码实现tasks 阶段落地)建议位置:
    • server/app/features/personalized_reco/scoring/
    • 包含:
      • 纯函数 score_content(...) -> ScoreResult
      • ScoreConfig(场景默认参数 + 可覆盖)
      • ScoreBreakdown(稳定字段集合,用于可观测)
  • 单元测试tasks 阶段落地):
    • 缺失字段一致性
    • Push 不确定性惩罚生效
    • Widget 情绪软降权生效(区间外明显更低)

2. 模块职责边界V1 约定)

2.1 本模块负责

  • 计算并返回:
    • S_need/S_context/S_stage/S_emotion
    • S_core
    • S_personal
    • P_uncertainty按开关控制Push 默认启用)
    • P_widget_emotion_out_of_rangeWidget 专用软降权项,归入 P_risk 或单独字段均可V1 建议单独字段,便于打点)
  • 生成 breakdown,用于可观测与调参。

2.2 不在本模块实现(但接口预留/可注入)

为避免与 rerank-freqcap / reco-engine 的职责重叠V1 约定以下项由外部模块产出并作为输入注入(若不提供,默认按 0 处理):

  • S_fresh:新鲜度/时间衰减相关(可由引擎或重排阶段计算)
  • P_fatigue:疲劳惩罚(基于历史触达/浏览/频控)
  • P_repeat:重复惩罚(同句/同作者/同模板等)
  • P_risk:软风险惩罚(例如 soft_health_sensitive 等)

说明硬过滤Hard Filter由引擎编排阶段执行本模块只消费 pass(是否通过硬过滤)并在总分中乘上 (\mathbb{I}[pass])。


3. 输入/输出与数据结构V1

3.1 输入(对齐 spec

  • scene: feed | push | widget
  • user_profileU允许字段缺失/跳过)
  • content_profileCᵢ
  • now:预留,用于 freshness/时间衰减V1 可不实现具体公式)
  • config
    • w_need/w_emotion/w_stage/w_context
    • alpha:个性化加成系数
    • beta:不确定性惩罚系数
    • enable_uncertainty_penalty:是否启用 P_uncertaintyPush 默认 true
    • widget_emotion_soft_rangeWidget 情绪软区间(默认 [0.4, 0.8]
    • widget_emotion_penalty_gammaWidget 情绪软降权强度V1 取“适中”默认 0.25
  • pass: boolean(来自 Hard Filter 结果;默认 true
  • external_terms(可选,来自其他模块注入):
    • S_freshP_fatigueP_repeatP_risk
    • 若未提供则按 0 处理

3.2 输出

  • final_score: float
  • breakdown(建议始终返回,便于可观测与断言):
    • S_need/S_context/S_stage/S_emotion
    • S_core/S_personal/S_fresh
    • P_fatigue/P_repeat/P_risk/P_uncertainty
    • P_widget_emotion_out_of_range(可选但建议保留)
    • missing_fields: string[]need/context/emotion
    • pass: boolean
    • scene: feed|push|widget

4. 核心公式与实现细则(必须对齐)

4.1 总分结构(线性加权 + 惩罚)

[ final_score(U,C_i)=\mathbb{I}[pass]\times\Big(S_{core}+S_{personal}+S_{fresh}-P_{fatigue}-P_{repeat}-P_{risk}-P_{uncertainty}\Big) ]

[ S_{core}=w_{need}S_{need}+w_{emotion}S_{emotion}+w_{stage}S_{stage}+w_{context}S_{context} ]

V1 约定:S_fresh/P_fatigue/P_repeat/P_risk 允许外部注入;若缺失则当作 0以确保函数可用且输出结构稳定。

4.2 分解项S_need / S_contextV1.2 缺失字段兜底)

  • S_need
    • U.need 缺失(为空对象 {} 或不存在)→ S_need = 0.5
    • 否则 → S_need = Cᵢ.need_suitability[U.need_key]
  • S_context
    • U.context 缺失(为空对象 {} 或不存在)→ S_context = 0.5
    • 否则 → S_context = Cᵢ.context_suitability[U.context_key]

工程约定:U.need/U.context 在客户端为稀疏 one-hot最多一个 key=1。实现时需提供一个“取唯一 key”的工具函数若出现多个 key=1按第一个字典序或插入序取值并记录告警V1 可先 debug 日志)。

4.3 分解项S_emotiongeneral=0.8;否则 1-|u-c|

  • U.emotion_score 缺失 → S_emotion = 0.8
  • 否则:
    • Cᵢ.emotion_score 为 generalNone)→ S_emotion = 0.8
    • 否则 → (S_{emotion}=1-|U.emotion_score - C_i.emotion_score|)

实现约束:

  • S_emotion clamp 到 [0,1],避免异常值导致负分或溢出。

4.4 分解项S_stage对齐算法规则

规则(按 设计说明文档/個性化推薦算法規則.md

  • Cᵢ.stage == "general"S_stage = 1
  • Cᵢ.stage 命中用户阶段(例如用户 expecting=1 且内容 stage="expecting")→ S_stage = 1
  • 若用户阶段为 unknown 且内容阶段为非 unknown → S_stage = 0.7
  • 其余 → S_stage = 0

4.5 个性化加成 S_personal含降个性化约束

[ S_{personal}=\alpha \cdot C_i.personalization_power \cdot \max(S_{need}, S_{context}) ]

降个性化约束(对齐 spec 与回退梯度):

  • fallback_level>=1 或字段缺失明显/低置信度:
    • L1限制 personalization_power ≤ 0.5
    • L2/L3限制 personalization_power = 0

工程实现:本模块只做“限制后的 effective_personalization_power”由调用方传入 fallback_level(或直接传入已限制后的 content_profile.personalization_power。V1 建议:在 content-repository/引擎侧先做候选池约束,本模块再做一次保护性 clamp防御式编程

4.6 不确定性惩罚 P_uncertaintyPush 默认启用)

[ P_{uncertainty}=\beta \cdot (1-conf_U)\cdot(1-conf_{C_i})\cdot C_i.personalization_power ]

其中:

  • conf_U = user_profile.profile_confidence
  • conf_{C_i} = content_profile.review_confidence(缺省 0.7

开关策略V1

  • scene=push:默认启用
  • scene=feed/widget:默认关闭(可通过 config 打开)

4.7 Widget 情绪区间软降权(适中默认实现)

目标:当 scene=widgetCᵢ.emotion_score 可计算(非 general若超出 [0.4, 0.8] 不硬过滤,但应产生明显降权。

V1 选择一个“适中、可调参、可解释”的惩罚函数:

  • 设区间为 [lo, hi](默认 0.4, 0.8
  • 距离:
    • e < lod = lo - e
    • e > hid = e - hi
    • 否则 d = 0
  • 惩罚:
    • (P_{widget} = \gamma \cdot \frac{d}{(hi-lo)})
    • 默认 γ = 0.25(适中强度)
    • clamp 到 [0, γ]

落地方式:

  • P_widget_emotion_out_of_range 单独输出到 breakdown
  • 在总分中计入:
    • P_risk_effective = external.P_risk + P_widget_emotion_out_of_range

解释:当 emotion_score 达到区间边界外最大距离约为 0.4(例如 0 或 1惩罚接近 γ,足以在 Widget 场景把“过低/过高情绪”的句子压到更靠后,但不会一刀切。


5. 场景默认参数V1 建议)

对齐 spec.md 与算法规则:

  • Feedw_need=0.35, w_emotion=0.20, w_stage=0.15, w_context=0.30
  • Pushw_need=0.45, w_emotion=0.35, w_stage=0.15, w_context=0.05,并默认 enable_uncertainty_penalty=true
  • Widgetw_need=0.25, w_emotion=0.25, w_stage=0.30, w_context=0.20,并启用 widget_emotion_soft_range=[0.4,0.8]

推荐默认:

  • alpha=0.15可调V1 用于让个性化加成“次要但可见”)
  • beta=0.30可调V1 用于在低置信度时明显压低高个性化内容)
  • widget_emotion_penalty_gamma=0.25(适中软降权强度)

注:alpha/beta/gamma 为工程默认建议值,后续应通过回归测试与线上指标调参;本模块必须允许 config 覆盖。


6. V1S_fresh / P_fatigue / P_repeat 的处理方案(能落地且可演进)

6.1 V1 解决方式

  • ScoreBreakdown保留 S_fresh/P_fatigue/P_repeat 字段;
  • 本模块计算时:
    • 若上游未提供对应值,则默认按 0
    • 若提供,则原样计入总分(本模块不解释其来源/计算方式)。

6.2 接口建议(为后续模块对接预留)

  • external_terms 中携带:
    • S_fresh:例如时间衰减、跨日新鲜度(未来可由 rerank-freqcapreco-engine 计算)
    • P_fatigue/P_repeat:由 rerank-freqcap 基于历史集合与冷却窗口计算

好处V1 先保证“可排序 + 可解释 + 可插拔”;后续接入重排/频控时无需改动打分主干,只需注入外部项。


7. 测试计划V1

7.1 单元测试(纯函数)

  • 缺失字段一致性:
    • U.need 缺失 → S_need=0.5
    • U.context 缺失 → S_context=0.5
    • U.emotion_score 缺失 → S_emotion=0.8
  • 不确定性惩罚Push 默认启用):
    • conf_U/conf_C 低且 personalization_power 高 → final_score 明显降低
    • 关闭开关后 P_uncertainty=0
  • Widget 软降权:
    • emotion_score=0.6(区间内)→ P_widget=0
    • emotion_score=0.0/1.0(区间外)→ P_widget 接近 gammafinal_score 明显更低
  • pass=false
    • final_score 必须为 0或按实现约定为 0且 breakdown 中保留分解项(便于排查)

7.2 断言建议

  • 断言 breakdown 字段集合稳定(不会因缺省而缺字段)。
  • 断言所有分项均为有限数(非 NaN/Infinity并在合理范围内可对 S_* clamp 到 [0,1])。

8. 风险与后续演进

8.1 已知风险

  • U.need/U.context 若出现多个 key=1会导致取值歧义V1 需明确选择策略并记录告警,避免 silent bug。
  • Widget 软降权强度(gamma)对结果影响较大,需要配合回归测试与线上指标调参。

8.2 后续演进方向V1.1+

  • P_risk 细化为可配置的多项惩罚(例如 health sensitive、过度个性化翻车风险等并在 breakdown 中拆分输出。
  • 引入 S_fresh 的时间衰减公式,并与 rerank-freqcap 的跨日多样性联动。