广告 / NOTEBOOK 课程

18 · 真实广告点击:概率、校准与模拟拍卖

真实曝光点击 · 独立校准与审计 · 模拟预算市场

真实数据 + 机制 / 模拟只读 · 已保存的实验

已有输出来自保存的教学实验,并非打开页面时重新执行。 代码和原始输出保持不变;数据与历史注释保留原始语言。

18 · 同一系统:真实点击概率与模拟广告决策

先预测 → 只改一个变量 → 执行 → 解释。沿用同一实验记录、下载校验器、FastAPI和工作台07。

真实部分是 Criteo 官方曝光点击日志;模拟部分是单独生成的二价市场。两者没有真实收益或因果联系。日志正负抽样比例未知,模型概率只属于公开样本分布。

先预测:更复杂的树模型一定超过线性模型吗?校准一定改善 logloss 吗?恒定先验的 ECE 很小就代表有用吗?

PYTHON · 01
from pathlib import Path
import sys
ROOT=Path.cwd()
if not (ROOT/'reclab').is_dir(): ROOT=ROOT.parent
sys.path.insert(0,str(ROOT))
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from reclab.ads import latest
from reclab.paths import ARTIFACTS
receipt, result=latest()
folder=ARTIFACTS/'experiments/ads'/receipt['run_id']
print('Shared run:', receipt['run_id'])
print(result['scope'])
display(pd.DataFrame(result['splits']).T)
assert sum(x['n'] for x in result['splits'].values())==receipt['config']['rows']
print('Feature duplicate rows:',result['feature_duplicate_rows'])
print('Grouping uses features only; no temporal/user holdout is claimed.')
Notebook 保存的输出 · 本页未执行
Shared run: 20261030T125738-f82b6582
Criteo official sampled click logs: first 200,000 rows of one 766,864-row parquet shard (2015-02-15). Real CTR task on a subsampled distribution. Auction is a separate synthetic market.
n positives click_rate unique_feature_groups
train 109935.0 3614.0 0.032874 109934.0
select 29984.0 1016.0 0.033885 29984.0
calibration 30153.0 1023.0 0.033927 30153.0
audit 29928.0 992.0 0.033146 29928.0
Feature duplicate rows: 1
Grouping uses features only; no temporal/user holdout is claimed.

先比较真实概率,而不是套用检索指标

AUC衡量区分度;logloss、Brier衡量概率质量。ECE采用10个等宽箱,低点击率时恒定先验也可得到很小的ECE,不能独立选型。编码器仅拟合train;select仅在80/160棵树之间选择,calibration单独拟合sigmoid和isotonic。审计不选型,线性模型仍是独立对照。

执行下面一格,观察负结果。系数全表在本机实验目录 linear_coefficients.csv,权重不上传。

PYTHON · 02
summary=pd.DataFrame([{'method':r['method'],**{k:r['audit'][k] for k in ['auc','logloss','brier','ece']},'fit_seconds':r['fit_seconds']} for r in result['rows']])
display(summary.round(6))
print('GBDT family selected by select logloss:',result['selected_base'])
fig,axes=plt.subplots(1,2,figsize=(11,4))
for row in result['rows']:
    if row['method'] in [result['selected_base'],'selected_sigmoid','selected_isotonic']:
        bins=row['audit']['reliability']
        axes[0].plot([b['mean_probability'] for b in bins],[b['click_rate'] for b in bins],marker='o',label=row['method'])
axes[0].plot([0,1],[0,1],'k--',alpha=.5)
axes[0].set(xlabel='Mean predicted probability',ylabel='Observed click rate',title='Audit reliability: sparse high bins')
axes[0].legend(fontsize=8)
axes[1].barh(summary.method,summary.logloss)
axes[1].set(xlabel='Audit logloss (lower is better)',title='All baselines and negative results')
fig.tight_layout()
preview=ROOT/'artifacts/notebook-previews';preview.mkdir(parents=True,exist_ok=True)
fig.savefig(preview/'18_probability.png',dpi=140)
plt.show()
Notebook 保存的输出 · 本页未执行
method auc logloss brier ece fit_seconds
0 train_prior 0.500000 0.145515 0.032048 0.000272 0.000000
1 linear_onehot 0.721468 0.135423 0.031193 0.004382 4.102396
2 gbdt_80 0.706329 0.136029 0.031221 0.001738 0.555461
3 gbdt_160 0.703035 0.137404 0.031358 0.005350 0.932227
4 selected_sigmoid 0.706329 0.135968 0.031219 0.001149 0.008740
5 selected_isotonic 0.703064 0.136437 0.031302 0.001163 0.006879
GBDT family selected by select logloss: gbdt_80
<Figure size 1100x400 with 2 Axes>
Notebook 保存的图表

只改一个变量:固定树模型,改变概率映射

原始分数与sigmoid使用完全相同的审计行。查看配对logloss差异及300次行bootstrap区间;区间跨零时,不能说改善已被证实。行bootstrap没有覆盖训练随机性或用户相关性。Isotonic可能在样本稀疏区过拟合并改变AUC的并列关系。

预测:缺失多和出现训练未见类别的样本,哪一段更难?先看各段点击率与样本数;这不是人口属性或公平性结论。

