当前位置: 首页 > news >正文

使用lightGCN完整训练用户 + 商品向量的 3 步指南

一、问题背景

在用 LightGCN 给 NLP 商品向量加协同信号中,我们往往:

  • 把预训练的 item_emb(Sentence-T5)冻结
  • 只训练 user_emb
  • 取得了不错的召回指标(Recall@20=0.38,NDCG@20=0.21)。

但很多时候我们需要:

“如果商品特征也想跟着图结构一起微调,该怎么改?”
“预训练的特征会不会被图结构破坏?”
“如何控制商品向量的更新速度?”

本文用中文一次性讲透:如何把 item_emb 也变成可学习参数,同时避免踩坑。我们将从原理分析到代码实现,给出完整解决方案。


二、核心思路

LightGCN 本身 没有可训练权重(只有消息传递)。
真正需要优化的是:

  1. 用户向量 user_emb(随机初始化);
  2. 商品向量 item_emb(用预训练向量 warm-start)。

技术方案:

  • 将两者都设为 nn.Parameter,赋予梯度计算能力
  • 使用同一个优化器进行联合优化
  • 通过调整学习率控制更新速度

数学表达:

∂L/∂θ = [∂L/∂user_emb, ∂L/∂item_emb]
θ ← θ - η·∂L/∂θ

三、代码实战(3 步完成)

Step1:把商品向量变成可学习参数

import torch
import torch.nn as nn
import numpy as np# 1. 读取预训练商品向量(跳过 padding 行 0)
item_emb_np = np.memmap('All_Beauty.sent_emb', dtype='float32',mode='r', shape=(num_items + 1, 768))# 2. 转换为可学习参数
item_emb = nn.Parameter(torch.from_numpy(item_emb_np[1:1 + num_items]).clone().to(device),requires_grad=True  # 显式声明可训练
)# 关键细节:
# - .clone() 断开与 memmap 的共享内存
# - 默认 requires_grad=True
# - 建议先做归一化:item_emb.data = F.normalize(item_emb.data, p=2, dim=1)

Step2:优化器里同时放进用户 + 商品

# 用户向量初始化(推荐Xavier初始化)
user_emb = nn.Parameter(torch.empty(num_users, 768, device=device)
)
nn.init.xavier_uniform_(user_emb)# 基础版优化器
opt = torch.optim.Adam([user_emb, item_emb], lr=1e-3, weight_decay=1e-4)# 进阶版:差异化学习率(商品向量学习率更小)
opt = torch.optim.Adam([{'params': user_emb, 'lr': 1e-3},{'params': item_emb, 'lr': 3e-4}  # item学习率设为user的30%
], weight_decay=1e-4)

Step3:训练循环里动态拼接

for epoch in range(20):# 组装完整节点特征 [user ; item]full_feat = torch.cat([user_emb, item_emb], dim=0)# LightGCN前向传播emb = model(full_feat)  # 采样和损失计算pos_scores, neg_scores = sample_and_score(emb)loss = bpr_loss(pos_scores, neg_scores)# 反向传播opt.zero_grad()loss.backward()# 可选:梯度裁剪torch.nn.utils.clip_grad_norm_([user_emb, item_emb], max_norm=5.0)opt.step()# 可选:监控梯度变化print(f"User grad norm: {user_emb.grad.norm()}, Item grad norm: {item_emb.grad.norm()}")

四、常见疑问 & 技巧

疑问专业解答实践建议
预训练向量会不会被完全洗没?可以保留残差: item_emb = α * frozen + (1-α) * learnable,α 可衰减。初始α=0.8,每epoch线性衰减0.02
显存爆炸?100万商品×768维≈3GB显存。超大规模时:
1. 使用ZeRO优化器
2. 采用混合精度训练
3. 使用AdaFactor替代Adam
对于>500万商品,建议分shard训练
学习率策略商品向量需要更保守的更新:
- base_lr=3e-4
- 配合warmup(5epoch)
- cosine衰减到1e-5
使用torch.optim.lr_scheduler组合策略
效果验证除了Recall@K,建议监控:
1. 向量相似度分布
2. 预训练特征的保留率
3. 消融实验对比
保存每个epoch的checkpoint做分析

