📖 开场:书店导购员
想象你在书店 📚:
没有推荐(迷茫):
你:进入书店
↓
看到10万本书 😵
↓
不知道看什么 🤷
↓
逛了2小时,一本没买 ❌
结果:
- 选择困难 ❌
- 浪费时间 ❌
- 体验差 ❌
有推荐(精准):
导购员:
"看您之前买过《三体》,
推荐您看《流浪地球》,
同类型科幻小说!"
↓
你:哇,正是我想看的!✅
↓
立即下单 💰
结果:
- 精准推荐 ✅
- 节省时间 ✅
- 体验好 ✅
- 提高销量 ✅
这就是推荐系统:帮你找到想要的东西!
🤔 推荐算法
算法1:协同过滤(Collaborative Filtering)⭐⭐⭐
用户协同过滤(User-Based)
原理:
"和你相似的人喜欢什么,就推荐给你"
例子:
用户A喜欢:《三体》《流浪地球》
用户B喜欢:《三体》《流浪地球》《球状闪电》
↓
用户A和用户B相似度高
↓
推荐《球状闪电》给用户A ✅
相似度计算(余弦相似度):
用户A的评分向量:[5, 4, 0, 0, 3]
用户B的评分向量:[5, 5, 4, 0, 0]
↓
余弦相似度 = cos(A, B) = (A·B) / (|A| × |B|)
= (5×5 + 4×5 + 0×4) / (√50 × √66)
= 45 / 57.4
= 0.78
相似度0.78(很高)✅
物品协同过滤(Item-Based)⭐⭐⭐
原理:
"你喜欢A商品,推荐和A相似的B商品"
例子:
用户看了《三体》
↓
计算和《三体》相似的商品
↓
《流浪地球》相似度:0.85
《球状闪电》相似度:0.80
↓
推荐这两本书 ✅
优点:
- 物品数量稳定,相似度可以预计算 ✅
- 性能好 ✅
代码实现:
@Service
public class ItemBasedCF {
@Autowired
private UserBehaviorMapper userBehaviorMapper;
// 物品相似度矩阵(提前计算好)
private Map<Long, Map<Long, Double>> itemSimilarityMatrix = new ConcurrentHashMap<>();
/**
* ⭐ 推荐商品(物品协同过滤)
*/
public List<Long> recommend(Long userId, int topN) {
// 1. 获取用户历史行为(浏览/购买过的商品)
List<UserBehavior> behaviors = userBehaviorMapper.selectByUserId(userId);
// 2. 计算推荐分数
Map<Long, Double> recommendScores = new HashMap<>();
for (UserBehavior behavior : behaviors) {
Long itemId = behavior.getItemId();
// 获取和该商品相似的商品
Map<Long, Double> similarItems = itemSimilarityMatrix.get(itemId);
if (similarItems != null) {
for (Map.Entry<Long, Double> entry : similarItems.entrySet()) {
Long similarItemId = entry.getKey();
Double similarity = entry.getValue();
// 累加推荐分数
recommendScores.merge(similarItemId, similarity, Double::sum);
}
}
}
// 3. 排序,取TopN
return recommendScores.entrySet().stream()
.sorted(Map.Entry.<Long, Double>comparingByValue().reversed())
.limit(topN)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
/**
* ⭐ 计算物品相似度矩阵(离线计算)
*/
@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点
public void calculateItemSimilarity() {
// 1. 获取所有用户行为数据
List<UserBehavior> behaviors = userBehaviorMapper.selectAll();
// 2. 构建 用户-物品 评分矩阵
Map<Long, Map<Long, Integer>> userItemMatrix = new HashMap<>();
for (UserBehavior behavior : behaviors) {
userItemMatrix.computeIfAbsent(behavior.getUserId(), k -> new HashMap<>())
.put(behavior.getItemId(), 1); // 1表示有交互
}
// 3. 计算物品相似度
List<Long> itemIds = userBehaviorMapper.selectAllItemIds();
for (Long itemId1 : itemIds) {
Map<Long, Double> similarities = new HashMap<>();
for (Long itemId2 : itemIds) {
if (itemId1.equals(itemId2)) {
continue;
}
// 计算itemId1和itemId2的相似度
double similarity = calculateSimilarity(itemId1, itemId2, userItemMatrix);
if (similarity > 0) {
similarities.put(itemId2, similarity);
}
}
// 保存相似度
itemSimilarityMatrix.put(itemId1, similarities);
}
System.out.println("⭐ 物品相似度矩阵计算完成");
}
/**
* 计算两个物品的相似度(余弦相似度)
*/
private double calculateSimilarity(Long itemId1, Long itemId2,
Map<Long, Map<Long, Integer>> userItemMatrix) {
Set<Long> users1 = new HashSet<>();
Set<Long> users2 = new HashSet<>();
// 找出交互过itemId1和itemId2的用户
for (Map.Entry<Long, Map<Long, Integer>> entry : userItemMatrix.entrySet()) {
Map<Long, Integer> items = entry.getValue();
if (items.containsKey(itemId1)) {
users1.add(entry.getKey());
}
if (items.containsKey(itemId2)) {
users2.add(entry.getKey());
}
}
// 计算交集(同时交互过两个物品的用户数)
Set<Long> intersection = new HashSet<>(users1);
intersection.retainAll(users2);
if (intersection.isEmpty()) {
return 0;
}
// 余弦相似度
return (double) intersection.size() /
Math.sqrt(users1.size() * users2.size());
}
}
算法2:内容推荐(Content-Based)📝
原理:
"根据物品的内容特征推荐"
例子:
用户喜欢:《三体》
特征:科幻、刘慈欣、获奖作品
↓
推荐具有相似特征的商品:
《流浪地球》:科幻、刘慈欣 ✅
《球状闪电》:科幻、刘慈欣 ✅
代码实现:
@Service
public class ContentBasedRecommender {
/**
* ⭐ 基于内容推荐
*/
public List<Long> recommend(Long userId, int topN) {
// 1. 获取用户历史喜欢的商品
List<Long> likedItemIds = userBehaviorMapper.selectLikedItems(userId);
// 2. 提取这些商品的特征标签
Set<String> userInterestTags = new HashSet<>();
for (Long itemId : likedItemIds) {
Item item = itemMapper.selectById(itemId);
userInterestTags.addAll(item.getTags());
}
// 3. 查找具有相似标签的商品
List<Item> candidateItems = itemMapper.selectAll();
Map<Long, Double> scores = new HashMap<>();
for (Item item : candidateItems) {
// 计算标签匹配度
double score = calculateTagSimilarity(userInterestTags, item.getTags());
scores.put(item.getId(), score);
}
// 4. 排序,取TopN
return scores.entrySet().stream()
.sorted(Map.Entry.<Long, Double>comparingByValue().reversed())
.limit(topN)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
/**
* 计算标签相似度
*/
private double calculateTagSimilarity(Set<String> tags1, Set<String> tags2) {
Set<String> intersection = new HashSet<>(tags1);
intersection.retainAll(tags2);
Set<String> union = new HashSet<>(tags1);
union.addAll(tags2);
return union.isEmpty() ? 0 : (double) intersection.size() / union.size();
}
}
算法3:混合推荐(Hybrid)⭐⭐⭐
混合策略:
1. 协同过滤 权重0.5
2. 内容推荐 权重0.3
3. 热门推荐 权重0.2
↓
加权求和,综合排序 ✅
优点:
- 结合多种算法优点 ✅
- 推荐更准确 ✅
🎯 系统架构
推荐系统架构
┌────────────────────────────────────┐
│ 客户端(App/Web) │
└──────────────┬─────────────────────┘
│
↓
┌────────────────────────────────────┐
│ 推荐服务(实时) │
│ - 召回层 │
│ - 排序层 │
│ - 重排层 │
└──────────────┬─────────────────────┘
│
┌───────┼───────┐
↓ ↓ ↓
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 协同过滤 │ │ 内容推荐 │ │ 热门推荐 │
└──────────┘ └──────────┘ └──────────┘
↓ ↓ ↓
┌────────────────────────────────────┐
│ 离线计算(Spark/Flink) │
│ - 相似度矩阵 │
│ - 用户画像 │
│ - 物品特征 │
└────────────────────────────────────┘
召回层(Recall)
作用:
从百万商品中,快速召回1000个候选商品
召回策略:
1. 协同过滤召回:300个
2. 内容推荐召回:300个
3. 热门推荐召回:200个
4. 兴趣标签召回:200个
↓
合并去重 → 1000个候选商品 ✅
排序层(Ranking)
作用:
对1000个候选商品精排,取Top100
排序模型(CTR预估):
特征:
- 用户特征:年龄、性别、地域
- 物品特征:价格、类目、销量
- 交叉特征:用户-物品历史交互
模型:
- 逻辑回归(LR)
- GBDT
- 深度学习(DNN)
输出:
每个商品的点击率预估 → 排序 → Top100
代码实现:
@Service
public class RankingService {
@Autowired
private ModelService modelService;
/**
* ⭐ 排序(CTR预估)
*/
public List<Long> rank(Long userId, List<Long> candidateItemIds) {
// 1. 特征工程
List<RankingItem> rankingItems = new ArrayList<>();
for (Long itemId : candidateItemIds) {
// 提取特征
Map<String, Double> features = extractFeatures(userId, itemId);
// CTR预估
double ctr = modelService.predictCTR(features);
RankingItem rankingItem = new RankingItem();
rankingItem.setItemId(itemId);
rankingItem.setCtr(ctr);
rankingItems.add(rankingItem);
}
// 2. 排序
return rankingItems.stream()
.sorted(Comparator.comparingDouble(RankingItem::getCtr).reversed())
.map(RankingItem::getItemId)
.collect(Collectors.toList());
}
/**
* 提取特征
*/
private Map<String, Double> extractFeatures(Long userId, Long itemId) {
Map<String, Double> features = new HashMap<>();
// 用户特征
User user = userMapper.selectById(userId);
features.put("user_age", (double) user.getAge());
features.put("user_gender", user.getGender() == "M" ? 1.0 : 0.0);
// 物品特征
Item item = itemMapper.selectById(itemId);
features.put("item_price", item.getPrice().doubleValue());
features.put("item_sales", (double) item.getSales());
// 交叉特征
int historyCount = userBehaviorMapper.countInteraction(userId, itemId);
features.put("history_interaction", (double) historyCount);
return features;
}
}
重排层(Re-Ranking)
作用:
对Top100商品进行业务规则调整
规则:
1. 去重(用户已购买的商品)
2. 多样性(不要全推荐同一类目)
3. 新品扶持(新品加权)
4. 广告插入(第3、8位插入广告)
↓
最终推荐列表 ✅
🎓 面试题速答
Q1: 推荐算法有哪些?
A: 三大算法:
-
协同过滤(最常用)⭐⭐⭐:
- 用户协同:和你相似的人喜欢什么
- 物品协同:推荐相似的商品
-
内容推荐:
- 根据物品特征推荐
-
混合推荐:
- 结合多种算法
Q2: 物品协同过滤如何实现?
A: 相似度矩阵 + 离线计算:
// 离线计算物品相似度矩阵
@Scheduled(cron = "0 0 2 * * ?")
public void calculateItemSimilarity() {
// 计算物品之间的余弦相似度
// 保存到相似度矩阵
}
// 在线推荐
public List<Long> recommend(Long userId) {
// 获取用户历史行为
List<Long> historyItems = getHistory(userId);
// 从相似度矩阵查询相似商品
Map<Long, Double> scores = new HashMap<>();
for (Long itemId : historyItems) {
Map<Long, Double> similarItems = similarityMatrix.get(itemId);
// 累加分数
}
// 排序返回
}
Q3: 推荐系统架构分哪几层?
A: 三层架构:
-
召回层:
- 从百万商品召回1000个候选
-
排序层:
- CTR预估精排,取Top100
-
重排层:
- 业务规则调整
Q4: 如何评估推荐效果?
A: 四大指标:
-
点击率(CTR):
- 推荐商品被点击的比例
-
转化率(CVR):
- 推荐商品被购买的比例
-
准确率(Precision):
- 推荐正确的比例
-
召回率(Recall):
- 用户感兴趣的被推荐的比例
Q5: 冷启动问题如何解决?
A: 三种策略:
-
新用户:
- 推荐热门商品
- 引导用户填写兴趣标签
-
新商品:
- 内容推荐(根据特征)
- 新品扶持(加权)
-
新用户+新商品:
- 基于人口统计学(年龄、性别)
Q6: A/B测试如何做?
A: 分流实验:
// A/B测试分流
public List<Long> recommend(Long userId) {
// 用户ID取模分流
int group = (int) (userId % 100);
if (group < 50) {
// A组:老算法(50%流量)
return oldAlgorithm.recommend(userId);
} else {
// B组:新算法(50%流量)
return newAlgorithm.recommend(userId);
}
}
// 统计对比指标
// A组CTR、CVR
// B组CTR、CVR
// 如果B组效果好,全量上线
🎬 总结
推荐系统核心
┌────────────────────────────────────┐
│ 1. 协同过滤 ⭐⭐⭐ │
│ - 物品协同(最常用) │
│ - 相似度矩阵离线计算 │
└────────────────────────────────────┘
┌────────────────────────────────────┐
│ 2. 三层架构 │
│ - 召回层(1000候选) │
│ - 排序层(Top100) │
│ - 重排层(业务规则) │
└────────────────────────────────────┘
┌────────────────────────────────────┐
│ 3. CTR预估 │
│ - 特征工程 │
│ - 机器学习模型 │
└────────────────────────────────────┘
┌────────────────────────────────────┐
│ 4. 冷启动 │
│ - 热门推荐 │
│ - 内容推荐 │
└────────────────────────────────────┘
┌────────────────────────────────────┐
│ 5. A/B测试 │
│ - 分流实验 │
│ - 效果对比 │
└────────────────────────────────────┘
🎉 恭喜你!
你已经完全掌握了推荐系统的设计!🎊
核心要点:
- 协同过滤:物品协同最常用,离线计算相似度矩阵
- 三层架构:召回→排序→重排
- CTR预估:机器学习模型,特征工程
- 冷启动:热门推荐+内容推荐
- A/B测试:分流实验,效果对比
下次面试,这样回答:
"推荐系统采用物品协同过滤算法。离线每天凌晨2点计算物品相似度矩阵,使用余弦相似度衡量两个商品的相似程度。在线推荐时,获取用户历史行为的商品ID列表,从相似度矩阵查询每个商品的相似商品,累加推荐分数后排序返回TopN。
系统架构分为召回、排序、重排三层。召回层从百万商品中快速召回1000个候选,使用协同过滤、内容推荐、热门推荐等多路召回策略。排序层对1000个候选商品进行CTR预估精排,提取用户特征、物品特征和交叉特征,输入机器学习模型预测点击率,排序后取Top100。重排层进行业务规则调整,包括去重、多样性、新品扶持和广告插入。
CTR预估模型使用深度学习DNN。特征包括用户年龄、性别、地域,商品价格、类目、销量,以及用户商品历史交互次数。特征经过Embedding后输入多层神经网络,最后一层Sigmoid输出点击概率。模型离线训练,在线预测毫秒级响应。
冷启动问题分三种情况。新用户推荐热门商品,同时引导填写兴趣标签。新商品使用内容推荐,根据商品特征匹配用户兴趣,并给予新品加权扶持。新用户加新商品使用人口统计学推荐,根据年龄性别等属性推荐相应商品。
A/B测试用于评估新算法效果。将用户按ID取模分成A组和B组各50%流量,A组使用老算法,B组使用新算法。统计对比CTR和CVR指标,如果B组效果显著优于A组,则新算法全量上线。"
面试官:👍 "很好!你对推荐系统的设计理解很深刻!"
本文完 🎬
上一篇: 218-设计一个视频网站系统.md
下一篇: 220-设计一个监控告警系统.md
作者注:写完这篇,我都想去当算法工程师了!🎯
如果这篇文章对你有帮助,请给我一个Star⭐!