推荐系统设计
设计一个个性化推荐系统,实现千人千面的内容推荐
🎯 面试重点
- 推荐算法选型与组合
- 用户画像构建
- 实时推荐与离线推荐
- 推荐效果评估
📖 需求分析
业务场景
/**
* 推荐系统应用场景
*/
public class RecommendationScenarios {
// 1. 电商推荐
/*
* 商品推荐:"猜你喜欢"
* 关联推荐:"买了又买"
* 相似推荐:"看了又看"
*/
// 2. 内容推荐
/*
* 新闻推荐:个性化资讯
* 视频推荐:短视频feed流
* 音乐推荐:每日推荐歌单
*/
// 3. 社交推荐
/*
* 好友推荐:"可能认识的人"
* 群组推荐:"你可能感兴趣的群"
* 内容推荐:朋友圈动态
*/
// 4. 广告推荐
/*
* 精准广告投放
* 搜索广告推荐
* 信息流广告
*/
}
核心指标
/**
* 推荐系统评估指标
*/
public class EvaluationMetrics {
// 1. 用户满意度
/*
* 点击率(CTR)
* 转化率(CVR)
* 留存率(Retention)
*/
// 2. 业务指标
/*
* 总点击量
* 总销售额
* 用户活跃度
*/
// 3. 技术指标
/*
* 推荐响应时间
* 推荐覆盖率
* 新颖性(Novelty)
* 多样性(Diversity)
*/
// 4. 公平性指标
/*
* 马太效应控制
* 长尾物品曝光
* 新用户冷启动
*/
}
📖 系统架构设计
整体架构
┌─────────────────────────────────────────────────────────────┐
│ 客户端 │
│ (App/Web/小程序) │
└─────────────────────────────┬───────────────────────────────┘
│ HTTP/API
┌─────────────────────────────▼───────────────────────────────┐
│ API网关 │
│ (流量分发、认证) │
└──────────────┬────────────────┬──────────────────────────────┘
│ │
┌──────────▼──────┐ ┌──────▼──────────┐
│ 实时推荐服务 │ │ 离线推荐服务 │
│ (Online) │ │ (Offline) │
└──────────┬──────┘ └──────┬──────────┘
│ │
┌──────────▼────────────────▼──────────┐
│ 推荐引擎 (Recall + Rank) │
└──────────┬─────────────────────────────┘
│
┌──────────▼─────────────────────────────┐
│ 特征存储与计算 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 用户特征 │ │ 物品特征 │ │ 场景特征 │ │
│ └─────────┘ └─────────┘ └─────────┘ │
└──────────┬─────────────────────────────┘
│
┌──────────▼─────────────────────────────┐
│ 数据源 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 用户行为 │ │ 物品信息 │ │ 上下文信息│ │
│ └─────────┘ └─────────┘ └─────────┘ │
└────────────────────────────────────────┘
推荐流程
/**
* 推荐系统核心流程
*/
public class RecommendationPipeline {
// 1. 召回(Recall)
/*
* 从海量物品中筛选出几百个候选集
* 策略:协同过滤、内容过滤、热门推荐
* 目标:保证召回率,覆盖用户兴趣
*/
// 2. 排序(Ranking)
/*
* 对召回结果进行精排序
* 使用机器学习模型预测CTR
* 策略:LR、GBDT、DNN、DeepFM
*/
// 3. 重排(Re-ranking)
/*
* 考虑业务规则和多样性
* 去重、打散、多样性控制
* 插入广告、运营位
*/
// 4. 展示与反馈
/*
* 推荐结果展示
* 用户行为收集
* 实时反馈学习
*/
}
📖 详细设计
1. 数据采集与处理
/**
* 用户行为数据采集
*/
public class DataCollection {
// 用户行为类型
public enum UserAction {
VIEW, // 浏览
CLICK, // 点击
BUY, // 购买
COLLECT, // 收藏
SHARE, // 分享
COMMENT, // 评论
LIKE // 点赞
}
// 行为日志格式
@Data
public class UserBehavior {
private String userId; // 用户ID
private String itemId; // 物品ID
private UserAction action; // 行为类型
private Long timestamp; // 时间戳
private String scene; // 场景:首页、详情页等
private Map<String, Object> ext; // 扩展字段
}
// 数据采集方案
public class CollectionStrategy {
/*
* 客户端埋点:SDK自动采集
* 服务端日志:Nginx访问日志
* 实时流:Kafka实时收集
* 批量处理:HDFS存储,Spark处理
*/
}
}
2. 特征工程
/**
* 特征体系构建
*/
public class FeatureEngineering {
// 用户特征
@Data
public class UserFeatures {
// 静态特征
private String userId;
private Integer age;
private String gender;
private String location;
// 动态特征
private List<String> recentViewedItems; // 最近浏览
private List<String> recentBoughtItems; // 最近购买
private Map<String, Integer> categoryPref; // 品类偏好
private Double avgPricePref; // 价格偏好
// 统计特征
private Integer totalOrders; // 总订单数
private Double avgOrderValue; // 客单价
private String favoriteBrand; // 偏好品牌
}
// 物品特征
@Data
public class ItemFeatures {
private String itemId;
private String category; // 品类
private String brand; // 品牌
private Double price; // 价格
private List<String> tags; // 标签
private String title; // 标题
private String description; // 描述
// 统计特征
private Integer totalSales; // 总销量
private Double avgRating; // 平均评分
private Integer viewCount; // 浏览数
}
// 场景特征
@Data
public class ContextFeatures {
private String scene; // 场景:首页、搜索等
private String timeOfDay; // 时间段:早晨、中午等
private String dayOfWeek; // 周几
private String season; // 季节
private String location; // 地理位置
private String network; // 网络环境
}
}
3. 召回策略
/**
* 多路召回策略
*/
public class RecallStrategies {
// 1. 协同过滤(Collaborative Filtering)
public class CollaborativeFiltering {
/*
* 用户协同过滤:找到相似用户,推荐他们喜欢的物品
* 物品协同过滤:找到相似物品,推荐给用户
* 实现:Spark MLlib ALS算法
* 优势:发现用户潜在兴趣
* 劣势:冷启动问题
*/
}
// 2. 内容过滤(Content-based)
public class ContentBased {
/*
* 基于物品特征和用户历史偏好
* 物品特征:品类、品牌、价格、标签
* 用户画像:历史行为构建用户偏好
* 相似度计算:余弦相似度、Jaccard相似度
* 优势:解决冷启动,可解释性强
*/
}
// 3. 热门推荐(Popularity)
public class Popularity {
/*
* 基于全局热门度
* 策略:最近24小时热门、周热门、历史热门
* 加权公式:热度 = 销量 * 0.5 + 浏览 * 0.3 + 收藏 * 0.2
* 优势:解决冷启动,保证基础体验
*/
}
// 4. 实时推荐(Real-time)
public class RealTimeRecall {
/*
* 基于用户实时行为
* 策略:用户刚刚浏览的物品的相似物品
* 实现:Redis存储实时行为,快速召回
* 优势:捕捉实时兴趣变化
*/
}
// 5. 多样性召回
public class DiversityRecall {
/*
* 保证推荐结果多样性
* 策略:按品类、品牌、价格段等多维度召回
* 实现:多路召回结果融合
* 优势:避免信息茧房,探索用户兴趣
*/
}
}
4. 排序模型
/**
* 排序模型演进
*/
public class RankingModels {
// 1. 逻辑回归(LR)
/*
* 优点:简单、可解释性强、训练快
* 缺点:特征需要人工组合,无法学习非线性关系
* 应用场景:基础排序模型
*/
// 2. 梯度提升树(GBDT)
/*
* 优点:自动特征组合,非线性拟合能力强
* 缺点:训练慢,模型大
* 应用场景:主流排序模型
*/
// 3. 因子分解机(FM)
/*
* 优点:自动学习特征交叉,稀疏数据表现好
* 缺点:高阶特征交叉能力有限
* 应用场景:推荐系统经典模型
*/
// 4. 深度神经网络(DNN)
/*
* 优点:自动特征提取,拟合能力强
* 缺点:需要大量数据,可解释性差
* 应用场景:复杂特征场景
*/
// 5. 深度因子分解机(DeepFM)
/*
* 优点:结合FM和DNN,兼顾记忆和泛化
* 缺点:模型复杂,训练资源消耗大
* 应用场景:CTR预测最优模型之一
*/
// 6. 多任务学习(MMOE)
/*
* 优点:同时优化多个目标(点击、转化、时长)
* 缺点:模型复杂,需要多目标数据
* 应用场景:多目标优化场景
*/
}
5. 实时推荐架构
/**
* 实时推荐实现
*/
public class RealTimeRecommendation {
// 实时特征计算
public class RealTimeFeatures {
/*
* 用户实时行为特征(最近5分钟)
* 物品实时热度特征
* 上下文实时特征(时间、位置等)
* 实现:Flink实时计算,Redis存储
*/
}
// 实时模型预测
public class RealTimePrediction {
/*
* 在线模型服务(TensorFlow Serving)
* 特征实时拼接
* 模型实时推理
* 响应时间要求:< 100ms
*/
}
// 实时反馈学习
public class RealTimeLearning {
/*
* 用户行为实时反馈
* 模型在线学习(Online Learning)
* 特征权重实时调整
* A/B测试实时分流
*/
}
}
📖 关键技术实现
1. 协同过滤实现
// Spark ALS实现协同过滤
import org.apache.spark.ml.recommendation.ALS
val ratings = spark.read.parquet("hdfs://user_behavior.parquet")
.select($"userId".cast("int"), $"itemId".cast("int"), $"rating".cast("float"))
// 训练ALS模型
val als = new ALS()
.setMaxIter(10)
.setRegParam(0.01)
.setUserCol("userId")
.setItemCol("itemId")
.setRatingCol("rating")
.setColdStartStrategy("drop")
val model = als.fit(ratings)
// 为用户推荐物品
val userRecs = model.recommendForAllUsers(10)
.select($"userId", explode($"recommendations").as("rec"))
.select($"userId", $"rec.itemId", $"rec.rating")
// 保存推荐结果
userRecs.write.parquet("hdfs://user_recommendations.parquet")
2. 特征存储设计
-- 用户特征表
CREATE TABLE user_features (
user_id VARCHAR(64) PRIMARY KEY,
age INT,
gender VARCHAR(10),
location VARCHAR(100),
category_pref JSON COMMENT '品类偏好 {category: weight}',
price_pref DECIMAL(10,2) COMMENT '价格偏好',
last_update TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_update(last_update)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 物品特征表
CREATE TABLE item_features (
item_id VARCHAR(64) PRIMARY KEY,
category_id VARCHAR(32),
brand_id VARCHAR(32),
price DECIMAL(10,2),
tags JSON COMMENT '标签数组',
total_sales INT DEFAULT 0,
avg_rating DECIMAL(3,2),
view_count INT DEFAULT 0,
INDEX idx_category(category_id),
INDEX idx_brand(brand_id),
INDEX idx_sales(total_sales)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 用户实时行为表(Redis)
/*
Key: user:recent:{userId}
Type: List
Value: [itemId1:action1:timestamp1, itemId2:action2:timestamp2, ...]
Expire: 7 days
*/
3. 实时特征计算(Flink)
public class RealTimeFeatureJob {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 消费用户行为Kafka
DataStream<UserBehavior> behaviorStream = env
.addSource(new FlinkKafkaConsumer<>("user-behavior",
new SimpleStringSchema(), properties))
.map(json -> JsonUtils.parse(json, UserBehavior.class));
// 实时特征计算
DataStream<UserFeature> featureStream = behaviorStream
.keyBy(UserBehavior::getUserId)
.window(SlidingProcessingTimeWindows.of(Time.minutes(5), Time.minutes(1)))
.process(new ProcessWindowFunction<UserBehavior, UserFeature, String, TimeWindow>() {
@Override
public void process(String userId, Context context,
Iterable<UserBehavior> behaviors, Collector<UserFeature> out) {
UserFeature feature = new UserFeature();
feature.setUserId(userId);
feature.setTimestamp(System.currentTimeMillis());
// 计算5分钟内行为统计
Map<String, Integer> actionCounts = new HashMap<>();
List<String> recentItems = new ArrayList<>();
for (UserBehavior behavior : behaviors) {
actionCounts.merge(behavior.getAction().name(), 1, Integer::sum);
recentItems.add(behavior.getItemId());
}
feature.setRecentActions(actionCounts);
feature.setRecentItems(recentItems);
out.collect(feature);
}
});
// 写入Redis
featureStream.addSink(new RedisSink<>(
new FlinkJedisPoolConfig.Builder().setHost("redis").build(),
new RedisMapper<UserFeature>() {
@Override
public RedisCommandDescription getCommandDescription() {
return new RedisCommandDescription(RedisCommand.HSET);
}
@Override
public String getKeyFromData(UserFeature feature) {
return "user:realtime:" + feature.getUserId();
}
@Override
public String getValueFromData(UserFeature feature) {
return JsonUtils.toJson(feature);
}
}
));
env.execute("Real-time Feature Computation");
}
}
4. 模型服务(TensorFlow Serving)
# 模型训练
import tensorflow as tf
# 构建DeepFM模型
def build_deepfm_model(user_features, item_features, context_features):
# FM部分
fm_user_emb = tf.keras.layers.Embedding(user_vocab_size, embedding_dim)(user_features)
fm_item_emb = tf.keras.layers.Embedding(item_vocab_size, embedding_dim)(item_features)
# 特征交叉
fm_cross = tf.reduce_sum(fm_user_emb * fm_item_emb, axis=1, keepdims=True)
# DNN部分
concat_features = tf.keras.layers.Concatenate()([user_features, item_features, context_features])
dnn_output = tf.keras.layers.Dense(128, activation='relu')(concat_features)
dnn_output = tf.keras.layers.Dense(64, activation='relu')(dnn_output)
dnn_output = tf.keras.layers.Dense(32, activation='relu')(dnn_output)
# 合并FM和DNN
combined = tf.keras.layers.Concatenate()([fm_cross, dnn_output])
output = tf.keras.layers.Dense(1, activation='sigmoid')(combined)
model = tf.keras.Model(inputs=[user_features, item_features, context_features],
outputs=output)
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
return model
# 保存模型
model.save('/models/deepfm/1', save_format='tf')
# 启动TensorFlow Serving
# docker run -p 8501:8501 --mount type=bind,source=/models,target=/models -e MODEL_NAME=deepfm tensorflow/serving
// 客户端调用模型服务
public class ModelClient {
public double predictCTR(String userId, String itemId, Context context) {
// 准备特征
Map<String, Object> features = new HashMap<>();
features.put("user_id", userId);
features.put("item_id", itemId);
features.put("time_of_day", context.getTimeOfDay());
features.put("category", getItemCategory(itemId));
// 调用TensorFlow Serving
String url = "http://tf-serving:8501/v1/models/deepfm:predict";
Map<String, Object> request = Map.of("instances", List.of(features));
ResponseEntity<Map> response = restTemplate.postForEntity(url, request, Map.class);
List<List<Double>> predictions = (List<List<Double>>) response.getBody().get("predictions");
return predictions.get(0).get(0); // CTR概率
}
}
📖 效果评估与优化
1. A/B测试框架
/**
* A/B测试实现
*/
public class ABTestFramework {
// 实验配置
@Data
public class Experiment {
private String experimentId; // 实验ID
private String name; // 实验名称
private List<Variant> variants; // 实验变体
private String targetMetric; // 目标指标:CTR、CVR等
private Date startTime; // 开始时间
private Date endTime; // 结束时间
private Double trafficRatio; // 流量比例
}
// 实验变体
@Data
public class Variant {
private String variantId; // 变体ID
private String name; // 变体名称
private Double trafficRatio; // 流量分配比例
private Map<String, Object> params; // 参数配置
}
// 流量分配
public class TrafficAllocation {
/*
* 基于用户ID哈希分流
* 保证同一用户始终进入同一变体
* 支持多实验层叠(正交实验)
*/
public String assignVariant(String userId, String experimentId) {
String hashKey = userId + ":" + experimentId;
int hash = Math.abs(hashKey.hashCode()) % 100;
// 根据流量比例分配
if (hash < 50) return "control"; // 50%对照组
else if (hash < 75) return "variant_a"; // 25%变体A
else return "variant_b"; // 25%变体B
}
}
}
2. 评估指标计算
-- CTR计算
SELECT
experiment_id,
variant_id,
COUNT(DISTINCT user_id) as user_count,
SUM(IF(action = 'click', 1, 0)) as click_count,
SUM(IF(action = 'view', 1, 0)) as view_count,
SUM(IF(action = 'click', 1, 0)) / SUM(IF(action = 'view', 1, 0)) as ctr
FROM user_behavior_logs
WHERE date = '2023-10-01'
AND experiment_id = 'rec_algorithm_v2'
GROUP BY experiment_id, variant_id;
-- 显著性检验(t检验)
WITH stats AS (
SELECT
variant_id,
AVG(ctr) as mean_ctr,
STDDEV(ctr) as std_ctr,
COUNT(*) as sample_size
FROM user_daily_ctr
WHERE experiment_id = 'rec_algorithm_v2'
GROUP BY variant_id
)
SELECT
a.variant_id as variant_a,
b.variant_id as variant_b,
-- t统计量
ABS(a.mean_ctr - b.mean_ctr) /
SQRT(POWER(a.std_ctr, 2)/a.sample_size + POWER(b.std_ctr, 2)/b.sample_size) as t_stat
FROM stats a CROSS JOIN stats b
WHERE a.variant_id != b.variant_id;
📖 冷启动解决方案
1. 用户冷启动
/**
* 新用户推荐策略
*/
public class NewUserStrategy {
// 策略1:热门推荐
public List<String> recommendByPopularity(String userId) {
// 返回全局热门物品
return redisService.zrevrange("global:popular:items", 0, 9);
}
// 策略2:地域推荐
public List<String> recommendByLocation(String userId, String location) {
// 返回同地域热门物品
return redisService.zrevrange("location:popular:" + location, 0, 9);
}
// 策略3:人口统计推荐
public List<String> recommendByDemographics(UserProfile profile) {
// 基于年龄、性别等 demographic 信息推荐
String key = String.format("demo:rec:%s:%s:%s",
profile.getAgeGroup(), profile.getGender(), profile.getLocation());
return redisService.zrevrange(key, 0, 9);
}
// 策略4:探索式推荐
public List<String> exploreRecommendation() {
// 推荐多样化的物品,探索用户兴趣
List<String> recommendations = new ArrayList<>();
recommendations.addAll(getPopularItems()); // 热门
recommendations.addAll(getNewItems()); // 新品
recommendations.addAll(getDiverseCategories()); // 多品类
return recommendations;
}
}
2. 物品冷启动
/**
* 新物品推荐策略
*/
public class NewItemStrategy {
// 基于内容相似度
public List<String> findSimilarItems(Item newItem) {
// 基于品类、品牌、价格、标签等特征
return contentBasedService.findSimilar(newItem, 10);
}
// 探索性曝光
public void exploreExposure(String itemId, double exploreRatio) {
// 将新物品混入推荐列表中
// 探索比例:5%流量用于探索新物品
}
// 运营扶持
public void operationalSupport(String itemId) {
// 运营手动配置曝光
// 新物品专区展示
// 新手优惠券引导
}
}
📖 面试真题
Q1: 推荐系统的主要模块有哪些?
答:
- 数据层:用户行为采集、特征存储
- 召回层:从海量物品中快速筛选候选集(协同过滤、内容过滤、热门召回等)
- 排序层:对候选集精排序(CTR预估模型)
- 重排层:业务规则调整、多样性控制
- 评估层:A/B测试、效果评估、模型迭代
Q2: 协同过滤有哪些类型?各有什么优缺点?
答:
- 用户协同过滤:
- 优点:发现用户潜在兴趣
- 缺点:用户矩阵稀疏,计算量大
- 物品协同过滤:
- 优点:物品矩阵相对稠密,可离线计算
- 缺点:难以应对物品冷启动
- 矩阵分解(MF):
- 优点:解决稀疏性问题,可扩展性好
- 缺点:可解释性差,需要定期重训
- 深度学习协同过滤:
- 优点:自动特征学习,效果好
- 缺点:计算资源消耗大,可解释性差
Q3: 如何处理推荐系统的冷启动问题?
答: 用户冷启动:
- 基于热门推荐
- 基于地域推荐
- 基于人口统计信息推荐
- 引导用户明确兴趣(标签选择)
- 探索式推荐
物品冷启动:
- 基于内容相似度推荐
- 探索性曝光(Epsilon-Greedy)
- 运营扶持(人工曝光)
- 结合用户反馈快速学习
Q4: 如何评估推荐系统的效果?
答:
- 离线评估:
- 准确率:Precision、Recall、F1-score
- 覆盖率:Coverage
- 多样性:Intra-list Diversity
- 新颖性:Novelty
- 在线评估:
- 业务指标:CTR、CVR、GMV
- 用户指标:留存率、使用时长
- A/B测试:统计显著性检验
- 长期评估:
- 用户满意度调研
- 生态健康度(马太效应)
- 系统可扩展性
Q5: 推荐系统如何保证多样性?
答:
- 召回阶段:多路召回(不同策略保证多样性)
- 排序阶段:在损失函数中加入多样性正则项
- 重排阶段:
- MMR(Maximal Marginal Relevance)算法
- 品类打散(同一品类不超过2个)
- 价格段分布(高、中、低均衡)
- 时间新鲜度(新旧内容混合)
- 探索机制:ε-greedy、Thompson Sampling、Bandit算法
📚 延伸阅读
⭐ 重点:推荐系统是数据驱动和算法驱动的典型应用,需要平衡准确性、多样性、新颖性和实时性