NeuralNDCG — 可微分排序优化与Learning to Rank
Skill-NeuralNDCG-Learning-to-Rank · 05-推荐系统
causalexperimentforecastingoptimizationrecommendationpricing供应链与补货推荐与搜索定价与利润WF-A 智能补货WF-F 动态定价WF-G Listing内容优化
年化 ROI10万
业务视角
适用角色运营负责人 / 选品负责人 · 产品经理 · 广告优化师
适用平台Amazon · DTC 独立站 · 邮件/SMS 个性化
什么情况下用老客来了只买一件就走,相关产品没被推出去;Bundle 商品连带销售做不起来;站内推荐位点击率低
成功是什么样的老客连带购买率提升 20-35%,客单价提升,品类交叉销售做起来
业务痛点
1. 解决的问题
是排序评估指标与训练损失函数之间的不匹配。
2. 核心算法逻辑
Learning to Rank(LTR)的核心问题是排序评估指标与训练损失函数之间的不匹配。模型用交叉熵或MSE训练,但业务用 NDCG 评估——这就像用"练习册分数"预测"考试成绩",两者可能背道而驰。
3. 业务应用场景
背景:用户在 Amazon/Shopify 店铺搜索"baby bottle",系统返回 100+ 商品。当前按销量排序,导致新品/高利润品无法曝光。
LTR建模: 1. 特征工程(Pointwise输入): - 商品特征:价格、评分、评论数、退货率、库存深度 - 用户特征:浏览历史、购买历史、地域、设备 - 交互特征:CTR、加购率、转化率 2. Pairwise 训练数据构建: - 从点击日志提取偏好对:用户点击了商品A但没点击商品B -> A > B - 用 LambdaRank 损失训练初始模型 3. Listwise 精调(NeuralNDCG): - 用 NeuralNDCG 损失替代 LambdaRank - 直接优化 NDCG@10(前10个结果的排序质量) - 温度参数 tau=0.1,Sinkhorn 迭代30次 4. 评
预期效果:搜索转化率提升 10-15%,新品曝光量增加 30%。
4. 输入数据要求
请查看原始代码模板获取输入规格。
5. 输出结果
请查看原始代码模板获取输出规格。
6. 业务价值 / ROI
- 假设母婴出海平台日均搜索UV 10万,搜索转化率从 2.5% -> 2.9%(+16%)
- 客单价 ¥300,日均GMV增量 = 10万 x 0.4% x ¥300 = ¥12万/天
- 年GMV增量:约 ¥4380万
- REVISION 解决"用户搜什么"(意图识别)
- NeuralNDCG 解决"结果怎么排"(排序优化)
- 两者结合形成完整的智能搜索链路:意图理解 → 候选召回 → 精排优化 → 结果展示
7. 代码模板
代码块数量:4 · 路径:未检测到
"""
NeuralNDCG: Learning to Rank with Differentiable Sorting
基于可微分排序松弛的NDCG直接优化
论文: NeuralNDCG: Direct Optimisation of a Ranking Metric via
Differentiable Relaxation of Sorting
arXiv: 2102.07831
代码: https://github.com/allegro/allRank
"""
import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
from typing import List, Tuple, Optional
# ============ 1. NeuralSort: 可微分排序松弛 ============
def neural_sort(scores: torch.Tensor, tau: float = 1.0) -> torch.Tensor:
"""
NeuralSort: 用softmax生成近似排序分配矩阵
P_hat[b, i, j] = 第j个item排在第i位的概率
"""
batch_size, list_size = scores.shape
# 计算 pairwise 绝对差和
scores_j = scores.unsqueeze(1) # [B, 1, N]
scores_k = scores.unsqueeze(2) # [B, N, 1]
abs_diff = torch.abs(scores_j - scores_k) # [B, N, N]
abs_diff_sum = abs_diff.sum(dim=2, keepdim=True) # [B, N, 1]
# row_factor[i] = n + 1 - 2i
row_indices = torch.arange(1, list_size + 1, device=scores.device, dtype=scores.dtype)
row_factor = (list_size + 1 - 2 * row_indices) # [N]
# term1[b, i, j] = row_factor[i] * scores[b, j]
term1 = row_factor.view(1, -1, 1) * scores.unsqueeze(1) # [B, N, N]
sorted_scores = term1 - abs_diff_sum # [B, N, N]
P_hat = F.softmax(sorted_scores / tau, dim=2)
return P_hat
def sinkhorn_scaling(P: torch.Tensor, num_iter: int = 30,
epsilon: float = 1e-6) -> torch.Tensor:
"""Sinkhorn迭代归一化:使矩阵行列和均为1"""
for _ in range(num_iter):
P = P / (P.sum(dim=2, keepdim=True) + epsilon)
P = P / (P.sum(dim=1, keepdim=True) + epsilon)
return P
# ============ 2. NDCG相关函数 ============
def ndcg_at_k(scores: torch.Tensor, relevances: torch.Tensor,
k: Optional[int] = None) -> torch.Tensor:
"""计算标准NDCG@k(不可微,仅用于评估)"""
if k is None:
k = scores.shape[1]
8. 论文来源
- 2102.07831