多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

MATLAB纳什均衡计算:从收益矩阵到可执行源码

MATLAB纳什均衡计算:从收益矩阵到可执行源码 简介针对博弈论中纳什均衡的计算问题这份MATLAB源码资源包提供了从理论推导到代码实现的完整参考内容覆盖纯策略均衡的逐点比较、混合策略均衡的期望收益优化以及线性方程组求解等关键环节适合想用程序验证概念的初学者也适合需要进行博弈分析的研究者。资源包共6个文件包括4个.m源码脚本、1个txt计算说明和1个pdf理论文档压缩包整体仅424KB轻量且便于快速下载使用。目前已有817人学习/下载可用于数学建模、经济博弈分析、算法验证等场景。源码中既包含博弈矩阵构建与收益比较方法也演示了fminunc、fmincon等优化函数和自定义迭代算法在均衡计算中的应用txt文件逐步列出公式推导与计算流程PDF则补充理论背景与实例分析二者结合可帮助读者快速复现求解过程。对于纯策略均衡不存在的博弈资源还给出了混合策略求解示例为进一步扩展和应用提供参考。1. 纳什均衡计算多数人卡在公式到代码之间两只销售团队争同一个季度奖金池你降价我也降最后谁都不赚钱这种局面就是纳什均衡——一个“单方面改策略不会变得更好”的稳定点。它很有用但要真的算出来尤其是存在混合策略时大多数人不缺概念缺的是把公式落到 MATLAB 代码上的路径。本文用一个小规模双矩阵博弈为主线先写出严格的计算公式再给一套能直接跑的 matlab 源码顺带把多均衡识别、数值容差和中文注释乱码这些实际工程问题讲清楚。适合博弈论课程作业、机制设计验证和策略仿真前期的均衡求解工作。2. 纳什均衡计算公式条件式、支撑集与互补关系2.1 双矩阵博弈的符号约定与均衡定义式假设行玩家有 n 个纯策略列玩家有 m 个纯策略收益矩阵 A 和 B 都是 n 行 m 列。A(i,j) 表示行玩家选 i、列玩家选 j 时行玩家的收益B(i,j) 是同一局面下列玩家的收益。行玩家的混合策略记作列向量 pn 维非负且和为 1列玩家的混合策略记作列向量 qm 维非负且和为 1。策略对 (p,q) 是纳什均衡当且仅当对行玩家p 是对 A q 这个列向量的最佳反应即 p 只把正概率放在 A q 取最大值的那些纯策略上对列玩家q 只把正概率放在 B p 这个列向量取最大值的那些纯策略上。用数学式写就是 pAq max_i (Aq)_i 且 pBq max_j (Bp)_j。这个定义式看起来简单但它隐含了一个非常工程化的结论某个玩家混合策略支撑集内部的所有纯策略收益必须相等。符号含义维度A行玩家收益矩阵n x mB列玩家收益矩阵n x mp行玩家混合策略n x 1q列玩家混合策略m x 1u行玩家在 q 下的最优收益标量v列玩家在 p 下的最优收益标量支撑集“内部等值”这个性质是所有计算方法的起点。设行玩家支撑集为 S列玩家支撑集为 T那么对任意 i1, i2 属于 S一定有 A(i1,:)q A(i2,:)q否则行玩家把概率从低收益纯策略挪到高收益纯策略就能提高总收益。同理对任意 j1, j2 属于 T一定有 pB(:,j1) pB(:,j2)。这就是后面支撑集枚举法的线性方程来源。2.2 从不等式到互补关系写出可直接求解的形式把行玩家的最优收益记作 u则对每个纯策略 i有 u - (Aq)_i 0。令 rR_i u - (Aq)_i这是行玩家的“未实现收益残差”。因为 sum(p)1 且 p0所以 prR u - pAq 0。当 (p,q) 是均衡时u pAq于是 prR 0。由于 rR 的每一项都非负p 的每一项也非负二者内积为 0 就意味着p_i 0 的位置必须有 rR_i 0也就是支撑集外的策略收益不高于支撑集内收益。列玩家完全同理v - (Bp)_j 0且 q(v*1 - Bp) 0。把行、列两式合起来得到一组不等式加等式rR u1 - Aq 0rC v1 - Bp 0prR qrC 0。这是最朴素的互补条件形式。在 n m 的方阵博弈里加上松弛变量可以改写成标准的线性互补问题 LCP这也是很多教材里 Lemke-Howson 算法的出发点。但 MATLAB 没有内置 Lemke-Howson实际工程里我一般直接把这组条件喂给优化器或者用支撑集枚举法解线性方程。两种做法都不需要自己实现互补转轴算法思路更直白调试也更方便。2.3 一个 2x2 例子的手算验证用经典的协调博弈做例子。A [2 0; 0 1]B [1 0; 0 2]。这个博弈有两个纯策略均衡都选策略 1 和都选策略 2还有一个混合均衡。设行玩家以概率 x 选策略 1列玩家以概率 y 选策略 1根据支撑集内部等值条件行玩家等值条件A(1,:)q A(2,:)q即 2y 0(1-y) 0y 1(1-y)解得 y 1/3列玩家等值条件pB(:,1) pB(:,2)即 1x 0(1-x) 0x 2(1-x)解得 x 2/3。在 MATLAB 里验证这个结果只需要三行A [2 0; 0 1]; B [1 0; 0 2]; y 1/3; x 2/3; row_val A * [y; 1-y] % 行玩家两个纯策略的收益应相等 col_val [x, 1-x] * B % 列玩家两个纯策略的收益应相等输出结果中 row_val 的两个分量都等于 2/3col_val 的两个分量也等于 2/3说明这个混合策略对确实满足等值条件。注意这里 x 和 y 并不是像多数人直觉想的那样投票达成一致而是各自让对手的两个选择无差异这正是指标“计算公式”和“均衡判定”之间容易脱节的地方。3. 用 MATLAB 源码算纳什均衡支撑集枚举的完整实现3.1 源码结构与输入约定支撑集枚举法的基本思路是枚举行玩家所有可能的支撑集 S 和列玩家所有可能的支撑集 T对每个 (S,T) 组合利用“支撑集内收益相等”列出线性方程解出候选概率再验证支撑集外策略没有更高收益。这个算法对 n 和 m 都小于等于 6 的小规模博弈非常可靠也是我现在处理课程和论文类小例子时优先使用的方法。函数签名如下输入 A、B 都是 n x m 收益矩阵tol 是数值容差。输出 eqs 是一个结构体数组每个元素对应一个均衡字段 p 和 q 是概率列向量u 和 v 是双方的最优收益S 和 T 是实际支撑集。function eqs nash_enumerate(A, B, tol) % nash_enumerate 支撑集枚举法计算双矩阵博弈纳什均衡 % A, B: n x m 双矩阵收益行玩家为 A列玩家为 B % tol: 收益比较容差默认 1e-8 % eqs: 结构体数组元素含 p, q, u, v, S, T if nargin 3, tol 1e-8; end [n, m] size(A); if ~isequal(size(B), [n, m]), error(A 和 B 维度必须一致); end Ss subsets(n); % 行玩家所有非空支撑集 Ts subsets(m); % 列玩家所有非空支撑集 eqs []; for si 1:numel(Ss) S Ss{si}; for tj 1:numel(Ts) T Ts{tj}; % 行玩家A(S,T)*q(T) - u 0且 sum(q(T)) 1 Hr [A(S,T), -ones(numel(S),1); ones(1,numel(T)), 0]; br [zeros(numel(S),1); 1]; try xr Hr \ br; catch continue; end if any(~isfinite(xr)), continue; end qT xr(1:numel(T)); u xr(end); if min(qT) -tol, continue; end % 列玩家B(S,T)*p(S) - v 0且 sum(p(S)) 1 Hc [B(S,T), -ones(numel(T),1); ones(1,numel(S)), 0]; bc [zeros(numel(T),1); 1]; try xc Hc \ bc; catch continue; end if any(~isfinite(xc)), continue; end pS xc(1:numel(S)); v xc(end); if min(pS) -tol, continue; end p zeros(n,1); p(S) pS; q zeros(m,1); q(T) qT; % 支撑集外策略收益不得高于支撑集内收益 if max(A*q - u) tol, continue; end if max(p*B - v) tol, continue; end eq struct(p,p,q,q,u,u,v,v,S,S,T,T); eqs [eqs; eq]; %#okAGROW end end end function Ss subsets(n) % 用二进制枚举所有非空子集避免依赖统计工具箱的 nchoosek Ss {}; for code 1:(2^n - 1) idx find(bitget(code, 1:n)); Ss{end1} idx; end end代码里最核心的是两个线性方程组的构造。行玩家的未知量是 q(T) 和 u方程数量等于 |S| 加 1其中 |S| 个方程描述“支撑集内每个纯策略收益相等”最后一个方程是概率和为 1。列玩家对称。每个候选解都要过三道关卡概率非负、支撑集内等值方程有解、支撑集外无严格更优策略。3.2 判定阈值 tol 怎么设tol 这个参数在三个地方起作用概率负值检查、支撑集外收益检查和最终去重。默认 1e-8 适合收益量级在 1 附近的小规模博弈如果收益矩阵里有 100 这样的量级建议放到 1e-6否则浮点误差可能把真正的均衡误杀。如果收益量级很小比如 0.01 到 0.1就收紧到 1e-10。判断标准很简单先跑一次看输出里有没有明明应该成立却没通过的候选解有就放宽一个数量级。A [2 0; 0 1]; B [1 0; 0 2]; eqs nash_enumerate(A, B, 1e-8); for i 1:numel(eqs) fprintf(均衡 %d: p[%.6f, %.6f], q[%.6f, %.6f], u%.6f, v%.6f\n, ... i, eqs(i).p(1), eqs(i).p(2), eqs(i).q(1), eqs(i).q(2), eqs(i).u, eqs(i).v); end这个例子会输出三个均衡包括两个纯策略均衡和一个混合均衡。纯策略均衡的支撑集大小是 1枚举法会自动覆盖不需要单独分支处理。3.3 输出字段与常见误用字段说明使用场景p行玩家混合策略列向量传给后续仿真作为策略输入q列玩家混合策略列向量与 p 配合做收益核算u行玩家的均衡期望收益用于福利比较和帕累托排序v列玩家的均衡期望收益用于福利比较S, T实际支撑集判断解类型S 和 T 只有一个元素说明是纯策略一个常见误用是把 p 和 q 当成行向量传入其他函数。nash_enumerate 输出的 p 是 n x 1q 是 m x 1如果你后续要用p * A * q计算期望收益那是标量没关系但如果你直接把 p 和 A 做矩阵乘法必须保持维度方向一致否则 MATLAB 会先报维度错误等你“修正”成行向量之后方向反了却不报错结果数值全错。这是我见过最多的一类问题。4. 多均衡与规模化用 MATLAB 优化工具箱的 fmincon 直接求互补条件4.1 把均衡公式改写成约束优化支撑集枚举法在小规模问题上非常可靠但 n 和 m 超过 8 之后支撑集数量按 2 的幂次增长枚举会变得很吃力。这时更工程化的做法是把第 2 章的互补条件直接写成约束优化问题丢给 fmincon 求数值解。沿用 2.2 的记号令 z [p; q; u; v]定义目标函数为互补残差的和J(z) u - pAq v - pBq约束条件包括p 和 q 非负、sum(p)1、sum(q)1、Aq u1、Bp v1。在这个约束集合下J(z) 的每一项都非负所以 J 的最小值 0 对应的就是精确均衡。用 fmincon 的好处是不需要自己推导 LCP 的 M 矩阵和 q 向量直接照抄公式就能实现。function sols nash_fmincon(A, B, Nstart) % nash_fmincon 用多起点约束优化求解纳什均衡 % Nstart: 随机初值次数建议 10~50 [n, m] size(A); nv n m 2; % 不等式约束Aq - u 0Bp - v 0 Ainq [zeros(n, n), A, -ones(n, 1), zeros(n, 1); B, zeros(m, m), zeros(m, 1), -ones(m, 1)]; bq zeros(size(Ainq, 1), 1); % 等式约束sum(p)1sum(q)1 Aeq [ones(1, n), zeros(1, m), 0, 0; zeros(1, n), ones(1, m), 0, 0]; beq [1; 1]; lb [zeros(n m, 1); -inf; -inf]; ub []; J (z) z(nm1) - z(1:n)*A*z(n1:nm) ... z(nm2) - z(n1:nm)*B*z(1:n); opts optimoptions(fmincon, Algorithm, interior-point, ... Display, off, StepTolerance, 1e-10, ... ConstraintTolerance, 1e-9, MaxFunctionEvaluations, 1e5); sols []; for k 1:Nstart p0 rand(n,1); p0 p0 / sum(p0); q0 rand(m,1); q0 q0 / sum(q0); z0 [p0; q0; rand; rand]; z fmincon(J, z0, Ainq, bq, Aeq, beq, lb, ub, [], opts); if J(z) 1e-6, continue; end sols [sols; struct(p, z(1:n), q, z(n1:nm), ... u, z(nm1), v, z(nm2))]; end end这段代码的关键点在于目标函数 J 是非凸的fmincon 只能保证找到局部最优。多起点策略就是弥补这个缺点的通行做法每次用随机归一化向量做初值让搜索尽量覆盖不同的支撑集区域。4.2 三个必调参数与多起点策略参数作用推荐设定StepTolerance步长收敛阈值太小会多跑很多轮1e-10ConstraintTolerance约束可行域精度直接影响收益残差1e-9MaxFunctionEvaluations上限太小容易提前结束1e5 或更高Nstart 的取值取决于均衡个数。像 2x2 协调博弈这种最多 3 个均衡的问题10 个起点就够如果是 5x5 的随机收益矩阵建议至少 30 个起点。判断“够不够”的方法是连续跑三次看看发现的均衡集合是否稳定如果不稳定就加 Nstart。这个方法不是替代 Lemke-Howson 那种精确算法的最优选择但对大多数仿真前期的均衡搜索已经够用。4.3 多均衡时如何选一组fmincon 得到的候选解往往比枚举法更密因为不同起点可能收敛到同一个均衡所以要先做去重再按目标函数 J 值排序。J 越小说明互补残差越小数值上越可信。去重判据用 p 和 q 的无穷范数距离阈值放 1e-6 比 1e-8 更稳妥。tol_dup 1e-6; uniq []; for i 1:numel(sols) is_dup false; for j 1:numel(uniq) if norm(sols(i).p - uniq(j).p, inf) tol_dup ... norm(sols(i).q - uniq(j).q, inf) tol_dup is_dup true; break; end end if ~is_dup, uniq [uniq; sols(i)]; end end去重之后如果还剩下多个均衡就按收益向量做帕累托筛选。比如协调博弈里两个纯策略均衡收益分别是 (2,1) 和 (1,2)没有帕累托占优关系这时选哪个取决于你外部规定的协调机制如果其中一个均衡的 u 和 v 同时不低于另一个直接选它就行这比强行用“混合策略期望最高”这种理由筛选更符合博弈论惯例。5. 纳什均衡计算的排错细节编码、矩阵方向与奇异支撑集5.1 “matlab源码.rar”解压后的中文注释乱码从 .rar 包里解出来的 .m 文件经常在 MATLAB 编辑器里显示乱码这通常不是文件损坏而是编码问题。MATLAB R2021a 之后默认用 UTF-8而很多老代码保存为 GBK/GB2312。处理方式有两个如果注释只是乱码但代码能跑直接在“主页 - 预设 - 编辑器/调试器 - 语言”里把文件编码改为 GBK 再重新打开如果新版 MATLAB 已经弱化了这个选项就用文本编辑器把文件转存为 UTF-8 后放回原目录。注意转换后要检查中文字符串字面量特别是 error 和 fprintf 里的中文转码可能改变字节长度导致显示异常。5.2 矩阵方向与收益维度是最常见的错误源A 和 B 必须是“同一个 n x m 布局下的双方收益”很多人把列玩家收益存成 m x n 矩阵传入后所有支撑集等值方程全部错位。判断方法很简单随机生成一个纯策略对 (i,j)A(i,j) 和 B(i,j) 应该是同一对策略下的两个数字而不是转置关系。另一个问题是把 2x2 的收益写成了 2x3 或 3x2这小问题不会报错但支撑集枚举会直接少算。最好在函数开头加一行尺寸断言错误信息提示“A 和 B 维度必须一致”能省下不少调试时间。5.3 奇异支撑集与非唯一解的处理当收益矩阵中存在两行完全相同或者某个支撑集内的等值方程线性相关时Hr 或 Hc 是奇异矩阵mldivide 会给出 NaN 或警告。枚举法里 try-catch 已经跳过这种情况但代价是那个支撑集对应的连续统均衡只输出了一个基础解。真要处理连续统均衡常见做法是把奇异支撑集对应的线性系统化作参数化形式再对自由参数做网格采样逐个验证互补条件。工程上我一般只做一步先看输出的均衡集合里是否存在同一组的 S、T 对应多个解如果存在就对自由参数做等距采样再取采样点里 J 值最小的一个作为代表解。这样不会漏掉关键均衡也避免了手工推导参数化的麻烦。一个小技巧整套代码里最后收尾的那部分我会把第 2 章的等值条件校验函数单独存成 verify_nash.m每次改完 A、B 之后先跑一遍校验再用枚举或 fmincon 求解。这样就算后面换了求解器、改了收益矩阵也能在五分钟内确认“算出来的均衡到底是不是真均衡”。本文还有配套的精品资源点击获取
返回列表