Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 155 additions & 0 deletions .claude/skills/deepctr-benchmark/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
---
name: deepctr-benchmark
description: >-
运行 DeepCTR 模型 benchmark 套件(benchmarks/),在真实或自带数据上端到端验证、
对比 CTR 模型并产出排序后的 leaderboard。当被要求 benchmark、对比、验证或评测
DeepCTR 模型(单任务 CTR / 多任务 / 序列),或在改动代码后重跑、扩展 benchmark 时使用。
Use to benchmark, compare, or validate DeepCTR CTR models.
---

# DeepCTR benchmark 套件

一个自包含的 harness(`benchmarks/`),训练 DeepCTR 模型并写出排序的 leaderboard。
三个 track:`single`(21 个单任务 CTR 模型,Criteo)、`multitask`
(SharedBottom/ESMM/MMOE/PLE,Census-Income)、`sequence`(DIN/BST/DSIN,DIEN 在
TF≥2.0 上跳过)。完整文档见 `benchmarks/README.md`,历史结果见 `benchmarks/RESULTS.md`。

## 每次必做的两件事

1. **强制 CPU**:加 `CUDA_VISIBLE_DEVICES=""`。本机 GPU 存在 CUDA/PTX 不兼容,在 GPU 上
每个模型都会以 `CUDA_ERROR_UNSUPPORTED_PTX_VERSION` 失败。harness 会逐模型隔离错误,
所以在 GPU 上你会得到一张全部 `failed` 的 leaderboard 而非崩溃——解决办法永远是走 CPU。
2. **用 legacy Keras**:`TF_USE_LEGACY_KERAS=1`(DeepCTR 面向 Keras 2 API;在 TF≥2.16 上
即 `tf-keras`)。套件自身会设置它,但用 pytest 调用时要显式设上。

长时间 sweep 放后台跑并轮询日志——真实数据上 21 个单任务模型的完整 sweep 在 CPU 上需要
几分钟到约 30 分钟。

## 本机数据

真实数据在 `benchmarks/data/`(git-ignored)。若缺失,需重新生成或下载(见 README;内置的
Criteo DAC URL 已失效——匿名 HF 镜像会限流,下载 `reczoo/Criteo_x1` 需要 `HF_TOKEN`)。

| 文件 | 样本数 | 用途 |
| ---- | ---- | --- |
| `benchmarks/data/criteo_x1_500k.csv` | 500,000 | 单任务,AUC 有意义 |
| `benchmarks/data/criteo_x1_200k.csv` | 200,000 | 单任务,更快 |
| `benchmarks/data/census-income.data`(+ `.test`) | 199,523(+99,762) | 多任务,官方切分 |
| `examples/criteo_sample.txt` | 200 | 仅冒烟——AUC 是噪声 |

上面的 `criteo_x1_*` 是 **FuxiCTR Criteo_x1 完整集**(~4584 万行)的切片。需要更大规模时,下载完整集
并按需截取(不要把完整集直接喂给 loader——它会把整个文件读进 pandas):

```bash
# 1) 下载完整集(2.9GB zip,需 HF token;匿名会被限流)
curl -L -C - -H "Authorization: Bearer $HF_TOKEN" \
-o benchmarks/data/Criteo_x1.zip \
"https://huggingface.co/datasets/reczoo/Criteo_x1/resolve/main/Criteo_x1.zip"
# zip 内含官方 8:1:1 切分: train.csv(8.2GB) / valid.csv(2.0GB) / test.csv(1.1GB)
# 列格式与切片一致: label,I1..I13,C1..C26(带表头)

# 2) 流式抽取前 N 行(避免解压/读入整个 8.2GB),生成自定义规模切片
python -c "
import zipfile, io
N = 2_000_000
z = zipfile.ZipFile('benchmarks/data/Criteo_x1.zip')
with z.open('train.csv') as raw, io.TextIOWrapper(raw) as fin, \
open('benchmarks/data/criteo_x1_2m.csv','w') as fo:
fo.write(fin.readline()) # 表头
for i, line in enumerate(fin):
if i >= N: break
fo.write(line)
"
```

