Skip to content

推荐系统 · 多路召回从 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. 场景:为什么"多路召回"是必经之路 ​

1.1 从一个具体反面案例开始 ​

假设你想让首页 feed 变个性化,最朴素的第一版:

sql
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 上线一周后会遇到:

  1. 性能崩了:OR + 多个子查询 → 走全表扫、执行计划抖动、300ms+ 响应
  2. 相关性差:用户 A 关注 500 人 + 有 3 个爱好 category + 在北京 → 结果里北京城的旅游帖占了 80%(因为 city 匹配的候选池最大)
  3. 无法调优:想让"关注的作者"权重高一点、"附近的帖"权重低一点 —— 一个 SQL 里改不动
  4. 无法降级:如果 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 演进四阶段(每个阶段都可能是终点) ​

阶段特征项目对应
阶段 0SELECT * ORDER BY create_time DESC项目当前 PostQueryApplicationService.getRecommendedPosts
阶段 1加一个 category 参数 → 分类目 order by项目当前(category 参数已支持)
阶段 2引入"热度公式" → ORDER BY hot_score DESCP3 完成后可到
阶段 3多路召回:4 路独立候选 + 合并排序P4 目标
阶段 4多路召回 + 粗排(LR/GBDT)+ 精排(DIN)触发式(DAU 1w+)

关键判断:从阶段 2 到阶段 3 是质变,从阶段 3 到阶段 4 是量变。所以业界很多中小 App 永远停在阶段 3(够用了)。

2.2 为什么"合并候选池"不能用 UNION ALL ​

初学者会写:

sql
(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 层并行调用:

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 四路对比表 ​

特征关注兴趣地理热门
候选数20302030
权重1.00.70.60.4
缓存 TTL1 分钟30 分钟3 分钟5 分钟
数据源user_follows + postsuser_profile + postsLBS + postsRedis ZSet
降级跳过跳过跳过降级到简陋 SQL
典型耗时30ms20ms40ms5ms

4. 合并 + 去重 + 排序 ​

4.1 合并(Merge) ​

4 路返回后合并到一个 Map<PostId, Candidate>:

java
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 起步版本):

java
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) 算法(最简单可用版本):

java
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 ZSet

7.2 编排逻辑(伪代码) ​

java
@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.getRecommendedPostsPostController.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重排打散 / 规则约束 / 广告插入
MMRMaximal Marginal Relevance平衡"相关性 vs 多样性"的经典算法
CFCollaborative Filtering协同过滤(user-user / item-item)
CTRClick-Through Rate点击率
Cold Start冷启动新用户/新物品无历史时的推荐策略

9.2 延伸阅读 ​

  • 《推荐系统实践》项亮 —— 中文经典,概念友好
  • 《深入浅出推荐系统》王喆 —— 现代深度学习推荐系统
  • 小红书公开分享(InfoQ / QCon)—— 有搜"小红书 推荐系统"的架构演进
  • 王喆知乎专栏「王喆的机器学习笔记」

9.3 项目内关联文档 ​

  • 产品需求: 推荐系统-使用场景清单.md §二 场景 A
  • 路线图: 推荐系统建设路线图.md §六 Phase 4
  • 时间衰减公式: ./05-时间衰减怎么调参.md
  • 冷启动策略: ./04-冷启动fallback.md
  • 事件驱动增量(P5): 待起草 推荐系统-事件驱动增量刷新.md

Powered by VitePress