PAYATHON 2026

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 也是合理选择。

备注:内容仅供参考。