推荐系统是典型的机器学习工程,机器学习工程通常分为两部分。
一部分是特征工程,用于收集数据、处理数据、将数据转为机器学习要使用的特征,特征决定了机器学习的上限。
二部分是机器学习算法,通过特征数据和算法得到模型。
特征工程
一、核心特征数据类型
1. 用户特征
-
静态特征:
- 人口统计学:年龄、性别、地域、职业
- 注册信息:会员等级、注册渠道、注册时间
-
动态特征:
- 行为统计:近7/30天活跃度、购买频次、平均客单价
- 兴趣偏好:常购品类、品牌偏好、价格敏感度
- 会话特征:当前设备、网络环境、地理位置
2. 商品特征
-
基础属性:
- 类别/标签:商品分类、人工标签、自动提取的关键词
- 物理属性:颜色、尺寸、材质、重量
-
商业属性:
- 价格:当前价、历史价、折扣率
- 库存状态:库存量、补货周期
-
表现指标:
- 销量:总销量、近期销量趋势
- 转化率:点击→购买转化率
- 评价:评分、好评率、评论情感分析
3. 交互特征
-
显性反馈:
- 购买记录:订单数据、购买数量、退货情况
- 评价数据:评分、文字评论、图片评价
-
隐性反馈:
- 浏览行为:浏览次数、停留时长、页面滚动深度
- 搜索行为:搜索词、点击结果、无结果搜索
- 交互行为:加入购物车、收藏、分享、客服咨询
二、特征收集方法
1. 数据收集系统架构
用户端 → 埋点SDK/API → 消息队列(Kafka) → 流处理(Flink) → 特征存储
↑ ↑
前端埋点事件 后端业务数据库
2. 具体收集技术
-
前端埋点:
// 商品点击埋点示例 trackEvent('product_click', { product_id: '12345', position: 'homepage_rec', timestamp: Date.now(), user_id: getUserId(), device_info: getDeviceInfo() });
-
后端日志:
- 访问日志(Nginx)
- 业务数据库变更捕获(CDC)
-
第三方数据:
- 社交媒体数据(通过API)
- 广告平台转化数据
3. 数据收集最佳实践
- 统一数据格式(JSON Schema)
- 区分事件类型(浏览/点击/购买)
- 包含完整上下文信息
- 用户隐私合规处理(匿名化/加密)
三、特征处理流程
1. 数据预处理流水线
# 特征处理示例
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
numeric_features = ['age', 'price', 'view_count']
numeric_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='median')),
('scaler', StandardScaler())
])
categorical_features = ['gender', 'category']
categorical_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='constant', fill_value='missing')),
('onehot', OneHotEncoder(handle_unknown='ignore'))
])
preprocessor = ColumnTransformer(
transformers=[
('num', numeric_transformer, numeric_features),
('cat', categorical_transformer, categorical_features)
])
2. 特征工程关键技术
数值型特征处理:
- 标准化/归一化
- 分箱处理
- 非线性变换(log/多项式)
- 缺失值处理
类别型特征处理:
- One-Hot编码
- 目标编码(Target Encoding)
- 嵌入表示(Embedding)
- 哈希编码
时序特征处理:
# 用户行为序列处理示例
def create_sequence_features(user_actions):
# 滑动窗口统计
df['rolling_7d_avg'] = df['clicks'].rolling(window='7D').mean()
# 时间衰减加权
decay_rate = 0.5
weights = np.array([decay_rate**(i) for i in range(len(user_actions))])
weighted_actions = np.dot(user_actions, weights)
# 时间间隔特征
df['time_since_last'] = df['timestamp'].diff().dt.total_seconds()
return features
文本特征处理:
# 商品描述文本处理
from sklearn.feature_extraction.text import TfidfVectorizer
from gensim.models import Word2Vec
# TF-IDF方法
tfidf = TfidfVectorizer(max_features=500)
tfidf_features = tfidf.fit_transform(product_descriptions)
# Word2Vec方法
w2v_model = Word2Vec(sentences=tokenized_descriptions,
vector_size=100, window=5, min_count=1)
图像特征处理:
- 预训练CNN模型提取特征
- 自动编码器降维表示
推荐算法实现
开发语言:java
使用机器学习框架:Spark-mllib
算法:ALS
ALS(交替最小二乘)算法是协同过滤推荐系统中的经典算法,特别适合用于电商推荐系统开发。
ALS
算法原理
ALS是矩阵分解的一种优化算法,通过交替固定用户矩阵或物品矩阵来优化另一个矩阵,逐步逼近真实的用户-物品评分矩阵。
公式
R ≈ U × V^T
其中:
- R:用户-物品评分矩阵(m×n)
- U:用户潜在特征矩阵(m×k)
- V:物品潜在特征矩阵(n×k)
- k:潜在因子维度
ALS在推荐系统中的适用性
优势
- 处理稀疏数据:能有效处理用户-物品交互矩阵的高稀疏性
- 可扩展性:适合分布式计算,能处理大规模数据
- 隐式反馈支持:可以处理点击、浏览等隐式反馈数据
- 实时推荐:模型更新效率较高
局限性
- 冷启动问题:对新用户和新物品效果有限
- 解释性弱:潜在因子难以直观解释
- 数据敏感性:对数据质量要求较高
典型应用场景
- "猜你喜欢"推荐:基于用户历史行为的个性化推荐
- "相似商品"推荐:利用物品潜在特征计算相似度
- 购物车搭配推荐:基于物品共现和潜在特征
一、环境准备
1. Maven依赖配置
<dependencies>
<!-- Spark Core -->
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_2.12</artifactId>
<version>3.3.0</version>
</dependency>
<!-- Spark SQL -->
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-sql_2.12</artifactId>
<version>3.3.0</version>
</dependency>
<!-- Spark MLlib -->
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-mllib_2.12</artifactId>
<version>3.3.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
二、数据准备与加载
1. 数据结构定义
public class Rating implements Serializable {
private int userId;
private int itemId;
private float rating;
private long timestamp;
// 构造器、getter和setter方法
public Rating(int userId, int itemId, float rating) {
this.userId = userId;
this.itemId = itemId;
this.rating = rating;
}
// 其他方法...
}
2. 数据加载代码
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SparkSession;
public class DataLoader {
public static Dataset<Row> loadRatings(SparkSession spark, String path) {
// 从CSV文件加载数据
Dataset<Row> df = spark.read()
.option("header", "true")
.option("inferSchema", "true")
.csv(path);
// 数据预处理
df = df.selectExpr(
"cast(userId as int) as userId",
"cast(itemId as int) as itemId",
"cast(rating as float) as rating",
"cast(timestamp as long) as timestamp"
);
return df;
}
public static JavaRDD<Rating> loadRawRatings(JavaSparkContext jsc, String path) {
// 从文本文件加载原始数据
JavaRDD<String> data = jsc.textFile(path);
// 转换为Rating对象
JavaRDD<Rating> ratings = data.map(line -> {
String[] parts = line.split(",");
return new Rating(
Integer.parseInt(parts[0]),
Integer.parseInt(parts[1]),
Float.parseFloat(parts[2])
);
});
return ratings;
}
}
三、ALS模型训练
1. 模型训练实现
import org.apache.spark.ml.evaluation.RegressionEvaluator;
import org.apache.spark.ml.recommendation.ALS;
import org.apache.spark.ml.recommendation.ALSModel;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
public class ALSTrainer {
public ALSModel train(Dataset<Row> ratings) {
// 划分训练集和测试集
Dataset<Row>[] splits = ratings.randomSplit(new double[]{0.8, 0.2});
Dataset<Row> training = splits[0];
Dataset<Row> test = splits[1];
// 构建ALS模型
ALS als = new ALS()
.setMaxIter(15)
.setRegParam(0.01)
.setUserCol("userId")
.setItemCol("itemId")
.setRatingCol("rating")
.setColdStartStrategy("drop");
// 训练模型
ALSModel model = als.fit(training);
// 评估模型
evaluateModel(model, test);
return model;
}
private void evaluateModel(ALSModel model, Dataset<Row> testData) {
// 预测
Dataset<Row> predictions = model.transform(testData);
// 计算RMSE
RegressionEvaluator evaluator = new RegressionEvaluator()
.setMetricName("rmse")
.setLabelCol("rating")
.setPredictionCol("prediction");
double rmse = evaluator.evaluate(predictions);
System.out.println("Root-mean-square error = " + rmse);
}
}
2. 参数调优实现
import org.apache.spark.ml.tuning.ParamGridBuilder;
import org.apache.spark.ml.tuning.CrossValidator;
import org.apache.spark.ml.tuning.CrossValidatorModel;
public class ALSParameterTuner {
public CrossValidatorModel tuneParameters(Dataset<Row> ratings) {
// 初始化ALS
ALS als = new ALS()
.setUserCol("userId")
.setItemCol("itemId")
.setRatingCol("rating")
.setColdStartStrategy("drop");
// 创建参数网格
ParamGridBuilder paramGrid = new ParamGridBuilder()
.addGrid(als.rank(), new int[]{5, 10, 20})
.addGrid(als.regParam(), new double[]{0.01, 0.1, 1.0})
.addGrid(als.maxIter(), new int[]{10, 15});
// 创建交叉验证器
CrossValidator cv = new CrossValidator()
.setEstimator(als)
.setEvaluator(new RegressionEvaluator()
.setMetricName("rmse")
.setLabelCol("rating")
.setPredictionCol("prediction"))
.setEstimatorParamMaps(paramGrid.build())
.setNumFolds(3); // 3折交叉验证
// 运行交叉验证
CrossValidatorModel cvModel = cv.fit(ratings);
// 输出最佳参数
System.out.println("Best rank: " +
cvModel.bestModel().getOrDefault(als.rank()));
System.out.println("Best regParam: " +
cvModel.bestModel().getOrDefault(als.regParam()));
System.out.println("Best maxIter: " +
cvModel.bestModel().getOrDefault(als.maxIter()));
return cvModel;
}
}
四、推荐生成
1. 为用户生成推荐
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
public class Recommender {
public Dataset<Row> recommendForUsers(ALSModel model, int numRecommendations) {
// 为所有用户生成推荐
Dataset<Row> userRecs = model.recommendForAllUsers(numRecommendations);
// 展平推荐结果
userRecs = userRecs.selectExpr(
"userId",
"explode(recommendations) as rec");
userRecs = userRecs.selectExpr(
"userId",
"rec.itemId as itemId",
"rec.rating as predictedRating");
return userRecs;
}
public Dataset<Row> recommendForItems(ALSModel model, int numRecommendations) {
// 为所有物品生成相似物品推荐
Dataset<Row> itemRecs = model.recommendForAllItems(numRecommendations);
// 展平推荐结果
itemRecs = itemRecs.selectExpr(
"itemId as itemId1",
"explode(recommendations) as rec");
itemRecs = itemRecs.selectExpr(
"itemId1",
"rec.itemId as itemId2",
"rec.rating as similarity");
return itemRecs;
}
}
2. 实时推荐服务
import org.apache.spark.ml.recommendation.ALSModel;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SparkSession;
import org.apache.spark.sql.types.DataTypes;
import org.apache.spark.sql.types.StructType;
public class RealTimeRecommender {
private ALSModel model;
private SparkSession spark;
public RealTimeRecommender(ALSModel model, SparkSession spark) {
this.model = model;
this.spark = spark;
}
public Dataset<Row> recommendForUser(int userId, int numRecs) {
// 创建包含用户ID的数据集
StructType schema = new StructType()
.add("userId", DataTypes.IntegerType);
Dataset<Row> users = spark.createDataFrame(
Arrays.asList(RowFactory.create(userId)),
schema
);
// 生成推荐
Dataset<Row> recs = model.recommendForUserSubset(users, numRecs);
// 展平结果
recs = recs.selectExpr(
"userId",
"explode(recommendations) as rec");
recs = recs.selectExpr(
"userId",
"rec.itemId as itemId",
"rec.rating as predictedRating");
return recs;
}
}
五、生产环境部署
1. 模型保存与加载
import org.apache.spark.ml.recommendation.ALSModel;
public class ModelPersistence {
public void saveModel(ALSModel model, String path) {
// 保存模型
model.write().overwrite().save(path);
}
public ALSModel loadModel(SparkSession spark, String path) {
// 加载模型
return ALSModel.load(path);
}
}
2. 完整工作流示例
import org.apache.spark.sql.SparkSession;
public class RecommendationWorkflow {
public static void main(String[] args) {
// 初始化Spark
SparkSession spark = SparkSession.builder()
.appName("ALS Recommendation System")
.master("local[*]")
.getOrCreate();
// 1. 加载数据
Dataset<Row> ratings = DataLoader.loadRatings(spark, "data/ratings.csv");
// 2. 训练模型
ALSTrainer trainer = new ALSTrainer();
ALSModel model = trainer.train(ratings);
// 3. 参数调优(可选)
// ALSParameterTuner tuner = new ALSParameterTuner();
// CrossValidatorModel cvModel = tuner.tuneParameters(ratings);
// ALSModel bestModel = (ALSModel) cvModel.bestModel();
// 4. 生成推荐
Recommender recommender = new Recommender();
Dataset<Row> userRecs = recommender.recommendForUsers(model, 10);
userRecs.show();
// 5. 保存模型
ModelPersistence persistence = new ModelPersistence();
persistence.saveModel(model, "models/als_model");
spark.stop();
}
}
六、性能优化技巧
1. 数据预处理优化
public class DataPreprocessor {
public Dataset<Row> preprocessRatings(Dataset<Row> ratings) {
// 1. 处理缺失值
ratings = ratings.na().fill(0, new String[]{"rating"});
// 2. 过滤异常值
ratings = ratings.filter("rating >= 0 AND rating <= 5");
// 3. 归一化评分
double maxRating = ratings.agg(functions.max("rating")).first().getDouble(0);
double minRating = ratings.agg(functions.min("rating")).first().getDouble(0);
ratings = ratings.withColumn("normalizedRating",
functions.expr(String.format("(rating - %f) / (%f - %f)",
minRating, maxRating, minRating)));
return ratings;
}
}
2. 分布式计算配置
SparkSession spark = SparkSession.builder()
.appName("ALS Recommendation System")
.master("spark://master:7077") // 集群地址
.config("spark.executor.memory", "8g")
.config("spark.driver.memory", "4g")
.config("spark.executor.cores", "4")
.config("spark.default.parallelism", "200")
.getOrCreate();
七、处理冷启动问题
1. 混合推荐策略
public class HybridRecommender {
private ALSModel alsModel;
private PopularityRecommender popularityModel;
public HybridRecommender(ALSModel alsModel, PopularityRecommender popularityModel) {
this.alsModel = alsModel;
this.popularityModel = popularityModel;
}
public Dataset<Row> recommend(int userId, int numRecs) {
// 检查是否新用户
boolean isNewUser = checkIfNewUser(userId);
if (isNewUser) {
// 新用户使用热门推荐
return popularityModel.recommend(numRecs);
} else {
// 老用户使用ALS推荐
return alsModel.recommendForUserSubset(
spark.createDataFrame(Arrays.asList(RowFactory.create(userId)),
new StructType().add("userId", DataTypes.IntegerType)),
numRecs
);
}
}
private boolean checkIfNewUser(int userId) {
// 实现检查逻辑
return false;
}
}
八、评估与监控
1. 离线评估指标
import org.apache.spark.ml.evaluation.RankingEvaluator;
public class ModelEvaluator {
public void evaluateRanking(ALSModel model, Dataset<Row> testData) {
// 转换为每个用户的推荐列表和实际交互列表
Dataset<Row> predictions = model.transform(testData);
// 使用RankingEvaluator计算指标
RankingEvaluator evaluator = new RankingEvaluator()
.setMetricName("meanAveragePrecision")
.setLabelCol("rating")
.setPredictionCol("prediction")
.setK(10);
double map = evaluator.evaluate(predictions);
System.out.println("Mean Average Precision = " + map);
}
}
2. 在线A/B测试框架
public class ABTestFramework {
private List<ALSModel> models;
private Map<Integer, Integer> userGroupMapping;
public ABTestFramework(List<ALSModel> models) {
this.models = models;
this.userGroupMapping = new HashMap<>();
}
public Dataset<Row> recommend(int userId, int numRecs) {
// 分配用户到测试组
int group = userGroupMapping.computeIfAbsent(userId, k -> k % models.size());
// 使用对应模型生成推荐
return models.get(group).recommendForUserSubset(
spark.createDataFrame(Arrays.asList(RowFactory.create(userId)),
new StructType().add("userId", DataTypes.IntegerType)),
numRecs
);
}
public void trackConversion(int userId, int itemId) {
// 实现转化跟踪逻辑
}
}