Guess Where You Go: Generative Next Point-of-Interest Recommendation in Amap
AMAP, Alibaba Group
把 SID tokenizer 的训练目标从重建换成纯对比:融合文本、图像、空间、协同四路 POI 表征后做 Gumbel-softmax 软残差量化,量化结果与各模态做 NT-Xent,不再用 MSE 重建,目标是提高 POI 之间的可区分度。拿到 SID 后,在 Qwen2.5 上做 SID/Geohash/描述对齐与轨迹语料的继续预训练、SFT,再用曝光日志做 EAKTO(带正奖励锚定和条件负样本更新的 KTO),把 0.5B 模型部署在高德首页「猜你想去」,线上 P-CTR +5.83%。
核心思路
Gwhere 是高德在首页「猜你想去」卡片上落地的生成式 next-POI 推荐系统,核心改动有三处:
- SID tokenizer 不用重建损失,只用对比损失:作者认为 RQ-VAE 的 MSE 重建目标和生成式检索要的「item 之间可区分」不一致,长尾 POI 容易码坍缩。于是先把文本、图像、空间、协同四路 POI 表征做注意力融合,再做 Gumbel-softmax 软残差量化,最后让量化向量和每个模态的原始表征做 NT-Xent 对比。
- 用两类领域语料继续预训练 LLM:SID-Geohash-描述三元组解决 SID 和 Geohash 不在词表里的问题;签到轨迹叙述语料让模型学通勤、周末出游这类时空规律。
- EAKTO 偏好对齐:KTO 只需要「好/不好」的二元信号,正好对应曝光日志里的点击和曝光未点。作者在 KTO 上加了正奖励锚定和条件负样本更新,处理推荐场景中的奖励漂移和噪声负样本。
上线的是 Qwen2.5-0.5B 版本,p99 约 30ms,一个月 A/B 对比生产级级联排序,P-CTR +5.83%、U-CTR +6.20%、负反馈率 -11.11%。
整体实现思路
- 离线训练:POI 多模态特征 → 对比式软 RQ 得到 3 层 SID → Qwen2.5 继续预训练(10B token)→ SFT(三级用户画像 + 最近 50 条签到 + 当前时空情境 → 目标 POI 的 SID)→ EAKTO(点击为正,曝光未点为负)。
- 在线服务:数据服务引擎把用户画像、最近签到、当前时间、位置、天气拼成指令 prompt,模型生成 SID,再查 SID→POI 表还原成可读地点返回客户端。
- 问题形式化:p(p_{u,m+1} | h_u, l_u, s_u) = ∏_{k=1}^{L} p(c^k | S_u, l_u, s_u, c^{<k}),S_u 是历史签到的 SID 序列,l_u 是当前位置,s_u 是情境(天气、节假日、画像)。
子模块实现(可复现细节)
1. 多模态特征与融合
| 模态 | 编码方式 |
|---|---|
| 文本 z_text | 预训练语言模型(如 BERT)编码标题、描述 |
| 图像 z_img | 预训练 ViT 编码 POI 图片(有图时) |
| 空间 z_spatial | GraphSAGE 编码空间图,边由 Haversine 距离构建(实现细节节说还结合 mobility flow) |
| 协同 z_cf | 用户-POI 交互矩阵上做 ALS |
每个模态先投影到共享 d 维空间,再用可学习注意力向量 q 加权:
- a_m = exp(q^T z_m) / Σ_{m’} exp(q^T z_{m’})
- z = Σ_m a_m z_m
2. 软残差量化 + 纯对比损失
- 初始化 L 个可学习码本 C_l = {e_l^k, k=1..K},r_0 = z。
- 每层 Gumbel-softmax 软分配:c_l^k ∝ exp((−‖r_{l−1} − e_l^k‖² + ε_l^k)/α),ε ~ Gumbel(0,1),α 退火。
- 残差更新:r_l = r_{l−1} − Σ_k c_l^k e_l^k;量化表示 ẑ = Σ_l Σ_k c_l^k e_l^k。
- 损失:L = −Σ_{m∈M} log [ exp(ĥ·h_m⁺/τ) / Σ_{h⁻∈B} exp(ĥ·h⁻/τ) ],ĥ = g(ẑ),h_m⁺ = g(z_m),g 是 2 层 MLP,τ = 0.1,负样本取 batch 内。没有重建项。
- 默认配置:96 维潜向量,3 层码本,每层几十个码字(公开集 48,AMAP「更大」,未给具体数)。SID 写成
<a_82><b_59><c_191>这种形式。
论文没有说明 SID 冲突(多个 POI 同 SID)如何消歧。
3. 时空继续预训练(CPT)
两类语料(均来自脱敏的高德轨迹和 POI 元数据):
| 语料 | 内容 | 例子 |
|---|---|---|
| SID-Location-Description | (SID, 6 位 Geohash, 文本描述) 三元组 | 「天坛公园是北京的景点,位于 |
| 时空行为序列 | 按时间排序的签到,带时间戳、动作类型、情境标注、画像提示 | 「6 月 10 日(工作日)早 7 点去了 <a_124><b_192><c_41>(公司);晚 6 点搜索了 …(餐厅);晚 9 点导航到 …(家)」 |
两个训练目标:
- Masked SID Prediction:在行为语料上根据周围签到、时间戳、情境预测被遮住的 SID。
- SID-Attribute Alignment:在对齐语料中随机打乱 SID,训练模型区分匹配和不匹配的 (SID ↔ Geohash ↔ 描述)。
CPT 最终用了 10B token。作者观察到效果随 token 量近线性增长、尚未饱和。
4. SFT
- Prompt:用户画像(静态画像、长期偏好、周期性需求)+ 最近签到的 SID 序列 + 当前位置/时间/情境(天气、交通),要求推荐下一个 POI。
- Response:用户实际点击或交互的 POI 的 SID,交叉熵训练。
- 超参:Qwen2.5 底座,lr 1e-5 常数,warm-up 20 步,200 张 H20,每卡 batch 16,梯度累积 8,序列长度 4096,输入取最近 50 条签到。
5. EAKTO(Exposure-Aware KTO)
KTO 在推荐场景里的两个问题:
- 奖励漂移:训练久了正负样本的奖励都变成负数,对点击样本的偏好被削弱。
- 噪声负样本:未点击常常是没注意、加载慢等临时原因,并不代表不喜欢,模型会变得过于保守。
损失形式:
- r_θ(x,y) = log π_θ(y|x) / π_ref(y|x),z_0 = KL(π_θ(y’|x) ‖ π_ref(y’|x))
- L_EAKTO = E_{x,y}[ λ_y − v(x,y) + α · 1[y∈Y_D] · σ_D(x,y) ]
- 正样本(点击):v = λ_D · σ(β(r_θ − z_0))
- 负样本(曝光未点):v = λ_U · 1[r_θ > 0] · σ(β(z_0 − r_θ)),只有模型错误地给负样本正奖励时才更新(条件负样本更新)
- σ_D(x,y) = max(0, log π_ref(y|x)/π_θ(y|x)),即点击样本奖励跌到 0 以下时加罚(正奖励锚定;分子分母方向按 PDF 排版还原,推断)
- 超参:λ_D = λ_U = 1.0,β = 0.1,α = 0.3
6. 在线服务
- continuous batching + PagedAttention 管 KV-Cache。
- 分级监控:P0 看 token 吞吐和端到端延迟,P1 看 KV-Cache 命中率和显存碎片。
- 异步 handler + 心跳检测处理长尾请求,避免高峰期 worker 超时。
- 结论节提到 PD 分离和 multi-token prediction,正文没有展开。
实验设置与结果
数据集
| 数据集 | 用户 | POI | 交互 | 平均序列长 |
|---|---|---|---|---|
| NYC | 1,083 | 5,135 | 104,074 | 136 |
| TKY | 2,293 | 7,873 | 361,430 | 195 |
| CA | 6,592 | 14,027 | 349,375 | 53 |
| AMAP | 10,000k | 83,100k | 380,000k | 38 |
预处理沿用 GNPR-SID:去掉交互少于 10 的 POI 和用户,按时间 80/10/10 切分,每个用户最后一次访问作为目标,指标为 Acc@1。
公开数据集(Acc@1,基线数字引自 [13,26])
| 模型 | POI/User 表示 | NYC | TKY | CA |
|---|---|---|---|---|
| STHGCN | RID/UID | 0.2734 | 0.2950 | 0.1730 |
| ROTAN | RID/UID | 0.3106 | 0.2458 | 0.2199 |
| LLM4POI | RID/UID | 0.3372 | 0.3035 | 0.2065 |
| GNPR-SID(LLaMA-7B) | SID/UID | 0.3618 | 0.3062 | 0.2403 |
| Gwhere-0.5B | SID/UP | 0.3705 | 0.3125 | 0.2555 |
| Gwhere-1.5B | SID/UP | 0.3727 | 0.3170 | 0.2585 |
| Gwhere-3B | SID/UP | 0.3872 | 0.3202 | 0.2611 |
| Gwhere-7B | SID/UP | 0.4027 | 0.3310 | 0.2721 |
Gwhere-7B 相对 GNPR-SID 提升 11.3% / 8.1% / 13.2%。注意 Gwhere 用的是用户画像(UP),基线用的是 one-hot UID。
AMAP 对比级联排序(Acc@1)
| Baseline 级联 | 0.5B | 1.5B | 3B | 7B |
|---|---|---|---|---|
| 0.2675 | 0.2820 | 0.2862 | 0.2923 | 0.3071(+14.8%) |
消融(Gwhere-3B,AMAP)与偏好对齐对比
| 消融 | Acc@1 |
|---|---|
| Gwhere(完整) | 0.2923 |
| w/o SID(换随机 ID) | 0.2455(−16.01%) |
| w/o RL | 0.2580(−11.73%) |
| w/o CPT | 0.2632(−9.96%) |
| w/o SFT | 0.2725(−6.77%) |
| w/o ST(去掉 prompt 中时空描述) | 0.2785(−4.72%) |
| w/o SLD(去掉 SID-位置-描述语料) | 0.2822(−3.46%) |
| 对齐方式 | Acc@1 |
|---|---|
| SFT-only | 0.2492 |
| SFT + DPO | 0.2634 |
| SFT + GRPO | 0.2759 |
| SFT + EAKTO | 0.2820 |
(对齐表的 0.2820 与 Gwhere-0.5B 一致,推断这张表用的是 0.5B。)
其他分析
- 模态:逐个叠加文本、图像、空间、协同,Acc@1 单调上升,空间特征单独增益最大(仅有图,没有数值)。
- 上下文长度:2048 token(约 30 条签到)之前增益明显,4096(约 50 条)之后基本饱和。
- 延迟:50 QPS、2×H20 压测,级联 p99 约 20ms,Gwhere-0.5B 约 30ms;7B 离线最好但成本过高,最终上线 0.5B。
线上 A/B(高德首页「猜你想去」,一个月,相对生产级联)
| 版本 | P-CTR | U-CTR | SC-Rate | RE-Rate | AC-Rate | NF-Rate |
|---|---|---|---|---|---|---|
| w/o EAKTO | +4.23% | +4.59% | +1.01% | +1.65% | +5.99% | −5.85% |
| Gwhere | +5.83% | +6.20% | +2.18% | +2.60% | +9.67% | −11.11% |
生产基线由多路召回、粗排、精排和认知重排组成,使用相同的请求上下文、流量分配、页面位置和指标口径。
思考与可参考价值
值得借鉴的:
- EAKTO 适合直接迁移到电商。曝光未点是最便宜的负信号,但噪声大。「只有当模型给负样本正奖励时才更新」的门控,加上「点击样本奖励不能掉到 0 以下」的锚定,实现成本很低,线上负反馈率的改善(−5.85% → −11.11%)说明它确实改变了排序行为。迁移时应单独消融两项改动,并加入原版 KTO 作对照,论文这两点都没做。
- SID 词表对齐语料。把 SID 和 Geohash、文本描述写成三元组,再加上打乱 SID 后的匹配判别任务,是一个很便宜的 CPT 设计。电商可以换成 SID + 类目/店铺/价格带 + 标题。
- 规模取舍。0.5B 比级联多约 10ms p99,已经拿到大部分线上收益,7B 的离线提升没能转化成可上线方案。这对电商首页这类强时延场景是一个参考点。
需要打折的:
- 对比式 tokenizer「优于重建式、缓解碰撞」是本文最核心的 SID 论点,但没有碰撞率、码本利用率、长尾分桶等直接证据,也没有和 RQ-VAE / RQ-KMeans 在同一底座下对比;唯一相关消融是 SID 对随机 ID。电商团队想换 tokenizer 时需要自己补这组对照。
- 8310 万 POI 只用 3 层码本,码字数未公开,必然存在大量 SID 冲突,论文没说如何消歧。
- 公开集基线数字是照抄的,Gwhere 多用了用户画像,底座模型也不同,11.3% 的提升不能全算在方法头上。
- 只报 Acc@1。生成式召回在线上通常要 beam 出多个候选,Top-K 指标缺失会影响判断。
- 去掉纯重建项之后,SID 是否还保留足够的语义层级(前缀相近即语义相近)也没有分析。如果下游依赖前缀做粗粒度召回或约束解码,这一点要先验证(推断)。