【机器学习】深度学习推荐系统(二十七): X 推荐算法rerank机制详解

X 推荐算法分数修正机制详解

前言

在 X(原 Twitter)的推荐系统中,多模型融合后的分数并不是最终分数。融合分数会经过多个步骤的修正,包括启发式规则、多样性调整、Phoenix 重评分等。本文将详细解析这些分数修正机制。


一、分数修正流程概览

1.1 完整流程

复制代码
┌─────────────────────────────────────────────────────────┐
│  步骤1: 多模型融合                                        │
│  WeighedModelRerankingScorer                            │
│  输出: WeightedModelScoreFeature = Σ(score_i × weight_i) │
└─────────────────────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────────────────────┐
│  步骤2: 启发式重评分                                      │
│  HeuristicScorer                                        │
│  应用多个乘法因子调整分数                                 │
│  输出: ScoreFeature = WeightedModelScore × scaleFactor  │
└─────────────────────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────────────────────┐
│  步骤3: Phoenix 重评分(可选)                            │
│  PhoenixRescoringFeatureHydrator                        │
│  使用 Phoenix 分数调整                                   │
└─────────────────────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────────────────────┐
│  步骤4: 多样性重评分(可选)                              │
│  DiversityRescoringFeatureHydrator                     │
│  基于嵌入的多样性调整(MMR算法)                           │
└─────────────────────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────────────────────┐
│  步骤5: 类别多样性重评分(可选)                          │
│  CategoryDiversityRescoringFeatureHydrator              │
│  基于类别的多样性调整                                     │
└─────────────────────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────────────────────┐
│  最终分数(ScoreFeature)                                 │
│  用于排序推文                                             │
└─────────────────────────────────────────────────────────┘

1.2 关键发现

多模型融合后的分数不是最终分数! 它还会经过:

  1. 启发式重评分:应用多个业务规则
  2. Phoenix 重评分:使用 Phoenix 模型调整
  3. 多样性重评分:确保内容多样性
  4. 类别多样性重评分:确保类别多样性

二、启发式重评分(HeuristicScorer)

2.1 概述

HeuristicScorer 是第一个修正步骤,通过乘法因子调整融合后的分数。

2.2 核心算法

scala 复制代码
val scaleFactor = rescorers.map(_(query, candidate)).product
val updatedScore = scoreOpt.map { score =>
  if (score < Epsilon && noNegHeuristic) score 
  else score * scaleFactor
}

公式

复制代码
finalScore = weightedModelScore × scaleFactor

其中:
scaleFactor = rescore1 × rescore2 × ... × rescoreN

2.3 重评分规则列表

系统应用以下 20+ 个重评分规则

2.3.1 基础重评分规则
规则名称 说明 影响
RescoreOutOfNetwork 外部网络内容调整 降低外部网络推文分数
RescoreReplies 回复内容调整 调整回复推文的分数
RescoreLiveContent 直播内容调整 提升直播内容的分数
2.3.2 归一化规则
规则名称 说明 影响
RescoreMTLNormalization 多任务学习归一化 使用 MTL 归一化器调整分数

MTL 归一化参数

  • AlphaParam: Alpha 参数(除以 100)
  • BetaParam: Beta 参数
  • GammaParam: Gamma 参数
2.3.3 列表式重评分规则(Listwise Rescoring)

这些规则基于整个候选列表进行调整:

规则名称 说明 影响
ContentExplorationListwiseRescoringProvider 内容探索列表式重评分 调整内容探索候选的分数
DeepRetrievalListwiseRescoringProvider 深度检索列表式重评分 调整深度检索候选的分数
EvergreenDeepRetrievalListwiseRescoringProvider 常青深度检索列表式重评分 调整常青深度检索候选的分数
EvergreenDeepRetrievalCrossBorderListwiseRescoringProvider 跨境常青深度检索列表式重评分 调整跨境常青深度检索候选的分数
AuthorBasedListwiseRescoringProvider 基于作者列表式重评分 调整作者多样性
ImpressedAuthorDecayRescoringProvider 已读作者衰减列表式重评分 降低已印象作者的分数
ImpressedMediaClusterBasedListwiseRescoringProvider 已读媒体聚类列表式重评分 降低已印象媒体聚类的分数
ImpressedImageClusterBasedListwiseRescoringProvider 已读图片聚类列表式重评分 降低已印象图片聚类的分数
CandidateSourceDiversityListwiseRescoringProvider 候选源多样性列表式重评分 调整候选源多样性
GrokSlopScoreRescorer Grok Slop 分数重评分 基于 Grok Slop 分数调整
MultimodalEmbeddingRescorer 多模态嵌入重评分 基于多模态嵌入调整
2.3.4 其他重评分规则
规则名称 说明 影响
RescoreFeedbackFatigue 反馈疲劳重评分 降低频繁反馈内容的分数
ControlAiRescorer.allRescorers AI 控制重评分器 各种 AI 控制规则