PYTHON · 03
print(result['paired_sigmoid'])
display(pd.DataFrame([{'segment':s['segment'],'n':s['raw']['n'],'CTR':s['raw']['click_rate'],'raw_logloss':s['raw']['logloss'],'sigmoid_logloss':s['sigmoid']['logloss']} for s in result['segments']]))
failures=result['failures']
print('Eight highest sigmoid-loss audit exposures: clicked count =',sum(r['label'] for r in failures))
print('Detailed failure rows and all per-row predictions stay in the ignored local artifact directory.')
print('Explain: rare positive misses, missingness and unseen categories are hypotheses; anonymous features cannot establish causes.')
Notebook 保存的输出 · 本页未执行
{'calibrated_minus_raw_logloss': -6.130390957040765e-05, 'iid_row_bootstrap_95': [-0.00016953362229380587, 6.253575759107997e-05], 'replicates': 300, 'limitation': 'conditional on fitted models; repeated/related rows and one shard limit independence and generalization'}
segment n CTR raw_logloss sigmoid_logloss
0 low_numeric_missing 17016 0.036495 0.147043 0.146935
1 high_numeric_missing 12912 0.028733 0.121515 0.121515
2 any_unseen_category 11668 0.035996 0.146929 0.146712
3 all_categories_seen 18260 0.031325 0.129065 0.129103
Eight highest sigmoid-loss audit exposures: clicked count = 8
Detailed failure rows and all per-row predictions stay in the ignored local artifact directory.
Explain: rare positive misses, missingness and unseen categories are hypotheses; anonymous features cannot establish causes.

只改一个变量:同一模拟市场的出价倍数

以下不是Criteo回放。3个虚构广告主,6000次请求,独立生成真实点击概率、带噪估计概率和所有潜在点击;后半程流量更有价值。报价单位为“每曝光货币”,得分是估计点击率×虚构点击价值;赢家支付次高报价(含外部竞争价)。每广告主预算60。

先预测:固定seed、预算和pacing,仅将倍数从1降为0.5,会少花钱还是赢得不同曝光?再解释累计花费曲线。合成市场中的后半程优势是构造假设,不能据此发布生产出价策略。

PYTHON · 04
from reclab.auction import market, simulate
synthetic=market(seed=42)
controls=[('bid x1',simulate(synthetic,multiplier=1.,paced=True)),('bid x0.5',simulate(synthetic,multiplier=.5,paced=True))]
display(pd.DataFrame([{'policy':name,**{k:r[k] for k in ['spend','expected_click_value','expected_advertiser_surplus','clicks','budget_ok']}} for name,r in controls]))
fig,ax=plt.subplots(figsize=(8,4))
for name,r in controls:
    ax.plot([t['fraction'] for t in r['trace']],[sum(t['spend']) for t in r['trace']],label=name)
    assert r['budget_ok']
    assert all(max(t['spend'])<=60*t['fraction']+1e-8 for t in r['trace'])
ax.plot([0,1],[0,180],'k--',label='Combined paced budget ceiling')
ax.set(xlabel='Request fraction',ylabel='Simulated cumulative spend',title='Synthetic second-price impression market')
ax.legend();fig.tight_layout();fig.savefig(preview/'18_market.png',dpi=140);plt.show()
print('All fixed-seed policies:')
display(pd.DataFrame([{'policy':r['name'],'seed':r['seed'],'simulated_revenue':r['spend'],'expected_value':r['expected_click_value'],'budget_ok':r['budget_ok']} for r in result['simulations']]))
Notebook 保存的输出 · 本页未执行
policy spend expected_click_value expected_advertiser_surplus clicks budget_ok
0 bid x1 179.352946 363.687988 184.335042 181 True
1 bid x0.5 179.667495 594.607116 414.939622 297 True
<Figure size 800x400 with 1 Axes>
Notebook 保存的图表
All fixed-seed policies:
policy seed simulated_revenue expected_value budget_ok
0 flat 41 179.946768 310.260151 True
1 value 41 179.954436 312.713379 True
2 value_paced 41 179.523495 362.738625 True
3 value_paced_halfbid 41 179.584778 588.807349 True
4 flat 42 179.958459 320.400544 True
5 value 42 179.950958 325.099130 True
6 value_paced 42 179.352946 363.687988 True
7 value_paced_halfbid 42 179.667495 594.607116 True
8 flat 43 179.880956 302.367394 True
9 value 43 179.955613 328.793557 True
10 value_paced 43 179.534714 362.467222 True
11 value_paced_halfbid 43 179.659235 589.567131 True

解释并回到共享系统

本轮线性模型胜过两个小树模型;sigmoid的小幅改善不确定,isotonic没有胜出。恒定先验的低ECE不能替代区分度和适当评分。模拟pacing和半价出价只说明预设市场中的资源分配,所有广告主同时改变报价、外部竞价不变、请求价值随时间增长等假设限制结论。

工作台07可修改匿名特征并实际调用训练模型,也可独立调整模拟市场预算。训练需显式执行 python -m reclab.ads --download,每次创建新记录,不覆盖旧模型。详细来源与边界见 docs/ADS_EXPERIMENTS.md。没有完成全量Criteo、线上校准、反事实收益评估或论文完整复现。

继续阅读原文

本章来源

作者教程英文

张伟楠 / 徐健 · CIKM 2016 RTB教程

领域背景资料;具体复现边界见本章正文。

164页原作者讲义:响应预测、竞价、归因、pacing、定向、底价。Lab机制自编,非完整论文配方复现。

作者论文索引英文

张伟楠 · RTB论文索引

领域背景资料;具体复现边界见本章正文。

用于延伸阅读和追溯论文;索引本身不是讲义。

学习标记可选,仅保存在当前浏览器中。打开页面不会自动标记已学。