推荐系统 · 多路召回从 0 到 1
日期: 2026-07-16 | 作者: sunxin(+ Cursor AI 结对讲解) 关联路线图:
推荐系统建设路线图.md §六 Phase 4关联产品需求:推荐系统-使用场景清单.md §二 场景 A预计触发: ADR-017「首页 feed 多路召回架构」
TL;DR
- 多路召回(Multi-Way Recall)= 从不同信号源并行产生多个候选池,然后合并去重排序 —— 是从"简单 order by"演进到"个性化推荐"的第一个真正的分水岭。
- 业界通用是 4 路:关注召回 + 兴趣召回 + 地理召回 + 热门召回。每一路解决用户的一个不同需求("我关心的人" / "我关心的话题" / "我周围发生的" / "大家都在看的")。
- 每路独立可关可换:某一路失败不影响其他路(fail-open);某一路数据不好可以单独优化;某一路可以独立开关做 A/B 测试。
- 对标产品:小红书发现页 = 4 路召回 + 精排 + 打散;B站首页 = 4 路 + 协同过滤;抖音 = 4 路 + 深度模型。你的 P4 只做 4 路 + 规则排序 + 打散,就已经比 90% 中小 App 强。
- 最容易踩的坑:把 4 路揉在一个 SQL 里写
UNION ALL。这是 SQL 主义者的直觉但是错的 —— 每路的候选数、时间衰减、缓存策略都不同,硬揉会失去所有优化空间。
目录
- 1. 场景:为什么"多路召回"是必经之路
- 2. 从单路 SQL 演进到多路召回
- 3. 四路召回详解
- 4. 合并 + 去重 + 排序
- 5. 打散(重排层)
- 6. 对标:小红书 / B站 / 抖音怎么做
- 7. 项目里的具体落地(P4 未来架构)
- 8. 教学收获
- 9. 附录
1. 场景:为什么"多路召回"是必经之路
1.1 从一个具体反面案例开始
假设你想让首页 feed 变个性化,最朴素的第一版:
SELECT * FROM posts
WHERE (
-- 用户关注的作者的帖
author_id IN (SELECT following_id FROM user_follows WHERE follower_id = :userId)
OR
-- 用户过去 like 过的类目
category IN (SELECT DISTINCT target_category FROM user_action_log
WHERE user_id = :userId AND action_type = 'LIKE')
OR
-- 附近城市的帖子
city = :userCity
)
ORDER BY create_time DESC
LIMIT 20这个 SQL 上线一周后会遇到:
- 性能崩了:
OR+ 多个子查询 → 走全表扫、执行计划抖动、300ms+ 响应 - 相关性差:用户 A 关注 500 人 + 有 3 个爱好 category + 在北京 → 结果里北京城的旅游帖占了 80%(因为 city 匹配的候选池最大)
- 无法调优:想让"关注的作者"权重高一点、"附近的帖"权重低一点 —— 一个 SQL 里改不动
- 无法降级:如果
user_action_log挂了,整个查询报错,用户看到白屏
1.2 多路召回的解法
核心思想:不同信号源产生独立的候选池,最后再合并。
┌─────────────────────────────────────┐
│ 用户 A 打开首页 │
└─────────────────────────────────────┘
↓ 并行触发 4 路
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│关注召回│ │兴趣召回│ │地理召回│ │热门召回│
│top 20 │ │top 30 │ │top 20 │ │top 30 │
└────────┘ └────────┘ └────────┘ └────────┘
│ │ │ │
└─────┬─────┴──────────┴──────────┘
↓ 合并 + 去重(保留最高分)
┌──────────────┐
│候选池 100 条 │
└──────────────┘
↓ 排序(加权公式)
┌──────────────┐
│排序后 100 条 │
└──────────────┘
↓ 打散重排
┌──────────────┐
│最终 20 条 │
└──────────────┘
↓ 返回给用户优势对比:
| 维度 | 单路 SQL | 多路召回 |
|---|---|---|
| 性能 | 一个 SQL 300ms+ | 4 路各 50ms 并行,总耗时 60ms |
| 可调优性 | 改 SQL 权重要重上线 | 每路配置参数独立可改 |
| 可降级 | SQL 报错整个白屏 | 一路挂了其他 3 路还在,产品可用 |
| 可 A/B | 只能整段替换 | 单独关闭某一路做实验 |
| 相关性 | 靠 ORDER BY 硬排 | 靠"每路负责一个用户需求"天然平衡 |
2. 从单路 SQL 演进到多路召回
2.1 演进四阶段(每个阶段都可能是终点)
| 阶段 | 特征 | 项目对应 |
|---|---|---|
| 阶段 0 | SELECT * ORDER BY create_time DESC | 项目当前 PostQueryApplicationService.getRecommendedPosts |
| 阶段 1 | 加一个 category 参数 → 分类目 order by | 项目当前(category 参数已支持) |
| 阶段 2 | 引入"热度公式" → ORDER BY hot_score DESC | P3 完成后可到 |
| 阶段 3 | 多路召回:4 路独立候选 + 合并排序 | P4 目标 |
| 阶段 4 | 多路召回 + 粗排(LR/GBDT)+ 精排(DIN) | 触发式(DAU 1w+) |
关键判断:从阶段 2 到阶段 3 是质变,从阶段 3 到阶段 4 是量变。所以业界很多中小 App 永远停在阶段 3(够用了)。
2.2 为什么"合并候选池"不能用 UNION ALL
初学者会写:
(SELECT postId FROM ... WHERE follow_condition LIMIT 20)
UNION ALL
(SELECT postId FROM ... WHERE interest_condition LIMIT 30)
UNION ALL
(SELECT postId FROM ... WHERE geo_condition LIMIT 20)
UNION ALL
(SELECT postId FROM ... WHERE hot_condition LIMIT 30)
ORDER BY create_time DESC
LIMIT 20这样写会失去多路召回的所有优势:
- 4 个子查询在一个 SQL 里,一样是全表扫
- 无法给每路配不同的缓存策略(关注召回可缓存 5 分钟,热门召回缓存 30 秒)
- 无法给每路配不同的降级方案(关注召回挂了应该跳过而不是报错)
- 无法给每路配不同的权重(
UNION ALL之后所有候选权重都是 1)
正解:4 路各自是独立的服务方法,Java 层并行调用:
public List<PostId> recall(String userId) {
CompletableFuture<List<Candidate>> followRecall =
CompletableFuture.supplyAsync(() -> followRecallService.recall(userId, 20), recallExecutor);
CompletableFuture<List<Candidate>> interestRecall =
CompletableFuture.supplyAsync(() -> interestRecallService.recall(userId, 30), recallExecutor);
CompletableFuture<List<Candidate>> geoRecall =
CompletableFuture.supplyAsync(() -> geoRecallService.recall(userId, 20), recallExecutor);
CompletableFuture<List<Candidate>> hotRecall =
CompletableFuture.supplyAsync(() -> hotRecallService.recall(30), recallExecutor);
// 并行等待,任一路超时/失败不影响其他路
CompletableFuture.allOf(followRecall, interestRecall, geoRecall, hotRecall)
.orTimeout(200, TimeUnit.MILLISECONDS)
.join();
return merge(followRecall, interestRecall, geoRecall, hotRecall);
}3. 四路召回详解
3.1 关注召回(Follow Recall)
解决的用户需求:我关心的人发了什么
原理:
输入:userId
步骤:
1. 查用户的关注列表 followings = SELECT following_id FROM user_follows WHERE follower_id = :userId
2. 查这些人过去 7 天发的帖 = SELECT * FROM posts WHERE author_id IN (:followings) AND create_time > 7d AND status = 'PUBLISHED'
3. 按 create_time 倒序,取 top 20
输出:List<PostId>(每个带一个"关注召回分" = 100)缓存策略:
- 关注列表变化不频繁 → 缓存 5 分钟
- 关注的作者的最新帖变化中频 → 缓存 1 分钟
降级方案:
- 用户无关注 → 跳过这路(不报错,让其他路兜底)
user_follows表挂了 → 跳过
候选数量:20 条(关注的人再多,一次也不需要展示超过 20 条最新帖)
权重:最高(用户主动关注 = 最强信号)—— 建议 weight = 1.0
3.2 兴趣召回(Interest Recall)
解决的用户需求:我感兴趣的话题有什么新东西
原理:
输入:userId
步骤:
1. 查用户画像 = SELECT top_categories, top_topics FROM user_profile WHERE user_id = :userId
2. 拿 top 3 category(如 ["food", "sports", "travel"])
3. 拿 top 5 topic(如 ["露营", "美食探店", "徒步"...])
4. 查这些 category/topic 下的高热度帖 = SELECT * FROM posts WHERE (category IN :top3 OR topic IN :top5) ORDER BY hot_score DESC LIMIT 30
输出:List<PostId>(带一个"兴趣召回分" = 用户对该 category/topic 的画像分数)缓存策略:
- 画像变化慢(T+1 更新)→ 缓存 30 分钟
- 各 category/topic 的热榜变化中频 → 缓存 5 分钟
降级方案:
- 用户画像不足(
action_count_30d < 30)→ 跳过这路,走"冷启动 fallback" user_profile表挂了 → 跳过
候选数量:30 条(兴趣是最大众化的信号,应给足量)
权重:weight = 0.7(比关注低但比热门高)
3.3 地理召回(Geo Recall)
解决的用户需求:我周围发生了什么
原理:
输入:userId + userLocation(longitude, latitude, city)
步骤:
1. 判断 user 是否授权地理 → 未授权则跳过整路
2. 优先按 city 精确匹配 = SELECT * FROM posts WHERE city = :userCity AND create_time > 3d ORDER BY hot_score DESC LIMIT 20
3. 若 city 结果不足 20 条,扩到 5km / 10km / 全省范围
输出:List<PostId>(带"地理召回分" = 100 - distance × 10,越近分越高)缓存策略:
- 城市维度的热榜变化中频 → 缓存 3 分钟
- 精确经纬度的 KNN 查询不缓存(每人都不一样)
降级方案:
- 用户未授权地理 → 跳过
- LBS 服务挂了 → 降级到 city 字段模糊匹配
候选数量:20 条
权重:weight = 0.6(旅游搭子场景地理很重要,可以调高)
3.4 热门召回(Hot Recall)
解决的用户需求:大家都在看什么(兜底 + 破圈)
原理:
输入:无(全站维度)
步骤:
1. 定时任务每 5 分钟计算 = SELECT postId, hot_score FROM posts WHERE create_time > 3d ORDER BY hot_score DESC LIMIT 500
2. 存到 Redis ZSet = ZADD recommend:hot:posts <score> <postId>
3. 召回时直接 = ZREVRANGE recommend:hot:posts 0 29 WITHSCORES
输出:List<PostId>(带"热度分" = ZSet 里的 score)缓存策略:
- 全站热榜低频变化 → Redis ZSet 5 分钟刷新
- 单次召回 = O(log N) 查 ZSet,比 SQL 快 100 倍
降级方案:
- Redis 挂了 → fallback 到
SELECT ... ORDER BY like_count DESC LIMIT 30(不带时间衰减,简陋但可用) - 全站帖子 < 30 条(新项目冷启动)→ 补充"编辑精选池"
候选数量:30 条
权重:weight = 0.4(最低,主要用于"破圈"和兜底)
3.5 四路对比表
| 特征 | 关注 | 兴趣 | 地理 | 热门 |
|---|---|---|---|---|
| 候选数 | 20 | 30 | 20 | 30 |
| 权重 | 1.0 | 0.7 | 0.6 | 0.4 |
| 缓存 TTL | 1 分钟 | 30 分钟 | 3 分钟 | 5 分钟 |
| 数据源 | user_follows + posts | user_profile + posts | LBS + posts | Redis ZSet |
| 降级 | 跳过 | 跳过 | 跳过 | 降级到简陋 SQL |
| 典型耗时 | 30ms | 20ms | 40ms | 5ms |
4. 合并 + 去重 + 排序
4.1 合并(Merge)
4 路返回后合并到一个 Map<PostId, Candidate>:
public Map<PostId, Candidate> merge(List<List<Candidate>> allRecalls) {
Map<PostId, Candidate> merged = new HashMap<>();
for (List<Candidate> recall : allRecalls) {
for (Candidate c : recall) {
// 同一 postId 来自多路 → 保留分数最高的
merged.merge(c.getPostId(), c, (existing, incoming) ->
incoming.getScore() > existing.getScore() ? incoming : existing);
}
}
return merged;
}为什么保留最高分:一个帖子被多路命中 = 它对用户有多重相关性(既是关注的人发的,又是感兴趣的类目)→ 应该保留最强信号。
4.2 排序(Rank)
基础打分公式(P4 起步版本):
finalScore = recallScore × recallWeight // 召回路的权重
× (1 + categoryMatch × 0.5) // 类目匹配画像加分
× (1 + topicMatch × 0.3) // 话题匹配画像加分
× (1 + isFollowedAuthor × 1.0) // 关注作者加分
× timeDecay(post.createTime) // 时间衰减
× qualityScore(post) // 内容质量分quality score 的定义:
qualityScore = 1.0 // 默认
× (1 + like_count × 0.001) // 点赞加分(log 化避免爆款吃独食)
× (1 + comment_count × 0.005) // 评论加分(评论比点赞更强信号)
× (post.hasImage ? 1.2 : 1.0) // 有图加分
× (post.reportCount > 3 ? 0 : 1) // 被举报多的直接排除为什么各因子相乘而不是相加:
- 相加 = 各因子独立贡献 → 某一项爆表能压死其他项
- 相乘 = 各因子协同 → 都好才最好,一个 0 全归 0
- 业界惯例:
recallScore × pushWeight相加 vs 相乘的选择要看具体场景,起步阶段用相乘不容易出问题
4.3 去重(Dedup)
除了"多路合并去重",还要做:
- 已看过去重:查
user_action_log里近 7 天 VIEW 过的 postId,排除 - 已互动去重:
LIKE / FAVORITE过的降权而非排除(用户可能想再看看) - 已举报作者去重:查
user_report表,永久排除
5. 打散(重排层)
排完序后不能直接返回给用户 —— 排完序的 top 20 可能是:
[
帖子1(作者A,category=食物),
帖子2(作者A,category=食物),
帖子3(作者A,category=食物),
帖子4(作者B,category=食物),
帖子5(作者B,category=食物),
...
]用户看到 5 条同作者同类目 → 立刻怀疑"这 App 什么破推荐" → 打散必做。
5.1 打散规则(业界通用)
- 同作者最多连续 1 条(其他移到后面)
- 同 category 最多连续 3 条
- 同 topic 最多占 40%
- 广告帖(如果有)最多 6 条里 1 条
5.2 实现算法
MMR (Maximal Marginal Relevance) 算法(最简单可用版本):
public List<Candidate> diversify(List<Candidate> sorted, int topN) {
List<Candidate> result = new ArrayList<>();
Set<String> recentAuthors = new HashSet<>();
Map<String, Integer> categoryCount = new HashMap<>();
for (Candidate c : sorted) {
if (result.size() >= topN) break;
// 规则 1:同作者最多连续 1 条
if (recentAuthors.contains(c.getAuthorId())) continue;
// 规则 2:同 category 最多 3 条
if (categoryCount.getOrDefault(c.getCategory(), 0) >= 3) continue;
result.add(c);
recentAuthors.add(c.getAuthorId());
// recentAuthors 只维护窗口 3(滑动窗口)
if (recentAuthors.size() > 3) recentAuthors.clear();
categoryCount.merge(c.getCategory(), 1, Integer::sum);
}
return result;
}5.3 探索性内容注入
打散后再插入 2-3 条探索性内容(跟用户画像完全无关的爆款),防止信息茧房:
最终 top 20 结构建议:
├─ position 1-3: 强相关(关注 + 高兴趣分)
├─ position 4-15: 中相关(兴趣 + 地理混合)
├─ position 16-18: 探索性(画像 top 4-6 的类目,避免只推 top 3)
└─ position 19-20: 全站爆款(跟画像无关,破圈)6. 对标:小红书 / B站 / 抖音怎么做
6.1 小红书发现页(对标核心)
架构大致(据公开分享):
用户请求 → 特征查询(画像 + 上下文)→ 多路召回 → 粗排 → 精排 → 打散 → 返回
↓ ↓ ↓
~500 条 ~100 条 ~30 条召回路(据 2020 前公开资料):
- 关注召回
- 兴趣召回(category / topic 双路)
- 地理召回
- 热门召回
- 协同过滤召回("看了 A 的人也看了 B")
- 双塔 embedding 召回(用户 emb × 内容 emb 内积 top K)
关键决策:小红书 2013-2018 只做前 4 路 + 简单排序,用户量做到 1000w+ DAU;协同过滤和双塔是 2018 之后引入的。
给你的启发:不要一次做 6 路。P4 做 4 路已经够了。
6.2 B站首页推荐
特色:协同过滤召回("看了 A 的用户也看了 B")在 B站占比很高,因为 B站用户"追番/追 UP"的行为模式特别强,item-item 相似矩阵非常稠密。
给你的启发:协同过滤是 P4 之后的加法,不是必需。旅游搭子场景下"内容分类"和"地理"更重要,协同过滤优先级低。
6.3 抖音关注 tab vs 推荐 tab
关注 tab:只走"关注召回",按时间倒序 —— 纯粹的写扩散(每个粉丝的信箱表拉一下)
推荐 tab:4 路召回 + 粗排(LR)+ 精排(DIN)+ 冷启动池,是算法的重头戏
给你的启发:可以把首页做成 tab 切换:关注 | 推荐 | 附近,每个 tab 走独立的服务方法,产品和技术都更清晰。
7. 项目里的具体落地(P4 未来架构)
7.1 类目结构
tour-mate-platform-domain/src/main/java/com/alisunxin/api/domain/recommend/
├── model/
│ ├── aggregate/
│ │ └── RecommendationAggregate.java // 一次推荐请求的上下文
│ ├── entity/
│ │ └── CandidateEntity.java // 单个候选帖 + 分数
│ └── valobj/
│ ├── RecallSource.java // FOLLOW / INTEREST / GEO / HOT
│ └── RecommendContext.java // 请求上下文(userId + location + scene)
├── port/
│ ├── in/
│ │ └── IRecommendApplicationService.java // recommendForUser(userId, context)
│ └── out/
│ ├── IFollowRecallPort.java
│ ├── IInterestRecallPort.java
│ ├── IGeoRecallPort.java
│ └── IHotRecallPort.java
└── service/
├── RecommendApplicationService.java // 编排 4 路 + 合并 + 排序 + 打散
├── ranking/
│ ├── ScoringStrategy.java // 打分公式
│ └── DiversifyStrategy.java // MMR 打散
└── recall/
├── FollowRecallService.java
├── InterestRecallService.java
├── GeoRecallService.java
└── HotRecallService.java
tour-mate-platform-infrastructure/src/main/java/com/alisunxin/api/infrastructure/adapter/repository/
├── FollowRecallAdapter.java // 查 user_follows + posts
├── InterestRecallAdapter.java // 查 user_profile + posts
├── GeoRecallAdapter.java // LBS + posts
└── HotRecallAdapter.java // Redis ZSet7.2 编排逻辑(伪代码)
@Service
public class RecommendApplicationService implements IRecommendApplicationService {
private final IFollowRecallPort followRecall;
private final IInterestRecallPort interestRecall;
private final IGeoRecallPort geoRecall;
private final IHotRecallPort hotRecall;
private final ScoringStrategy scoring;
private final DiversifyStrategy diversify;
private final Executor recallExecutor; // 独立线程池
public PageData<PostView> recommendForUser(String userId, RecommendContext ctx) {
// 1. 并行 4 路召回(超时 200ms,任一路挂不阻塞)
CompletableFuture<List<Candidate>> f1 = CompletableFuture.supplyAsync(
() -> safeRecall(() -> followRecall.recall(userId, 20)), recallExecutor);
CompletableFuture<List<Candidate>> f2 = CompletableFuture.supplyAsync(
() -> safeRecall(() -> interestRecall.recall(userId, 30)), recallExecutor);
CompletableFuture<List<Candidate>> f3 = CompletableFuture.supplyAsync(
() -> safeRecall(() -> geoRecall.recall(userId, ctx.getLocation(), 20)), recallExecutor);
CompletableFuture<List<Candidate>> f4 = CompletableFuture.supplyAsync(
() -> safeRecall(() -> hotRecall.recall(30)), recallExecutor);
CompletableFuture.allOf(f1, f2, f3, f4)
.orTimeout(200, TimeUnit.MILLISECONDS).join();
// 2. 合并去重
Map<PostId, Candidate> merged = merge(f1.join(), f2.join(), f3.join(), f4.join());
// 3. 排序(打分)
List<Candidate> scored = merged.values().stream()
.map(c -> scoring.score(c, userId, ctx))
.sorted(Comparator.comparingDouble(Candidate::getFinalScore).reversed())
.collect(Collectors.toList());
// 4. 去重(已看过、已举报)
scored.removeIf(c -> hasSeenRecently(userId, c.getPostId()));
// 5. 打散
List<Candidate> diversified = diversify.apply(scored, ctx.getSize());
// 6. 兜底:< 20 条时补充热榜
if (diversified.size() < ctx.getSize()) {
diversified.addAll(hotRecall.fallback(ctx.getSize() - diversified.size()));
}
return toPageData(diversified);
}
private List<Candidate> safeRecall(Supplier<List<Candidate>> recall) {
try { return recall.get(); }
catch (Exception e) {
log.warn("单路召回失败,返回空", e);
return Collections.emptyList();
}
}
}7.3 与现有代码的融合点
| 现状 | P4 后 |
|---|---|
PostController.recommend 直调 PostQueryApplicationService.getRecommendedPosts | PostController.recommend 判断:如果 experimentGroup == 'A' 走新 RecommendApplicationService,否则走旧逻辑(灰度) |
IContentAggregateRepository.findPublished* 一个 SQL 搞定 | 保留(作为热门召回的 SQL fallback) |
无 user_profile 表 | P3 完成后 interestRecall 可用;否则该路自动跳过 |
| 无 Redis ZSet 热榜 | P4 中同时上;上不完则 hotRecall 走 SQL 兜底 |
7.4 逐步上线路径
Milestone 1:先把 4 路的 Port 接口 + 空实现 + 编排逻辑跑通(返回空也没关系) Milestone 2:HotRecallService 走 Redis ZSet 上线(收益最大且不依赖画像) Milestone 3:FollowRecallService 上线(不依赖画像,收益中) Milestone 4:等 P3 画像完成 → InterestRecallService 上线 Milestone 5:GeoRecallService 上线(依赖前端上报地理) Milestone 6:打分公式 + 打散 + 兜底完整
8. 教学收获
8.1 「多路召回」是内容推荐的第一分水岭
从「按时间倒序」到「多路召回 + 排序」是一次质变。质变的核心不是算法多复杂,而是"用不同信号源分别解决用户不同的需求"。
- 关注召回 = 满足"社交需求"
- 兴趣召回 = 满足"探索需求"
- 地理召回 = 满足"本地需求"
- 热门召回 = 满足"从众需求"
如果把 4 个需求硬揉在一个 SQL 里,任何算法都救不了。
8.2 「各路独立」的工程价值
多路召回的最大工程价值不是"更准",而是:
- 各路独立可关:某路数据源出问题不影响整体
- 各路独立可调:调 A 路权重不影响 B 路
- 各路独立可缓存:不同 TTL 匹配不同变化频率
- 各路独立可 A/B:只做单路实验不影响全局
这些工程价值加起来 = 系统的可运营性,远比多 1% 的 CTR 重要。
8.3 「打散」是新手最容易忽视的一步
打散不改变模型的准确度,但决定用户体感。如果你的排序算法把最好的 20 条选出来了,但都是同一作者 —— 用户会觉得算法糟糕。
教训:产品指标 ≠ 排序 top-k 的相关性。产品指标是"用户对整个列表的观感"。
8.4 「渐进式上线」的价值
从 4 路里最简单的热门召回先上线(连画像都不用,Redis ZSet 一个星期就能上),能立刻看到效果、验证链路是否通、Redis 是否稳定。然后再逐步加复杂的路。
教训:不要等 4 路都完美了才上线。永远优先出可用版本、观察数据、迭代。
9. 附录
9.1 术语对照
| 术语 | 中文 | 说明 |
|---|---|---|
| Recall | 召回 | 从大池子里粗筛出候选集(追求高 Recall 率) |
| Ranking | 排序 | 对候选集打分排序 |
| Coarse Ranking | 粗排 | 用轻模型(LR/GBDT)从上万候选压到几百 |
| Fine Ranking | 精排 | 用重模型(DIN/DeepFM)从几百精挑几十 |
| Reranking | 重排 | 打散 / 规则约束 / 广告插入 |
| MMR | Maximal Marginal Relevance | 平衡"相关性 vs 多样性"的经典算法 |
| CF | Collaborative Filtering | 协同过滤(user-user / item-item) |
| CTR | Click-Through Rate | 点击率 |
| Cold Start | 冷启动 | 新用户/新物品无历史时的推荐策略 |
9.2 延伸阅读
- 《推荐系统实践》项亮 —— 中文经典,概念友好
- 《深入浅出推荐系统》王喆 —— 现代深度学习推荐系统
- 小红书公开分享(InfoQ / QCon)—— 有搜"小红书 推荐系统"的架构演进
- 王喆知乎专栏「王喆的机器学习笔记」
9.3 项目内关联文档
- 产品需求:
推荐系统-使用场景清单.md §二 场景 A - 路线图:
推荐系统建设路线图.md §六 Phase 4 - 时间衰减公式:
./05-时间衰减怎么调参.md - 冷启动策略:
./04-冷启动fallback.md - 事件驱动增量(P5): 待起草
推荐系统-事件驱动增量刷新.md