2.4 重评分规则的工作原理

每个重评分规则返回一个乘法因子(通常是 0.5 到 2.0 之间):

scala 复制代码
trait RescoringFactorProvider {
  def apply(query: PipelineQuery, candidate: CandidateWithFeatures[TweetCandidate]): Double
}

示例

  • RescoreOutOfNetwork 可能返回 0.8(降低外部网络内容 20%)
  • RescoreLiveContent 可能返回 1.2(提升直播内容 20%)

2.5 完整代码

scala 复制代码
object HeuristicScorer extends Scorer[PipelineQuery, TweetCandidate] {
  override def apply(
    query: PipelineQuery,
    candidates: Seq[CandidateWithFeatures[TweetCandidate]]
  ): Stitch[Seq[FeatureMap]] = {
    val rescorers = Seq(
      RescoreOutOfNetwork,
      RescoreReplies,
      RescoreMTLNormalization(...),
      RescoreListwise(ContentExplorationListwiseRescoringProvider(...)),
      // ... 其他重评分规则
      RescoreLiveContent
    ) ++ ControlAiRescorer.allRescorers

    val updatedScores = candidates.map { candidate =>
      val scoreOpt = candidate.features.getOrElse(ScoreFeature, None)
      
      // 计算所有重评分规则的乘积
      val scaleFactor = rescorers.map(_(query, candidate)).product
      
      // 应用乘法因子
      val updatedScore = scoreOpt.map { score =>
        if (score < Epsilon && noNegHeuristic) score 
        else score * scaleFactor
      }
      
      FeatureMap(ScoreFeature, updatedScore)
    }
    
    Stitch.value(updatedScores)
  }
}

2.6 实际示例

假设一个推文的融合分数是 10.0,应用以下重评分规则:

规则 乘法因子
RescoreOutOfNetwork 0.8
RescoreReplies 1.1
RescoreMTLNormalization 0.95
RescoreListwise(AuthorBased) 1.05
RescoreFeedbackFatigue 0.9

计算

复制代码
scaleFactor = 0.8 × 1.1 × 0.95 × 1.05 × 0.9 = 0.79
finalScore = 10.0 × 0.79 = 7.9

三、Phoenix 重评分(PhoenixRescoringFeatureHydrator)

3.1 概述

PhoenixRescoringFeatureHydrator 使用 Phoenix 模型的分数来调整最终分数。

3.2 使用条件

scala 复制代码
val usePhoenixRescoring =
  query.params(EnablePhoenixScorerParam) &&
  query.params(EnablePhoenixRescoreParam) &&
  !query.params(EnablePhoenixScoreParam)

条件

  • 启用 Phoenix Scorer
  • 启用 Phoenix 重评分
  • 启用 Phoenix 分数(如果已启用,则直接使用 Phoenix 分数)

3.3 重评分算法

scala 复制代码
val score = candidate.features.getOrElse(ScoreFeature, None).getOrElse(0.0)
val weightedModelScore = candidate.features.getOrElse(WeightedModelScoreFeature, None).getOrElse(0.0)
val phoenixScore = candidate.features.getOrElse(PhoenixScoreFeature, None).getOrElse(0.0)

if (score == 0.0 || weightedModelScore == 0.0) {
  0.0
} else if (usePhoenixRescoring) {
  phoenixScore * (score / weightedModelScore)
} else {
  score
}

公式

复制代码
finalScore = phoenixScore × (currentScore / weightedModelScore)

其中:
- currentScore: 当前分数(经过启发式重评分后)
- weightedModelScore: 原始融合分数
- phoenixScore: Phoenix 模型预测分数

3.4 工作原理

Phoenix 重评分通过比例缩放的方式调整分数:

  1. 计算当前分数相对于原始融合分数的比例
  2. 使用 Phoenix 分数乘以这个比例

示例

  • 原始融合分数:10.0
  • 启发式重评分后:8.0(比例 = 0.8)
  • Phoenix 分数:12.0
  • 最终分数:12.0 × 0.8 = 9.6

3.5 使用场景

  • Phoenix 模型更准确:当 Phoenix 模型的预测更准确时
  • A/B 测试:用于测试 Phoenix 模型的效果
  • 渐进式迁移:从旧模型逐步迁移到 Phoenix 模型

四、多样性重评分(DiversityRescoringFeatureHydrator)

