- 机器学习
【免费下载链接】lightfm
A Python implementation of LightFM, a hybrid recommendation algorithm.
本文以 LightFM 仓库 doc/examples/warp_loss.rst 的官方示例为主线,系统讲解 WARP(Weighted Approximate-Rank Pairwise)损失函数在隐式反馈学习排序中的原理、与 BPR 的对比实验、max_sampled超参数的调优方法,并结合 lightfm/lightfm.py 与 lightfm/_lightfm_fast.pyx.template 的源码实现,让读者既能直接复现示例,又能理解 WARP 每一步背后的算法细节。
为什么选择 WARP Loss?
在推荐系统的隐式反馈(如点击、播放、购买记录)场景下,我们只知道用户“喜欢”哪些物品,却不知道用户“不喜欢”什么。LightFM 提供四种损失函数(见 lightfm.py 中 LightFM 类文档):
- logistic:适用于同时存在正样本(1)和负样本(-1)的显式反馈;
- BPR(Bayesian Personalised Ranking):最大化正样本与随机负样本的预测差值,适用于仅含正样本且以 ROC AUC 为优化目标的情形;
- WARP(Weighted Approximate-Rank Pairwise):通过不断采样负样本直到发现违反排序的样本,以提升正样本在推荐列表中的排名,适用于仅含正样本且以推荐列表头部质量(precision@k)为优化目标的情形;
- k-OS WARP:WARP 的 k 阶统计量变体,以每个用户第 k 个正样本作为 pairwise 更新的基准。
LightFM 很可能是目前唯一实现了 WARP 损失的推荐库。WARP 最初由 Weston 等人在 WSABIE 论文中用于图像标注,后经 2013 年 k-order statistic loss 论文扩展为适合推荐场景的 k-OS WARP 形式,LightFM 两种变体均已实现。在隐式反馈排序任务中,WARP 通常能显著优于更流行的 BPR 损失,本示例的实验将给出直观验证。
WARP 的核心思想:主动采样违反排序的负样本
BPR 与 WARP 都基于 (user, positive item, negative item) 三元组,但二者的负样本选取方式截然不同:
- BPR随机采样负样本,每次采样到一个负样本就进行一次梯度更新;
- WARP同样随机采样负样本,但只有当采样到的负样本“违反期望排序”时才执行梯度更新——即负样本的预测得分超过了正样本的得分加上一个 margin(源码中该 margin 为 1,见 fit_warp 实现 中的
negative_prediction > positive_prediction - 1判断)。
这种“只学自己不会的样本”的策略,近似于一种主动学习:模型挑选出当前无法正确排序的三元组来训练自己。WARP 的完整算法流程可以概括为:
- 给定一个 (user, positive item) 对,从其余物品中随机采样一个负样本,分别计算两者预测得分;
- 若负样本得分超过正样本得分 + margin,说明发生了排序违反,执行梯度更新,把正样本排名推高、负样本排名压低;
- 若未发生违反,则继续采样负样本,直到找到违反排序的样本或达到采样上限;
- 若第一次采样就找到违反样本,说明模型当前排序错误很多,执行大幅更新;若采样了很多次才找到,说明模型已接近最优,执行小幅更新。
这一“越难找、更新越小”的机制,是通过对样本次数取对数来加权的:在 fit_warp 源码 中,更新损失的权重为loss = weight * log(max(1.0, floor((item_features.rows - 1) / sampled))),即采样次数sampled越大,损失权重越低,并且损失被裁剪到MAX_LOSS = 10.0以下以保证数值稳定。直观理解是:sampled小意味着大量负样本排在正样本之前,需要大步更新;sampled大意味着违反排序的负样本已不多,应小幅更新。对 WARP 与 BPR 的理论细节,可参考 WSABIE 论文、k-OS WARP 论文及 Rendle 的 WSDM 2014 论文。
实验准备:加载 MovieLens 100K 数据集
示例使用 MovieLens 100K 数据集(约 1000 名用户对 1700 部电影的 100,000 条评分交互),通过 lightfm/datasets/movielens.py 中的fetch_movielens()获取。默认参数下它会以指示特征(indicator features)构建物品特征矩阵,数据缺失时自动下载到~/lightfm_data/目录。
import time import numpy as np import matplotlib import matplotlib.pyplot as plt from lightfm import LightFM from lightfm.datasets import fetch_movielens from lightfm.evaluation import auc_score movielens = fetch_movielens() train, test = movielens['train'], movielens['test']实验一:WARP 与 BPR 的精度对比
用完全相同的超参数分别训练 WARP 与 BPR 两个模型,逐 epoch 记录训练耗时与测试集 AUC:
alpha = 1e-05 epochs = 70 num_components = 32 warp_model = LightFM(no_components=num_components, loss='warp', learning_schedule='adagrad', max_sampled=100, user_alpha=alpha, item_alpha=alpha) bpr_model = LightFM(no_components=num_components, loss='bpr', learning_schedule='adagrad', user_alpha=alpha, item_alpha=alpha) warp_duration = [] bpr_duration = [] warp_auc = [] bpr_auc = [] for epoch in range(epochs): start = time.time() warp_model.fit_partial(train, epochs=1) warp_duration.append(time.time() - start) warp_auc.append(auc_score(warp_model, test, train_interactions=train).mean()) for epoch in range(epochs): start = time.time() bpr_model.fit_partial(train, epochs=1) bpr_duration.append(time.time() - start) bpr_auc.append(auc_score(bpr_model, test, train_interactions=train).mean())关键点说明:
fit_partial(train, epochs=1)每次只跑一个 epoch,便于逐轮观察指标与耗时;auc_score来自 lightfm/evaluation.py,其含义是“随机一个正样本得分高于随机一个负样本得分的概率”,完美分数为 1.0。传入train_interactions=train可让评估时排除训练集已知正样本,避免重复推荐已交互物品,详见 auc_score 的文档与实现;- 两者采用
adagrad学习率调度,L2 正则系数alpha = 1e-05,均为官方推荐的默认组合。
绘制两组 AUC 随 epoch 的变化曲线:
x = np.arange(epochs) plt.plot(x, np.array(warp_auc)) plt.plot(x, np.array(bpr_auc)) plt.legend(['WARP AUC', 'BPR AUC'], loc='upper right') plt.show()结果立刻显示出 WARP 的优越性:更聪明的负样本选取方式带来了更高质量的排序。同时注意,测试 AUC 在最初的约 10 个 epoch 后就开始下降,这说明 WARP 开始过拟合——此时应考虑加大正则化(user_alpha/item_alpha)或采用早停(early stopping)。这一结论也与仓库测试形成印证:tests/test_movielens.py 中的test_warp_precision系列测试在固定 epoch 数下对 WARP 精度设定断言,tests/test_api.py 的test_warp_few_items则专门验证仅有少量物品时 WARP 训练不会出错。
实验二:WARP 与 BPR 的拟合速度对比
同样的耗时数据,绘制逐 epoch 的训练时间曲线:
x = np.arange(epochs) plt.plot(x, np.array(warp_duration)) plt.plot(x, np.array(bpr_duration)) plt.legend(['WARP duration', 'BPR duration'], loc='upper right') plt.show()可以看到 WARP 在所有 epoch 上都比 BPR 慢,而且随着 epoch 增加越来越慢。原因在于 WARP 的自适应负采样:模型拟合训练数据越好,越难在有限的尝试内找到违反排序的负样本,就需要采样更多次,拟合时间随之变长。从源码 fit_warp 可以清楚看到这一行为:内层while sampled < lightfm.max_sampled循环反复采样并计算负样本表示与预测,直到出现negative_prediction > positive_prediction - 1才break执行更新;模型越收敛,这个循环就越难提前跳出。
为了控制这种“越训越慢”的现象,LightFM 暴露了max_sampled超参数(见 LightFM 构造参数文档):它限制 WARP 为找到一个违反排序的负样本所允许的最大采样次数。默认值为 50。调高它通常会让训练更慢、但某些情况下可能提升精度;调低它则能显著缩短训练时间。
实验三:降低 max_sampled 后的耗时与精度
将max_sampled从 100 降到 3,重新训练并绘图:
warp_model = LightFM(no_components=num_components, max_sampled=3, loss='warp', learning_schedule='adagrad', user_alpha=alpha, item_alpha=alpha) warp_duration = [] warp_auc = [] for epoch in range(epochs): start = time.time() warp_model.fit_partial(train, epochs=1) warp_duration.append(time.time() - start) warp_auc.append(auc_score(warp_model, test, train_interactions=train).mean()) x = np.arange(epochs) plt.plot(x, np.array(warp_duration)) plt.legend(['WARP duration'], loc='upper right') plt.title('Duration') plt.show() x = np.arange(epochs) plt.plot(x, np.array(warp_auc)) plt.legend(['WARP AUC'], loc='upper right') plt.title('AUC') plt.show()把max_sampled设低后,训练耗时反而随 epoch 递减。原因是:当在指定尝试次数内找不到违反排序的负样本时,模型根本不执行更新。模型越接近收敛,越容易出现“采样失败”,于是大量样本被跳过,每个 epoch 的实际计算量减少、耗时下降。代价是精度也随之下降,因为许多本可更新的三元组被放弃了——这正是一个典型的“训练速度与最终精度”权衡旋钮。
仓库测试 tests/test_movielens.py 的test_warp_precision_max_sampled从反面印证了这一点:把max_sampled设为 0 时,训练等价于“空转”,此时训练集与测试集的 AUC 都不超过 0.55(即不优于随机猜测)。
从源码看 WARP 的实现细节
loss='warp'时,LightFM._run_epoch 会先构建一个positives_lookup(用于快速判断采样到的物品是否为已知正样本),然后调用 Cython 层 fit_warp。整个流程的关键步骤包括:
- 过滤负值交互:
if not Y[row] > 0: continue——只有取值为正的交互才参与 WARP 训练; - 计算用户与正样本表示:通过
compute_representation将特征嵌入加权求和得到用户、物品的隐向量; - 自适应负采样循环:
while sampled < lightfm.max_sampled内随机采样负物品并计算其预测,若负样本得分高于正样本得分减 1(即违反排序 margin),则通过in_positives检查采样到的物品确实不是该用户的正样本(见 in_positives 实现,内部用二分查找判断),随后用对数加权损失执行warp_update梯度更新并跳出循环; - 损失裁剪:损失按
log(max(1.0, floor((n_items - 1) / sampled)))计算并被裁剪到MAX_LOSS = 10.0,防止数值不稳定; - 正则化:每轮结束后对超过
MAX_REG_SCALE的嵌入执行正则化(regularize),对应user_alpha/item_alpha两个 L2 惩罚参数。
此外,loss='warp-kos'会走 fit_warp_kos 分支:对每个用户先采样最多n个正样本并计算各自预测,按预测排序取第k个作为基准正样本(k 由构造参数k指定),再执行与 WARP 相同的负采样循环。当用户的正样本数不足时,no_positives = int_min(n, user_pids_stop - user_pids_start)保证不越界。仓库测试 tests/test_movielens.py 的test_warp_kos_precision验证了loss='warp-kos', k=5在 10 个 epoch 后即可获得可观的 precision 与 AUC。
调参实践建议
综合官方示例与源码,可将 WARP 在 LightFM 中的调参要点总结如下:
| 参数 | 默认值 | 对 WARP 的影响 | 调参方向 |
|---|---|---|---|
loss='warp' | — | 选择 WARP 损失 | 隐式反馈、追求列表头部排序质量时使用 |
max_sampled | 50 | 每个三元组负采样上限;值越大训练越慢、潜在精度越高;值越小训练越快、精度越低 | 数据量大或模型易过拟合时调低;追求极致精度时可调高 |
no_components | 32 | 隐向量维度,决定模型容量 | 数据规模大时可增大 |
learning_schedule | 'adagrad' | 学习率调度;'adadelta' 为另一选择 | 示例中使用 'adagrad' |
user_alpha/item_alpha | 0.0 | L2 正则强度;示例用 1e-05 | 观察测试 AUC 过拟合(如示例中 10 个 epoch 后下降)时加大;注意过大可能导致嵌入权重趋零 |
learning_rate | 0.05 | 初始学习率(adagrad) | 过大易震荡,过小收敛慢 |
epochs | 10 | 训练轮数 | 结合早停使用,示例实验跑 70 轮以观察过拟合趋势 |
工程实践要点:
- 关注前几个 epoch 的测试指标:官方示例显示 WARP 的测试 AUC 在约 10 个 epoch 后开始下降,说明 WARP 收敛快、也更容易过拟合,务必用验证集早停而非盲目多跑 epoch;
- 用
fit_partial做逐轮评估:fit_partial(train, epochs=1)循环可让你在训练的同时逐轮记录auc_score与耗时,复现本文的全部曲线; - 评估时排除训练正样本:调用
auc_score(model, test, train_interactions=train),避免把已知交互当作可推荐项,得到更真实的排序质量; - 警惕
max_sampled过低:max_sampled=0时 WARP 退化为空转(见test_warp_precision_max_sampled),精度不优于随机; max_sampled的耗时拐点可作为收敛信号:若每个 epoch 耗时随训练递减,说明模型已很难找到违反排序的样本,接近收敛,可考虑停止训练。
本文的完整可运行 Notebook 位于 examples/movielens/warp_loss.ipynb,对应的文档源文件为 doc/examples/warp_loss.rst。想深入验证 WARP 与 BPR 的数值行为,可阅读并运行 tests/test_api.py 与 tests/test_movielens.py 中的相关测试;想进一步研究底层数值实现,可阅读 Cython 模板 lightfm/_lightfm_fast.pyx.template 中的fit_warp与fit_warp_kos。
- 机器学习
【免费下载链接】lightfm
A Python implementation of LightFM, a hybrid recommendation algorithm.
相关推荐
LightFM 隐式反馈推荐实战:基于 MovieLens 数据集的 BPR 与 WARP 模型训练与评估
LightFM 隐式反馈推荐实战:基于 MovieLens 数据集的 BPR 与 WARP 模型训练与评估 本篇技术指南围绕 LightFM 开源仓库中的 mo
机器学习d2l-en 推荐系统个性化排序:BPR 与 Hinge 成对损失(Pairwise Loss)实战解析
d2l en 推荐系统个性化排序:BPR 与 Hinge 成对损失(Pairwise Loss)实战解析 个性化排序(Personalized Ranking)
文档教程人工智能深度学习NLP计算机视觉强化学习LightFM 官方示例全解:从隐式反馈推荐到冷启动混合模型实战指南
LightFM 官方示例全解:从隐式反馈推荐到冷启动混合模型实战指南 LightFM 是一个基于 Python 的混合推荐算法实现:它既支持隐式反馈(impli
机器学习
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考