在当今数据驱动的时代,人工智能已经渗透到各行各业,而推荐系统作为AI最成功的商业应用之一,驱动着电商、短视频、新闻资讯等平台的用户增长和商业变现。Java作为企业级应用开发的主流语言,凭借其卓越的性能、完善的生态系统和强大的并发处理能力,在构建大规模、高可用的推荐系统中扮演着核心角色。然而,将AI算法落地到生产环境面临着诸多挑战:如何处理海量用户行为数据?如何设计高效的召回和排序算法?如何保证推荐结果的实时性?如何进行A/B测试和模型迭代?
本文将从零构建一个生产级智能推荐系统,完整呈现Java+AI项目的实战流程,涵盖数据采集、特征工程、召回算法、排序模型、在线服务、A/B测试等全链路环节。通过这个实战项目,你将掌握Java在大规模机器学习系统中的应用技巧,理解推荐系统的核心原理,并具备独立构建智能推荐引擎的能力。
第一部分:项目概述与架构设计
1.1 项目背景与目标
在电商场景中,用户面对海量商品时往往难以找到自己感兴趣的商品。个性化推荐系统通过分析用户的历史行为、实时行为和商品属性,主动为用户推荐可能感兴趣的商品,从而提升用户体验和平台转化率。
项目名称: SmartRec - 智能推荐系统
核心目标:
1.2 推荐系统核心概念
推荐系统的本质是一个信息过滤系统,它从海量物品中筛选出用户最可能感兴趣的物品。从数学角度来看,推荐系统要解决的核心问题是:如何预测用户u对物品i的评分(或点击概率)。
推荐系统的三个核心阶段:
┌─────────────────────────────────────────────────────────────────────┐
│ 推荐系统架构 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 数据层 │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │用户行为日志│ │商品信息 │ │用户画像 │ │上下文信息│ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 召回层(Recall) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │协同过滤 │ │向量召回 │ │热门召回 │ │品类召回 │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
│ │ 从百万商品 → 千级候选 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 排序层(Ranking) │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ 深度学习排序模型(DNN) │ │ │
│ │ │ 特征交叉 | 用户兴趣网络 | 商品特征网络 │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ │ 千级候选 → 排序后的Top-N │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 重排层(Re-ranking) │ │
│ │ 多样性调整 | 业务规则过滤 | 去重 | 打散 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ 最终推荐结果 │ │
│ └─────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
核心概念解释:
召回(Recall):从海量候选集中快速筛选出用户可能感兴趣的一小部分物品(通常几百到几千)。召回阶段要求速度快、覆盖广,可以接受一定的误差。
排序(Ranking):对召回阶段的候选物品进行精细化的点击率预测,按照预测分数排序。排序阶段要求精度高,通常使用复杂的机器学习模型。
重排(Re-ranking):对排序结果进行后处理,包括多样性调整、业务规则过滤、去重等,保证推荐结果的用户体验。
1.3 技术选型
1.4 项目结构
smart-rec/
├── pom.xml # Maven父POM
├── README.md
│
├── smart-rec-common/ # 公共模块
│ ├── src/main/java/
│ │ └── com/smartrec/common/
│ │ ├── entity/ # 公共实体
│ │ ├── enums/ # 枚举定义
│ │ ├── exception/ # 异常定义
│ │ └── utils/ # 工具类
│ └── src/main/resources/
│
├── smart-rec-data/ # 数据模块
│ ├── src/main/java/
│ │ └── com/smartrec/data/
│ │ ├── collector/ # 数据采集
│ │ ├── processor/ # 数据加工
│ │ ├── feature/ # 特征工程
│ │ └── storage/ # 数据存储
│ └── src/main/resources/
│
├── smart-rec-recall/ # 召回模块
│ ├── src/main/java/
│ │ └── com/smartrec/recall/
│ │ ├── collaborative/ # 协同过滤
│ │ ├── vector/ # 向量召回
│ │ ├── hot/ # 热门召回
│ │ ├── context/ # 上下文召回
│ │ └── merge/ # 多路召回合并
│ └── src/main/resources/
│
├── smart-rec-ranking/ # 排序模块
│ ├── src/main/java/
│ │ └── com/smartrec/ranking/
│ │ ├── feature/ # 特征工程
│ │ ├── model/ # 排序模型
│ │ ├── predict/ # 在线预测
│ │ └── rerank/ # 重排
│ └── src/main/resources/
│
├── smart-rec-api/ # API服务模块
│ ├── src/main/java/
│ │ └── com/smartrec/api/
│ │ ├── controller/ # REST控制器
│ │ ├── service/ # 推荐服务
│ │ ├── cache/ # 结果缓存
│ │ └── abtest/ # A/B测试
│ └── src/main/resources/
│
├── smart-rec-flink/ # 实时计算模块
│ ├── src/main/java/
│ │ └── com/smartrec/flink/
│ │ ├── source/ # 数据源
│ │ ├── transform/ # 数据转换
│ │ └── sink/ # 数据输出
│ └── src/main/resources/
│
├── scripts/ # 运维脚本
│ ├── deploy.sh
│ ├── train.sh
│ └── benchmark.sh
│
└── docker/ # Docker配置
├── Dockerfile
└── docker-compose.yml
第二部分:数据采集与特征工程
2.1 数据采集架构
推荐系统的质量很大程度上取决于数据的质量和丰富度。我们需要采集三类核心数据:
- 用户行为数据(User Behavior Log)
用户行为数据是推荐系统最重要的信号,包括:
- 商品属性数据(Item Attributes)
商品信息是推荐系统的另一个重要输入:
基础信息:商品ID、标题、价格、品牌、类目
内容信息:图片特征、描述文本
统计信息:销量、点击率、转化率 - 用户画像数据(User Profile)
用户画像描述用户的长期兴趣:
人口属性:年龄、性别、地域
长期兴趣:类目偏好、品牌偏好、价格偏好
行为统计:活跃度、购买力、复购率
```
// smart-rec-data/src/main/java/com/smartrec/data/collector/UserBehaviorCollector.java
package com.smartrec.data.collector;
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.springframework.stereotype.Component;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
- 用户行为数据采集器
- 用户行为日志是推荐系统最重要的数据源。本采集器负责:
-
- 从前端埋点SDK接收用户行为事件
-
- 对事件进行格式化和校验
-
- 异步发送到Kafka消息队列
- 数据流向:
- 前端埋点 → Nginx → Logstash → Kafka → Flink实时计算
- ↓
HDFS离线存储
*/
@Slf4j
@Component
public class UserBehaviorCollector {private static final String TOPIC_USER_BEHAVIOR = "user-behavior-topic";
private final KafkaProducer producer;
private final ExecutorService asyncExecutor;public UserBehaviorCollector() {
// 配置Kafka生产者
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("acks", "1"); // 只等待leader确认,平衡可靠性和性能
props.put("compression.type", "snappy"); // 启用压缩,减少网络传输
props.put("linger.ms", "5"); // 批量发送延迟,提高吞吐量this.producer = new KafkaProducer<>(props);
this.asyncExecutor = Executors.newSingleThreadExecutor();log.info("UserBehaviorCollector initialized");
}/**
- 记录用户行为事件
- @param event 用户行为事件
- 事件结构示例:
- {
- "userId": 12345,
- "itemId": 67890,
- "actionType": "click",
- "timestamp": 1700000000000,
- "sessionId": "abc-def-ghi",
- "page": "home",
- "position": 3,
- "duration": 2500
- }
*/
public void collect(BehaviorEvent event) {
// 异步发送,不阻塞主线程
asyncExecutor.submit(() -> {
try {
String json = event.toJson();
ProducerRecord record = new ProducerRecord<>(
TOPIC_USER_BEHAVIOR,
String.valueOf(event.getUserId()), // 使用userId作为key,保证同一用户的事件有序
json
);
producer.send(record, (metadata, exception) -> {
if (exception != null) {
log.error("Failed to send behavior event: {}", json, exception);
} else {
log.debug("Behavior event sent: offset={}", metadata.offset());
}
});} catch (Exception e) {
log.error("Failed to collect behavior event", e);
}
});
}/**
- 批量采集
- 适用于离线批处理场景,如每日全量同步
*/
public void batchCollect(List events) {
for (BehaviorEvent event : events) {
collect(event);
}
}
@PreDestroy
public void destroy() {
asyncExecutor.shutdown();
try {
if (!asyncExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
asyncExecutor.shutdownNow();
}
} catch (InterruptedException e) {
asyncExecutor.shutdownNow();
}
producer.close();
log.info("UserBehaviorCollector destroyed");
}
}**2.2 特征工程** 特征工程是机器学习中最重要的环节。好的特征能让简单的模型发挥出惊人效果,而糟糕的特征即使使用深度学习也难以弥补。// smart-rec-data/src/main/java/com/smartrec/data/feature/FeatureExtractor.java
package com.smartrec.data.feature;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
- 特征提取器
- 特征工程的核心思想是将原始数据转换为模型可以理解的数值特征。
- 特征类型:
-
- 数值特征(Numerical Feature):用户年龄、商品价格、历史点击次数
-
- 类别特征(Categorical Feature):用户性别、商品类目、品牌
-
- 序列特征(Sequence Feature):用户最近点击的商品ID序列
-
- 交叉特征(Cross Feature):用户年龄×商品价格区间
-
- 统计特征(Statistical Feature):商品近7天点击率
- 特征处理的三个关键步骤:
-
- 缺失值处理:填充默认值或使用统计值
-
- 归一化:将不同量纲的特征映射到相同范围
-
离散化:将连续值转换为离散桶(适用于线性模型)
*/
@Slf4j
@Component
public class FeatureExtractor {// 特征名称常量
public static final String FEAT_USER_ID = "user_id";
public static final String FEAT_ITEM_ID = "item_id";
public static final String FEAT_USER_AGE = "user_age";
public static final String FEAT_USER_GENDER = "user_gender";
public static final String FEAT_USER_CITY = "user_city";
public static final String FEAT_USER_PURCHASE_POWER = "user_purchase_power";
public static final String FEAT_ITEM_PRICE = "item_price";
public static final String FEAT_ITEM_CATEGORY = "item_category";
public static final String FEAT_ITEM_BRAND = "item_brand";
public static final String FEAT_ITEM_SALES_7D = "item_sales_7d";
public static final String FEAT_ITEM_CTR_7D = "item_ctr_7d";
public static final String FEAT_USER_ITEM_CLICK_COUNT = "user_item_click_count";
public static final String FEAT_USER_ITEM_BUY_COUNT = "user_item_buy_count";
public static final String FEAT_USER_CATEGORY_PREF = "user_category_pref";
public static final String FEAT_USER_BRAND_PREF = "user_brand_pref";
public static final String FEAT_USER_PRICE_PREF = "user_price_pref";
public static final String FEAT_HOUR = "hour";
public static final String FEAT_WEEKDAY = "weekday";
public static final String FEAT_IS_WEEKEND = "is_weekend";
public static final String FEAT_USER_ACTIVE_LEVEL = "user_active_level";// 用户特征缓存(定期从数据库加载)
private final Map userFeatureCache = new ConcurrentHashMap<>();
// 商品特征缓存
private final Map itemFeatureCache = new ConcurrentHashMap<>();
// 统计特征缓存(实时更新)
private final Map statFeatureCache = new ConcurrentHashMap<>();private final RedisTemplate redisTemplate;
public FeatureExtractor(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
// 启动定时刷新任务
startRefreshTask();
}/**
- 提取用户特征
- @param userId 用户ID
@return 用户特征向量
*/
public UserFeatures extractUserFeatures(Long userId) {
// 先从缓存获取
UserFeatures features = userFeatureCache.get(userId);
if (features != null) {return features;}
// 从数据库加载
features = loadUserFeaturesFromDB(userId);
userFeatureCache.put(userId, features);
return features;
}
/**
- 提取商品特征
- @param itemId 商品ID
@return 商品特征向量
*/
public ItemFeatures extractItemFeatures(Long itemId) {
ItemFeatures features = itemFeatureCache.get(itemId);
if (features != null) {return features;}
features = loadItemFeaturesFromDB(itemId);
itemFeatureCache.put(itemId, features);
return features;
}
/**
- 提取交叉特征
- 交叉特征是推荐系统中最重要的特征类型。
- 例如:用户是否点击过该商品、用户对该类目的偏好程度等。
交叉特征的计算通常涉及实时查询用户历史行为。
*/
public CrossFeatures extractCrossFeatures(Long userId, Long itemId) {
CrossFeatures features = new CrossFeatures();// 1. 用户-商品交互特征
features.setUserItemClickCount(getUserItemClickCount(userId, itemId));
features.setUserItemBuyCount(getUserItemBuyCount(userId, itemId));
features.setUserItemLastClickTime(getUserItemLastClickTime(userId, itemId));// 2. 用户-类目偏好
ItemFeatures itemFeatures = extractItemFeatures(itemId);
String category = itemFeatures.getCategory();
features.setUserCategoryClickCount(getUserCategoryClickCount(userId, category));
features.setUserCategoryBuyCount(getUserCategoryBuyCount(userId, category));
features.setUserCategoryPreference(calculateUserCategoryPreference(userId, category));// 3. 用户-品牌偏好
String brand = itemFeatures.getBrand();
features.setUserBrandClickCount(getUserBrandClickCount(userId, brand));
features.setUserBrandPreference(calculateUserBrandPreference(userId, brand));// 4. 用户-价格偏好匹配
double itemPrice = itemFeatures.getPrice();
double userPricePreference = getUserPricePreference(userId);
features.setPriceMatchScore(calculatePriceMatchScore(itemPrice, userPricePreference));// 5. 实时兴趣特征(最近1小时的点击类目)
List recentCategories = getRecentUserCategories(userId, 60);
features.setIsCategoryHot(recentCategories.contains(category));
features.setCategoryRecencyScore(calculateRecencyScore(recentCategories, category));return features;
}
/**
- 提取上下文特征
- 上下文特征描述用户当前的场景,如时间、设备、地理位置等。
这些特征可以帮助模型理解用户的即时需求。
*/
public ContextFeatures extractContextFeatures() {
ContextFeatures features = new ContextFeatures();Calendar cal = Calendar.getInstance();
features.setHour(cal.get(Calendar.HOUR_OF_DAY));
features.setWeekday(cal.get(Calendar.DAY_OF_WEEK));
features.setIsWeekend(features.getWeekday() == Calendar.SATURDAY ||features.getWeekday() == Calendar.SUNDAY);features.setIsWorkTime(features.getHour() >= 9 && features.getHour() <= 18);
// 季节性特征
int month = cal.get(Calendar.MONTH);
features.setIsShoppingFestival(isShoppingFestival(month));return features;
}
/**
- 提取序列特征
- 序列特征记录了用户行为的时序信息,对于捕捉用户兴趣演化至关重要。
- 常用处理方法:
-
- 序列Embedding:使用RNN/LSTM对序列建模
-
- Attention机制:对序列中不同位置赋予不同权重
-
- 序列统计:最近N次行为的类目分布、价格变化趋势
*/
public SequenceFeatures extractSequenceFeatures(Long userId, int sequenceLength) {
SequenceFeatures features = new SequenceFeatures();
// 获取用户最近的行为序列
List recentActions = getUserRecentActions(userId, sequenceLength);// 提取类目序列
List categorySequence = recentActions.stream().map(action -> getItemCategory(action.getItemId())) .collect(Collectors.toList());features.setCategorySequence(categorySequence);
// 提取商品ID序列(用于序列召回)
List itemIdSequence = recentActions.stream().map(UserAction::getItemId) .collect(Collectors.toList());features.setItemIdSequence(itemIdSequence);
// 序列统计特征
features.setUniqueCategoriesCount(categorySequence.stream().distinct().count());
features.setCategoryDiversity(calculateDiversity(categorySequence));// 时间间隔序列
List timeIntervals = calculateTimeIntervals(recentActions);
features.setAvgTimeInterval(timeIntervals.stream().mapToLong(Long::longValue).average().orElse(0));// 行为类型分布
Map actionTypeCount = recentActions.stream().collect(Collectors.groupingBy(UserAction::getActionType, Collectors.counting()));features.setClickCount(actionTypeCount.getOrDefault(ACTION_CLICK, 0L));
features.setCartCount(actionTypeCount.getOrDefault(ACTION_CART, 0L));
features.setBuyCount(actionTypeCount.getOrDefault(ACTION_BUY, 0L));return features;
} - 序列统计:最近N次行为的类目分布、价格变化趋势
/**
- 构建完整的特征向量
- 这是特征工程的核心方法,将上述所有特征整合成一个特征向量,
供排序模型使用。
*/
public FeatureVector buildFeatureVector(Long userId, Long itemId, ContextFeatures context) {
FeatureVector featureVector = new FeatureVector();// 1. 用户特征
UserFeatures userFeatures = extractUserFeatures(userId);
featureVector.putAll(userFeatures.toMap());// 2. 商品特征
ItemFeatures itemFeatures = extractItemFeatures(itemId);
featureVector.putAll(itemFeatures.toMap());// 3. 交叉特征
CrossFeatures crossFeatures = extractCrossFeatures(userId, itemId);
featureVector.putAll(crossFeatures.toMap());// 4. 上下文特征
featureVector.putAll(context.toMap());// 5. 序列特征(压缩为统计值)
SequenceFeatures sequenceFeatures = extractSequenceFeatures(userId, 20);
featureVector.putAll(sequenceFeatures.toStatFeatures());// 6. 归一化处理
normalize(featureVector);return featureVector;
}
/**
- 特征归一化
- 将不同量纲的特征映射到[0,1]区间,避免某些特征主导模型。
- 常用归一化方法:
-
- Min-Max归一化:x' = (x - min) / (max - min)
-
- Z-Score归一化:x' = (x - μ) / σ
-
- Log变换:x' = log(1 + x)(适用于长尾分布的特征)
*/
private void normalize(FeatureVector features) {
// 对价格特征做log变换(价格分布通常是长尾的)
if (features.containsKey(FEAT_ITEM_PRICE)) {
double price = (double) features.get(FEAT_ITEM_PRICE);
features.put(FEAT_ITEM_PRICE + "_log", Math.log1p(price));
}
// 对点击次数做Min-Max归一化
if (features.containsKey(FEAT_USER_ITEM_CLICK_COUNT)) {double count = (double) features.get(FEAT_USER_ITEM_CLICK_COUNT); double maxCount = 100.0; // 可根据历史数据确定 features.put(FEAT_USER_ITEM_CLICK_COUNT + "_norm", Math.min(count / maxCount, 1.0));}
// 对时间间隔做指数衰减
if (features.containsKey("user_item_last_click_time")) {long lastClickTime = (long) features.get("user_item_last_click_time"); long now = System.currentTimeMillis(); long hoursSinceLastClick = (now - lastClickTime) / (3600 * 1000); // 指数衰减:越近的点击权重越大 double recencyWeight = Math.exp(-hoursSinceLastClick / 24.0); features.put("recency_weight", recencyWeight);}
} - Log变换:x' = log(1 + x)(适用于长尾分布的特征)
// ========== 以下是从Redis/数据库加载特征的实现 ==========
private UserFeatures loadUserFeaturesFromDB(Long userId) {
// 从数据库或Redis加载用户画像
// 实际实现中会查询用户表
UserFeatures features = new UserFeatures();
features.setUserId(userId);
features.setAge(25);
features.setGender(1);
features.setCity("beijing");
features.setPurchasePower(2); // 1低 2中 3高
features.setActiveLevel(3); // 1-5
return features;
}private ItemFeatures loadItemFeaturesFromDB(Long itemId) {
ItemFeatures features = new ItemFeatures();
features.setItemId(itemId);
features.setPrice(99.9);
features.setCategory("electronics");
features.setBrand("apple");
features.setSales7d(5000);
features.setCtr7d(0.032);
return features;
}private int getUserItemClickCount(Long userId, Long itemId) {
String key = String.format("user_item_click:%d:%d", userId, itemId);
Integer count = (Integer) redisTemplate.opsForValue().get(key);
return count != null ? count : 0;
}private int getUserItemBuyCount(Long userId, Long itemId) {
String key = String.format("user_item_buy:%d:%d", userId, itemId);
Integer count = (Integer) redisTemplate.opsForValue().get(key);
return count != null ? count : 0;
}private long getUserItemLastClickTime(Long userId, Long itemId) {
String key = String.format("user_item_last_click:%d:%d", userId, itemId);
Long time = (Long) redisTemplate.opsForValue().get(key);
return time != null ? time : 0;
}private int getUserCategoryClickCount(Long userId, String category) {
String key = String.format("user_category_click:%d:%s", userId, category);
Integer count = (Integer) redisTemplate.opsForValue().get(key);
return count != null ? count : 0;
}private double calculateUserCategoryPreference(Long userId, String category) {
// 计算用户对某个类目的偏好分数
// 偏好 = 该类目点击占比 权重 + 该类目购买占比 更高权重
int totalClicks = getUserTotalClicks(userId);
int categoryClicks = getUserCategoryClickCount(userId, category);int totalBuys = getUserTotalBuys(userId);
int categoryBuys = getUserCategoryBuyCount(userId, category);if (totalClicks == 0) return 0;
double clickScore = (double) categoryClicks / totalClicks;
double buyScore = totalBuys > 0 ? (double) categoryBuys / totalBuys : 0;// 购买行为的权重更高
return clickScore 0.3 + buyScore 0.7;
}private void startRefreshTask() {
// 定时刷新特征缓存
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(() -> {log.info("Refreshing feature cache..."); // 刷新统计特征 refreshStatFeatures(); // 清理过期的用户特征缓存 userFeatureCache.clear(); itemFeatureCache.clear();}, 1, 1, TimeUnit.HOURS);
}private void refreshStatFeatures() {
// 从数据库加载最新的统计特征
// 如商品7日CTR、用户活跃度等
}private List getUserRecentActions(Long userId, int limit) {
// 从Redis获取用户最近的行为序列
// 使用Sorted Set存储,时间戳作为score
List actions = new ArrayList<>();
// 实现省略
return actions;
}private List getRecentUserCategories(Long userId, int minutes) {
// 获取最近几分钟内用户点击的类目
return new ArrayList<>();
}private double getUserPricePreference(Long userId) {
// 获取用户的平均购买价格
return 200.0;
}private double calculatePriceMatchScore(double itemPrice, double userPricePref) {
// 计算价格匹配度
double ratio = Math.min(itemPrice, userPricePref) / Math.max(itemPrice, userPricePref);
return ratio;
}private double calculateRecencyScore(List recentCategories, String category) {
// 计算类目在最近序列中的新鲜度分数
if (recentCategories.isEmpty()) return 0;
for (int i = 0; i < recentCategories.size(); i++) {if (recentCategories.get(i).equals(category)) { // 越近的位置权重越高 return Math.exp(-i); }}
return 0;
}private double calculateDiversity(List sequence) {
// 计算序列多样性
long uniqueCount = sequence.stream().distinct().count();
return (double) uniqueCount / sequence.size();
}private List calculateTimeIntervals(List actions) {
List intervals = new ArrayList<>();
for (int i = 1; i < actions.size(); i++) {intervals.add(actions.get(i).getTimestamp() - actions.get(i-1).getTimestamp());}
return intervals;
}private boolean isShoppingFestival(int month) {
// 双11、618等购物节
return month == Calendar.NOVEMBER || month == Calendar.JUNE;
}private String getItemCategory(Long itemId) {
// 获取商品类目
return "electronics";
}private int getUserTotalClicks(Long userId) {
// 获取用户总点击次数
return 100;
}private int getUserTotalBuys(Long userId) {
// 获取用户总购买次数
return 10;
}private int getUserCategoryBuyCount(Long userId, String category) {
// 获取用户在某类目的购买次数
return 2;
}
}
```
来源:
https://tmywi.cn/