4.1 概述

DiversityRescoringFeatureHydrator 使用 MMR(Maximal Marginal Relevance)算法来确保内容的多样性。

4.2 MMR 算法

MMR 算法在相关性和多样性之间取得平衡:

复制代码
score = relevance + diversityWeight × minDistance

其中:
- relevance: 当前候选的相关性分数(ScoreFeature)
- diversityWeight: 多样性权重
- minDistance: 与已选择候选的最小距离

4.3 算法实现

scala 复制代码
def mmr(
  query: PipelineQuery,
  candidates: Seq[CandidateWithFeatures[TweetCandidate]],
  distanceMatrix: DenseMatrix[Double]
): Seq[Double] = {
  val diversityRatio = query.params(TwhinDiversityRescoringRatioParam)
  val diversityWeight = query.params(TwhinDiversityRescoringWeightParam)
  val selected = scala.collection.mutable.Set[Int]()
  val newScores = Array.fill(n)(0.0)

  for (i <- 0 until n) {
    var maxScore = Double.NegativeInfinity
    var bestCandidateIndex = -1

    for ((candidate, index) <- candidatesWithIndex) {
      if (!selected.contains(index)) {
        val relevance = candidate.features.getOrElse(ScoreFeature, None).getOrElse(0.0)
        val minDistance = {
          if (selected.isEmpty || selected.size < (1 - diversityRatio) * n) 
            None
          else
            selected.map(j => distanceMatrix(index, j)).reduceOption(_ min _)
        }
        val score = relevance + diversityWeight * minDistance.getOrElse(2.0)
        if (score > maxScore) {
          maxScore = score
          bestCandidateIndex = index
        }
      }
    }

    if (bestCandidateIndex != -1) {
      selected += bestCandidateIndex
      newScores(bestCandidateIndex) = maxScore
    }
  }

  newScores.toSeq
}

4.4 距离计算

使用嵌入向量的欧氏距离

scala 复制代码
// 1. 获取嵌入向量
val embeddings = candidates.map { candidate =>
  candidate.features
    .getOrElse(TransformerPostEmbeddingJointBlueFeature, EmptyDataRecord)
    .getTensors
    .get(PostTransformerEmbeddingsJointBlueFeature.getFeatureId)
    .getFloatTensor.floats
    .map(_.doubleValue)
    .toSeq
}

// 2. 归一化嵌入向量
val denseEmbeddingsNormalized = embeddings.map { seq =>
  val denseVector = DenseVector(seq.toArray)
  val normVal = norm(denseVector)
  if (normVal != 0) denseVector / normVal
  else defaultDenseVector
}

// 3. 计算距离矩阵
for (i <- denseEmbeddingsNormalized.indices; j <- denseEmbeddingsNormalized.indices) {
  distanceMatrix(i, j) = norm(denseEmbeddingsNormalized(i) - denseEmbeddingsNormalized(j))
}

4.5 参数配置

参数名称 说明 默认值
TwhinDiversityRescoringWeightParam 多样性权重 可配置
TwhinDiversityRescoringRatioParam 多样性比例 可配置

4.6 工作流程

  1. 计算嵌入向量:获取每个候选的嵌入向量
  2. 归一化嵌入:归一化嵌入向量
  3. 计算距离矩阵:计算所有候选之间的欧氏距离
  4. MMR 选择:使用 MMR 算法重新计算分数
  5. 更新分数 :更新 ScoreFeature

4.7 实际示例

假设有 3 个候选推文:

候选 原始分数 嵌入向量
A 10.0 [0.8, 0.6, ...]
B 9.0 [0.7, 0.5, ...]
C 8.0 [0.9, 0.7, ...]

距离矩阵

复制代码
    A    B    C
A  0.0  0.2  0.3
B  0.2  0.0  0.4
C  0.3  0.4  0.0

MMR 选择(假设 diversityWeight = 0.5):

  1. 选择 A(分数最高)
  2. 选择 C(虽然 B 分数更高,但 C 与 A 距离更远)
  3. 选择 B

最终分数

  • A: 10.0 + 0.5 × 0.0 = 10.0
  • C: 8.0 + 0.5 × 0.3 = 8.15
  • B: 9.0 + 0.5 × 0.2 = 9.1

五、类别多样性重评分(CategoryDiversityRescoringFeatureHydrator)

5.1 概述

CategoryDiversityRescoringFeatureHydrator 基于内容类别来确保多样性。

5.2 算法原理

使用惩罚机制来降低重复类别的分数:

复制代码
score = max(relevance - weight × penalty, 0.00001)

其中:
penalty = Σ log2(count(category) + 1)