CPU 上 200 万行单 epoch:DeepFM ~60s,慢模型(ONN/DeepFEFM/FiBiNET)数分钟到十几分钟;全量 21
模型约 1 小时。要用完整集的官方 train/valid/test 切分(而非我们的随机切分),需给 loader 加多文件
读取(参照 Census 官方切分的实现)。

### Sequence 真实数据(MovieLens-25M,≥200 万样本)

自带的 `examples/movielens_sample.txt` 只有 200 行(冒烟用);ml-1m 仅 ~100 万评分 → ~99 万样本,
**不够 200 万**。要 ≥200 万真实序列样本用 **MovieLens-25M**(2500 万评分),切片后合并成 loader
期望的列(`user_id,movie_id,rating,timestamp,genres,gender`):

```bash
# 1) 下载并解压(无需 token;约 250MB)
curl -s -o benchmarks/data/ml-25m.zip \
https://files.grouplens.org/datasets/movielens/ml-25m.zip
python -c "import zipfile; zipfile.ZipFile('benchmarks/data/ml-25m.zip').extractall('benchmarks/data')"

# 2) 取前 ~230 万评分(ml-25m 按 userId 排序)→ ~228 万 (历史→目标) 样本
python -c "
import pandas as pd
r = pd.read_csv('benchmarks/data/ml-25m/ratings.csv', nrows=2_300_000)
m = pd.read_csv('benchmarks/data/ml-25m/movies.csv')[['movieId','genres']]
df = r.merge(m, on='movieId', how='left').rename(columns={'userId':'user_id','movieId':'movie_id'})
df['genres'] = df['genres'].fillna('(no genres listed)')
df['gender'] = df['user_id'].map(lambda u: 'M' if u % 2 == 0 else 'F') # ml-25m 无用户画像,确定性合成
df[['user_id','movie_id','rating','timestamp','genres','gender']].to_csv('benchmarks/data/ml25m_seq_2m.csv', index=False)
print('samples ~', len(df) - df.user_id.nunique())
"
```

样本数 ≈ 评分行数 − 用户数(每个用户首条评分无历史被丢弃)。`gender` 是次要稀疏特征,合成不影响
序列信号(用户行为历史 → 目标 item/类目)的真实性。CPU 上 228 万样本单 epoch:DIN ~16s、BST ~45s、
DSIN ~77s(DIEN 在 TF≥2.0 自动 skip)。**注意 GPU 若驱动 PTX 版本与 TF 内核不匹配会全部 failed,
务必带 `CUDA_VISIBLE_DEVICES=""` 强制 CPU。**

## 命令

```bash
# 快速冒烟(1 epoch、每 track 2 个模型、自带数据)—— 验证 harness 正常
CUDA_VISIBLE_DEVICES="" python -m benchmarks.benchmark --track all --quick

# 单任务,真实 Criteo 500k —— 主力对比
CUDA_VISIBLE_DEVICES="" python -m benchmarks.benchmark --track single \
--data-path benchmarks/data/criteo_x1_500k.csv \
--epochs 1 --batch-size 1024 --val-split 0 --seed 2020

# 多任务,真实 Census,使用其官方 train/test 切分
CUDA_VISIBLE_DEVICES="" python -m benchmarks.benchmark --track multitask \
--data-path benchmarks/data/census-income.data --epochs 3 --batch-size 1024

# 序列,真实 MovieLens-25M(先按上文构建 ml25m_seq_2m.csv)
CUDA_VISIBLE_DEVICES="" python -m benchmarks.benchmark --track sequence \
--seq-source movielens --data-path benchmarks/data/ml25m_seq_2m.csv \
--epochs 1 --batch-size 1024 --embedding-dim 8

# 子集 / 排除慢模型;例如丢掉交互密集的几个以省时间
# --models DeepFM,DCN,xDeepFM 只跑这些
# --exclude FiBiNET,ONN,DeepFEFM,FwFM,FGCNN 仅 ONN 就有 77M 参数 / 约 245s
```

