Loading...
正在加载...
请稍候

智柴论坛推荐系统技术文档

✨步子哥 (steper) 2025年11月12日 06:06
## 目录 1. [系统概述](#系统概述) 2. [架构设计](#架构设计) 3. [核心组件](#核心组件) 4. [推荐算法](#推荐算法) 5. [数据流与缓存机制](#数据流与缓存机制) 6. [降级策略](#降级策略) 7. [性能优化](#性能优化) 8. [使用示例](#使用示例) 9. [未来改进方向](#未来改进方向) --- ## 系统概述 智柴论坛推荐系统是一个**混合推荐系统**,结合了 **协同过滤(Collaborative Filtering)** 和 **基于内容的推荐(Content-Based)** 两种算法,为用户提供个性化的话题推荐。 ### 核心特性 - **混合推荐算法**:协同过滤(60%)+ 内容相似度(40%) - **异步缓存架构**:通过 Redis 缓存 + SQLite 持久化,确保高性能 - **智能降级策略**:推荐失败时自动回退到热门话题 - **实时计算**:支持用户推荐和话题相似度推荐两种场景 - **中文分词支持**:针对中英文混合内容优化的分词算法 ### 推荐场景 1. **用户推荐**:基于用户历史行为(发布话题、回复)推荐感兴趣的话题 2. **话题推荐**:基于话题内容相似度推荐相关话题(用于话题详情页) --- ## 架构设计 ### 整体架构图 ``` ┌─────────────────────────────────────────────────────────────┐ │ 前端请求层 │ │ TopicController.showTopic() │ │ └─> RecommendationService.getRelatedTopics() │ └──────────────────────┬──────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 服务层 (RecommendationService) │ │ - 缓存键管理 │ │ - 参数验证与限制 │ │ - 调用 DataService 获取数据 │ └──────────────────────┬──────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 缓存层 (DataService) │ │ - Redis 缓存查询 │ │ - 缓存未命中时发送异步填充请求 │ │ - 等待异步结果(最多 2.5 秒) │ └──────────────────────┬──────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 异步队列 (cache_fill_queue) │ │ Redis List: zhichai:cache_fill_queue │ └──────────────────────┬──────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 后台处理进程 (process_sqlite_queue.php) │ │ AsyncSQLiteWriter.executeCacheFillTask() │ │ ├─> handleUserRecommendations() │ │ └─> handleTopicRecommendations() │ └──────────────────────┬──────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 推荐引擎 (RecommendationEngine) │ │ ├─> buildHybridScores() (用户推荐) │ │ │ ├─> computeCollaborativeScores() │ │ │ └─> computeContentScores() │ │ └─> rankRelatedTopics() (话题推荐) │ │ └─> computeTfIdfSimilarities() │ └──────────────────────┬──────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 数据存储层 │ │ ├─> SQLite: topics, replies, user_topic_views │ │ └─> Redis: 推荐结果缓存 (TTL: 15分钟) │ └─────────────────────────────────────────────────────────────┘ ``` ### 关键设计原则 1. **异步优先**:推荐计算在后台进程异步执行,不阻塞主请求 2. **缓存优先**:所有推荐结果优先从 Redis 缓存读取 3. **降级保障**:推荐失败时自动回退到热门话题,确保用户体验 4. **数据分离**:推荐引擎是纯计算逻辑,不依赖外部资源,便于测试 --- ## 核心组件 ### 1. RecommendationService **职责**:推荐服务的入口,负责缓存管理和参数验证 **关键方法**: ```php // 获取用户推荐 getRecommendationsForUser(int $userId, int $limit = 10): array // 获取话题相关推荐 getRelatedTopics(int $topicId, int $limit = 10): array ``` **配置参数**: - `DEFAULT_LIMIT = 10`:默认推荐数量 - `MAX_LIMIT = 20`:最大推荐数量 - `CACHE_TTL = 900`:缓存有效期(15分钟) - `WAIT_TIMEOUT_MS = 2500`:等待异步结果超时(2.5秒) ### 2. RecommendationEngine **职责**:纯计算引擎,实现推荐算法核心逻辑 **关键方法**: ```php // 混合推荐得分计算(用户推荐) buildHybridScores( int $targetUserId, array $targetInteractions, // 目标用户与话题的交互权重 array $allUserInteractions, // 所有用户与话题的交互权重 array $topicContents, // 话题内容数据 array $excludeTopicIds = [], // 排除的话题ID int $limit = 10 ): array // 话题相似度排序(话题推荐) rankRelatedTopics( array $queryTopic, // 查询话题 array $candidateDocs, // 候选话题 int $limit = 10 ): array ``` **算法权重**: - `CF_WEIGHT = 0.6`:协同过滤权重 - `CONTENT_WEIGHT = 0.4`:内容相似度权重 - `MIN_SIMILARITY = 0.01`:最小相似度阈值 ### 3. AsyncSQLiteWriter **职责**:异步处理推荐计算,负责数据收集和结果构建 **关键方法**: ```php // 处理用户推荐请求 handleUserRecommendations(\PDO $pdo, array $payload): array // 处理话题推荐请求 handleTopicRecommendations(\PDO $pdo, array $payload): array // 收集用户交互数据 collectUserInteractionsForRecommendations(\PDO $pdo, int $userId): array // 收集全局交互数据 collectGlobalInteractionsForRecommendations(\PDO $pdo, array &$topicDetails, int $excludeUserId): array ``` --- ## 推荐算法 ### 1. 用户推荐算法(混合推荐) #### 1.1 数据收集 **用户交互权重**: - 发布话题:权重 `3.0` - 回复话题:权重 `1.0` **数据范围**: - 用户发布的话题:最近 200 条 - 用户回复的话题:最近 400 条 - 全局话题数据:最近 1000 条(用于协同过滤) - 全局回复数据:最近 2000 条(用于协同过滤) #### 1.2 协同过滤(Collaborative Filtering) **算法**:基于用户的协同过滤(User-Based CF) **步骤**: 1. **计算用户相似度**: ```php similarity(userA, userB) = cosine_similarity( userA_interactions, // 用户A与话题的交互向量 userB_interactions // 用户B与话题的交互向量 ) ``` 2. **推荐得分计算**: ```php score(topic) = Σ(similarity(user, similar_user) × weight(similar_user, topic)) ``` 其中: - `similar_user`:与目标用户相似的其他用户 - `weight(similar_user, topic)`:相似用户对话题的交互权重 - 只考虑相似度 ≥ 0.01 的用户 3. **排除规则**: - 排除用户已交互过的话题 - 排除隐藏的话题 #### 1.3 内容相似度(Content-Based) **算法**:TF-IDF + 余弦相似度 **步骤**: 1. **构建用户兴趣向量**: ```php user_vector = Σ(topic_vector × (1 + interaction_weight)) ``` 其中: - `topic_vector`:话题的 TF-IDF 向量 - `interaction_weight`:用户对该话题的交互权重 2. **计算候选话题相似度**: ```php similarity(topic) = cosine_similarity(user_vector, topic_tfidf_vector) ``` 3. **TF-IDF 计算**: ```php TF(term, doc) = count(term in doc) IDF(term) = log((total_docs + 1) / (docs_containing_term + 1)) + 1 TF-IDF(term, doc) = TF(term, doc) × IDF(term) ``` #### 1.4 混合得分计算 **公式**: ```php final_score = 0.6 × normalized_cf_score + 0.4 × normalized_content_score ``` **归一化**: - 协同过滤得分和内容得分分别归一化到 [0, 1] - 归一化方法:`normalized_score = score / max_score` **排序规则**: 1. 按最终得分降序 2. 得分相同时,优先协同过滤得分高的 3. 协同过滤得分也相同时,优先内容得分高的 ### 2. 话题推荐算法(内容相似度) **算法**:TF-IDF + 余弦相似度 **步骤**: 1. **候选话题筛选**: - 排除当前话题本身 - 排除隐藏的话题 - 从最近创建的话题中选择(默认 100 条) 2. **TF-IDF 向量化**: - 对查询话题和候选话题进行分词 - 计算每个话题的 TF-IDF 向量 3. **相似度计算**: ```php similarity(topic) = cosine_similarity(query_tfidf_vector, candidate_tfidf_vector) ``` 4. **排序与筛选**: - 按相似度降序排序 - 返回前 N 个话题(N = limit × 2,用于后续过滤) ### 3. 分词算法 **分词策略**: 1. **文本预处理**: - 转换为小写 - HTML 实体解码 2. **正则匹配**: ```regex /[\p{L}\p{N}]+/u ``` - 匹配所有字母和数字的连续序列 3. **中文处理**: - 如果 token 包含中文且长度 > 1,拆分为单字 - 例如:"人工智能" → ["人", "工", "智", "能"] 4. **停用词过滤**: - 过滤常见停用词(中英文) - 过滤单字符 token(除非是中文单字) 5. **停用词表**: ```php // 英文停用词 'the', 'and', 'for', 'with', 'that', 'this', ... // 中文停用词 '的', '了', '和', '是', '我', '我们', '你', ... // 技术停用词 'php', 'code', 'topic', 'reply', 'forum', 'http', 'https', ... ``` --- ## 数据流与缓存机制 ### 数据流图 ``` 用户请求推荐 │ ▼ RecommendationService.getRelatedTopics() │ ├─> 检查 Redis 缓存 │ ├─> 缓存命中 → 直接返回 │ └─> 缓存未命中 → 继续 │ ▼ DataService.get() │ ├─> 发送异步填充请求到 cache_fill_queue │ └─> 等待异步结果(最多 2.5 秒) │ ├─> 收到结果 → 返回并写入缓存 │ └─> 超时 → 返回 null │ ▼ process_sqlite_queue.php (后台进程) │ ├─> 从 cache_fill_queue 读取请求 ├─> 调用 AsyncSQLiteWriter.executeCacheFillTask() │ └─> handleTopicRecommendations() │ ├─> 从 SQLite 查询话题数据 │ ├─> 调用 RecommendationEngine.rankRelatedTopics() │ └─> 构建推荐结果 │ ▼ 写入 Redis 缓存 │ └─> 缓存键: topic_recommendations:{topicId}:{limit} TTL: 900 秒(15分钟) ``` ### 缓存策略 **缓存键格式**: - 用户推荐:`user_recommendations:{userId}:{limit}` - 话题推荐:`topic_recommendations:{topicId}:{limit}` **缓存结构**: ```json { "id": 123, "title": "话题标题", "snippet": "内容摘要...", "score": 0.8523, "cf_score": 0.6234, "content_score": 0.4567, "created_at": "2025-01-15 18:30:00", "author_id": 456 } ``` **缓存 TTL**: - 推荐结果:900 秒(15分钟) - NULL 结果:2 秒(防止频繁查询不存在的数据) ### 异步填充机制 **队列名称**:`zhichai:cache_fill_queue` **消息格式**: ```json { "type": "topic_recommendations", "payload": { "topic_id": 123, "limit": 5 }, "response_key": "cache_response:req_67890abcdef" } ``` **响应机制**: 1. 后台进程计算完成后,将结果写入 `cache_response:{requestId}` 2. 主进程通过 `BRPOP` 等待响应(最多 2.5 秒) 3. 收到响应后,将结果写入主缓存键并返回 --- ## 降级策略 ### 降级场景 1. **用户无交互数据**:回退到热门话题 2. **协同过滤无结果**:仅使用内容推荐 3. **内容推荐无结果**:回退到热门话题 4. **话题不存在或隐藏**:回退到热门话题 5. **缓存填充超时**:返回空数组(前端不显示推荐) ### 热门话题算法 **排序规则**: ```sql ORDER BY ( COALESCE(rc.reply_count, 0) * 2 + (julianday('now') - julianday(t.created_at)) * -0.1 ) DESC ``` **得分公式**: ``` score = reply_count × 2 - days_since_created × 0.1 ``` **特点**: - 回复数越多,得分越高 - 时间越新,得分越高 - 平衡了热度和新鲜度 --- ## 性能优化 ### 1. 数据量限制 **用户推荐**: - 用户发布话题:最多 200 条 - 用户回复话题:最多 400 条 - 全局话题:最多 1000 条 - 全局回复:最多 2000 条 **话题推荐**: - 候选话题:最多 100 条(或 `limit × 10`) ### 2. 计算优化 **预计算候选集**: - 话题推荐时,先筛选候选话题(排除隐藏、排除自身) - 减少需要计算相似度的话题数量 **相似度阈值**: - 最小相似度:0.01 - 低于阈值的结果直接丢弃 **结果限制**: - 用户推荐:计算 `limit × 3` 个结果,最终返回 `limit` 个 - 话题推荐:计算 `limit × 2` 个结果,最终返回 `limit` 个 ### 3. 缓存优化 **缓存预热**: - 热门话题可以定期预热 - 用户推荐可以在用户活跃时预热 **缓存分层**: - 主缓存:完整的推荐结果(TTL: 15分钟) - NULL 缓存:防止频繁查询不存在的数据(TTL: 2秒) ### 4. 异步处理 **非阻塞设计**: - 推荐计算在后台进程异步执行 - 主请求最多等待 2.5 秒,超时后返回空结果 **队列机制**: - 使用 Redis List 作为消息队列 - 支持多个后台进程并发处理 --- ## 使用示例 ### 1. 在话题详情页展示推荐 ```php // TopicController.php $recommendationService = new \Services\RecommendationService(); $relatedTopicsRaw = $recommendationService->getRelatedTopics($topicId, 5); // 转换为 Topic 对象 foreach ($relatedTopicsRaw as $relatedTopicData) { if (isset($relatedTopicData['id'])) { $relatedTopic = $this->topicService->getTopicByID((int)$relatedTopicData['id']); // ... 添加到推荐列表 } } ``` ### 2. 获取用户推荐 ```php // 获取用户推荐(最多10个) $recommendationService = new \Services\RecommendationService(); $recommendations = $recommendationService->getRecommendationsForUser($userId, 10); foreach ($recommendations as $rec) { echo "推荐话题: {$rec['title']} (得分: {$rec['score']})\n"; echo " - 协同过滤得分: {$rec['cf_score']}\n"; echo " - 内容得分: {$rec['content_score']}\n"; } ``` ### 3. 前端展示 ```html
相关推荐
``` --- ## 未来改进方向 ### 1. 算法优化 **深度学习推荐**: - 引入神经网络模型(如 Wide & Deep) - 使用 Embedding 技术学习用户和话题的向量表示 **实时学习**: - 基于用户实时行为(点击、浏览时长)更新推荐 - 使用在线学习算法(如 FTRL) **多样性优化**: - 避免推荐结果过于相似 - 引入多样性指标(如 Intra-List Diversity) ### 2. 数据增强 **行为数据**: - 记录用户浏览时长 - 记录用户点赞、收藏行为 - 记录用户搜索关键词 **内容特征**: - 提取话题标签 - 分析话题情感倾向 - 识别话题类别(技术、生活、讨论等) ### 3. 性能提升 **分布式计算**: - 使用 Spark 或 Flink 进行大规模推荐计算 - 支持实时推荐和离线推荐两种模式 **缓存优化**: - 使用 Redis Cluster 支持更大规模 - 引入多级缓存(L1: 本地缓存,L2: Redis) **预计算**: - 定期批量计算热门推荐 - 使用增量更新机制 ### 4. 评估体系 **离线评估**: - A/B 测试框架 - 评估指标:准确率、召回率、覆盖率、多样性 **在线评估**: - 点击率(CTR) - 转化率(用户从推荐进入话题详情) - 用户满意度反馈 ### 5. 冷启动优化 **新用户**: - 基于注册信息(兴趣标签)推荐 - 基于地理位置推荐 - 推荐热门话题 **新话题**: - 基于话题标签匹配 - 基于作者历史话题推荐 - 基于话题类别推荐 --- ## 附录 ### A. 配置参数 | 参数 | 默认值 | 说明 | |------|--------|------| | `DEFAULT_LIMIT` | 10 | 默认推荐数量 | | `MAX_LIMIT` | 20 | 最大推荐数量 | | `CACHE_TTL` | 900 | 缓存有效期(秒) | | `WAIT_TIMEOUT_MS` | 2500 | 等待异步结果超时(毫秒) | | `CF_WEIGHT` | 0.6 | 协同过滤权重 | | `CONTENT_WEIGHT` | 0.4 | 内容相似度权重 | | `MIN_SIMILARITY` | 0.01 | 最小相似度阈值 | ### B. 数据库表结构 **topics 表**: - `id`: 话题ID - `title`: 话题标题 - `content`: 话题内容 - `author_id`: 作者ID - `created_at`: 创建时间 - `is_hidden`: 是否隐藏 **replies 表**: - `id`: 回复ID - `topic_id`: 话题ID - `author_id`: 作者ID - `content`: 回复内容 - `created_at`: 创建时间 **user_topic_views 表**(用于推荐系统): - `user_id`: 用户ID - `topic_id`: 话题ID - `view_count`: 浏览次数 - `last_viewed_at`: 最后浏览时间 ### C. 相关文件 - `src/Services/RecommendationService.php`:推荐服务入口 - `src/Services/RecommendationEngine.php`:推荐算法引擎 - `src/Services/AsyncSQLiteWriter.php`:异步数据处理 - `src/Services/DataService.php`:缓存服务 - `src/Controllers/TopicController.php`:话题控制器(使用推荐服务) - `views/topic_content.html`:话题详情页模板(展示推荐) --- **文档版本**:v1.0 **最后更新**:2025-01-15 **维护者**:智柴论坛开发团队

讨论回复

1 条回复
✨步子哥 (steper) #1
11-12 07:35
已将 emoji 纳入推荐权重计算。总结如下: ## 实现内容 ### 1. **Emoji 权重映射** 定义了 7 种 emoji 的权重,反映不同含义: - ❤️ (爱心):5.0 - 非常喜欢 - 🚀 (火箭):4.0 - 支持/推荐 - 👍 (点赞):3.0 - 喜欢 - ✅ (对勾):2.0 - 认可 - 👀 (眼睛):0.5 - 浏览/关注 - 👎 (点踩):-2.0 - 不喜欢(负权重) - ❌ (叉):-3.0 - 反对(负权重) ### 2. **核心方法** - `getEmojiWeights()`:返回 emoji 权重映射 - `getTopicEmojiWeight()`:获取单个话题的 emoji 权重总和 - `getTopicsEmojiWeights()`:批量获取多个话题的 emoji 权重 - `getUserEmojiWeightsForTopics()`:获取特定用户对特定话题的 emoji 权重 ### 3. **集成到推荐流程** - 用户推荐:在 `collectUserInteractionsForRecommendations()` 中,获取用户对所有话题的 emoji 权重,归一化后累加到交互权重 - 全局交互:在 `collectGlobalInteractionsForRecommendations()` 中,为每个用户获取其对话题的 emoji 权重,同样进行归一化处理 ### 4. **权重计算方式** - 归一化:emoji 权重除以 10,使其与基础权重(1.0-3.0)在同一量级 - 累加公式:`final_weight = base_weight + (emoji_weight_sum / 10.0)` - 负权重处理:👎 和 ❌ 会降低话题的推荐得分,避免推荐用户不喜欢的内容 ### 5. **文档更新** - 在“推荐算法”部分添加了 emoji 权重说明 - 新增“Emoji 权重系统”章节,详细说明设计理念、权重映射、计算方式和效果 ## 效果预期 1. 更准确反映用户偏好:❤️ 和 🚀 比 👍 更能体现强烈兴趣 2. 避免推荐不喜欢内容:负权重会降低不感兴趣话题的得分 3. 提升推荐质量:结合 emoji 的推荐更符合用户真实意图 4. 性能优化:使用批量查询,减少数据库访问次数 系统已集成 emoji 权重计算,每次推荐都会考虑用户的 emoji 表态。