- relevance: 当前候选的相关性分数
- weight: 多样性权重
- count(category): 已选择候选中该类别的数量

5.3 算法实现

scala 复制代码
private def diversityPenalty(
  query: PipelineQuery,
  candidates: Seq[CandidateWithFeatures[TweetCandidate]],
): Seq[Double] = {
  val weight = query.params(CategoryDiversityRescoringWeightParam)
  val k = query.params(CategoryDiversityKParam)
  val selected = scala.collection.mutable.Set[Int]()
  val newScores = Array.fill(n)(0.0)
  val clusterCntMap = scala.collection.mutable.Map[String, Int]()

  val topKCategories = getCandidateCategory(k, candidates)

  for (i <- 0 until n) {
    var maxScore = Double.NegativeInfinity
    var bestCandidateIndex = -1

    for ((candidate, index, categories) <- candidatesWithIndexWithCategories) {
      if (!selected.contains(index)) {
        val relevance = candidate.features.getOrElse(ScoreFeature, None).getOrElse(0.0)
        
        // 计算惩罚
        var penalty = 0.0
        categories.foreach { category =>
          val cnt = clusterCntMap.getOrElse(category, 0)
          penalty += math.log(cnt + 1) / math.log(2)  // log2
        }

        val score = math.max(relevance - weight * penalty, 0.00001)

        if (score > maxScore) {
          maxScore = score
          bestCandidateIndex = index
          candidateCategories = categories
        }
      }
    }

    if (bestCandidateIndex != -1) {
      selected += bestCandidateIndex
      newScores(bestCandidateIndex) = maxScore
      // 更新类别计数
      candidateCategories.foreach { category =>
        clusterCntMap.put(category, clusterCntMap.getOrElse(category, 0) + 1)
      }
    }
  }

  newScores.toSeq
}

5.4 类别提取

从 SimClusters 特征中提取 Top-K 类别:

scala 复制代码
private def getCandidateCategory(
  k: Int,
  candidates: Seq[CandidateWithFeatures[TweetCandidate]]
): Seq[Seq[String]] = {
  candidates.map { candidate =>
    val topKClustersMap = candidate.features
      .getOrElse(SimClustersLogFavBasedTweetFeature, new DataRecord())
      .getSparseContinuousFeatures
      .get(SimclustersSparseTweetEmbeddingsFeature.getFeatureId)
    
    topKClustersMap
      .asScala
      .toSeq
      .sortBy(-_._2)  // 按分数降序
      .take(k)
      .map(_._1)  // 提取类别名称
  }
}

5.5 参数配置

参数名称 说明 默认值
CategoryDiversityRescoringWeightParam 多样性权重 可配置
CategoryDiversityKParam Top-K 类别数 可配置

5.6 实际示例

假设有 3 个候选推文:

候选 原始分数 类别
A 10.0 [sports, tech]
B 9.0 [sports, news]
C 8.0 [tech, science]

选择过程(假设 weight = 0.5):

  1. 选择 A(分数最高)

    • 分数:10.0 - 0.5 × (log2(1) + log2(1)) = 10.0 - 0.5 × (0 + 0) = 10.0
    • 更新计数:sports=1, tech=1
  2. 选择 C(虽然 B 分数更高,但类别更不同)

    • B 惩罚:0.5 × (log2(2) + log2(1)) = 0.5 × (1 + 0) = 0.5,分数 = 9.0 - 0.5 = 8.5
    • C 惩罚:0.5 × (log2(2) + log2(1)) = 0.5 × (1 + 0) = 0.5,分数 = 8.0 - 0.5 = 7.5
    • 选择 B(分数更高)
    • 更新计数:sports=2, news=1
  3. 选择 C

    • 惩罚:0.5 × (log2(3) + log2(2)) = 0.5 × (1.58 + 1) = 1.29,分数 = 8.0 - 1.29 = 6.71

六、分数修正流程总结

6.1 完整流程

复制代码
原始融合分数 (WeightedModelScoreFeature)
    ↓
启发式重评分 (HeuristicScorer)
    ↓ ScoreFeature = WeightedModelScore × scaleFactor
Phoenix 重评分 (可选)
    ↓ ScoreFeature = PhoenixScore × (ScoreFeature / WeightedModelScore)
多样性重评分 (可选)
    ↓ ScoreFeature = MMR算法重新计算
类别多样性重评分 (可选)
    ↓ ScoreFeature = ScoreFeature - penalty
最终分数 (ScoreFeature)

6.2 分数修正的影响