leaderboard 打印到 stdout,并写出 `benchmarks/results/<track>_<dataset>.csv` 和 `.md`。
要长期保存一次运行结果,把数字抄进 `benchmarks/RESULTS.md`(`results/` 目录是 git-ignored)。

## 训练 / 测试集划分 —— 这点要做对

- **默认 = 随机切分**,`--test-size 0.2`,固定 `--seed`。对 Criteo_x1 / DAC 是正确的:该数据
已匿名化并打散、无时间戳,所以随机切分无穿越且为标准做法。
- **`--temporal-split`(可选 `--time-col COL`)**:按时间留出——最近 `test_size` 比例作为测试集,
杜绝未来行泄漏进训练。有 `--time-col` 时按它排序,否则信任文件既有顺序。仅用于带时间顺序的
日志;自带的 Criteo 没有时间戳。
- **Census 官方切分**:当 `--data-path` 为 `census-income.data` 且同目录存在 `census-income.test`
时,loader 自动使用官方划分(编码器在并集上 fit)。不要再把它打散重切。
- **`--val-split`** 从训练集中切出验证集,但**没有接任何早停 / checkpoint**——它不影响训练过程
也不参与模型选择。固定 epoch 的短跑应设 `--val-split 0`,让训练用满整个训练集;只有在加了
EarlyStopping callback 时才保留它。

## 结果解读

- 报告的 AUC/LogLoss 来自 hold-out 的**测试集**(`fit` 从未见过)。
- 要有足够数据才有真实排名:200 行自带数据上 AUC≈0.5 是噪声;到 200k–500k 时,深层交互模型
(DeepFEFM/FiBiNET/DeepFM/xDeepFM)才与简单模型拉开。更多 epoch 会抬高绝对 AUC,但很少改变
头部梯队。
- 汇报 leaderboard 时务必同时注明切分方式与 epoch 数——排名只在相同 数据/切分/epoch 下可比。

## 测试

```bash
CUDA_VISIBLE_DEVICES="" TF_USE_LEGACY_KERAS=1 python -m pytest tests/benchmark_test.py -q
```

覆盖每个 track 在小数据上的运行,以及切分保证(时序留出把最近的行作为测试集;Census 使用
官方 `.test`)。
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
*.h5
*.ipynb
.pytest_cache/

