协同过滤是推荐系统的核心算法。本文将用工程师的视角,深入解析 BPR 算法,避免复杂数学,重点理解"为什么这么设计"。
目录
- 协同过滤的直观理解
- 矩阵分解:降维的艺术
- BPR算法:成对学习的智慧
- 源码剖析:Gorse 的 BPR 实现
- 训练优化技巧
- 实战:手写简化版 BPR
- 性能调优指南
协同过滤的直观理解
什么是协同过滤?
场景:你在选电影- 传统方式(基于内容):
- 你喜欢科幻片 → 推荐科幻片
- 协同过滤:
- 和你口味相似的人喜欢X → 推荐X给你
复制代码 "协同"的含义:利用群体智慧- alice 喜欢:A, B, C
- bob 喜欢: A, B, D
- charlie 喜欢:A, C, E
- 观察:
- - alice 和 bob 都喜欢 A, B → 口味相似
- - 推荐:D 给 alice(bob 喜欢但 alice 没看过)
- - 推荐:C 给 bob(alice 喜欢但 bob 没看过)
复制代码 两种协同过滤
User-Based(基于用户):- 1. 找到和你相似的用户
- 2. 看他们喜欢什么
- 3. 推荐给你
- 问题:
- - 用户数量大时计算慢(100万用户 → 100万²次比较)
- - 用户兴趣变化快
复制代码 Item-Based(基于物品):- 1. 找到你喜欢物品的相似物品
- 2. 推荐相似物品
- 优点:
- - 物品数量相对稳定
- - 可以预计算
复制代码 Matrix Factorization(矩阵分解):- 最现代的方法!
- - 用向量表示用户和物品
- - 通过机器学习找到最佳向量
- - 这就是我们要讲的重点
复制代码 矩阵分解:降维的艺术
问题引入
用户-物品交互矩阵:- movie1 movie2 movie3 movie4 movie5
- alice 1 0 1 0 0
- bob 0 1 0 1 0
- charlie 1 0 0 0 1
- 1 = 喜欢,0 = 未交互
- 问题:
- 1. 矩阵很稀疏(99% 是 0)
- 2. 无法预测未交互的(?)
复制代码 矩阵分解的想法
核心思想:用低维向量表示用户和物品- 原始矩阵:100万用户 × 100万物品 = 1万亿个数
- 分解后:
- - 用户向量:100万 × 50维 = 5000万个数
- - 物品向量:100万 × 50维 = 5000万个数
- - 总计:1亿个数
- 压缩率:1万亿 / 1亿 = 10000 倍!
复制代码 数学表示(简化版)
- 评分预测:
- r̂ = 用户向量 · 物品向量
- 具体例子:
- alice 的向量:[0.8, 0.2, 0.5] (3维)
- movie1 的向量:[0.9, 0.1, 0.6]
- 预测 alice 对 movie1 的评分:
- r̂ = 0.8×0.9 + 0.2×0.1 + 0.5×0.6
- = 0.72 + 0.02 + 0.3
- = 1.04 (归一化后接近 1,表示喜欢)
复制代码 向量的含义(可解释性)
假设用 3 维向量表示:- 维度1: 科幻程度
- 维度2: 动作程度
- 维度3: 文艺程度
- alice 的向量:[0.9, 0.2, 0.1]
- → 非常喜欢科幻,不太喜欢动作,不喜欢文艺
- 电影《星际穿越》的向量:[0.95, 0.1, 0.3]
- → 科幻片,少量动作,有点文艺
- 预测 alice 对《星际穿越》的评分:
- 0.9×0.95 + 0.2×0.1 + 0.1×0.3 = 0.905(很高!)
复制代码 注意:实际中向量维度更高(50-200维),含义不一定可解释。
BPR算法:成对学习的智慧
为什么需要 BPR?
传统方法的问题:- 问题:预测评分(1-5星)
- 数据:alice 给 movie1 打了 5 星
- 传统方法:
- 目标 = 让预测值接近 5
- 问题:
- - 推荐系统中大部分是隐式反馈(点击、浏览)
- - 没有明确的评分
- - 只知道用户喜欢什么,不知道具体多喜欢
复制代码 BPR 的创新:- 不预测绝对评分,而是预测相对偏好
- 数据:
- - alice 看了 movie1(正样本)
- - alice 没看 movie2(负样本)
- 目标:
- 让 score(alice, movie1) > score(alice, movie2)
- 这就是"成对学习"(Pairwise Learning)
复制代码 BPR 的核心思想
- # 伪代码
- for each user:
- positive_item = 用户交互过的物品
- negative_item = 用户没交互过的物品(随机采样)
-
- score_pos = predict(user, positive_item)
- score_neg = predict(user, negative_item)
-
- # 目标:正样本分数 > 负样本分数
- loss = -log(sigmoid(score_pos - score_neg))
-
- # 梯度下降更新参数
- update_parameters()
复制代码 为什么用 sigmoid?- score_pos - score_neg 的范围:(-∞, +∞)
- sigmoid(x) = 1 / (1 + e^(-x))
- - x > 0 时,sigmoid(x) → 1(正样本分数高,好!)
- - x < 0 时,sigmoid(x) → 0(负样本分数高,不好)
- - x = 0 时,sigmoid(x) = 0.5(分不清)
- -log(sigmoid(x)):
- - x >> 0 时,loss → 0(已经很好了)
- - x << 0 时,loss → ∞(很差,需要优化)
- - x = 0 时,loss = 0.69(中等)
复制代码 学习率(lr):- func sampleNegative(user User, items []Item, interacted Set) Item {
- for {
- idx := rand.Int() % len(items)
- item := items[idx]
- if !interacted.Contains(item.ID) {
- return item // 找到一个未交互的
- }
- }
- }
复制代码 正则化(reg):- // 按热度的平方根采样(降低热门物品权重)
- func sampleNegativeByPopularity(items []Item, popularity []int) Item {
- weights := make([]float64, len(items))
- for i, pop := range popularity {
- weights[i] = math.Sqrt(float64(pop)) // 平方根
- }
- return weightedRandomSample(items, weights)
- }
复制代码 Gorse 的 AutoML
Gorse 使用 TPE(Tree-structured Parzen Estimator)自动搜索最佳超参数:- // model/cf/model.go
- type BPR struct {
- BaseMatrixFactorization
-
- // 超参数
- nFactors int // 向量维度(默认50)
- nEpochs int // 训练轮数(默认100)
- lr float32 // 学习率(默认0.05)
- reg float32 // 正则化系数(默认0.01)
-
- // 模型参数
- UserFactor [][]float32 // 用户向量 [n_users × n_factors]
- ItemFactor [][]float32 // 物品向量 [n_items × n_factors]
- }
复制代码 效果:- func NewBPR(params Params) *BPR {
- bpr := &BPR{
- nFactors: params.GetInt("n_factors", 50),
- nEpochs: params.GetInt("n_epochs", 100),
- lr: params.GetFloat("lr", 0.05),
- reg: params.GetFloat("reg", 0.01),
- }
- return bpr
- }
- // 初始化向量(小随机数)
- func (bpr *BPR) Init(trainSet dataset.CFSplit) {
- nUsers := trainSet.CountUsers()
- nItems := trainSet.CountItems()
-
- // 用户向量
- bpr.UserFactor = make([][]float32, nUsers)
- for i := range bpr.UserFactor {
- bpr.UserFactor[i] = make([]float32, bpr.nFactors)
- for j := range bpr.UserFactor[i] {
- // 小随机数初始化(-0.01 到 0.01)
- bpr.UserFactor[i][j] = (rand.Float32() - 0.5) * 0.02
- }
- }
-
- // 物品向量(同样方式)
- // ...
- }
复制代码 来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |