这是我在 TAAC 2026 腾讯广告算法大赛 UNI-REC 赛道中的实验代码和复盘记录。最终方案是单模型、单 checkpoint、单推理流程,没有使用 ensemble。
| 指标 | 数值 |
|---|---|
| 训练后 AUC | 0.867879 |
| Public AUC | 0.830964 |
| 推理时间 | 403.25s |
相对初始 baseline 0.806617,最终 public AUC 提升 +0.024347。
python3 -u "${SCRIPT_DIR}/train.py" \
--ns_tokenizer_type rankmixer \
--user_ns_tokens 5 \
--item_ns_tokens 2 \
--num_queries 2 \
--ns_groups_json "" \
--emb_skip_threshold 1000000 \
--use_global_time_features \
--use_dense_group_projector \
--loss_type focal \
--focal_alpha 0.25 \
--focal_gamma 0.25 \
--eval_every_n_steps 2000 \
--num_workers 8 \
"$@"flowchart TB
A["TencentGR Parquet<br/>user / item / timestamp / sequences"] --> B["PCVRParquetDataset<br/>schema 对齐、padding、序列截断"]
B --> U0["User Int Sparse<br/>46 fids"]
U0 --> U1["RankMixerNSTokenizer<br/>5 user NS tokens"]
B --> I0["Item Int Sparse<br/>14 fids"]
I0 --> I1["RankMixerNSTokenizer<br/>2 item NS tokens"]
B --> D0["User Dense<br/>61, 87, 62-66, 89-91"]
D0 --> D1["DenseGroupProjector<br/>分组投影 + gate 融合<br/>1 个 user dense token"]
B --> S0["Behavior Sequences<br/>seq_a / seq_b / seq_c / seq_d"]
S0 --> S1["Per-domain Transformer Encoders"]
S1 --> Q0["MultiSeqQueryGenerator<br/>2 queries per domain"]
B --> T0["Global Timestamp"]
T0 --> T1["day/week sin-cos<br/>Global Time Projection"]
U1 --> H["MultiSeqHyFormerBlock x2<br/>RankMixer full interaction<br/>d_model=64, heads=4"]
I1 --> H
D1 --> H
Q0 --> H
H --> R0["Final Representation"]
T1 --> R0
R0 --> C0["MLP Classifier"]
C0 --> O0["Sigmoid Prediction"]
O0 --> L0["Focal Loss<br/>alpha=0.25, gamma=0.25"]
O0 --> E0["Step-level Validation<br/>eval_every_n_steps=2000"]
E0 --> P0["single model.pt<br/>public AUC 0.830964"]
| 轮次 | 改动 | Public AUC | 说明 |
|---|---|---|---|
| 1 | 第一轮 best baseline | 0.812437 |
RankMixer + user_ns=5,item_ns=2,num_queries=2 |
| 6 | 全局时间特征 | 0.818828 |
加入当前样本 timestamp 的日周期、周周期信息 |
| 10 | Focal Loss | 0.825397 |
最终采用 alpha=0.25,gamma=0.25 |
| 15 | User dense 分组 projector | 0.829808 |
将 dense 61、87、62-66、89-91 分组建模后 gate 融合 |
| 30 | Step-level validation | 0.830964 |
eval_every_n_steps=2000,选择更稳的 checkpoint |
原始行为序列里有 time bucket,但缺少当前样本在全局时间中的位置。第 6 轮加入当前 timestamp 的日周期和周周期 sin/cos 特征后,public AUC 从 0.812437 提升到 0.818828。
第 8-12 轮只调 focal 参数。gamma=1.0 -> 0.5 -> 0.25 连续涨分,但 gamma=0.1 掉分,最后保留温和版本:
--loss_type focal
--focal_alpha 0.25
--focal_gamma 0.25官方 demo 字段分析显示,user dense 不是同一种语义。原来所有 dense 直接拼接进一个 Linear,容易把 embedding 类特征和统计类特征混在一起。最终改成分组 projector:
dense 61 -> projector
dense 87 -> projector
dense 62-66 -> projector
dense 89-91 -> projector
group vectors -> gate fusion -> 1 个 user_dense token
这个改动不新增 RankMixer token,只改变 dense token 内部建模方式,是后期最明显的提分点之一。
第 30 轮只新增:
--eval_every_n_steps 2000public AUC 从第 18 轮的 0.830596 提升到 0.830964。后续测试 1000 反而略低,说明更频繁验证不一定更好,容易选到本地 valid 噪声。
后期很多看起来合理的方向都没有迁移到 public:
valid_ratio=0.05- 更改 NS token 配比
- 提高
lr - day bucket / sequence length 特征
- missing indicators
- dense stat
log1p - CDF 推荐的
seq_c=256 - global time scale down
- warmup + cosine scheduler
- raw item id hash
- DIN-lite
- sequence hash embedding
- user dense wide logit
- GroupNSTokenizer
- target-aware query pooling
- user sparse-dense pair residual
一个比较明显的现象是:有些实验训练 AUC 更高,但 public AUC 掉很多。后期瓶颈不是单纯提升模型表达能力,而是控制 local-public gap。
完整 35 轮实验细节见:
docs/第一轮以来提交实验记录.md
| 文件 | 说明 |
|---|---|
run.sh |
官方平台训练入口,当前为最高分配置 |
train.py |
参数解析、数据构建、模型构建、训练入口 |
trainer.py |
训练循环、早停、step validation、checkpoint 保存 |
infer.py |
官方推理入口,单 checkpoint 推理 |
dataset.py |
Parquet 数据读取、schema、padding、截断 |
model.py |
PCVRHyFormer、RankMixer、dense group projector |
seq_truncation_ablation_cdf.py |
序列截断长度分析工具,不参与最终推理 |
docs/第一轮以来提交实验记录.md |
每一轮实验参数、分数和结论 |
docs/demo_1000字段分析.md |
官方 demo 字段分析 |
| 分支 | 说明 |
|---|---|
main |
最终开源分支,README 和 run.sh 面向复现最高分配置 |
experiment/best-training |
lizuju 的测试分支,保留更多实验过程和失败方向 |
origin/dev |
zhudachang234 的测试分支,包含更激进的探索尝试 |
original |
历史保留分支 |
完整训练数据不在仓库中,需要在官方平台通过 TRAIN_DATA_PATH 读取。Hugging Face demo 数据只用于字段结构分析,不代表完整训练集分布。
本地可以做基础检查:
bash -n run.sh
PYTHONDONTWRITEBYTECODE=1 python3 -m py_compile dataset.py train.py infer.py trainer.py model.py seq_truncation_ablation_cdf.py
git diff --check