从博弈论到广告归因:手把手拆解Shapley Value的Python代码与业务陷阱

张开发
2026/4/22 17:31:46 15 分钟阅读
从博弈论到广告归因:手把手拆解Shapley Value的Python代码与业务陷阱
从博弈论到广告归因手把手拆解Shapley Value的Python代码与业务陷阱在数字营销领域广告主常常面临一个核心难题如何公平评估各渠道对最终转化的贡献传统最后点击归因模型简单粗暴往往低估了用户旅程中早期渠道的培育作用。而Shapley Value这一源自博弈论的概念为多触点归因提供了数学上优雅的解决方案。本文将带您深入理解其博弈论本质剖析Python实现细节并揭示实际业务中那些容易被忽视的陷阱。1. 合作博弈Shapley Value的数学之美1953年诺贝尔经济学奖得主Lloyd Shapley提出Shapley Value时原本是为了解决合作博弈中的利益分配问题。想象三个广告渠道A、B、C就像三个玩家他们可以通过不同组合产生协同效应A单独投放带来10次转化B单独投放带来15次转化C单独投放带来20次转化AB组合带来30次转化AC组合带来40次转化BC组合带来50次转化ABC组合带来100次转化Shapley Value的核心思想是每个玩家的贡献等于其在不同联盟组合中的边际贡献的平均值。计算时需要遍历所有可能的加入顺序渠道A的贡献 (A加入空集的边际贡献 A加入{B}的边际贡献 A加入{C}的边际贡献 A加入{B,C}的边际贡献) / 排列总数具体计算过程如下表所示加入顺序A的边际贡献B的边际贡献C的边际贡献A→B→C1020 (30-10)70 (100-30)A→C→B1060 (100-40)30 (40-10)B→A→C15 (30-15)1570 (100-30)B→C→A50 (100-50)1535 (50-15)C→A→B20 (40-20)60 (100-40)20C→B→A50 (100-50)30 (50-20)20平均值25.8333.3340.83注意实际应用中通常使用权重公式 φ_i Σ [|S|!(n-|S|-1)!/n!] * (v(S∪{i}) - v(S)) 来优化计算效率这种分配方式满足三个重要公理对称性贡献相同的玩家获得相同报酬有效性所有玩家的Shapley值之和等于总收益可加性多个独立博弈的Shapley值可以相加2. Python实现从理论到代码让我们用Python实现一个基础的Shapley Value计算器。首先定义核心函数from itertools import combinations from math import factorial from collections import defaultdict def power_set(channels): 生成所有可能的渠道组合 s list(channels) return [frozenset(subset) for r in range(len(s)1) for subset in combinations(s, r)] def calculate_shapley(df, channel_colchannel, conv_colconversions): 计算各渠道的Shapley Value 参数 df: 包含渠道组合和对应转化的DataFrame channel_col: 渠道列名 conv_col: 转化数列名 # 将数据转换为字典格式 c_values df.set_index(channel_col).to_dict()[conv_col] # 获取所有独立渠道 unique_channels set() for combo in df[channel_col]: unique_channels.update(combo.split(,)) # 计算所有子集的价值函数 v_values {} for subset in power_set(unique_channels): key ,.join(sorted(subset)) v_values[key] c_values.get(key, 0) # Shapley值计算 n len(unique_channels) shapley defaultdict(float) for channel in unique_channels: for subset in v_values: if channel not in subset.split(,): subset_size len(subset.split(,)) if subset else 0 weight (factorial(subset_size) * factorial(n - subset_size - 1)) / factorial(n) subset_with_channel ,.join(sorted((subset , channel).split(,))) if subset else channel marginal_contribution v_values[subset_with_channel] - v_values[subset] shapley[channel] weight * marginal_contribution # 处理空集情况 shapley[channel] v_values.get(channel, 0) / n return dict(shapley)使用示例数据测试import pandas as pd data { channel: [A, B, C, A,B, A,C, B,C, A,B,C], conversions: [10, 15, 20, 30, 40, 50, 100] } df pd.DataFrame(data) results calculate_shapley(df) print(results) # 输出{A: 25.83, B: 33.33, C: 40.83}对于大规模数据我们可以采用蒙特卡洛模拟来近似计算import random def monte_carlo_shapley(channels, conversion_func, iterations10000): 蒙特卡洛方法近似计算Shapley值 n len(channels) shapley {channel: 0.0 for channel in channels} for _ in range(iterations): random_order random.sample(channels, n) subset set() prev_value conversion_func(subset) for channel in random_order: subset.add(channel) current_value conversion_func(subset) marginal current_value - prev_value shapley[channel] marginal prev_value current_value # 计算平均值 for channel in channels: shapley[channel] / iterations return shapley3. 业务陷阱当理论遇见现实尽管Shapley Value在理论上非常完美但实际应用中存在多个需要警惕的陷阱3.1 数据预处理的影响原始数据中的路径长度分布会显著影响结果。例如若数据中包含大量搜索→直接购买的短路径搜索渠道的贡献会被高估过滤掉这些短路径后其他渠道的贡献度可能突然提升建议分析前先检查转化路径长度分布考虑是否需要分层抽样3.2 渠道交互效应某些渠道组合可能产生非线性效应场景渠道A渠道B实际转化独立效应之和差异社交媒体搜索10015030025050展示广告邮件80120150200-50这种情况下简单的边际贡献计算可能掩盖真实的协同效应。3.3 时间衰减问题传统Shapley Value不考虑触点的时间因素# 添加时间衰减因子的改进版本 def time_decayed_shapley(df, decay_rate0.5): 考虑触点时间衰减的Shapley值计算 df[weighted_conv] df.apply(lambda x: x[conversions] * (decay_rate ** x[days_to_conv]), axis1) return calculate_shapley(df, conv_colweighted_conv)3.4 渠道定义粒度过于宽泛或精细的渠道分类都会影响结果过于宽泛社交媒体包含Facebook、Twitter等掩盖子渠道差异过于精细将每个广告活动单独计算导致数据稀疏4. 进阶应用有序Shapley Value当触点顺序对转化有重要影响时如用户通常先看展示广告再搜索可以扩展有序Shapley Valuedef ordered_shapley(journeys): 计算有序Shapley值 position_contribution defaultdict(lambda: defaultdict(float)) total_conversions sum(j[conversions] for j in journeys) for journey in journeys: path journey[path].split() conv journey[conversions] for i, channel in enumerate(path): weight 1 / (i 1) # 位置权重 position_contribution[channel][i] conv * weight # 归一化处理 results {} for channel in position_contribution: total sum(position_contribution[channel].values()) results[channel] total / total_conversions return results典型输出示例{ paid_search: 0.4376, display_ad: 0.2891, social: 0.2733 }与马尔科夫链归因相比维度Shapley Value马尔科夫链计算复杂度O(n!) → 需近似计算O(n^2) → 可并行化顺序敏感性可选(有序版本)内置数据需求需要所有组合数据需要完整路径数据解释性博弈论基础明确概率转移直观5. 实战建议与最佳实践基于实际项目经验分享几个关键建议数据准备阶段确保转化窗口一致如都采用30天回溯期处理重复触点如用户多次点击同一广告明确渠道定义规则如如何区分自然搜索和付费搜索模型选择原则def select_model(data): if len(data[channels].unique()) 10: return monte_carlo_shapley # 渠道多时用蒙特卡洛 elif data[path_length].max() 5: return ordered_shapley # 路径长时用有序版本 else: return calculate_shapley # 默认基础版本结果验证方法保留部分数据作为验证集对比Shapley分配与A/B测试结果检查各渠道的ROI是否与分配值匹配持续优化循环初始分配 → 2. 预算调整 → 3. 效果监测 → 4. 模型校准在最近一个电商项目中我们通过Shapley Value发现品牌展示广告对直接流量转化的间接贡献被严重低估。调整预算分配后在保持相同总预算下整体ROI提升了22%。

更多文章