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 的书中论断。讲解时请结合实际代码、输入输出与原书页码。

源码结构与阅读入口

  1. shape · 第 35 行乘法公式单因子变换 s = (beta + alpha * p)^gamma。
  2. gini · 第 43 行
  3. spearman · 第 52 行a、b 是两组分数,返回其秩相关。
  4. print_shape_table · 第 69 行打印 p 网格上的 s 值表;每行一组超参数。
  5. make_candidates · 第 90 行[MOCK] 合成候选集:每个候选有 CTR / LTR / CVR 三个 pxtr。
  6. fuse · 第 111 行params: {factor: (alpha, beta, gamma)};返回每个候选的融合分。

完整源码 · 252 行

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)

如何用于学习或编写新代码

  1. 先看章节 README 的依赖和运行成本,定位这个例子的输入、输出及关键函数。
  2. 在原书 PDF 中找到对应概念的具体页;代码演示与书中论断分开标注。
  3. 只在依赖和耗时可控时运行单个示例,修改一个参数并记录真实输出与失败情形。
  4. 参考实现新代码时说明复用、改动、测试与尚未完成的生产依赖;教学示例并非生产系统。