# DeepCTR benchmark suite: downloaded datasets and generated leaderboards
benchmarks/data/
benchmarks/results/
.vscode/
tests/unused/*
# Byte-compiled / optimized / DLL files
Expand Down
79 changes: 79 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# AGENTS.md

Guidance for AI agents (Codex, Claude Code, etc.) working in this repo.

## Environment setup (required before running tests or benchmarks)

DeepCTR targets the **Keras 2** API. This box ships TF with Keras 3, so you must
install legacy Keras and force it on, or every model test fails on label-rank /
serialization errors.

```bash
pip install pytest # not preinstalled
pip install "tf-keras==<TF major.minor>" # e.g. tf-keras==2.19.0 for TF 2.19; required on TF>=2.16
```

Prefix every test / benchmark / onboarding command with:

```bash
CUDA_VISIBLE_DEVICES="" TF_USE_LEGACY_KERAS=1 <command>
```

(`CUDA_VISIBLE_DEVICES=""` avoids a GPU CUDA/PTX mismatch; `TF_USE_LEGACY_KERAS=1`
selects the Keras-2 backend.)

## Running tests

```bash
CUDA_VISIBLE_DEVICES="" TF_USE_LEGACY_KERAS=1 python -m pytest tests/ -q
CUDA_VISIBLE_DEVICES="" TF_USE_LEGACY_KERAS=1 python -m pytest tests/models/DeepFM_test.py -q # single model
```

## Correctness contract for model work

- A training smoke test is not proof of mathematical correctness. Follow the
C0-C6 ladder in `tests/README.md`.
- Every model uses `check_model` / `check_mtl_model` for finite inference and
prediction-equivalent weight/full-model serialization.
- Every custom mathematical layer adds an independent NumPy equation test plus
finite gradients via `tests/correctness.py`; add numerical gradients on tiny
tensors and domain invariants for masking, causality, symmetry, or padding.
- Benchmark AUC validates effectiveness only. Claim paper reproduction only
after an official-code differential or matching paper-scale protocol.

## Adding a new model — use the onboarding pipeline

The `benchmarks.onboard` CLI standardizes adding a new CTR model across four
stages. Full reference: `benchmarks/onboard/README.md`.

```bash
export CUDA_VISIBLE_DEVICES="" TF_USE_LEGACY_KERAS=1

python -m benchmarks.onboard discover --list # candidate models
python -m benchmarks.onboard scaffold <Name> --category single # codegen + auto-wire all registration points
# -> implement the model core in deepctr/models/<name>.py (replace the TODO block)
python -m benchmarks.onboard verify <Name> # unit test + audit + benchmark vs baseline
python -m benchmarks.onboard docs <Name> # update README/Features/rst/History/RESULTS
python -m benchmarks.onboard audit # confirm every model is fully wired
```

Categories: `single` | `sequence` | `multitask`. Add `--with-layer` to also
scaffold a custom layer, `--wire-only` for an already-written model.

### Gotchas

- `scaffold` generates `tests/models/<Name>_test.py` with a **generic** constructor
signature (`dnn_hidden_units=...`). If your model's signature differs, edit the
generated test to pass the right args (e.g. FinalMLP uses `mlp1_hidden_units`).
- Every custom layer must end up in `deepctr/layers/__init__.py` `custom_objects`
(scaffold does this), create sub-layers in `__init__` not `build`, avoid
`Lambda(lambda ...)` (use a small serializable layer), and not collide on class
name with another registered layer — or `save_model`/`load_model` will fail.
Run `python -m benchmarks.onboard audit` to verify wiring.

## Conventions

- Models are factory functions returning a `tf.keras.Model`; see `deepctr/models/wdl.py`
(single), `deepctr/models/sequence/din.py` (sequence), `deepctr/models/multitask/mmoe.py`.
- Use `tensorflow.keras` imports (not `tensorflow.python.keras`).
- Commit only when asked; branch off `master` for PRs.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ Introduction](https://zhuanlan.zhihu.com/p/53231955)) and [welcome to join us!](
| MMOE | [KDD 2018][Modeling Task Relationships in Multi-task Learning with Multi-gate Mixture-of-Experts](https://dl.acm.org/doi/abs/10.1145/3219819.3220007) |
| PLE | [RecSys 2020][Progressive Layered Extraction (PLE): A Novel Multi-Task Learning (MTL) Model for Personalized Recommendations](https://dl.acm.org/doi/10.1145/3383313.3412236) |
| EDCN | [KDD 2021][Enhancing Explicit and Implicit Feature Interactions via Information Sharing for Parallel Deep CTR Models](https://dlp-kdd.github.io/assets/pdf/DLP-KDD_2021_paper_12.pdf) |
| OneTrans | [industrial 2024][OneTrans: Unified Feature Interaction and Sequence Modeling with One Transformer in Industrial Recommender Systems]() |
| FinalMLP | [AAAI 2023][FinalMLP: An Enhanced Two-Stream MLP Model for CTR Prediction](https://arxiv.org/abs/2304.00902) |
| MaskNet | [DLP-KDD 2021][MaskNet: Introducing Feature-Wise Multiplication to CTR Ranking Models by Instance-Guided Mask](https://arxiv.org/abs/2102.07619) |
| WuKong | [ICML 2024][Wukong: Towards a Scaling Law for Large-Scale Recommendation](https://arxiv.org/abs/2403.02545) |

## Citation

Expand Down
Loading
Loading