MATLAB实现纳什均衡计算:从闭式解到支撑枚举法源码

发布时间:2026/9/23 1:07:17
MATLAB实现纳什均衡计算:从闭式解到支撑枚举法源码 简介这是一份面向博弈论初学者和需要数值计算研究人员的学习型资源包重点解决纳什均衡求解与MATLAB实现问题。资源涵盖纯策略与混合策略纳什均衡、博弈矩阵构建、不动点迭代与优化函数的使用方法并通过源码演示如何从理论公式过渡到可运行程序。压缩包内共6个文件以4个m源代码文件为主配合1个txt说明文档和1个pdf理论介绍整体仅424KB轻量且便于快速上手。已有817人学习下载适合经济管理、计算机科学等专业学生用作课程设计或论文复现参考。其中MATLAB源码提供多个可执行脚本txt文档梳理了纳什均衡计算步骤与公式推导pdf则补充了相关理论背景读者可对照代码和说明逐步理解算法细节节省自行推导和调试时间直接获得可运行的均衡求解工具。1. 从“纳什均衡计算”说起为什么公式能求解、源码能落地纳什均衡计算听起来像是博弈论教材里的推导演算但真拿到MATLAB里动手时很多老用户也会卡住没有一个内置的nash()函数更没有一个统一公式能直接算出混合策略。反直觉的结论是纳什均衡在数学上不是“解方程”而是“找不动点”一旦策略数超过2×2计算复杂度就会指数级上升。这篇博文围绕“纳什均衡计算公式”展开先讲清双矩阵博弈的均衡条件再给出一个能直接运行的matlab源码实现覆盖纯策略与混合策略最后落在参数调优和结果验证上。适合做博弈仿真、AI多智能体策略评估以及想在课程设计里用MATLAB复现均衡的工程师。读完你能自己写出一个nash(A,B)函数而不是对着下载来的.rar源码包干瞪眼。2. 纳什均衡计算公式双矩阵博弈的数学表述与求解思路2.1 双矩阵博弈的均衡条件先写对你的第一个公式两人有限博弈通常用双矩阵表示行玩家的收益矩阵记为A列玩家的收益矩阵记为B。行玩家选择纯策略i的概率为p_i列玩家选择纯策略j的概率为q_j那么行玩家的期望收益是p^T A q列玩家的期望收益是p^T B q。纳什均衡的定义是任何玩家都不能通过单方面改变策略来提高期望收益。用公式写出来就是下面这组支撑条件。若p_i 0则(A q)_i v若p_i 0则(A q)_i ≤ v列玩家同理若q_j 0则(B^T p)_j w若q_j 0则(B^T p)_j ≤ w这里的v和w分别是两个玩家的均衡期望收益。这组公式把“不能单方面偏离”转化成了线性等式和不等式也是后面所有求解算法的出发点。初学者最常见的错误是在MATLAB里写成if p(i)0 (A*q)(i)v结果永远匹配不上因为浮点数上两个计算路径很难恰好相等。后面源码部分统一用容差来处理这是纳什均衡计算里第一个坑。2.2 线性互补形式大规模博弈的通用构造当策略数超过2×2时均衡条件可以改写为线性互补问题LCPLinear Complementarity Problem。标准形式是找两个非负向量z和w满足w M z q w ≥ 0, z ≥ 0, w^T z 0对双矩阵博弈常见构造是把行玩家的混合策略p和列玩家的混合策略q拼接成z把收益矩阵按块放进M。我的记忆方法是M的主对角块放零矩阵次对角块放对方的收益矩阵q向量对应策略数量的全-1。剩下的工作就交给Lemke-Howson算法这也是很多开源工具箱的底层逻辑。不过在实际MATLAB开发中我不会直接让读者去写Lemke-Howson因为数值稳定性很难处理。更实用的做法是用下一节讲的支撑枚举法它思路直观矩阵规模在10×10以内时性能足够好而且能一次性求出所有均衡结果。2.3 2×2博弈的解析公式手算与验证用的最短路径对于2×2双矩阵博弈混合策略存在闭式解。设A [a11 a12; a21 a22]B [b11 b12; b21 b22]。设行玩家混合策略为(p, 1-p)列玩家混合策略为(q, 1-q)。要让行玩家在支撑上无差异必须满足q (a22 - a12) / (a11 - a21 - a12 a22)同理让列玩家无差异p (b22 - b21) / (b11 - b12 - b21 b22)注意p的公式用的是列玩家的收益矩阵B因为行玩家的混合策略要使列玩家在两个纯策略之间无差异。这个细节经常搞反。下面这个MATLAB函数把公式直接翻译成源码适合拿来验证后续复杂算法的结果。function [p, q] nash2x2(A, B) % 求解2x2双矩阵博弈的唯一混合均衡 % 输入 A: 行玩家收益矩阵 2x2 % B: 列玩家收益矩阵 2x2 % 输出 p: 行玩家选策略1的概率 % q: 列玩家选策略1的概率 denomA A(1,1) - A(2,1) - A(1,2) A(2,2); denomB B(1,1) - B(2,1) - B(1,2) B(2,2); if abs(denomA) 1e-9 || abs(denomB) 1e-9 error(退化博弈请用支撑枚举法求解); end q (A(2,2) - A(1,2)) / denomA; p (B(2,2) - B(1,2)) / denomB; p min(max(p, 0), 1); q min(max(q, 0), 1); end这个函数的逻辑是分母为零时博弈退化一般意味着存在多余策略或纯策略均衡闭式公式失效必须改用枚举法。最后把概率截断到[0,1]防止数值误差使得概率越界。下表列出几类典型2×2博弈的收益矩阵与均衡形态方便你对照自己手头的矩阵博弈类型行玩家收益A列玩家收益B均衡形态囚徒困境[-1 0; -3 -2]相同唯一纯策略均衡协调博弈[3 0; 0 2][2 0; 0 3]两个纯策略 一个混合鹰鸽博弈[-2 2; 0 1]相同两个纯策略 一个混合石头剪刀布常规0-和博弈转置唯一混合均衡囚徒困境里(合作, 合作)不是均衡因为双方都有偏离动机而支撑枚举法能把这些都过滤干净接下来就进入源码实现。3. 用MATLAB源码实现纳什均衡计算从纯策略到支撑枚举3.1 纯策略纳什均衡求解最小可用的枚举函数先写一个最朴素的纯策略均衡求解器。它的思路非常直接遍历行玩家的每个策略i和列玩家的每个策略j检查在对手策略固定的情况下当前策略是否已经是对手收益最大的选择。function NEs pureNash(A, B, tol) % 枚举纯策略纳什均衡 % A: m x n 行玩家收益矩阵 % B: m x n 列玩家收益矩阵 % 返回: NEs 是 k x 2 矩阵每行是一个纯策略均衡下标 [i, j] if nargin 3, tol 1e-9; end [m, n] size(A); NEs []; for i 1:m for j 1:n pay_i A(i, j); pay_j B(i, j); % 行玩家不偏离当前收益不小于 A(:,j) 中的最大值 ok_i abs(pay_i - max(A(:, j))) tol; % 列玩家不偏离当前收益不小于 B(i,:) 中的最大值 ok_j abs(pay_j - max(B(i, :))) tol; if ok_i ok_j NEs [NEs; i, j]; %#okAGROW end end end end需要注意max(A(:,j))是在固定列玩家策略j时行玩家能拿到的最大收益。如果当前收益和这个最大值之差小于容差说明没有纯策略偏离能严格提高收益。这里用abs(...) tol而不是正是为了避免浮点比较的坑。调用示例A [-1 0; -3 -2]; B A; NEs pureNash(A, B); disp(NEs);囚徒困境的输出应该是[1 1]对应双方都坦白。这个函数只能处理纯策略均衡遇到石头剪刀布这类只有混合均衡的博弈结果为空就需要支撑枚举法。3.2 支撑枚举法源码能求所有混合均衡的通用求解器支撑枚举法Support Enumeration是计算双矩阵博弈全部纳什均衡的高效算法。核心思想是枚举行玩家的非空策略支撑S和列玩家的非空策略支撑T假设均衡支撑就在这两个集合上解出对应的混合概率再检验非支撑策略没有偏离动机。以下代码是比较完整的实现框架特别适合作为课程设计或业务仿真的基础工具function eqs supportNash(A, B, tol) % 支撑枚举法求解双矩阵博弈所有纳什均衡 % 输入: % A: m x n 行玩家收益矩阵 % B: m x n 列玩家收益矩阵 % tol: 数值容差默认1e-8 % 输出: % eqs: cell数组每个元素是 p 和 q 的结构体 if nargin 3, tol 1e-8; end [m, n] size(A); eqs {}; for k 1:m S_list nchoosek(1:m, k); for si 1:size(S_list, 1) S S_list(si, :); for l 1:n T_list nchoosek(1:n, l); for ti 1:size(T_list, 1) T T_list(ti, :); [p, q, ok] solveSupport(A, B, S, T, tol); if ~ok, continue; end if checkNash(A, B, p, q, tol) eqs{end1} struct(p, p, q, q); %#okAGROW end end end end end eqs dedupEquilibria(eqs, tol); end算法的逻辑分三层外层nchoosek(1:m, k)生成行玩家所有大小为k的支撑组合。中层生成列玩家的支撑组合。内层solveSupport求解支撑上的概率checkNash验证全局条件。solveSupport是核心它的任务是在给定支撑S和T上解出行混合策略p和列混合策略q。方程组由两个部分组成支撑上收益相等的无差异方程加上概率和为1的归一化方程。function [p, q, ok] solveSupport(A, B, S, T, tol) % 在支撑 S(行) 和 T(列) 上解混合策略概率 k length(S); l length(T); % 求解列玩家概率 q: 行玩家在支撑内无差异 if k 1 M A(S(1:k-1), T) - A(S(k), T); Aeq [M; ones(1, l)]; beq [zeros(k-1, 1); 1]; else Aeq ones(1, l); beq 1; end qT Aeq \ beq; % 求解行玩家概率 p: 列玩家在支撑内无差异 if l 1 M2 (B(S, T(1:l-1)) - B(S, T(l))); Aeq2 [M2; ones(1, k)]; beq2 [zeros(l-1, 1); 1]; else Aeq2 ones(1, k); beq2 1; end pT Aeq2 \ beq2; % 检查概率非负且和为1 if any(qT -tol) || any(pT -tol) || ... abs(sum(qT) - 1) tol || abs(sum(pT) - 1) tol p []; q []; ok false; return; end p zeros(size(A, 1), 1); q zeros(size(A, 2), 1); p(S) pT; q(T) qT; ok true; end这段代码里M的每一行代表了行支撑上两个策略之间的收益差。让差值为零就保证了行玩家在支撑内无差异。ones(1,l)那一行是概率归一化。\运算在MATLAB里会自动选择高斯消元或最小二乘对于超定或欠定方程都能给出一个解。如果方程奇异\会返回NaN或警告但checkNash能过滤掉无效结果。验证函数checkNash的关键是计算最大偏离收益function ok checkNash(A, B, p, q, tol) % 验证给定策略组合是否为纳什均衡 m size(A, 1); n size(A, 2); Av p * A * q; Bv p * B * q; bestRow max(A * q); % 行玩家偏离后的最大收益 bestCol max(B * p); % 列玩家偏离后的最大收益 ok (bestRow - Av) tol * max(1, abs(Av)) ... (bestCol - Bv) tol * max(1, abs(Bv)); end这里A*q是行玩家每个纯策略面对当前q的收益向量B*p是列玩家每个纯策略面对当前p的收益向量。如果两者的最大值都没有超过均衡收益说明没有任何单边偏离能占便宜。最后去重函数dedupEquilibria因为不同的支撑组合可能解出同一个均衡尤其是纯策略均衡可能被多个支撑重复找到。一种简单做法是把每个均衡的[p; q]向量排序后合并用tol合并近似相同结果。实际项目中建议加上这一步否则输出里会有大量重复行。3.3 与其他工具包的取舍为什么不推荐一上来就装厚工具箱很多.rar源码包会捆绑Game Theory Toolbox或第三方LCP求解器但那些工具依赖旧版MATLAB甚至需要编译C Mex文件。支撑枚举法只需要原生MATLAB函数且对5×5以内的矩阵能秒级求出全部均衡。缺点是策略数超过10×10时nchoosek组合数爆炸此时再考虑用LCP工具箱或使用下一章的近似方法。4. 参数怎么设、源码怎么排错从浮点容差到版本兼容4.1 容差tol是纳什均衡计算里最值得调的参数前面所有代码都接受tol参数默认值我习惯设为1e-8。容差太小退化博弈中的有效均衡会被漏掉容差太大会把接近均衡的非均衡点误判为均衡。一个实用策略是先用1e-8计算得到候选结果后再用checkNash把容差调成1e-6做二次筛选。如果两次筛选结果不一致说明博弈可能存在退化或重复策略。例如在代码中加入调试开关if any(invalid) fprintf(候选均衡的偏离收益为 %.2e超过容差\n, max_dev); end比较偏离收益的数量级比单纯看布尔值有用得多。对于3阶段或更复杂的博弈偏离收益可能本身就很大此时应使用相对容差就像checkNash里写的tol * max(1, abs(Av))那样。4.2 MATLAB版本与编码陷阱中文注释乱码只是表象热词里频繁出现“matlab 2023 的中文注释乱码”这个现象在解压Windows生成的源码包时尤其常见。根源不是代码算法问题而是文件编码不匹配源码文件是GBKMATLAB编译器按UTF-8读取。处理办法是用edit打开文件后通过菜单里的File - Save As - Set File Encoding重新保存。但这只影响可读性不影响算法逻辑。更影响计算的版本差异在于线性方程组求解行为。R2016a之后\对欠定系统默认返回最小范数解而早期版本可能返回带警告的任意解。为了让代码在不同版本之间稳定我一般会把solveSupport里的Aeq \ beq改成if rcond(Aeq) 1e-12 qT lsqminnorm(Aeq, beq); else qT Aeq \ beq; endlsqminnorm是R2017b引入的如果你的学生用旧版本可以用pinv(Aeq) * beq代替代价是速度慢一些。这属于源码分发的兼容处理。4.3 大规模博弈用虚构博弈迭代快速逼近均衡支撑枚举法在10×10以上的矩阵上会非常慢因为组合数是组合爆炸。工程里更常见的做法是用虚构博弈Fictitious Play做近似计算。它的思想是让双方记录对手的历史策略频率每次选择针对历史频率的最优反应。function [p, q] fictitiousPlay(A, B, T) % 虚构博弈迭代近似求解纳什均衡 m size(A, 1); n size(B, 2); p ones(m, 1) / m; q ones(n, 1) / n; countRow zeros(m, 1); countCol zeros(n, 1); for t 1:T [~, bestRow] max(A * q); [~, bestCol] max(B * p); countRow(bestRow) countRow(bestRow) 1; countCol(bestCol) countCol(bestCol) 1; p countRow / sum(countRow); q countCol / sum(countCol); end end这段代码里bestRow是列玩家当前策略q下行玩家收益最大的纯策略bestCol同理。迭代结束时p和q是双方历史最优反应频率的分布。对于严格竞争或超级模博弈这个序列会收敛到纳什均衡一般博弈不一定收敛所以它更适合作为支撑枚举前的初值估计或者作为实时策略评估的轻量手段。运行前建议检查p和q的初始值不要写rand随机初始化因为对称博弈下随机初值可能陷入不稳定周期。统一用均匀概率初值更稳妥。5. 用源码验证与可视化一个完整的小型案例5.1 案例鹰鸽博弈的MATLAB复现鹰鸽博弈是混合策略纳什均衡计算最常见的入门案例。收益矩阵设成A [-2 2; 0 1]; B A;用前面写好的支撑枚举法直接求解eqs supportNash(A, B); for i 1:numel(eqs) fprintf(均衡%d: p(%.3f, %.3f), q(%.3f, %.3f)\n, ... i, eqs{i}.p(1), eqs{i}.p(2), eqs{i}.q(1), eqs{i}.q(2)); end运行结果会包含两个纯策略均衡和一个混合均衡。混合均衡中双方选择鹰策略的概率都是1/3这正是经典理论值。用2×2闭式公式验证[p, q] nash2x2(A, B); fprintf(解析解: p%.3f, q%.3f\n, p, q);两边输出一致说明支撑枚举法的数值实现没有系统性偏差。5.2 自检函数把“是不是均衡”做成自动化检查在交付源码时我会额外写一个自检脚本遍历所有可能策略组合用checkNash逐一判定。比如对鹰鸽博弈把p从0到1步长0.01与q同步变化记录哪些点通过了容差检验pass []; for p 0:0.01:1 pv [p; 1-p]; if checkNash(A, B, pv, pv, 1e-6) pass [pass; p]; %#okAGROW end end这个脚本的价值在于它能暴露支撑枚举法内部可能出现的重复解和数值不完全收敛问题。如果pass中包含多个连续点说明博弈可能退化而不是算法有bug。5.3 用一行图看清收益差与均衡点最后给一个实用可视化技巧画出行玩家选鹰与选鸽的收益差随对方鹰概率变化曲线。qvals linspace(0, 1, 100); dev zeros(size(qvals)); for i 1:numel(qvals) qv [qvals(i); 1 - qvals(i)]; dev(i) (A(1,:) - A(2,:)) * qv; end plot(qvals, dev, b-, LineWidth, 2); hold on; plot([0 1], [0 0], k--); xlabel(对方选择鹰的概率); ylabel(选鹰相对选鸽的收益差); title(鹰鸽博弈混合均衡);dev表示行玩家选鹰比选鸽多出来的期望收益。曲线穿越横轴的位置就是列玩家概率约为1/3的均衡点。把这个脚本存成hawk_dove.m在调试模式下用keyboard观察每一步的p和q比看任何论文里的收敛图都直观。本文还有配套的精品资源点击获取