五、完整伪代码

class LightGCN(nn.Module):def __init__(self, g, num_layers):super().__init__()self.g = gself.num_layers = num_layersdef forward(self, x):# 多阶传播h = [x]  # 存储各层表征for _ in range(self.num_layers):x = self._propagate(x)h.append(x)# 层组合(平均池化)return torch.stack(h, dim=0).mean(0)def _propagate(self, h):# 异构图的特征传播g = self.g.local_var()h_u, h_i = h[:g.num_nodes('user')], h[g.num_nodes('user'):]# 用户->商品传播g.nodes['user'].data['h'] = h_ug.nodes['item'].data['h'] = h_ig.update_all(fn.copy_u('h', 'm'), fn.mean('m', 'h'), etype='ui')# 商品->用户传播 g.update_all(fn.copy_u('h', 'm'), fn.mean('m', 'h'), etype='iu')return torch.cat([g.nodes['user'].data['h'],g.nodes['item'].data['h']])# 初始化
user_emb = nn.Parameter(torch.randn(num_users, 768))
item_emb = nn.Parameter(load_pretrained_emb())# 训练循环
for epoch in range(epochs):# 特征拼接full_feat = torch.cat([user_emb, item_emb])# 图传播emb = model(full_feat)# 损失计算loss = calculate_loss(emb)# 反向传播loss.backward()optimizer.step()optimizer.zero_grad()

六、总结

  1. 核心原理
    LightGCN 无权重 → 把 user_embitem_emb 设为 nn.Parameter,通过反向传播联合优化。

  2. 工程实践

    • 预训练向量做 warm-start
    • 差异化学习率(user_lr=1e-3, item_lr=3e-4)
    • 推荐使用层组合(Layer Combination)而非最后一层
  3. 效果预期
    在Amazon-Beauty数据集上,相比固定item_emb的方案:

    • Recall@20 提升3-5个百分点
    • 训练时间增加约20%
  4. 扩展方向

    • 结合对比学习(CL)增强训练
    • 引入时间动态建模
    • 探索参数高效微调(PEFT)方法

祝你实验顺利!遇到问题欢迎在评论区交流。## 一、问题背景

在上一篇博客《用 LightGCN 给 NLP 商品向量加协同信号》中,我们:

  • 把预训练的 item_emb(Sentence-T5)冻结
  • 只训练 user_emb
  • 取得了不错的召回指标。

但很多同学留言:

“如果商品特征也想跟着图结构一起微调,该怎么改?”

本文用中文一次性讲透:如何把 item_emb 也变成可学习参数,同时避免踩坑。


二、核心思路

LightGCN 本身 没有可训练权重(只有消息传递)。
真正需要优化的是:

  1. 用户向量 user_emb(随机初始化);
  2. 商品向量 item_emb(用预训练向量 warm-start)。

把两者都设为 nn.Parameter,再交给同一个优化器即可。


三、代码实战(3 步完成)

Step1:把商品向量变成可学习参数

import torch
import torch.nn as nn# 1. 读取预训练商品向量(跳过 padding 行 0)
item_emb_np = np.memmap('All_Beauty.sent_emb', dtype='float32',mode='r', shape=(num_items + 1, 768))
item_emb = nn.Parameter(torch.from_numpy(item_emb_np[1:1 + num_items]).clone().to(device)
)
  • .clone() 断开与 memmap 的共享内存;
  • requires_grad=Truenn.Parameter 的默认行为。

Step2:优化器里同时放进用户 + 商品

user_emb = nn.Parameter(torch.randn(num_users, 768, device=device))opt = torch.optim.Adam([user_emb, item_emb], lr=1e-3, weight_decay=1e-4)
  • 如果想让商品向量慢速更新,可以单独给 item_emb 设更小的 lr
    torch.optim.Adam([{'params': user_emb, 'lr': 1e-3}, {'params': item_emb, 'lr': 3e-4}])

Step3:训练循环里动态拼接

for epoch in range(20):# 组装完整节点特征 [user ; item]full_feat = torch.empty(num_users + num_items, 768, device=device)full_feat[:num_users] = user_embfull_feat[num_users:] = item_embemb = model(full_feat)          # LightGCN 推理# ... 计算 BPR loss ...loss.backward()opt.step()

