修正文档中的源码文件计数 Co-Authored-By: Claude Opus 5 noreply@anthropic.com
修正文档中的源码文件计数
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
本项目是计图(Jittor)挑战赛 赛题一:基于图学习的动态推荐任务 五一队的完整方案实现。
赛题本质是时序链接预测的排序化形式:给定一条查询「源节点 src + 时间 time + 100 个候选 目标节点 dst」,对 100 个候选各给一个分数,官方只看行内相对排序,指标为 MRR (随机基线 ≈ 0.052),两个数据集的 MRR 直接相加为总分;测试集时间严格晚于训练集, 是真时序外推而非随机切分。
src
time
dst
本方案的特点是:不做单个大模型,而是让四类互相异质的信息源在一个 listwise 精排器里汇合, 并用 5 折 OOF 作为每次线上提交的硬门禁。四层管线在 A、B 两榜四个数据集上共用同一套代码, 靠 is_social(ds) 分派到「社交图 / 二部图」两个特征分支。所有神经网络均为计图(Jittor) 实现(SASRec / BPR-MF / LightGCN / listwise 精排器),numpy / scipy 仅用于图统计与稀疏矩阵运算。
is_social(ds)
train.csv (src, dst, time) test.csv (src, time, 100 candidates) │ │ ┌───────────────────────────────┴────────────────────────────┐ │ │ ① 图统计特征 │ │ │ 直连重复 / 反向边 / 共同邻居族(有向拆分) / Adamic-Adar / RA │ │ │ 马尔可夫转移 / 多窗口共现 / as-of 时点热度 / 小时级热度窗 │ │ │ 个性化 PageRank(PPR) / 有向 2-hop / 用户侧与物品侧 CF │ │ ├────────────────────────────────────────────────────────────┤ │ │ ② 表征模型分数列(全部计图实现,每类 2-4 个配置各出一列) │ │ │ SASRec ── 序列 Transformer │ │ │ BPR-MF ── 加权 BPR,热度^0.75 负采样 + 时间衰减 │ │ │ LightGCN ─ 端到端 K 跳对称归一传播(梯度穿过传播) │ │ │ item2vec ─ 共现传播物品嵌入 │ │ ├────────────────────────────────────────────────────────────┤ │ │ ③ 候选矩阵自指统计 ◄────────────────────────────────────────┼───────────┘ │ tfreq / tsib 族 / tsim 族 / trecip / tem 族(EM 软计数) │ 只统计官方发放的候选矩阵 │ —— 只对「输入」做聚合统计,全程不接触任何标签 │ 这一**输入**本身 └───────────────────────────────┬────────────────────────────┘ ▼ ④ listwise 精排器(计图):100 候选内 softmax 交叉熵,5 折 OOF,预测取 5 折平均 dataset1/3(社交图):MLP,d3 用**按时间划分**的 5 折 dataset2/4(二部图):单层候选间 self-attention 强信号列附一份**查询内百分位**副本(val/test 绝对分布会漂移,行内排名不会) ▼ 逐行 min-max 归一到 [0,1],8 位小数 → dataset{N}.csv
三条经验(每一条都有线上实测支撑,详见 docs/RESULTS.md):
jittor-wuyi-dynamic-rec/ ├── README.md 本文件 ├── LICENSE MIT ├── requirements.txt 统一依赖(两榜通用) ├── docs/ │ ├── METHOD.md 方法详解:四层管线、特征族清单、两个数据集家族的差异 │ └── RESULTS.md 成绩与上分路径:逐版本线上实测、增量归因、失败清单 ├── code_A/ ★ A 榜(dataset1 / dataset2)完整方案,一键复现 │ ├── README.md │ ├── run_reproduce.sh 从原始数据到 result_final.zip 的全链脚本(幂等、可续跑) │ └── *.py 18 个源码文件 ├── code_B/ ★ B 榜(dataset3 / dataset4)完整方案,一键复现 │ ├── README.md 两分钟上手 + 文档导航 │ ├── 说明文档.md B 榜方法说明(提交正文) │ ├── A_TO_B_CHANGES.md A 榜算法 → B 榜的逐项改动(含每项线上增量与判据) │ ├── REPRODUCE.md 详细复现:资源预算、分步耗时、常见故障与对策 │ ├── MANIFEST.md 文件清单 │ ├── reproduce_d3.sh / reproduce_d4.sh / pack_submission.py / env_check.py │ └── code/ 16 个 py + 1 个分片流水 sh └── ckpt/ B 榜最终精排器权重(5 折,可直接推理)
本项目在下列环境开发与验证(即比赛实际使用的 AutoDL 容器规格):
⚠️ numpy 必须是 1.x。 numpy 2.x 会让 jt.array 读出垃圾数据(表现为 IndexError / OOM, 是根因级的坑)。requirements.txt 已锁 numpy==1.24.4。
jt.array
requirements.txt
numpy==1.24.4
conda create -n jt python=3.10 -y && conda activate jt pip install -r requirements.txt python -m jittor_utils.install_cuda # 仅当报 "cudnn is not loaded" 时需要,约 5 分钟
安装完成后先跑环境自检,必须全部 PASS 再往下(自检包含 jt.scatter(reduce='add') 算子与 scipy 稀疏矩阵的可用性验证,LightGCN 依赖前者):
jt.scatter(reduce='add')
cd code_B && python env_check.py # A 榜同名脚本在 code_A/ 下
ckpt/ 目录内含 B 榜最终精排器的 5 折权重与归一化参数(约 1.8 MB,已随仓库分发):
ckpt/
ckpt/dataset3/ranker_cv_b15t_cv5*
ckpt/dataset3/ranker_cv_b15_cv5*
ckpt/dataset4/ranker_cv_b14_cv5*
d3 的时间划分 OOF(0.9536)与随机划分(0.9545)不可比 —— 折的难度不同,只能看线上。
各单模型权重(SASRec / BPR-MF / LightGCN / item2vec,val + full 两套,合计约 7GB)与特征张量 (d4 单个 val 就 7.7GB)不随仓库分发,由复现脚本现算;如需在训练完的机器上收集, 用 code_B/collect_ckpt.sh。
code_B/collect_ckpt.sh
官方数据不含任何节点属性,只有两个 csv(train.csv 为 (src, dst, time) 交互边, test.csv 为查询 + 100 个候选),无需额外预处理,解压后放到仓库根目录即可 (data_A/ 与 data_B/ 已写进 .gitignore,不会被提交):
train.csv
(src, dst, time)
test.csv
data_A/
data_B/
.gitignore
jittor-wuyi-dynamic-rec/ ├── data_A/ │ ├── dataset1/{train.csv, test.csv} │ └── dataset2/{train.csv, test.csv} ├── data_B/ │ ├── dataset3/{train.csv, test.csv} │ └── dataset4/{train.csv, test.csv} ├── code_A/ code_B/ ckpt/ docs/ ...
A 榜:code_A 的数据路径默认就是 <仓库根>/data_A,按上面摆好即可,无需任何设置。
code_A
<仓库根>/data_A
B 榜:reproduce_*.sh 会先 cd code_B/code,相对路径会失效,请用绝对路径:
reproduce_*.sh
cd code_B/code
export DATA_DIR="$(pwd)/data_B" # 在仓库根目录执行
数据也可以放在仓库外,此时两榜都用 export DATA_DIR=/abs/path/to/data_{A,B} 覆盖。
export DATA_DIR=/abs/path/to/data_{A,B}
特征工程(图统计表、候选矩阵统计表、各模型打分)全部由复现脚本在链路内完成,无独立预处理步骤。
训练与推理写在同一条幂等流水线里(**每一步先查产物,存在即 [skip]**,可随时中断续跑)。
[skip]
cd code_A bash run_reproduce.sh # 单卡约 8-10 小时,产出 out/result_final.zip
脚本分 4 个阶段:dataset1 模型训练 → dataset1 特征链 + 精排 → dataset2 模型训练 → dataset2 特征链 + 精排 → 打包。关键自检点(±0.002 属正常随机波动):
_v29_cv5
_v30_cv5
cd code_B export DATA_DIR=/abs/path/to/data_B # 见上一节;放在仓库根目录则是 $(cd .. && pwd)/data_B bash reproduce_d3.sh # dataset3 全链,5 段 20 步,约 6-8 小时 bash reproduce_d4.sh # dataset4 全链,6 段 13 步 + 九段分片,约 9 小时
两个脚本不要并行跑 —— 都要抢 GPU,且 d4 的内存峰值接近 40GB 容器上限。
关键自检点:d3 时间划分 OOF ≈ 0.9537、d4 OOF ≈ 0.5338。分步耗时、显存/内存峰值、 以及 6 类常见故障的对策见 code_B/REPRODUCE.md。
若已有 ckpt/,铺回去可跳过精排器重训:
cp -r ckpt/dataset3/* code_B/code/out/dataset3/ cp -r ckpt/dataset4/* code_B/code/out/dataset4/
复现脚本的最后一段即是推理,也可以在已有特征与精排器的前提下单独跑:
cd code_B/code python predict.py --dataset dataset3 --scorer cv --cv_tag _b15t_cv5 --out_tag _b15t
dataset4 的 test 有 232 万行、特征张量 32GB,单文件装不下,必须走九段分片流水 (逐片 augment → predict → 按序拼接,用完即删分片特征):
bash code_B/code/run_d4_shards.sh
打包与格式校验:
cd code_B && python pack_submission.py # → out/result_B_final.zip
pack_submission.py 带 4 道校验:行列数、定宽字节不变量(逐字节证明每行确实是 100 列)、 占位分数防呆、无 zip64。最后一条是踩出来的:Python 的 zipfile 对 >2GB 的成员强制启用 zip64,而官方检查器不认,会直接判「提交格式错误」。
pack_submission.py
zipfile
每个数据集一个 csv,无表头,行数与行序与 test.csv 一致,每行 100 个数 = 该行 100 个候选的 分数,行内 min-max 归一到 [0,1],8 位小数(评测只看行内排序)。
dataset3.csv
dataset4.csv
v9c
d1c
--rank_profile v9c|d1c
--tfreq / --tsib / --trecip / --tsim / --tem
data_A
data_B
一处术语说明:源码注释与部分开发文档里,第 ③ 类特征被用内部简称叫作「泄漏列 / 泄漏族」 (如 augment_features.py 的 --tsib / --trecip 注释、run_reproduce.sh 的链路注释)。 这是我们开发期的口头简称,指的是「利用了 transductive 设定」,不是指使用了标签: 这些特征只对官方发放的 test 候选矩阵这一输入做聚合统计,从不接触也不推断任何真值标签。 正式名称是「候选矩阵自指统计」,见 docs/METHOD.md §③。
augment_features.py
--tsib
--trecip
run_reproduce.sh
版权所有:中国计算机学会技术支持:开源发展技术委员会 京ICP备13000930号-9 京公网安备 11010802047560号
Jittor 计图挑战赛 赛题一:基于图学习的动态推荐任务 — 五一队
简介
本项目是计图(Jittor)挑战赛 赛题一:基于图学习的动态推荐任务 五一队的完整方案实现。
赛题本质是时序链接预测的排序化形式:给定一条查询「源节点
src+ 时间time+ 100 个候选 目标节点dst」,对 100 个候选各给一个分数,官方只看行内相对排序,指标为 MRR (随机基线 ≈ 0.052),两个数据集的 MRR 直接相加为总分;测试集时间严格晚于训练集, 是真时序外推而非随机切分。本方案的特点是:不做单个大模型,而是让四类互相异质的信息源在一个 listwise 精排器里汇合, 并用 5 折 OOF 作为每次线上提交的硬门禁。四层管线在 A、B 两榜四个数据集上共用同一套代码, 靠
is_social(ds)分派到「社交图 / 二部图」两个特征分支。所有神经网络均为计图(Jittor) 实现(SASRec / BPR-MF / LightGCN / listwise 精排器),numpy / scipy 仅用于图统计与稀疏矩阵运算。方法总览
三条经验(每一条都有线上实测支撑,详见 docs/RESULTS.md):
目录结构
安装
运行环境
本项目在下列环境开发与验证(即比赛实际使用的 AutoDL 容器规格):
安装依赖
安装完成后先跑环境自检,必须全部 PASS 再往下(自检包含
jt.scatter(reduce='add')算子与 scipy 稀疏矩阵的可用性验证,LightGCN 依赖前者):预训练模型
ckpt/目录内含 B 榜最终精排器的 5 折权重与归一化参数(约 1.8 MB,已随仓库分发):ckpt/dataset3/ranker_cv_b15t_cv5*ckpt/dataset3/ranker_cv_b15_cv5*ckpt/dataset4/ranker_cv_b14_cv5*各单模型权重(SASRec / BPR-MF / LightGCN / item2vec,val + full 两套,合计约 7GB)与特征张量 (d4 单个 val 就 7.7GB)不随仓库分发,由复现脚本现算;如需在训练完的机器上收集, 用
code_B/collect_ckpt.sh。数据预处理
官方数据不含任何节点属性,只有两个 csv(
train.csv为(src, dst, time)交互边,test.csv为查询 + 100 个候选),无需额外预处理,解压后放到仓库根目录即可 (data_A/与data_B/已写进.gitignore,不会被提交):A 榜:
code_A的数据路径默认就是<仓库根>/data_A,按上面摆好即可,无需任何设置。B 榜:
reproduce_*.sh会先cd code_B/code,相对路径会失效,请用绝对路径:数据也可以放在仓库外,此时两榜都用
export DATA_DIR=/abs/path/to/data_{A,B}覆盖。特征工程(图统计表、候选矩阵统计表、各模型打分)全部由复现脚本在链路内完成,无独立预处理步骤。
训练
训练与推理写在同一条幂等流水线里(**每一步先查产物,存在即
[skip]**,可随时中断续跑)。A 榜(dataset1 + dataset2)
脚本分 4 个阶段:dataset1 模型训练 → dataset1 特征链 + 精排 → dataset2 模型训练 → dataset2 特征链 + 精排 → 打包。关键自检点(±0.002 属正常随机波动):
_v29_cv5, MLP)_v30_cv5, attention)B 榜(dataset3 + dataset4)
关键自检点:d3 时间划分 OOF ≈ 0.9537、d4 OOF ≈ 0.5338。分步耗时、显存/内存峰值、 以及 6 类常见故障的对策见 code_B/REPRODUCE.md。
若已有
ckpt/,铺回去可跳过精排器重训:推理
复现脚本的最后一段即是推理,也可以在已有特征与精排器的前提下单独跑:
dataset4 的 test 有 232 万行、特征张量 32GB,单文件装不下,必须走九段分片流水 (逐片 augment → predict → 按序拼接,用完即删分片特征):
打包与格式校验:
pack_submission.py带 4 道校验:行列数、定宽字节不变量(逐字节证明每行确实是 100 列)、 占位分数防呆、无 zip64。最后一条是踩出来的:Python 的zipfile对 >2GB 的成员强制启用 zip64,而官方检查器不认,会直接判「提交格式错误」。结果格式
每个数据集一个 csv,无表头,行数与行序与
test.csv一致,每行 100 个数 = 该行 100 个候选的 分数,行内 min-max 归一到 [0,1],8 位小数(评测只看行内排序)。dataset3.csvdataset4.csv合规声明
code_A内置v9c/d1c两个「干净」列集 (--rank_profile v9c|d1c),跳过--tfreq / --tsib / --trecip / --tsim / --tem等 augment 步骤即可得到完全只用训练数据的管线;历史上完全不使用测试集统计的最高分版本线上为 1.4557(dataset1 0.85914 + dataset2 0.59658)。data_A/data_B。致谢