在现代推荐系统中,随着用户量和物品量的增长,传统的推荐算法可能会面临性能瓶颈。本文将介绍如何基于 ThinkPHP 实现一个高性能的推荐系统,结合显性反馈(如兴趣选择)、隐性反馈(如观看时长、评论、点赞、搜索等)、行为序列分析和关键词拆分(支持中文)等功能,并通过优化方案支持大规模用户场景。
目录
推荐系统简介
数据库设计
推荐算法类的实现
优化方案
总结与扩展
推荐系统简介
推荐系统的目标是根据用户的历史行为,预测用户可能感兴趣的物品。常见的推荐算法包括:
基于内容的推荐:根据物品的属性推荐相似物品。
协同过滤:根据用户的行为推荐相似用户喜欢的物品。
混合推荐:结合多种推荐算法,提升推荐效果。
本文将重点介绍基于协同过滤的推荐算法,并结合显性反馈和隐性反馈数据。
数据库设计
1. 用户表 (users
)
存储用户的基本信息。
CREATE TABLE users (user_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID,主键,自增',username VARCHAR(50) NOT NULL COMMENT '用户名,唯一标识用户'
) COMMENT='用户表,存储用户的基本信息';
2. 物品表 (items
)
存储物品的基本信息。
CREATE TABLE items (item_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '物品ID,主键,自增',item_name VARCHAR(100) NOT NULL COMMENT '物品名称,唯一标识物品',description TEXT COMMENT '物品描述,用于关键词拆分',tags TEXT COMMENT '物品标签,用于关键词拆分'
) COMMENT='物品表,存储物品的基本信息';
3. 显性反馈表 (explicit_feedback
)
存储用户对物品的显性反馈,如兴趣选择、评分等。
CREATE TABLE explicit_feedback (feedback_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '反馈ID,主键,自增',user_id INT COMMENT '用户ID,外键,关联用户表',item_id INT COMMENT '物品ID,外键,关联物品表',rating INT COMMENT '用户对物品的评分(1-5)',interest_level ENUM('low', 'medium', 'high') COMMENT '用户兴趣选择(低、中、高)',FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE, -- 用户删除时,关联的反馈也删除FOREIGN KEY (item_id) REFERENCES items(item_id) ON DELETE CASCADE -- 物品删除时,关联的反馈也删除
) COMMENT='显性反馈表,存储用户对物品的显性反馈';
4. 隐性反馈表 (implicit_feedback
)
存储用户的隐性行为,如观看时长、评论、点赞、搜索等。
CREATE TABLE implicit_feedback (feedback_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '反馈ID,主键,自增',user_id INT COMMENT '用户ID,外键,关联用户表',item_id INT COMMENT '物品ID,外键,关联物品表',action_type ENUM('view', 'comment', 'like', 'search') COMMENT '行为类型(观看、评论、点赞、搜索)',action_value FLOAT COMMENT '行为值(如观看时长、点赞次数等)',FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE, -- 用户删除时,关联的反馈也删除FOREIGN KEY (item_id) REFERENCES items(item_id) ON DELETE CASCADE -- 物品删除时,关联的反馈也删除
) COMMENT='隐性反馈表,存储用户的隐性行为';
5. 行为序列表 (behavior_sequence
)
存储用户的行为序列,按时间排序。
CREATE TABLE behavior_sequence (sequence_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '序列ID,主键,自增',user_id INT COMMENT '用户ID,外键,关联用户表',item_id INT COMMENT '物品ID,外键,关联物品表',action_type ENUM('view', 'comment', 'like', 'search') COMMENT '行为类型(观看、评论、点赞、搜索)',action_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '行为发生时间,默认为当前时间',FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE, -- 用户删除时,关联的行为也删除FOREIGN KEY (item_id) REFERENCES items(item_id) ON DELETE CASCADE -- 物品删除时,关联的行为也删除
) COMMENT='行为序列表,存储用户的行为序列';
6. 关键词表 (keywords
)
存储从物品描述和标签中提取的关键词。
CREATE TABLE keywords (keyword_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '关键词ID,主键,自增',keyword VARCHAR(50) NOT NULL COMMENT '关键词,用于推荐算法',item_id INT COMMENT '物品ID,外键,关联物品表',FOREIGN KEY (item_id) REFERENCES items(item_id) ON DELETE CASCADE -- 物品删除时,关联的关键词也删除
) COMMENT='关键词表,存储从物品描述和标签中提取的关键词';
ThinkPHP 推荐算法类
以下是基于 ThinkPHP 的推荐算法类实现,每行代码都有详细注释。
<?phpnamespace app\common\service;use think\Db;
use think\facade\Cache;class Recommender
{// 获取用户的显性反馈(兴趣选择和评分)private function getExplicitFeedback(int $userId): array{// 查询用户的显性反馈数据return Db::name('explicit_feedback')->where('user_id', $userId) // 过滤指定用户->field('item_id, rating, interest_level') // 选择需要的字段->select(); // 返回查询结果}// 获取用户的隐性反馈(观看时长、评论、点赞、搜索等)private function getImplicitFeedback(int $userId): array{// 查询用户的隐性反馈数据,按物品和行为类型分组return Db::name('implicit_feedback')->where('user_id', $userId) // 过滤指定用户->field('item_id, action_type, SUM(action_value) as total_value') // 选择需要的字段,并计算行为值的总和->group('item_id, action_type') // 按物品和行为类型分组->select(); // 返回查询结果}// 获取用户的行为序列private function getBehaviorSequence(int $userId): array{// 查询用户的行为序列,按时间排序return Db::name('behavior_sequence')->where('user_id', $userId) // 过滤指定用户->order('action_time ASC') // 按行为时间升序排序->field('item_id, action_type, action_time') // 选择需要的字段->select(); // 返回查询结果}// 从物品描述和标签中提取关键词(支持中文)private function extractKeywords(string $text): array{// 使用 jieba-php 分词库进行中文分词$words = \Fukuball\Jieba\Jieba::cut($text); // 将文本拆分为单词$stopWords = ['的', '了', '在', '是', '我']; // 中文停用词列表return array_diff($words, $stopWords); // 去除停用词,返回关键词数组}// 获取物品的关键词private function getItemKeywords(int $itemId): array{// 查询物品的描述和标签$item = Db::name('items')->where('item_id', $itemId) // 过滤指定物品->field('description, tags') // 选择需要的字段->find(); // 返回单行数据$keywords = [];if ($item) {// 从描述和标签中提取关键词$keywords = array_merge($this->extractKeywords($item['description']), // 提取描述中的关键词$this->extractKeywords($item['tags']) // 提取标签中的关键词);}return array_unique($keywords); // 去重后返回关键词数组}// 计算用户相似度(基于显性和隐性反馈)private function calculateUserSimilarity(int $user1, int $user2): float{// 获取用户1的显性反馈$feedback1 = $this->getExplicitFeedback($user1);// 获取用户2的显性反馈$feedback2 = $this->getExplicitFeedback($user2);$dotProduct = 0; // 点积$magnitude1 = 0; // 用户1的模长$magnitude2 = 0; // 用户2的模长// 遍历用户1的反馈数据foreach ($feedback1 as $item) {$itemId = $item['item_id']; // 物品ID$rating1 = $item['rating']; // 用户1的评分$interestLevel1 = $item['interest_level'] === 'high' ? 1 : 0; // 用户1的兴趣选择(高为1,否则为0)// 遍历用户2的反馈数据foreach ($feedback2 as $item2) {if ($item2['item_id'] === $itemId) { // 如果两个用户对同一物品有反馈$rating2 = $item2['rating']; // 用户2的评分$interestLevel2 = $item2['interest_level'] === 'high' ? 1 : 0; // 用户2的兴趣选择(高为1,否则为0)// 计算点积和模长$dotProduct += ($rating1 * $rating2) + ($interestLevel1 * $interestLevel2);$magnitude1 += ($rating1 * $rating1) + ($interestLevel1 * $interestLevel1);$magnitude2 += ($rating2 * $rating2) + ($interestLevel2 * $interestLevel2);}}}$magnitude1 = sqrt($magnitude1); // 用户1的模长$magnitude2 = sqrt($magnitude2); // 用户2的模长if ($magnitude1 == 0 || $magnitude2 == 0) {return 0; // 避免除零错误}return $dotProduct / ($magnitude1 * $magnitude2); // 返回余弦相似度}// 获取与目标用户最相似的用户private function getSimilarUsers(int $targetUserId): array{// 查询所有其他用户$users = Db::name('users')->where('user_id', '<>', $targetUserId) // 过滤掉目标用户->column('user_id'); // 返回用户ID列表$similarities = [];// 遍历所有用户,计算与目标用户的相似度foreach ($users as $userId) {$similarity = $this->calculateUserSimilarity($targetUserId, $userId); // 计算相似度$similarities[$userId] = $similarity; // 存储相似度}arsort($similarities); // 按相似度降序排序return $similarities; // 返回相似用户列表}// 为目标用户推荐物品(基于缓存)public function recommendItems(int $targetUserId, int $numRecommendations = 5): array{$cacheKey = "recommendations_{$targetUserId}"; // 缓存键$recommendations = Cache::get($cacheKey); // 从缓存中获取推荐结果if (!$recommendations) { // 如果缓存中没有推荐结果$similarUsers = $this->getSimilarUsers($targetUserId); // 获取相似用户$recommendations = [];// 遍历相似用户foreach ($similarUsers as $userId => $similarity) {$feedback = $this->getExplicitFeedback($userId); // 获取相似用户的显性反馈// 遍历相似用户的反馈数据foreach ($feedback as $item) {$itemId = $item['item_id']; // 物品ID$rating = $item['rating']; // 评分$interestLevel = $item['interest_level'] === 'high' ? 1 : 0; // 兴趣选择(高为1,否则为0)// 如果目标用户未评价过该物品,且评分或兴趣选择较高if (!isset($this->getExplicitFeedback($targetUserId)[$itemId]) && ($rating >= 4 || $interestLevel)) {if (!isset($recommendations[$itemId])) {$recommendations[$itemId] = 0; // 初始化推荐分数}// 计算推荐分数$recommendations[$itemId] += ($rating * $similarity) + ($interestLevel * $similarity);}}}arsort($recommendations); // 按推荐分数降序排序$recommendations = array_slice($recommendations, 0, $numRecommendations, true); // 返回前N个推荐物品// 缓存推荐结果,有效期1小时Cache::set($cacheKey, $recommendations, 3600);}return $recommendations; // 返回推荐结果}
}
优化方案
1.用户相似度计算的优化
- 离线计算:将用户相似度计算任务放到离线任务中,定期更新相似度矩阵。
- 分块计算:将用户分成多个块,分别计算块内用户的相似度,减少计算量。
- 近似算法:使用局部敏感哈希(LSH)或随机投影等近似算法,快速找到相似用户。
2、推荐物品的优化
- 基于物品的协同过滤:计算物品之间的相似度,推荐与用户历史行为相似的物品。
- 矩阵分解:使用矩阵分解(如SVD)将用户-物品评分矩阵分解为低维矩阵,减少计算量。
- 缓存推荐结果:将推荐结果缓存到Redis等内存数据库中,减少实时计算的压力。
3、数据库查询的优化
- 索引优化:为常用查询字段(如user_id、item_id)添加索引。
- 分库分表:将用户和物品数据分散到多个数据库或表中,减少单表数据量。
- 批量查询:减少数据库查询次数,尽量使用批量查询。
4、引入分布式计算
- 使用分布式计算框架(如Hadoop、Spark)处理大规模数据。
- 将用户相似度计算和推荐任务分布到多个节点上执行。
总结与扩展
通过上述代码和优化方案,可以实现一个高性能的推荐系统,支持大规模用户场景。以下是扩展方向:
- 引入机器学习模型:如矩阵分解(SVD)或深度学习模型,提升推荐效果。
- 实时推荐:根据用户的最新行为动态调整推荐结果。
- 多维度权重:为不同类型的隐性反馈(如观看时长、点赞)设置不同的权重。
希望本文对你理解和实现推荐系统有所帮助!如果有其他问题,欢迎留言讨论。