四、常见疑问 & 技巧

疑问回答
预训练向量会不会被完全洗没?可以保留残差: item_emb = α * frozen + (1-α) * learnable,α 可衰减。
显存爆炸?几百万商品时,用 Parameter + Adam 显存 ≈ num_items × 768 × 4 B ≈ 几 GB,可接受;若更大,用 分块 AdamAdaFactor
学习率怎么选?经验:user 1e-3,item 3e-4~1e-4;商品向量收敛慢,可推迟 5 个 epoch 再一起训练。
如何验证效果?训练后把 user_embitem_emb 分别保存,用 Faiss 做 ANN 召回,对比 Recall@K。

五、完整伪代码

class LightGCN(nn.Module):def __init__(self, g, num_layers):super().__init__()self.g, self.L = g, num_layersdef forward(self, x):h = xfor _ in range(self.L):h = self._propagate(h)return hdef _propagate(self, h):g = self.g.local_var()h_u, h_i = h[:g.num_nodes('user')], h[g.num_nodes('user'):]g.nodes['user'].data['h'] = h_ug.nodes['item'].data['h'] = h_ig.update_all(fn.copy_u('h', 'm'), fn.mean('m', 'h'), etype='ui')g.update_all(fn.copy_u('h', 'm'), fn.mean('m', 'h'), etype='iu')return torch.cat([g.nodes['user'].data['h'],g.nodes['item'].data['h']])user_emb = nn.Parameter(torch.randn(num_users, dim, device=device))
item_emb = nn.Parameter(item_emb_np[1:1 + num_items].clone().to(device))opt = torch.optim.Adam([user_emb, item_emb], lr=1e-3)for epoch in range(E):full_feat = torch.cat([user_emb, item_emb])emb = model(full_feat)# ... 采样、算 loss ...loss.backward()opt.step()

六、总结

  1. LightGCN 无权重 → 把 user_embitem_emb 设为 nn.Parameter
  2. 优化器同时优化Adam([user_emb, item_emb])
  3. 预训练向量做 warm-start,可冻结、可微调、可残差。

至此,商品 embedding 也能随图结构一起端到端学习,召回效果通常再涨 2~5 个点

http://www.dtcms.com/a/323646.html

相关文章:

  • 在JVM调优时,你关注哪些指标?
  • 裸露土堆识别准确率↑32%:陌讯多模态融合算法实战解析
  • 20250808组题总结
  • C++11中的移动语义
  • Python训练营打卡Day27-类的定义和方法
  • 【后端】Java Stream API 介绍
  • C++11 ---- 线程库
  • 机器学习(西瓜书)学习——绪论
  • 编译技术的两条演化支线:从前端 UI 框架到底层编译器的智能测试
  • 通过MQTT实现OTA升级方案
  • Linux-Redhat9.5静默安装Oracle19.25单实例教程【参照官方文档,超级详细】
  • 【35】C#实战篇——StopRecordingTimer_Tick事件函数中,解绑函数自己,那么该函数会立即结束吗?还会继续执行该函数中剩余部分吗?
  • windows、linux应急响应入侵排查
  • Oracle数据库重启后打开异常状态的检查步骤
  • 模拟人脑处理文本——从分句到分词,从段落到时间线叙事
  • MySQL时间类型
  • windows上LM-Studio下载安装教程
  • 谷歌搜索 sg_ss 逆向分析
  • 自闭和标签形式(self-closing tags)和标准标签形式
  • [概率 DP]808. 分汤
  • C++入门学习3
  • 开漏和推挽模式的区别
  • QT第一讲- Qt初探
  • XSS攻击演示
  • 常用信号深度解析(SIGINT、SIGPIPE、SIGALRM、SIGTERM等)
  • 101-基于Python的个性化音乐推荐系统
  • 码上爬第三题【协程+浏览器调试检测】
  • 本文章分享一个本地录音和实时传输录音给app的功能(杰理)
  • [GPU]什么是“硬件TL”在UnityURP中的体现
  • 疏老师-python训练营-Day40训练和测试的规范写法