CH 06 / SOURCE EXAMPLE
multiplicative_formula_shape.py
ch06 精排模块/code/ch04_multi_objective_fusion/multiplicative_formula_shape.py
这个示例讲什么
│ ├── multiplicative_formula_shape.py § 6.4.1 乘法公式 (β+α·p)^γ 的 α/β/γ 形状分析
乘法融合公式及形状分析(第 6 章 §6.4.1 乘法公式及形状分析) 对应正文概念: 正文给出精排多目标融合的乘法公式(Multiplicative Formula): s_i = (beta_i + alpha_i * p_i) ^ gamma_i 整体融合分为各因子相乘: Score = prod_{i=1..N} (beta_i + alpha_i * p_i) ^ gamma_i 三个超参数分别对应三个控制维度(正文原话): alpha_i 决定区分度(Scaling Factor); beta_i 决定基础贡献(Bias Term); gamma_i 决定流量分配形状(Exponent Term)。 并指出:gamma > 1 时为凸函数,强化头部效应(马太效应);0 < gamma < 1 时为凹 函数,压缩头尾差距、给中腰部更多曝
上面来自章节 README 或源码自身的说明,不是自动验证的运行结果,也不能代替 PDF 的书中论断。讲解时请结合实际代码、输入输出与原书页码。
源码结构与阅读入口
shape· 第 35 行乘法公式单因子变换 s = (beta + alpha * p)^gamma。gini· 第 43 行spearman· 第 52 行a、b 是两组分数,返回其秩相关。print_shape_table· 第 69 行打印 p 网格上的 s 值表;每行一组超参数。make_candidates· 第 90 行[MOCK] 合成候选集:每个候选有 CTR / LTR / CVR 三个 pxtr。fuse· 第 111 行params: {factor: (alpha, beta, gamma)};返回每个候选的融合分。
1"""2乘法融合公式及形状分析(第 6 章 §6.4.1 乘法公式及形状分析)34对应正文概念:5正文给出精排多目标融合的乘法公式(Multiplicative Formula):6 s_i = (beta_i + alpha_i * p_i) ^ gamma_i7整体融合分为各因子相乘:8 Score = prod_{i=1..N} (beta_i + alpha_i * p_i) ^ gamma_i9三个超参数分别对应三个控制维度(正文原话):10 alpha_i 决定区分度(Scaling Factor);11 beta_i 决定基础贡献(Bias Term);12 gamma_i 决定流量分配形状(Exponent Term)。13并指出:gamma > 1 时为凸函数,强化头部效应(马太效应);0 < gamma < 1 时为凹14函数,压缩头尾差距、给中腰部更多曝光;gamma = 1 时退化为线性变换。1516本实现做了什么(正文的图 6-x 是曲线图,这里按要求改为打印数值表):17 1) 形状表:固定其余两参数,分别扫 alpha / beta / gamma,打印 p=0.0~1.0 上的18 s 值表,并给出「归一化后的头尾比」量化区分度;19 2) 排序影响:构造 CTR/LTR/CVR 三因子的候选集,扫 gamma 观察 Top-10 的20 流量集中度(基尼系数、Top-1 占比、与各单因子排序的 Spearman 相关);21 3) 退化验证:数值验证 gamma=1 时 (beta + alpha*p)^1 == beta + alpha*p;22 4) 稀疏因子的 beta 作用:演示正文说的「稀疏目标直接相乘导致得分迅速衰减」,23 以及加偏置项后数值分布如何被稳定下来。2425关键公式:26 s_i = (beta_i + alpha_i * p_i) ^ gamma_i27 Score = prod_i s_i28 基尼系数 G = sum_i (2i - n - 1) * x_(i) / (n * sum_i x_i)29"""3031import math32import random333435def shape(p, alpha, beta, gamma):36 """乘法公式单因子变换 s = (beta + alpha * p)^gamma。"""37 base = beta + alpha * p38 if base <= 0:39 return 0.040 return base ** gamma414243def gini(xs):44 xs = sorted(xs)45 n = len(xs)46 tot = sum(xs)47 if tot <= 0:48 return 0.049 return sum((2 * (i + 1) - n - 1) * x for i, x in enumerate(xs)) / (n * tot)505152def spearman(a, b):53 """a、b 是两组分数,返回其秩相关。"""54 def ranks(v):55 order = sorted(range(len(v)), key=lambda i: v[i])56 r = [0.0] * len(v)57 for pos, i in enumerate(order):58 r[i] = float(pos)59 return r60 ra, rb = ranks(a), ranks(b)61 n = len(a)62 ma, mb = sum(ra) / n, sum(rb) / n63 cov = sum((x - ma) * (y - mb) for x, y in zip(ra, rb))64 va = math.sqrt(sum((x - ma) ** 2 for x in ra))65 vb = math.sqrt(sum((y - mb) ** 2 for y in rb))66 return cov / (va * vb) if va > 0 and vb > 0 else 0.0676869def print_shape_table(title, param_name, values, make_params):70 """打印 p 网格上的 s 值表;每行一组超参数。"""71 grid = [0.0, 0.1, 0.2, 0.3, 0.5, 0.7, 0.9, 1.0]72 print(f"\n{title}")73 print(f"{param_name:>10}" + "".join(f"{f'p={g:g}':>10}" for g in grid)74 + f"{'头尾比':>12}{'曲率':>10}")75 print("-" * 104)76 for v in values:77 a, b, g = make_params(v)78 row = [shape(p, a, b, g) for p in grid]79 # 归一化头尾比:s(1.0)/s(0.1),衡量该变换把高分与低分拉开多少80 ratio = row[-1] / row[1] if row[1] > 0 else float("inf")81 # 曲率判别:二阶差分符号82 mid = shape(0.5, a, b, g)83 lin = (shape(0.0, a, b, g) + shape(1.0, a, b, g)) / 284 curv = "凸(头部强化)" if mid < lin - 1e-9 else (85 "凹(压平头部)" if mid > lin + 1e-9 else "线性")86 print(f"{v:>10.2f}" + "".join(f"{x:>10.4f}" for x in row)87 + f"{ratio:>12.3f}{curv:>10}")888990def make_candidates(n, rng):91 """[MOCK] 合成候选集:每个候选有 CTR / LTR / CVR 三个 pxtr。9293 三个 pxtr 共享一部分内容质量 q,但各自带有独立成分——这与真实系统一致:94 点击率高的内容未必转化率高,否则任何单调融合公式都会给出同一个排序。95 """96 items = []97 for i in range(n):98 q = rng.random() # 共享的内容质量99 u1, u2, u3 = rng.random(), rng.random(), rng.random()100 ctr = min(0.95, max(0.001, 0.02 + 0.14 * q + 0.10 * u1))101 ltr = min(0.95, max(0.0001, 0.004 + 0.012 * q ** 2 + 0.016 * u2 ** 2))102 cvr = min(0.95, max(0.00001,103 0.0005 + 0.0025 * q ** 3 + 0.0035 * u3 ** 3))104 items.append({"id": f"item{i:03d}", "ctr": ctr, "ltr": ltr, "cvr": cvr})105 return items106107108FACTORS = ["ctr", "ltr", "cvr"]109110111def fuse(items, params):112 """params: {factor: (alpha, beta, gamma)};返回每个候选的融合分。"""113 out = []114 for it in items:115 s = 1.0116 for f in FACTORS:117 a, b, g = params[f]118 s *= shape(it[f], a, b, g)119 out.append(s)120 return out121122123if __name__ == "__main__":124 random.seed(42)125126 print("=" * 104)127 print("§6.4.1 乘法公式 s_i = (beta_i + alpha_i * p_i) ^ gamma_i 形状分析")128 print("=" * 104)129130 print_shape_table(131 "(a) 乘法系数 alpha 的影响(beta=0.5, gamma=1.0)—— 控制区分度",132 "alpha", [0.2, 0.5, 1.0, 2.0, 5.0],133 lambda v: (v, 0.5, 1.0))134 print("观察:alpha 越大,同样的 pxtr 差异被放大得越多(头尾比 1.346 -> 5.500),"135 "\n 排序更集中于头部;alpha 小则得分趋于平滑、探索性更强。")136137 print_shape_table(138 "(b) 偏置项 beta 的影响(alpha=1.0, gamma=1.0)—— 控制基础贡献",139 "beta", [0.0, 0.1, 0.5, 1.0, 5.0],140 lambda v: (1.0, v, 1.0))141 print("观察:beta 抬高基准水平,等于稀释该因子的相对影响力(头尾比单调下降);"142 "\n beta=0 时 p 接近 0 的候选得分直接归零,这正是正文说的"143 "「稀疏目标直接\n 参与乘法会让整体得分迅速衰减」。")144145 print_shape_table(146 "(c) 指数项 gamma 的影响(alpha=1.0, beta=0.5)—— 控制流量分配形状",147 "gamma", [0.3, 0.5, 1.0, 2.0, 3.0],148 lambda v: (1.0, 0.5, v))149 print("观察:gamma<1 为凹函数,压缩头尾差距、给中腰部更多机会;gamma=1 退化为"150 "\n 线性;gamma>1 为凸函数,强化头部效应(马太效应)。gamma 是正文"151 "\n 强调的「最需要重点调优的参数」。")152153 # ---------------- gamma=1 退化验证 ----------------154 print("\n" + "=" * 104)155 print("退化验证:gamma=1 时 (beta + alpha*p)^1 == beta + alpha*p")156 print("=" * 104)157 max_err = max(abs(shape(p / 20, 1.3, 0.4, 1.0) - (0.4 + 1.3 * p / 20))158 for p in range(21))159 print(f"p 在 [0,1] 上取 21 个点,最大绝对误差 = {max_err:.3e}(数值等价)")160161 # ---------------- 排序影响 ----------------162 items = make_candidates(200, random)163 print("\n" + "=" * 104)164 print("gamma 对最终排序与流量分配的影响(200 个候选,CTR/LTR/CVR 三因子乘法融合)")165 print("=" * 104)166 print(f"{'gamma(三因子同值)':>18}{'Top1占比':>12}{'Top10占比':>12}{'基尼系数':>12}"167 f"{'与CTR序相关':>14}{'与CVR序相关':>14}{'Top10与基线重合':>18}")168 print("-" * 104)169 base_top10 = None170 ctr_scores = [it["ctr"] for it in items]171 cvr_scores = [it["cvr"] for it in items]172 for g in [0.2, 0.5, 1.0, 2.0, 4.0, 8.0]:173 params = {"ctr": (1.0, 0.1, g), "ltr": (1.0, 0.1, g),174 "cvr": (1.0, 0.1, g)}175 sc = fuse(items, params)176 tot = sum(sc)177 order = sorted(range(len(sc)), key=lambda i: -sc[i])178 top10 = set(order[:10])179 if base_top10 is None:180 base_top10 = top10181 top1 = sc[order[0]] / tot182 t10 = sum(sc[i] for i in order[:10]) / tot183 print(f"{g:>18.2f}{top1:>12.4%}{t10:>12.4%}{gini(sc):>12.4f}"184 f"{spearman(sc, ctr_scores):>14.4f}"185 f"{spearman(sc, cvr_scores):>14.4f}"186 f"{len(top10 & base_top10) / 10:>18.0%}")187 print("-" * 104)188 print("(「与基线重合」以 gamma=0.2 的 Top10 为基线)")189 print("观察 1:三个因子取同一个 gamma 时,Score = (prod_i (beta+alpha*p_i))^gamma,"190 "是基线分的\n 单调幂变换,因此排序完全不变(Top10 重合恒为 100%,"191 "序相关恒为 0.9792/0.4515)。\n gamma 同值只改变「分数形状」而不改变"192 "「顺序」。")193 print("观察 2:但分数形状本身就是流量形状——gamma 0.2 -> 8 时基尼系数从 0.0291 "194 "升到 0.7243、\n Top1 流量占比从 0.55% 升到 5.96%。若下游按分数做"195 "概率抽样 / 分数配额,gamma\n 就直接决定马太效应强度,这正是正文说"196 "的 gamma 决定「流量分配形状」。真正改变\n 排序的是「各因子取不同 "197 "gamma」,见下一张表。")198199 # ---------------- 不同因子设不同 gamma ----------------200 print("\n" + "=" * 104)201 print("业务调控:对单个因子加权(其余因子固定 alpha=1,beta=0.1,gamma=1)")202 print("=" * 104)203 base_params = {f: (1.0, 0.1, 1.0) for f in FACTORS}204 base_sc = fuse(items, base_params)205 base_order = sorted(range(len(items)), key=lambda i: -base_sc[i])206 base_set = set(base_order[:20])207 print(f"{'调控':<22}{'Top20平均CTR':>14}{'Top20平均LTR':>14}"208 f"{'Top20平均CVR':>14}{'与基线Top20重合':>18}")209 print("-" * 104)210 configs = [("基线 (全 gamma=1)", {}),211 ("cvr gamma=3 (强化转化)", {"cvr": (1.0, 0.1, 3.0)}),212 ("cvr gamma=0.3 (弱化转化)", {"cvr": (1.0, 0.1, 0.3)}),213 ("ctr alpha=5 (放大点击)", {"ctr": (5.0, 0.1, 1.0)}),214 ("ltr beta=2 (稀释互动)", {"ltr": (1.0, 2.0, 1.0)})]215 for name, override in configs:216 pr = dict(base_params)217 pr.update(override)218 sc = fuse(items, pr)219 order = sorted(range(len(items)), key=lambda i: -sc[i])[:20]220 mctr = sum(items[i]["ctr"] for i in order) / 20221 mltr = sum(items[i]["ltr"] for i in order) / 20222 mcvr = sum(items[i]["cvr"] for i in order) / 20223 ov = len(set(order) & base_set) / 20224 print(f"{name:<22}{mctr:>14.4f}{mltr:>14.5f}{mcvr:>14.6f}{ov:>18.0%}")225 print("-" * 104)226227 # ---------------- 稀疏因子数值稳定性 ----------------228 print("\n" + "=" * 104)229 print("稀疏因子的 beta 作用:CVR 量级在 1e-4~1e-2,直接相乘导致得分数值塌陷")230 print("=" * 104)231 print(f"{'CVR 的 beta':>14}{'融合分最小值':>18}{'融合分最大值':>18}"232 f"{'最大/最小':>14}{'与CVR序相关':>14}{'Top20含CVR头部':>18}")233 print("-" * 104)234 cvr_top20 = set(sorted(range(len(items)),235 key=lambda i: -items[i]["cvr"])[:20])236 for b in [0.0, 0.001, 0.01, 0.1, 1.0, 10.0]:237 pr = dict(base_params)238 pr["cvr"] = (1.0, b, 1.0)239 sc = fuse(items, pr)240 lo, hi = min(sc), max(sc)241 ratio = hi / lo if lo > 0 else float("inf")242 top20 = set(sorted(range(len(sc)), key=lambda i: -sc[i])[:20])243 print(f"{b:>14.3f}{lo:>18.3e}{hi:>18.3e}{ratio:>14.2f}"244 f"{spearman(sc, cvr_scores):>14.4f}"245 f"{len(top20 & cvr_top20):>18d}")246 print("-" * 104)247 print("观察:beta=0 时融合分动态范围最大(最大/最小 22.94)、该因子影响力也最强"248 "\n (与 CVR 序相关 0.9455,Top20 里有 13 个 CVR 头部物品);beta 增到 10"249 " 后\n 相关性降到 0.4211、Top20 只剩 6 个,动态范围收敛到 3.08。beta"250 "既稳定数值分布,\n 又单调稀释该因子的基础影响力——正文所说 beta「调节"251 "不同目标在融合中的基础影响力」。")252 print("=" * 104)如何用于学习或编写新代码
- 先看章节 README 的依赖和运行成本,定位这个例子的输入、输出及关键函数。
- 在原书 PDF 中找到对应概念的具体页;代码演示与书中论断分开标注。
- 只在依赖和耗时可控时运行单个示例,修改一个参数并记录真实输出与失败情形。
- 参考实现新代码时说明复用、改动、测试与尚未完成的生产依赖;教学示例并非生产系统。