LightFM 购买数据如何设置交互权重?按用户最大购买频次归一化合理吗?
按用户最大购买频次归一化可以作为实验方案,但权重落在 (0, 1] 并不能说明它更合理。这种方法保留了用户内部的频次比例,同时去掉了不同用户之间的绝对频次尺度。是否采用,取决于重复购买对推荐目标有多大价值。
建议先建立已购买交互权重均为 1 的基线,再与平滑频次权重和当前方案比较。使用 LightFM 的 WARP 时,还要确保通过 sample_weight 传入权重。
WARP 中的交互值与权重有什么区别?
对于购买数据,可以将 interactions 中已购买的 (user, item) 设为 1,把重复购买带来的训练权重放入独立的 sample_weight。
LightFM 的 sample_weight 默认是 1。只把购买频次或归一化结果写入 interactions.data,再调用 model.fit(interactions),不会自动将这些值用作训练权重。具体可参考 LightFM 的权重处理源码。
WARP 寻找违反排序间隔的负样本后进行更新,sample_weight 参与对应交互的更新强度计算。这个权重不是购买概率,也不是模型需要预测的目标分数。权重为 1.0、0.4、0.1,并不意味着最终推荐分数要保持这一比例,也不直接要求三个已购买商品按此顺序排列。参见 WARP 实现。
按用户最大频次归一化会丢失什么?
原来的计算是:
weight(user, item) = frequency(user, item) / max_frequency_of_any_item_for_that_user
对同一个用户,所有频次都除以相同的数,因此 10:4:1 的比例没有改变。但如果另一个用户的频次是 100:40:10,两人的权重都会变成 1.0:0.4:0.1。
这样就忽略了两组记录之间的绝对强度差异。如果频次主要受用户活跃度、观察时长或补货习惯影响,归一化可能有帮助;如果多次购买确实代表更可靠的偏好证据,则可能损失这部分信息。
这种归一化还有两个影响:
- 最大频次很大时,其他已购买商品的权重会被压得很低。例如最高频次为 100,另一个商品只买过 1 次,后者的权重只有 0.01。
- 每个用户的最大权重为 1,并不代表每个用户的总权重相同。用户买过多少种商品、各商品的频次如何分布,仍会影响其训练贡献。
因此,最大值归一化不能直接实现“每个用户同等重要”。
重复购买权重应该怎样设计?
针对 top-K 推荐,可以先把购买作为正反馈,再决定重复购买应增加多少训练权重。
购买次数可能同时反映偏好、消耗速度、促销和购买机会。没有证据表明购买十次的可信度一定是购买一次的十倍,因此不宜把原始频次当作唯一标准。
可以比较三种方案:
| 方案 | 权重 | 用途与取舍 |
|---|---|---|
| 二值基线 | w = 1 | 先确认是否需要利用重复购买次数 |
| 平滑频次 | w = 1 + alpha * log(n),n >= 1 | 保留绝对频次差异,同时压缩高频值的影响 |
| 用户内最大值归一化 | w = n / max_user_frequency | 强调用户内部比例,去除绝对频次尺度 |
平滑公式是一个待验证的设计选项,并非 LightFM 的固定要求。alpha >= 0 控制频次的影响;alpha = 0 时退回二值基线。如果仍有极端高频记录,可以再实验设置权重上限。
选择相对偏好还是绝对强度,没有统一答案。如果目标是发现用户尚未买过的商品,可以先测试二值和温和的频次加权;如果目标包含补货、复购,则需要保留已购商品的推荐资格,并验证购买频次是否有帮助。仅靠累计频次无法表达距离上次购买的时间或补货周期。
如何把权重正确传给 LightFM?
下面的示例使用平滑频次方案。假设 train_transactions 是训练时间段内的交易 DataFrame,包含无缺失值的 user_id、item_id 两列,每行代表一次购买,且数据非空。
import numpy as np
import pandas as pd
from scipy.sparse import coo_matrix
from lightfm import LightFM
# Aggregate purchases within the training period only.
pairs = (
train_transactions.groupby(['user_id', 'item_id'])
.size()
.reset_index(name='frequency')
)
rows, user_ids = pd.factorize(pairs['user_id'], sort=False)
cols, item_ids = pd.factorize(pairs['item_id'], sort=False)
frequency = pairs['frequency'].to_numpy(dtype=np.float32)
interactions = coo_matrix(
(np.ones(len(pairs), dtype=np.float32), (rows, cols)),
shape=(len(user_ids), len(item_ids)),
)
# Illustrative value; choose alpha using validation data.
alpha = 0.5
sample_weight = interactions.copy()
sample_weight.data = (
1.0 + alpha * np.log(frequency)
).astype(np.float32)
model = LightFM(
loss='warp',
max_sampled=20,
no_components=32,
item_alpha=0.00001,
user_alpha=0.0000001,
random_state=42,
)
# Illustrative training duration, not a tuned optimum.
model.fit(
interactions,
sample_weight=sample_weight,
epochs=10,
num_threads=1,
)
sample_weight 应是与 interactions 形状相同的 COO 矩阵,两者的 row、col 数组也必须逐项一致。复制交互矩阵后替换 data,可以保留坐标顺序。这些约束见 LightFM 的 fit 文档。预测和评估时还需要复用这里生成的用户、商品 ID 映射。
如何判断哪种方案更好?
先按时间切分原始交易,再分别聚合。训练频次、用户最大频次和任何权重统计都只能使用训练时间段的数据,避免把未来购买信息带入训练。
接着固定候选商品范围、数据切分和评估口径,比较二值、平滑频次和当前归一化方案的 Recall@K 或 NDCG@K。初步对比可以使用相同的训练配置;确定候选方案后,再分别调整正则化和训练轮数,因为权重尺度变化也会改变训练过程。
评估时必须明确是否允许推荐已购商品:
- 新商品发现:从候选集合中排除已购商品,并使用未来首次购买的商品作为相关目标。
- 复购推荐:保留已购商品,允许训练期和测试期出现相同的用户商品对。不要直接套用会排除训练交互的评估设置,否则复购目标也会被排除。
当前的 max_sampled=20、no_components=32 和正则化参数,无法单独说明哪种权重更好。最终应以符合实际推荐场景的验证结果为依据;如果平滑频次不能稳定优于二值基线,保留权重 1 也是合理选择。
备注:内容仅供参考。
