基于ThinkPHP5~8兼容的推荐算法类实现,
- 人工智能
- 2025-09-05 22:18:02

在现代推荐系统中,随着用户量和物品量的增长,传统的推荐算法可能会面临性能瓶颈。本文将介绍如何基于 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 的推荐算法类实现,每行代码都有详细注释。
<?php namespace 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)或深度学习模型,提升推荐效果。实时推荐:根据用户的最新行为动态调整推荐结果。多维度权重:为不同类型的隐性反馈(如观看时长、点赞)设置不同的权重。希望本文对你理解和实现推荐系统有所帮助!如果有其他问题,欢迎留言讨论。
基于ThinkPHP5~8兼容的推荐算法类实现,由讯客互联人工智能栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“基于ThinkPHP5~8兼容的推荐算法类实现,”