简介:纳什均衡计算MATLAB源码与配套说明资料,面向博弈论学习者、经济学研究人员及需要实现均衡求解算法的MATLAB开发者。资源覆盖纯策略与混合策略纳什均衡的建模和计算,含博弈矩阵构建、期望收益推导、均衡点求解的实现思路,适用于课程设计、论文仿真或算法验证等场景。
包内共6个文件,以4个m格式MATLAB源程序为主,配合1个txt公式说明与1个pdf理论文档。其中MATLAB脚本覆盖博弈矩阵输入、混合策略求解、收益计算等功能模块,txt文件梳理纳什均衡的计算步骤与关键公式,pdf提供理论背景与案例参考,整体压缩包仅424KB。
已有817人学习下载。研读源码与文档,读者可快速掌握如何用MATLAB求解纯策略与混合策略纳什均衡,理解不动点迭代等优化思路,并将代码迁移到经济博弈、工程决策等实际分析中。
1. 从价格战说起:纳什均衡计算的真正难点在哪
两家公司在同一个市场打价格战,彼此盯着对方的报价调整策略,最后稳定在哪个价格组合上,不是拍脑袋定的,而是纳什均衡问题。很多人以为算均衡就是套公式,动手之后才发现,纯策略均衡可以逐格划线找出来,混合策略均衡却要解联立方程组,一旦策略数超过三个,数值求解的稳定性立刻变成主要矛盾。更麻烦的是,同一个博弈可能同时存在多个均衡,也可能一个纯策略均衡都没有,判断和验证的环节比求解本身更容易出错。这份 MATLAB 源码包正好覆盖了完整链路:simulationdata.m 负责构造博弈矩阵,gamer.m 封装单步最优反应逻辑,npg.m 通过迭代把整个流程收敛到均衡解,加上 untitled.pdf 里的理论推导和纳什均衡计算.txt 里的公式笔记,基本把从公式到代码落地的每一步都占上了。这篇文会把每个文件在计算流程里的位置讲清楚,给出可以直接抄的判定函数和迭代器,最后补上验证与容差处理的具体做法。
2. 纯策略均衡的判定:从划线法到收益矩阵遍历
2.1 划线法背后的判定逻辑
纯策略纳什均衡的经典判定方法是划线法:对每个列策略,找出行玩家的最优应对并划线;再对每个行策略,找出列玩家的最优应对并划线;两个方向都划到线的单元格就是纯策略均衡。这个方法实质上是在做最优反应映射的交叉点搜索,收益矩阵中每个单元格的二元组表示行玩家收益、列玩家收益。
| 行玩家 \ 列玩家 | 平价 | 高价 |
|---|---|---|
| 平价 | (4, 4) | (7, 2) |
| 高价 | (2, 7) | (5, 5) |
上面这个定价博弈里,列玩家选择平价时,行玩家在平价得到 4、在高价得到 2,所以行玩家划到平价单元格;列玩家选择高价时,行玩家平价得到 7、高价得到 5,仍划到平价单元格。反过来,行玩家选择平价时,列玩家平价得到 4、高价得到 2;行玩家选择高价时,列玩家平价得到 7、高价得到 5,列玩家始终选择平价。两个方向都落线的只有(平价,平价),这就是该博弈唯一的纯策略纳什均衡。这个例子的信息量在于:划线法看起来是先看对方再选自己,本质上是把双方的最优反应函数同时求交,代码实现时最容易出错的地方不是比较大小,而是处理并列最优的情况,后文会专门展开。
2.2 MATLAB 实现中容易漏均衡的写法
把划线法翻译成代码有两种常见做法:一种是用 max 取每一列的最大值下标,另一种是用 find 收集所有达到最大值的下标。前一种在收益矩阵存在并列最优时会直接漏掉多个均衡,某些课程代码甚至因此给出错误结论。下面这个版本是我在实际项目中用的写法,先把每个策略下标对应的最优应对集合全部算出来,再取交集:
function idx = findPureNE(A, B)
% A 行玩家收益矩阵, 维度 n1 x n2
% B 列玩家收益矩阵, 维度 n1 x n2
% idx 每行记录一个纯策略均衡 (i, j)
[n1, n2] = size(A);
rowBest = cell(1, n2); % 每个列策略下, 行玩家的最优应对集合
colBest = cell(n1, 1); % 每个行策略下, 列玩家的最优应对集合
for j = 1:n2
rowBest{j} = find(A(:, j) == max(A(:, j)));
end
for i = 1:n1
colBest{i} = find(B(i, :) == max(B(i, :)));
end
eq = [];
for i = 1:n1
for j = 1:n2
if ismember(i, rowBest{j}) && ismember(j, colBest{i})
eq = [eq; i, j];
end
end
end
idx = eq;
end
逻辑说明:rowBest 用 cell 数组按列存储,每一列 j 对应的元素是行玩家在该列下的所有最优行下标;colBest 按行存储所有最优列下标。最后两层循环遍历全部单元格,只有同时满足两个最优应对集合的格点才被判定为均衡。参数方面,A 和 B 分别表示两个玩家的收益矩阵,维度必须一致,否则 size 取到的 n1、n2 会直接错位。这里用 find 而不是 max 返回值,是为了把并列最优全部保留下来,判断时用 ismember 而不是 ==,避免了整数下标直接比较时在多均衡场景下的漏判。
这个实现还有一个隐含约定:A 的行方向表示行玩家的策略编号,B 的列方向表示列玩家的策略编号,收益是同一策略组合下各自的收益。很多人会把收益直接写成一个 n1 x n2 x 2 的三维数组,然后在函数内部拆,那样也能用,只是每次传参都要做一次 permute,代码可读性反而下降。我习惯把 A、B 分开传,后续扩展成多人博弈时,可以把第三个玩家的收益矩阵 C 追加为第三个参数,改动面最小。纯策略均衡存在的条件是这个交集非空,如果 eq 返回空数组,说明该博弈不存在纯策略均衡,下一步就轮到混合策略求解器接手。
3. 混合策略均衡的计算:方程组构造与数值方法
3.1 无差异原理与期望收益方程
纯策略均衡不存在的典型例子是抛硬币匹配博弈:行玩家出正面或反面,列玩家也出正面或反面,两人收益恰好相反。写成收益矩阵后,行玩家在每一列的最优应对都在变,列玩家在每一行的最优应对也在变,划不出双划线交点。此时玩家只能按概率随机选择策略,这就是混合策略。混合策略纳什均衡的核心是最大化条件从单点收益变成期望收益,每个玩家选择概率分布,使得自己在对方概率分布下的期望收益达到最大。
| 行玩家 \ 列玩家 | 正面 | 反面 |
|---|---|---|
| 正面 | (1, -1) | (-1, 1) |
| 反面 | (-1, 1) | (1, -1) |
无差异原理说的是:在混合策略均衡中,一个玩家以正概率选择的任意两个纯策略,带给他的期望收益必须相等。如果不等,玩家就会把概率全部压到期望收益更高的纯策略上,这就不构成均衡。对行玩家来说,设列玩家以概率 q 选正面、以概率 1-q 选反面,行玩家选正面的期望收益是 q 1 + (1-q) (-1),选反面的期望收益是 q*(-1) + (1-q)*1,两式相等推出 q=0.5。对称地,列玩家侧也得到 p=0.5。这个例子说明了混合均衡计算的基本套路:先设对方概率分布,再列自己的期望收益等式,最后联立求解。
3.2 2x2 双矩阵博弈的公式解
把无差异原理推广到一般 2x2 双矩阵博弈,行玩家收益矩阵 A、列玩家收益矩阵 B。设行玩家以 p 概率选第 1 行,以 1-p 选第 2 行;列玩家以 q 概率选第 1 列,以 1-q 选第 2 列。行玩家的两个纯策略期望收益分别为 q*a11 + (1-q) a12 和 q a21 + (1-q)*a22,令其相等得到 q = (a22 - a12) / (a11 - a12 - a21 + a22)。列玩家侧同理,p = (b22 - b21) / (b11 - b12 - b21 + b22)。注意分母出现的是四格收益的交叉组合,实际含义是无差异方程中 q 的系数,代码里可以直接用这个公式:
function [p, q] = mix_eq_2x2(A, B)
% A 行玩家 2x2 收益矩阵
% B 列玩家 2x2 收益矩阵
% p 行玩家选择第1行的概率, q 列玩家选择第1列的概率
a11 = A(1,1); a12 = A(1,2); a21 = A(2,1); a22 = A(2,2);
b11 = B(1,1); b12 = B(1,2); b21 = B(2,1); b22 = B(2,2);
denA = a11 - a12 - a21 + a22;
denB = b11 - b12 - b21 + b22;
if abs(denA) < 1e-12 || abs(denB) < 1e-12
p = NaN; q = NaN;
return;
end
q = (a22 - a12) / denA;
p = (b22 - b21) / denB;
if p < 0 || p > 1 || q < 0 || q > 1
p = NaN; q = NaN;
end
end
逻辑说明:q 的表达式来自行玩家无差异方程,p 的表达式来自列玩家无差异方程,两者使用的收益来源不同,写成两个分母是为了防止 A、B 数值差距悬殊时出现误导。参数方面,返回的 p、q 必须落在 [0,1] 区间内,越界意味着该方向上没有混合均衡,只有纯策略均衡或纯策略混合边界均衡,此时返回 NaN 比返回越界数值更安全。分母接近零的处理被放在最前面,这么做是因为差值型分母在收益矩阵数值较大时容易发生灾难性抵消,判断阈值取 1e-12 还是 1e-10 取决于你的收益量级,收益是千以内的整数就用 1e-12,换算成浮点收益后建议提到 1e-9。
这个公式解只适用于 2x2,一旦策略数超过两个,联立方程变成高维非线性系统,直接求根的机会很少。另一条路是把问题转成线性规划或线性互补问题,但实现复杂度立刻上升,这也是为什么 MATLAB 里更常用迭代法处理一般维度的均衡计算。
3.3 高维博弈的迭代求解思路
对策略数大于 2 的博弈,常见做法是不动点迭代、海萨尼梯度法,或者按最优反应动态逐轮调整。海萨尼梯度法的思路是把混合策略看作连续变量,沿着期望收益梯度方向修正概率分布,收敛效果依赖步长设置,步长太大容易震荡,步长太小则迭代次数暴涨。更稳定的思路是让双方轮流做一次最优反应,同时把历史选择记录下来,用历史频率估计对方的混合策略,这类方法就是虚拟博弈的一种工程化变体,npg.m 正是按这个方向实现的。下一章顺着源码把迭代流程拆开看。
4. npg.m 源码拆解:虚拟博弈与仿真数据生成
4.1 文件职责与计算链路
这份压缩包里的文件不是零散代码,而是围绕一条完整计算链组织的。我按测试驱动的方式把它拆成四层:simulationdata.m 负责生成随机收益矩阵作为输入,gamer.m 封装单个玩家的最优反应选择逻辑,npg.m 作为主求解器做迭代收敛,untitled.pdf 和纳什均衡计算.txt 提供公式推导和计算过程的文字说明,外层还有 Untitled2.m 这类交互脚本。说句题外话,源码里出现 Untitled2.m 这种名字,说明作者是在编辑器里随手存的名,接手这类资源时第一件事就是根据函数依赖改名,否则时间一长自己都会忘掉哪个脚本是入口。
| 文件 | 职责 | 主要接口 |
|---|---|---|
| npg.m | 主求解器,迭代计算混合策略均衡 | 输入收益矩阵 A、B,输出混合策略 x、y |
| gamer.m | 封装单个玩家的最优反应计算 | 输入收益矩阵和对方策略,输出最优纯策略下标 |
| simulationdata.m | 生成随机博弈收益矩阵 | 输入维度与收益区间,输出 A、B |
| Untitled2.m | 交互或批量测试脚本 | 调用 npg.m 与 simulationdata.m |
| untitled.pdf / 纳什均衡计算.txt | 理论推导与公式笔记 | 纯文本参考,无接口 |
4.2 虚拟博弈迭代器实现
虚拟博弈的思想是让两个玩家不断重复博弈,每轮都基于对方历史行动频率选择一个最优反应,再把这次选择计入历史频率。迭代足够多轮后,历史频率会收敛到混合策略均衡。实现时有两个细节要注意:一是最优反应要用当前经验频率而不是上一轮瞬时策略,否则容易来回震荡;二是收敛判据不能只看概率变化量,还要看收益侧是否存在单边改进空间。以下代码只保留核心循环逻辑:
function [x, y, info] = npg_fictitious_play(A, B, T, tol)
% A, B 分别为行玩家与列玩家的收益矩阵, 维度 n1 x n2
% x, y 为最终混合策略概率向量
% info 记录收敛状态、迭代次数与收益缺口
n1 = size(A, 1);
n2 = size(B, 2);
cntR = zeros(n1, 1);
cntC = zeros(n2, 1);
x = ones(n1, 1) / n1;
y = ones(n2, 1) / n2;
info.iters = T;
info.converged = false;
info.gap = Inf;
for t = 1:T
[~, i] = max(A * y); % 行玩家对 y 的最优反应
[~, j] = max(B' * x); % 列玩家对 x 的最优反应
cntR(i) = cntR(i) + 1;
cntC(j) = cntC(j) + 1;
x = cntR / t;
y = cntC / t;
brR = max(A * y) - x' * A * y;
brC = max(B' * x) - x' * B * y;
info.gap = brR + brC;
if info.gap < tol
info.converged = true;
info.iters = t;
return;
end
end
end
逻辑说明:A * y 是行玩家在当前 y 下每个纯策略的期望收益向量,max 取最大值的下标即最优反应;B' * x 对应列玩家视角,因为列玩家收益矩阵按常规写成 n1 x n2,转置后才能对 x 做期望收益计算。cntR 和 cntC 是历史选择计数,每轮把最优反应对应的策略加一,再归一化为混合策略概率,这个频率更新方式保证概率始终在单纯形内部。参数方面,T 控制最大迭代轮数,建议 2x2 用 200,3x3 用 1000,更大维度用 5000 起步;tol 是收益缺口阈值,量级在 1e-6 到 1e-8 之间比较合适。需要注意 info.gap 是双方收益缺口之和,它始终非负,gap 小于 tol 才代表没有人有单边改进动机。
4.3 仿真数据生成与批量收敛测试
simulationdata.m 在源码里的作用是为 npg.m 提供可重复的随机测试场景。我通常把它写成支持维度与收益区间控制的形式,方便批量观察收敛行为:
function [A, B] = simulationdata(m, n, range)
% 生成 m x n 的随机双人博弈收益矩阵
% range = [lo, hi] 控制收益整数值的区间
if nargin < 2, n = m; end
if nargin < 3, range = [-5, 5]; end
A = randi(range(1):range(2), m, n);
B = randi(range(1):range(2), m, n);
end
批量测试时把随机数种子固定下来,让每一轮测试结果可复现:
conv = 0;
total = 100;
for k = 1:total
rng(k);
[A, B] = simulationdata(3, 3, [-3, 3]);
[~, ~, info] = npg_fictitious_play(A, B, 5000, 1e-7);
conv = conv + info.converged;
end
fprintf('3x3 随机博弈收敛率: %.1f%%\n', conv / total * 100);
逻辑说明:rng(k) 固定随机数生成器状态,保证每次运行同一套测试序列,这对对比算法改动前后的收敛率至关重要。收敛率统计直接依赖 npg_fictitious_play 返回的 info.converged。实际操作中,3x3 随机博弈里虚拟博弈不收敛的比例并不低,因为存在一类收益矩阵会让最优反应形成循环,典型如 Shapley 构造的三人或特殊双人矩阵,历史频率会在几个支持集之间反复切换而不进入均衡邻域。遇到这类场景,不能用加大 T 硬扛,应该改用支持集枚举或 Lemke-Howson 类算法兜底。下一章把这些验证和兜底方法展开。
5. 均衡校验与容易误判的数值细节
5.1 用收益缺口代替概率差来判定收敛
迭代算法的收敛判断如果只看相邻两次概率向量的距离,很容易在收益平面特别平缓的区域误判。更好的做法是直接计算收益缺口:当前混合策略下,单方面换到最优纯策略能多拿多少收益。这个缺口为零时,无论概率向量是否还在动,实际已经处在均衡点。下面的函数可以独立验证任意策略组合是否构成均衡,不依赖迭代过程:
function ok = isNE(x, y, A, B, tol)
% 判定混合策略 (x, y) 是否为纳什均衡
% x 行玩家概率向量, y 列玩家概率向量
vR = x' * A * y; % 行玩家当前期望收益
vC = x' * B * y; % 列玩家当前期望收益
brR = max(A * y); % 行玩家最优纯应对收益
brC = max(B' * x); % 列玩家最优纯应对收益
ok = (brR - vR <= tol) && (brC - vC <= tol);
end
逻辑说明:vR 和 vC 是双方在混合策略组合下的期望收益,brR 和 brC 是各自能拿到的最高纯策略收益,两者差距即单边改进空间。参数方面,tol 需要跟收益量级匹配,收益在个位数时 1e-8 合理,收益放大到一万量级时建议调整到 1e-4 级别,否则纯策略下精确相等的均衡也可能被判为不收敛。这个函数对纯策略均衡同样有效,把 x 和 y 写成单位向量即可。
5.2 支持集枚举作为兜底方案
迭代算法失效时,小规模博弈可以用支持集枚举兜底。思路是枚举行玩家与列玩家的所有支持集组合,在每组支持集内解无差异方程组,再做外部策略不优于支持集内策略的边界检查。核心求解代码可以这样组织:
% 给定行支持集 I 与列支持集 J
% 未知量 yJ (列玩家在 J 上的概率) 与 u (行玩家内部期望收益)
C = [A(I, J), -ones(length(I), 1)];
d = zeros(length(I), 1);
sol = linsolve(C, d);
yJ = sol(1:length(J));
u = sol(end);
% 检查概率非负、归一化, 以及 I 外策略收益不超过 u
逻辑说明:A(I,J) 是从收益矩阵中抽取行支持集和列支持集对应的子矩阵,-ones 那一列对应无差异方程中的期望收益变量 u,方程组表达式为 A(I,J) yJ = u 1,求解后得到 yJ 和 u。边界检查包含三个条件:yJ 所有分量非负、分量和等于 1、行支持集外的纯策略收益不超过 u;列玩家侧用同样的方式解 B(I,J)' xI = v 1。枚举组合数在策略数为个位数时是可行方案,策略数超过 10 后组合爆炸,此时应改用 Lemke-Howson 或线性互补求解器。
5.3 对称博弈与容差处理的实用技巧
实际使用中还有一个常被忽略的细节:容差比较不要用绝对相等。浮点运算下 (1/3)*3 这类表达式可能得到 1.0000000000000002,直接比较必然出问题。所有概率检验都用 abs(p - 1) <= tol 而不是 p == 1,收益缺口比较同理。另一个实用技巧是识别对称博弈:当 B 等于 A 的转置时,均衡搜索空间可以直接减半,因为对称博弈一定存在对称均衡,令 x = y 后只需枚举单边支持集,省掉行、列组合的交叉枚举。判断代码就一行:
if isequal(B, A.')
% 对称博弈, 枚举行玩家支持集即可, 列玩家支持集取相同集合
% 组合数从 n1*n2 降到 C(n1,1)+...+C(n1,n1)
end
这个判断放在仿真数据生成之后、进入主求解器之前,可以让批量测试的速度提升接近一倍。配合 isNE 做最终校验,整套计算链路从随机博弈生成、迭代求解到结果验证就完整了。
729



被折叠的 条评论
为什么被折叠?