修正步骤 影响范围 主要目的
启发式重评分 所有候选 应用业务规则
Phoenix 重评分 所有候选 使用更准确的模型
多样性重评分 所有候选 确保内容多样性(嵌入)
类别多样性重评分 所有候选 确保类别多样性

6.3 修正顺序的重要性

修正顺序很重要,因为:

  1. 启发式重评分:应用基础业务规则
  2. Phoenix 重评分:基于启发式重评分后的分数
  3. 多样性重评分:基于前面的分数,使用 MMR 算法
  4. 类别多样性重评分:基于前面的分数,使用惩罚机制

6.4 实际示例

假设一个推文的完整修正过程:

步骤1:多模型融合

复制代码
WeightedModelScoreFeature = 10.0

步骤2:启发式重评分

复制代码
scaleFactor = 0.8 × 1.1 × 0.95 = 0.836
ScoreFeature = 10.0 × 0.836 = 8.36

步骤3:Phoenix 重评分(假设启用)

复制代码
PhoenixScore = 12.0
ScoreFeature = 12.0 × (8.36 / 10.0) = 12.0 × 0.836 = 10.032

步骤4:多样性重评分(假设启用)

复制代码
使用 MMR 算法重新计算
ScoreFeature = 9.5(假设)

步骤5:类别多样性重评分(假设启用)

复制代码
penalty = 0.3
ScoreFeature = 9.5 - 0.3 = 9.2

最终分数9.2


七、关键代码位置

7.1 启发式重评分

  • 文件home-mixer/server/src/main/scala/com/twitter/home_mixer/product/scored_tweets/scorer/HeuristicScorer.scala
  • 关键方法apply

7.2 Phoenix 重评分

  • 文件home-mixer/server/src/main/scala/com/twitter/home_mixer/functional_component/feature_hydrator/PhoenixRescoringFeatureHydrator.scala
  • 关键方法apply

7.3 多样性重评分

  • 文件home-mixer/server/src/main/scala/com/twitter/home_mixer/functional_component/feature_hydrator/DiversityRescoringFeatureHydrator.scala
  • 关键方法mmr

7.4 类别多样性重评分

  • 文件home-mixer/server/src/main/scala/com/twitter/home_mixer/functional_component/feature_hydrator/CategoryDiversityRescoringFeatureHydrator.scala
  • 关键方法diversityPenalty

八、总结

8.1 核心要点

  1. 多模型融合后的分数不是最终分数
  2. 启发式重评分:应用 20+ 个业务规则
  3. Phoenix 重评分:使用 Phoenix 模型调整
  4. 多样性重评分:使用 MMR 算法确保多样性
  5. 类别多样性重评分:使用惩罚机制确保类别多样性

8.2 修正公式总结

复制代码
最终分数 = 融合分数 
         × 启发式因子1 × 启发式因子2 × ... × 启发式因子N
         × (PhoenixScore / WeightedModelScore)  [可选]
         × MMR调整  [可选]
         - 类别惩罚  [可选]

8.3 设计理念

  1. 模块化:每个修正步骤独立,易于调整
  2. 可配置:通过 Feature Switches 控制是否启用
  3. 灵活性:支持 A/B 测试和渐进式迁移
  4. 多样性:确保推荐结果的多样性
相关推荐
LucDelton34 分钟前
模型微调思路
人工智能·深度学习·机器学习
Fleshy数模1 小时前
从一条直线开始:线性回归的底层逻辑与实战
人工智能·机器学习·概率论
哥布林学者2 小时前
吴恩达深度学习课程五:自然语言处理 第三周:序列模型与注意力机制 课后习题与代码实践
深度学习·ai
AAD555888992 小时前
压接工具检测识别----RPN-R50-Caffe-C4模型训练与优化
人工智能·深度学习
流㶡2 小时前
逻辑回归实战:从原理到不平衡数据优化(含欠拟合/过拟合诊断与召回率提升)
算法·机器学习·逻辑回归
OLOLOadsd1232 小时前
基于NAS-FCOS的拥挤路段车辆检测系统:R50-Caffe-FPN-NASHead-GN-Head模型训练与优化_1
人工智能·深度学习
lrh1228003 小时前
详解决策树算法:分类任务核心原理、形成流程与剪枝优化
算法·决策树·机器学习
Network_Engineer3 小时前
从零手写RNN&BiRNN:从原理到双向实现
人工智能·rnn·深度学习·神经网络
机器学习之心3 小时前
Bayes-TCN+SHAP分析贝叶斯优化深度学习多变量分类预测可解释性分析!Matlab完整代码
深度学习·matlab·分类·贝叶斯优化深度学习
WGS.3 小时前
fastenhancer DPRNN torch 实现
pytorch·深度学习