简介:一份面向供应链管理研究人员、高校物流相关专业师生及双渠道销售企业管理者的完整PDF资源,聚焦考虑跨渠道退货的双渠道闭环供应链决策优化。内容系统整合Stackelberg博弈与Nash均衡模型,对比集中式、制造商主导、零售商主导及Nash均衡结构下的定价策略,并通过Python代码实现参数定义、需求函数构建、最优化求解与利润对比,涵盖消费者渠道偏好、退货率等关键变量的敏感性与非线性关系分析。PDF共1个文件,包体约624KB,内含可运行的模型复现代码及逐步解释,便于读者快速复现研究结果,也可直接用于实际数据验证。目前已有41人学习下载,适合需要深入理解跨渠道退货影响机制、掌握不同博弈结构下最优定价与利润分配规律,以及寻求退货政策与收益共享契约设计参考的研究者和企业管理者。
1. 双渠道闭环供应链的定价困局:退货处理不好,利润表和渠道关系一起崩
考虑跨渠道退货的双渠道闭环供应链决策优化,本质上是在回答一个问题:当一个品牌的线上订单被顾客拿到线下门店退货,这笔退货损失该算谁的、价格该怎么定、渠道利润该怎么分。这不是单纯的物流成本题,而是Stackelberg博弈和Nash均衡同时存在的定价决策题——制造商线上直销和线下零售商之间既有主从关系,又在同一市场争夺顾客;退货品进入再制造闭环后,又反过来影响新品的定价空间。我最早跑这类模型时以为多设几个退货参数就能出结果,实际做下来发现:跨渠道退货的核心不在“退了多少”,而在“退回来的货由谁承担处理成本、谁享受再制造成本节约”。这两个主体不一致,Stackelberg主导方和Nash均衡下的利润分配结果能差出一倍。这篇文章给出完整建模过程、Python求解代码和五类高频踩坑点,适合做供应链定价研究、电商平台规则设计或毕业课题复现的从业者。
2. 把“跨渠道退货”写进模型:需求函数、成本结构与博弈顺序
2.1 跨渠道退货如何改变双渠道的净需求
双渠道闭环供应链里有两条正向销售渠道:制造商自营的线上直销渠道和线下零售商渠道。传统双渠道模型只写两个需求函数,比如线上需求对线上价格敏感、对线下价格交叉敏感。一旦引入跨渠道退货,事情变复杂:顾客在线上看中商品、下单、收货,然后拿到线下门店退货。线下门店因此产生验货、入库、二次包装的人工成本,这笔成本如果全部由零售商承担,零售商的利润会被退货率一点点吃掉。
建模时我把线上渠道的原始需求记为:
D_d = a_d - α * p_d + β * p_r线下渠道需求为:
D_r = a_r - α * p_r + β * p_d其中α是渠道自身价格敏感系数,β是交叉价格敏感系数。跨渠道退货对两个渠道的需求都产生修正:线上退货量λ_d * D_d最终发生在线下门店,这部分货既不能在线上形成净收入,又给零售商带来单位处理成本c_h。制造商如果不想让零售商拒绝处理退货,就必须给零售商单位补偿s。于是零售商的净利润里多了一项(s - c_h) * λ_d * D_d,制造商的净利润里多了一项负向的- s * λ_d * D_d,同时节省了新品制造成本,因为退回来的货可以翻新再售或拆解再利用。
真正让模型有意思的是退货品与再制造闭环的耦合:退回来的货越多,可再制造的材料基数越大;但退货也直接侵蚀了当期净需求。两个效应方向相反,定价高了退货少、再制造收益少,定价低了销量大、退货处理成本也大。最优价格不是一个固定值,而是退货率、补偿系数、再制造成本节约共同作用下的均衡点。
2.2 Stackelberg与Nash均衡在供应链定价里的区别
博弈顺序决定了求解方法。Stackelberg博弈适合“制造商主导、零售商跟随”的渠道结构,制造商先决定批发价w、线上直销价p_d和回收率τ,零售商观察到这些决策后再决定自己的线下零售价p_r。求解时先算零售商的反应函数,再把反应函数代回制造商的利润函数做外层优化。这里的核心是:Stackelberg 均衡体现的是“领导者利用先动优势”,领导者可以把价格定在让跟随者刚好能接受的位置,从而拿走大部分渠道利润。
Nash均衡则适合两个渠道主体同时决策的场景,制造商和零售商各自优化自己的利润,谁都不先动。此时联立一阶条件求交点。Nash均衡和Stackelberg均衡的结果通常不一样,Stackelberg 主导方利润更高,跟随方利润更低;而 Nash 均衡下双方的利润更接近,但总利润可能低于集中决策。原因在于双重边际化——各渠道只考虑自身毛利,批发价抬高导致最终零售价过高,总销量被压缩。
我一般建议研究者在建模前先想清楚一个问题:你所在的行业是制造商强势还是零售商强势?强势方不同,博弈顺序就不同,最优定价策略也完全不同。零售巨头主导的供应链适合让零售商做 Stackelberg 领导者,品牌制造商主导的供应链才适合制造商领导。标题里的“Stackelberg博弈和Nash均衡”其实是在做对比研究,不是只求一种均衡。
2.3 参数表与决策变量:没有一套参数,后面的代码全是空谈
模型里每个参数都有经济含义,参数值不同,均衡结果可能从“线上低价”翻转到“线下低价”。我常用的参数基准如下。
| 参数 | 含义 | 基准值 | 备注 |
|---|---|---|---|
a_d | 线上渠道基础市场规模 | 100 | 大于a_r时线上渠道市场潜力更大 |
a_r | 线下渠道基础市场规模 | 80 | |
α | 自身价格敏感系数 | 2.0 | 必须大于β |
β | 交叉价格敏感系数 | 0.8 | 渠道间替代强度,β < α保证均衡存在 |
c_n | 新品单位制造成本 | 10 | |
c_r | 再制造单位成本 | 4 | Δ = c_n - c_r为单件再制造成本节约 |
λ_d | 线上渠道跨渠道退货率 | 0.15 | 落在[0, 1),超过 0.3 模型对补偿敏感 |
c_h | 零售商处理跨渠道退货的单位成本 | 2.0 | 含验货、包装、库存占用 |
s | 制造商给零售商的单位退货补偿 | 1.5 | 合同外生参数,谈判可调整 |
c_t | 制造商对退货品的返修处理成本 | 1.0 | 返修后再进入再制造 |
η | 回收努力成本系数 | 20 | 回收成本是凸函数η * τ² |
Δ | 再制造单件成本节约 | c_n - c_r = 6 |
决策变量一共四个:批发价w、线上直销价p_d、线下零售价p_r、回收率τ。其中w、p_d、τ由制造商决策,p_r由零售商决策。跨渠道退货补偿s作为外生合同参数,在利润分配章节再讨论它的谈判空间。
回收率τ控制着闭环供应链的回收规模,回收量设为τ * (D_d + D_r),回收努力成本为η * τ²。这个二次成本函数很关键,它保证了回收率不会无限膨胀——边际回收成本递增,均衡回收率一定在0到1之间。若用线性回收成本函数,均衡点可能跑到边界上,数值求解时容易报错。
3. 从博弈到Python:Stackelberg与Nash均衡的完整求解代码
3.1 先写利润函数与零售商反应函数
我把求解代码分成三层:底层是参数类与利润函数,中间是零售商反应函数,顶层是两种博弈的求解器。先定义参数结构,方便后面做敏感性分析时批量改参。
from dataclasses import dataclass import numpy as np from scipy.optimize import minimize, root @dataclass class SupplyChainParams: a_d: float = 100.0 # 线上基础需求 a_r: float = 80.0 # 线下基础需求 alpha: float = 2.0 # 自身价格敏感系数 beta: float = 0.8 # 交叉价格敏感系数 c_n: float = 10.0 # 新品制造成本 c_r: float = 4.0 # 再制造成本 lam_d: float = 0.15 # 线上渠道跨渠道退货率 c_h: float = 2.0 # 零售商退货处理成本 s: float = 1.5 # 制造商给零售商的退货补偿 c_t: float = 1.0 # 制造商返修处理成本 eta: float = 20.0 # 回收努力成本系数 @property def delta(self): return self.c_n - self.c_r # 再制造单件成本节约 def demands(p_d, p_r, p: SupplyChainParams): D_d = p.a_d - p.alpha * p_d + p.beta * p_r D_r = p.a_r - p.alpha * p_r + p.beta * p_d return D_d, D_r def retailer_profit(p_d, p_r, w, p: SupplyChainParams): D_d, D_r = demands(p_d, p_r, p) # 零售商卖新品赚差价,同时处理跨渠道退货赚补偿差 return (p_r - w) * D_r + (p.s - p.c_h) * p.lam_d * D_d def manufacturer_profit(p_d, w, tau, p_r, p: SupplyChainParams): D_d, D_r = demands(p_d, p_r, p) rec_volume = tau * (D_d + D_r) # 回收总量 rec_cost = p.eta * tau**2 # 回收努力成本 return (w * D_r + (p_d - p.c_n) * D_d - p.s * p.lam_d * D_d # 给零售商的退货补偿 - p.c_t * p.lam_d * D_d # 返修处理成本 + p.delta * rec_volume # 再制造成本节约 - rec_cost) def retailer_reaction(p_d, w, p: SupplyChainParams): # 零售商利润对 p_r 求一阶导并令其为零 # d(pi_R)/dp_r = a_r - alpha*p_r + beta*p_d - alpha*(p_r - w) = 0 return (p.a_r + p.beta * p_d + p.alpha * w) / (2 * p.alpha)这段代码的逻辑有几个关键点。需求函数里的β参数是渠道间的交叉影响,它模拟的是顾客比价行为:线上价格涨了,一部分顾客转向线下;线下价格涨了,一部分顾客转向线上。零售商利润里多出的(p.s - p.c_h) * p.lam_d * D_d项不是凭空想象的收入,它对应的是“线上订单线下退货”场景下零售商代制造商处理退货获得的补偿净额。如果补偿s小于处理成本c_h,零售商就没有动力配合退货,这部分在模型里直接体现为负利润。
retailer_reaction返回的是零售商的最优价格反应函数,这个函数是解析解,不需要数值优化。它的推导逻辑是:零售商利润是p_r的二次函数,开口向下(-α项保证二阶导为负),所以一阶条件就是利润最大化的充要条件。这里p_r的表达式中同时出现p_d和w,直观含义是:线上直销价越高,线下越有涨价空间;批发价越高,零售商的成本越高,零售价自然跟着涨。
3.2 Stackelberg主从博弈求解:外层优化+内层反应
制造商是 Stackelberg 领导者时,它预判零售商会按反应函数定价,所以把p_r替换成retailer_reaction再优化自己的利润。这里我用scipy.optimize.minimize处理。
def stackelberg_solve(p: SupplyChainParams, x0=None): if x0 is None: x0 = np.array([12.0, 30.0, 0.5]) # [w, p_d, tau] def neg_profit(x): w, p_d, tau = x p_r = retailer_reaction(p_d, w, p) return -manufacturer_profit(p_d, w, tau, p_r, p) bounds = [(5, 50), (5, 80), (0.0, 0.95)] res = minimize(neg_profit, x0, method='SLSQP', bounds=bounds) w, p_d, tau = res.x p_r = retailer_reaction(p_d, w, p) pi_m = manufacturer_profit(p_d, w, tau, p_r, p) pi_r = retailer_profit(p_d, p_r, w, p) return { 'w': w, 'p_d': p_d, 'p_r': p_r, 'tau': tau, 'pi_m': pi_m, 'pi_r': pi_r, 'solver': res }这里minimize优化的是neg_profit,因为我用的是 SLSQP 算法,它默认找最小值,供应链利润最大化问题要取负号才能套进去。边界约束里的w下限设为 5 是因为如果批发价低于再制造成本,制造商更愿意收回所有货做再制造而不是卖给零售商,这属于模型边界条件,我在避坑章节会展开。
零售商反应函数retailer_reaction返回的p_r不能超过(a_r + β*p_d + α*w) / (2α)这个自然上界,所以即使外层优化尝试了很多w和p_d的组合,内层反应函数始终是解析解,不会像嵌套数值优化那样出现“内层不收敛”的问题。这也是我坚持把零售商反应函数手推成解析式的原因——嵌套优化每一层多一次数值求解,整体计算量翻倍且更容易踩局部最优。
3.3 Nash均衡求解:不动点迭代与方程组求根
Nash均衡的求解思路是认为制造商和零售商同时决策。制造商决策变量是w、p_d、τ,零售商决策变量是p_r,四个一阶条件联立求根。我直接用scipy.optimize.root,把一阶条件写成残差向量。
def nash_solve(p: SupplyChainParams, x0=None): if x0 is None: x0 = np.array([12.0, 30.0, 20.0, 0.5]) # [w, p_d, p_r, tau] def focs(x): w, p_d, p_r, tau = x D_d, D_r = demands(p_d, p_r, p) rec_volume = tau * (D_d + D_r) # d(pi_R)/dp_r = 0 foc_r = p.a_r - 2 * p.alpha * p_r + p.beta * p_d + p.alpha * w # d(pi_M)/dp_d = 0 d_pi_dpd = (p.a_d - 2 * p.alpha * p_d + p.beta * p_r + p.alpha * p.c_n + (p.beta - p.alpha) * p.lam_d * (p.s + p.c_t) - (p.delta * tau) * (p.alpha - p.beta)) # d(pi_M)/dw = 0 d_pi_dw = (p.a_r - p.alpha * p_r + p.beta * p_d - p.alpha * (w - p.c_n) + p.lam_d * (p.s + p.c_t) * p.beta - p.delta * tau * (p.alpha - p.beta)) # d(pi_M)/dtau = 0 d_pi_dtau = p.delta * (D_d + D_r) - 2 * p.eta * tau return [d_pi_dw, d_pi_dpd, foc_r, d_pi_dtau] # 注意:这里返回顺序与 x 顺序一致 res = root(focs, x0, method='hybr') w, p_d, p_r, tau = res.x pi_m = manufacturer_profit(p_d, w, tau, p_r, p) pi_r = retailer_profit(p_d, p_r, w, p) return { 'w': w, 'p_d': p_d, 'p_r': p_r, 'tau': tau, 'pi_m': pi_m, 'pi_r': pi_r, 'solver': res }Nash均衡的代码比 Stackelberg 短,但实际更易翻车。:hybr方法用的是 Powell 混合算法,它对初值敏感,我遇到多次“求出的根是负价格”的情况。这里的d_pi_dpd和d_pi_dw两个一阶条件展开时,必须把跨渠道退货项和回收项对各自决策变量的偏导数算全。
举个例子,p_d变化时,它不只影响D_d,还影响D_r(通过交叉项β),进而影响回收量τ * (D_d + D_r)。很多初写者在d_pi_dpd里漏掉- p.delta * tau * alpha这一项,只保留退货补偿部分,结果均衡线上价格明显偏离真实值。我在代码里直接用展开后的解析导数,而没有用自动微分,就是为了让每一行都能跟论文里的推导公式对上。
3.4 两种博弈结果的对比输出
求解器写好后,直接对比两组结果。
if __name__ == "__main__": p = SupplyChainParams() stack = stackelberg_solve(p) nash = nash_solve(p) print("Stackelberg 均衡: w=%.2f p_d=%.2f p_r=%.2f tau=%.3f | pi_m=%.2f pi_r=%.2f" % (stack['w'], stack['p_d'], stack['p_r'], stack['tau'], stack['pi_m'], stack['pi_r'])) print("Nash 均衡: w=%.2f p_d=%.2f p_r=%.2f tau=%.3f | pi_m=%.2f pi_r=%.2f" % (nash['w'], nash['p_d'], nash['p_r'], nash['tau'], nash['pi_m'], nash['pi_r']))输出结果通常表现为:Stackelberg 均衡下批发价和线上直销价更高,零售商利润被压缩;Nash 均衡下双方定价更温和,总利润略高但仍低于集中决策。你如果跑出“Nash 均衡价格反而高于 Stackelberg”的结果,优先检查交叉价格敏感系数β是不是设置得和α太接近,极端情况下会直接导致均衡不存在,数值求解器返回的根没有经济意义。
示例输出(基准参数): Stackelberg 均衡: w=17.32 p_d=30.15 p_r=38.24 tau=0.42 | pi_m=612.30 pi_r=198.45 Nash 均衡: w=15.81 p_d=28.40 p_r=36.12 tau=0.38 | pi_m=548.72 pi_r=286.63以上数值是演示形态,实际跑出来的数会随scipy版本和初值浮动,但量级关系是稳定的:Stackelberg 领导者的利润更高、跟随者利润更低,这是主从博弈的先动优势在定价上的体现。看到这个对比,才能理解为什么渠道强势方一定要争取“先定价”的权力。
4. 利润分配:用Nash协商把合作剩余分到不“翻车”
4.1 分散决策下双重边际化有多严重
把 Stackelberg 均衡的总利润与集中决策总利润对比,就能量化双重边际化的损失。集中决策相当于制造商和零售商合并成一家公司,统一决策p_d和p_r最大化总利润,没有批发价这个中间环节。
def centralized_solve(p: SupplyChainParams, tau_fixed=None): def neg_total(x): p_d, p_r, tau = x D_d, D_r = demands(p_d, p_r, p) rec_volume = tau * (D_d + D_r) rec_cost = p.eta * tau**2 total = ((p_d - p.c_n) * D_d + (p_r - p.c_n) * D_r - (p.c_t + p.c_h) * p.lam_d * D_d + p.delta * rec_volume - rec_cost) return -total x0 = np.array([35.0, 35.0, 0.5]) bounds = [(5, 80), (5, 80), (0, 0.95)] res = minimize(neg_total, x0, method='SLSQP', bounds=bounds) p_d, p_r, tau = res.x return p_d, p_r, tau, -res.fun集中决策下没有批发价,线上和线下统一定价思路,退货处理成本由“公司”整体承担。对比结果通常显示集中决策总利润比分散决策高 15% 到 30%。差异来自双重边际化:分散决策时零售商在批发价之上再加一层毛利,导致最终零售价偏高、总销量萎缩。
4.2 Nash协商解的计算代码
合作总利润比分散决策总利润高出部分就是合作剩余。如何分配这笔剩余,我使用 Nash 协商解——它要求分配结果让双方利润都高于不合作的威胁点,且满足帕累托最优。数学形式是在约束两方利润不低于威胁点条件下最大化协商函数:
max (φ_M - π_M^0) * (φ_R - π_R^0) s.t. φ_M + φ_R ≤ π_C, φ_M ≥ π_M^0, φ_R ≥ π_R^0威胁点π_M^0和π_R^0取 Stackelberg 均衡利润或 Nash 均衡利润均可,我默认用 Stackelberg 均衡利润,因为现实中制造商强势时威胁点就是领导者的“不合作保底利润”。
def nash_bargaining(p: SupplyChainParams, threat_m, threat_r): pi_C = centralized_solve(p)[-1] total_surplus = pi_C - threat_m - threat_r def neg_bargain(phi_m): phi_r = pi_C - phi_m if phi_m <= threat_m or phi_r <= threat_r: return 1e10 return -( (phi_m - threat_m) * (phi_r - threat_r) ) res = minimize(neg_bargain, x0=np.array([threat_m + total_surplus/2]), method='SLSQP', bounds=[(threat_m, pi_C)]) phi_m = res.x[0] phi_r = pi_C - phi_m return phi_m, phi_r, pi_C, total_surplus这个求解把二维约束优化压成了一维搜索:给定φ_M,φ_R自动等于π_C - φ_M,协商函数变成单变量函数,SLSQP很容易收敛。如果跑出来φ_M贴在下边界上,说明制造商的威胁点利润太高,合作剩余根本不足以让零售商接受合作,此时应该检查是不是参数设置让零售商在分散决策下几乎没有利润。
Nash协商分配的结果在基准参数下通常是:制造商拿到合作剩余的大头,零售商拿到的增量与其威胁点利润成比例。这一结果和直觉一致:威胁点利润越高,谈判中要价能力越强,分到的剩余越多。
4.3 分配比例的边界讨论
Nash协商解不是唯一的利润分配方案,Shapley值也常用。但 Shapley 值要求先定义所有子联盟的利润特征函数,在大联盟超可加性不满足时会出现负分配额。这里的“超可加性”指任意两个子联盟合并后的利润不低于分开的利润之和。在跨渠道退货模型里,制造商和零售商单独经营的利润之和很可能大于合作利润,因为单干时不需要互相承担退货补偿。若此时硬算 Shapley 值,某个主体会分到负数。
所以我更推荐 Nash 协商解,它只需要大联盟利润和两个威胁点利润,数据需求少,且天然保证分配结果不低于威胁点。如果你想在论文里同时报告 Shapley 值,必须先验证特征函数的超可加性,具体验证方法就是把制造商单干利润、零售商单干利润、两者合作利润放在同一张表里对比。
5. 避坑清单:从模型推导到代码复现的5个高发事故
5.1 退货率写成常数,均衡结果直接失真
现象:把λ_d设成固定值 0.15,跑敏感性分析时发现退货率从 0.1 调到 0.3,定价和利润的变化方向不符合业务直觉——退货率上升,线上价格反而下降。
原因:退货率其实不是独立的常数,它通常与价格相关。价格越高,顾客对商品期望越高,越容易因“不符合预期”退货。模型里把退货率写死,相当于切断了价格对退货的影响路径。
解决:把退货率建模为线上价格的增函数,常见做法是λ_d = λ0 + γ * p_d,其中γ是价格对退货率的边际影响系数。这样价格和退货率形成内生联动,定价模型才能反映“高价高退货”的真实权衡。
5.2 二阶条件没验证,最优解其实是“最差解”
现象:手推零售商反应函数时,一阶导数算对了,但求出的p_r使零售商利润取最小值而不是最大值。
原因:一阶条件是驻点条件,既可能是极大值也可能是极小值。零售商利润对p_r的二阶导数是-2α,只有α > 0才是凹函数、驻点是极大值。把α设成负数或没检查 Hessian 矩阵,就会得到完全反向的结论。
解决:在推导每个反应函数后,额外写一行二阶导数验证。代码里体现为对retailer_profit再求一次p_r的二阶导数,或直接数学验证-2α < 0。Stackelberg 外层优化同理,用minimize求解后最好用结果附近的网格点重新计算利润,确认没有比当前解更优的点。
5.3 Stackelberg嵌套求解对初值敏感,局部最优骗过所有人
现象:换了一组初值,minimize返回的批发价和线上价格完全不同,且后者的总利润明显更高。
原因:制造商利润函数关于w、p_d、τ不是全局凹函数,SLSQP是局部优化算法,初值落在不同盆地里就返回不同局部最优。这是嵌套优化叠加非凸函数的典型翻车场景。
解决:用多起点策略。我习惯跑 20 组随机初值,取利润最高的结果,并用网格扫描验证。具体做法是让w在[c_n, 2*c_n]之间取 10 个值,p_d在[20, 60]之间取 10 个值,τ在[0.1, 0.9]之间取 5 个值,每组组合作为初值运行一次求解器。成本是 500 次求解,换来的是均衡结果的稳定性。
5.4 跨渠道退货成本只记在制造商头上,零售商利润失真
现象:模型里把退货返修成本c_t和零售商处理成本c_h都算作制造商的支出,零售商利润只剩卖货差价。跑出来零售商利润极高,制造商利润极低,利润分配结果完全不符合行业认知。
原因:现实场景中,跨渠道退货发生在线下门店,零售商的人工成本已经发生。如果模型里只有制造商承担退货相关成本,相当于零售商白干活还能从补偿中获利,利润虚高;制造商承担了全部处理成本,利润虚低。
解决:把零售商处理成本c_h写进零售商利润函数,制造商通过补偿s承担一部分。补偿s与外生参数的关系是:s < c_h时零售商净承担退货成本,s > c_h时零售商从退货处理中赚服务费。这个参数是渠道博弈的谈判焦点,也可以在利润分配章节把它当决策变量内化。
5.5 回收率与退货率共用符号,代码静默出错
现象:代码能跑通,但回收率计算结果大于 1,或者退货率变化时回收率完全不变。
原因:变量命名混乱。我在初版代码里把回收率写成tau,把退货率写成lambda_d,某次重构后把退货率误写成tau的表达式,回收率直接变成了退货率与需求量的乘积,导致回收率超过 1 这类明显异常结果。Python 不会因为变量重名报警,模型结果却彻底失效。
解决:强制命名规范:退货率一律用lam_d,回收率一律用tau,再制造节约用delta。每段代码开头用注释声明变量含义。另外,在求解后加断言assert 0 <= tau <= 1,保证回收率落在可行域内。这个小习惯帮我提前拦下过两次静默污染。
6. 均衡唯一性与敏感性分析:验证模型不是“黑匣子”的最后一道工序
6.1 参数扫描与均衡轨迹可视化
当模型跑通后,我建议做三件事:参数扫描、均衡唯一性判断、灵敏度分析。这三步能回答“这个模型是不是我随手调参调出来的”这个灵魂拷问。
参数扫描常用做法是把高关注度的参数等距取几十个值,逐个求解均衡,画出均衡轨迹。比如s(制造商给零售商的退货补偿)从 0.5 扫到 3.0,观察p_d、p_r、双方利润如何变化。可以预期的是:补偿越高,零售商越愿意接受退货,但制造商成本越高,线上价格会被推高。若扫描曲线出现跳变点,说明模型在某个参数区间存在多重均衡,此时不能只看一个点下的结论。
def sensitivity_scan(param_name, values, solve_func): results = [] for v in values: p = SupplyChainParams() setattr(p, param_name, v) sol = solve_func(p) results.append(sol) return results这段通用扫描代码的要点是每次从默认参数复制新实例,避免参数污染。我建议你同时输出均衡解和求解器收敛标志solver.success,如果某个参数点求解失败就能准确定位到是哪一段区间出了问题,而不是整个扫描静默失败。
6.2 反应函数斜率条件与均衡唯一性判断
Nash均衡唯一性的充分条件在双渠道模型中就是交叉价格敏感系数小于自身价格敏感系数(β < α)。这个条件的直观解释:当线上价格上升 1 单位,线上需求下降α单位,同时线下需求上升β单位。如果β > α,说明渠道间替代效应强于渠道自身价格效应,一个渠道涨价反而让另一个渠道需求更大反弹,反应函数斜率大于 1,两条反应函数可能出现多个交点甚至相切。
用图形验证时,把零售商反应函数画成以p_r为纵轴、p_d为横轴的曲线,把制造商反应函数画成p_d为纵轴、p_r为横轴的曲线,在同一坐标系里看交点数量。这个交点在β < α下通常只有一个,因为零售商反应函数斜率是β / (2α),等于 0.2 左右,制造商反应函数的斜率类似,两条斜率小于 1 的曲线最多交一次。这是模型理论上的逆向验证手段,我强烈建议写进论文附录。
6.3 一个值得保留的验证脚本
最后一个技巧是给求解器加一个“合理性校验”函数,把结果同时放入原始利润函数和网格点利润列表里检查。具体实现可以用数值差分验证一阶条件残差是否接近 0,这是验证导数推导和求解器收敛的黄金标准。
def validate_focs(sol, p: SupplyChainParams, solve_type='nash'): eps = 1e-6 if solve_type == 'nash': x = np.array([sol['w'], sol['p_d'], sol['p_r'], sol['tau']]) w, p_d, p_r, tau = x D_d, D_r = demands(p_d, p_r, p) foc_r = p.a_r - 2*p.alpha*p_r + p.beta*p_d + p.alpha*w print(f"FOC_r residual: {foc_r:.2e}") def pi_m_from_x(xx): w_, pd_, pr_, tau_ = xx Dd_, Dr_ = demands(pd_, pr_, p) return manufacturer_profit(pd_, w_, tau_, pr_, p) # 数值梯度检查 grad_numeric = np.zeros(4) for i in range(4): xp = x.copy(); xp[i] += eps xm = x.copy(); xm[i] -= eps grad_numeric[i] = (pi_m_from_x(xp) - pi_m_from_x(xm)) / (2*eps) print(f"numeric grad pi_m: {grad_numeric}")这个脚本的价值在复现时最能体现。论文里的解析公式很长,任何一位读者抄到代码里都可能漏一个偏导数项。把数值梯度打印出来与解析一阶条件对照,哪一行公式抄错了立即显现。我自己的习惯是数值梯度残差超过1e-3就回去查公式,不值得在这个步骤上抱有“差不多能跑就行”的侥幸心理。
双渠道闭环供应链的决策优化做到最后,真正检验模型的不是数学复杂度,而是你能不能回答清楚三个问题:退货补偿该定在什么区间、Stackelberg 主导方该让利多少、合作剩余怎么分才让双方都有动力维持闭环。这三件事在模型里分别对应s的外生设定、威胁点利润的选择、Nash 协商函数的约束。希望这套建模加代码的路径能帮你在自己的场景里少走一点弯路。
提示:复现时先跑基准参数,确认输出符合直觉后再改参数;改完参数务必重新跑第 6 章的两个验证脚本,避免静默出错。
本文还有配套的精品资源,点击获取