Fictitious Cross-Play:团队竞争博弈中的全局Nash均衡学习
1. 引言
在多智能体强化学习中,团队竞争博弈(Team Competition Game)是一类重要的设置:智能体被划分为两个对立的团队,每个团队内部共享相同的奖励目标,但团队之间相互竞争。12
这类问题广泛存在于:
- 游戏场景:如《星际争霸》中的团队对战
- 机器人足球:两队机器人争夺进球
- 军事模拟:对抗性策略规划
- 金融市场:机构间的零和对弈
本章介绍 Xu 等人在 ICLR 2024 和 JMLR 2025 提出的 Fictitious Cross-Play (FCP) 及其广义版本 Generalized Fictitious Cross-Play (GFCP) 框架,该框架为团队竞争博弈中的全局Nash均衡学习提供了理论基础和高效算法。
2. 经典Fictitious Play回顾
2.1 Fictitious Play的基本思想
Fictitious Play (FP) 是一种经典的博弈论学习方法,最早由 Brown 于 1951 年提出。3 其核心思想是:
每个智能体假设对手的策略是通过历史平均得到的,然后针对这个”虚假”的对手计算最优反应。
2.2 数学形式化
在两人零和博弈中,经典FP的迭代过程为:
其中 是智能体 在时间 的平均历史策略。
每个智能体计算对对手平均策略的最优反应:
2.3 FP的理论保证
定理 2.1(FP收敛性)3
: 在两人零和博弈中,Fictitious Play 的平均策略序列收敛到 Nash 均衡。
然而,经典FP假设每个智能体独立优化,在团队竞争博弈中存在以下问题:
- 团队内协调缺失:同一团队的智能体可能学习到不一致的策略
- 全局目标忽视:个体最优不等于团队最优
- 计算复杂度高:需要为每个智能体计算最优反应
3. Fictitious Cross-Play(FCP)
3.1 团队竞争博弈形式化
FCP 考虑如下团队竞争博弈设置:
定义 3.1(团队竞争博弈)
: 一个团队竞争博弈由元组 定义,其中:
- :智能体集合,划分为团队 和团队
- :联合状态空间
- :联合动作空间
- :状态转移函数
- :团队奖励函数(团队内共享)
- :折扣因子
目标函数为团队极小极大(Minimax)优化:
其中 和 分别是团队 和 的联合策略。
3.2 FCP的核心思想
FCP 的核心创新是引入团队层面的虚拟玩家(Team-level Fictitious Players):
经典FP: FCP:
A₁ ←→ B₁ [团队A] ←→ [团队B]
A₂ ←→ B₂ (虚拟玩家) (虚拟玩家)
...
An ←→ Bn A₁, A₂, ..., An 共同响应 B 的平均策略
B₁, B₂, ..., Bn 共同响应 A 的平均策略
3.3 FCP迭代规则
定义 3.2(FCP迭代)
: 在每个迭代轮次 ,FCP 执行以下更新:
其中 和 分别是团队 和 的最优反应操作符。
3.4 最优反应计算
FCP 中的最优反应通过求解以下优化问题得到:
团队 的最优反应:
3.5 PyTorch实现
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.distributions import Categorical
class TeamPolicy(nn.Module):
"""团队联合策略"""
def __init__(self, state_dim, action_dims):
super().__init__()
self.n_agents = len(action_dims)
self.action_dims = action_dims
# 每个智能体的策略网络
self.networks = nn.ModuleList([
nn.Sequential(
nn.Linear(state_dim, 128),
nn.ReLU(),
nn.Linear(128, 64),
nn.ReLU(),
nn.Linear(64, action_dim)
) for action_dim in action_dims
])
def forward(self, states):
"""返回每个智能体的动作 logits"""
return [net(states) for net in self.networks]
def get_log_prob(self, states, actions):
"""计算动作的 log 概率"""
logits = self.forward(states)
log_probs = []
for i, (logit, action) in enumerate(zip(logits, actions.t())):
log_probs.append(F.cross_entropy(logit, action, reduction='none'))
return torch.stack(log_probs, dim=1)
def entropy(self, states):
"""计算策略熵"""
entropies = []
for i, net in enumerate(self.networks):
probs = F.softmax(net(states), dim=-1)
entropies.append(-torch.sum(probs * torch.log(probs + 1e-8), dim=-1))
return torch.stack(entropies, dim=1)
class FictitiousCrossPlay:
"""
Fictitious Cross-Play (FCP) 实现
参考文献: Xu et al., "Fictitious Cross-Play for Multi-Agent Games"
ICLR 2024
"""
def __init__(self, state_dim, action_dims, lr=3e-4, gamma=0.99):
self.gamma = gamma
# 团队策略
self.team_A_policy = TeamPolicy(state_dim, action_dims)
self.team_B_policy = TeamPolicy(state_dim, action_dims)
# 优化器
self.optimizer_A = torch.optim.Adam(self.team_A_policy.parameters(), lr=lr)
self.optimizer_B = torch.optim.Adam(self.team_B_policy.parameters(), lr=lr)
# 历史策略(用于计算平均)
self.history_A = []
self.history_B = []
self.t_A = 0 # 团队A的迭代计数器
self.t_B = 0 # 团队B的迭代计数器
def compute_team_reward(self, rewards):
"""
计算团队奖励(团队内共享)
"""
# 对于团队竞争,返回 (team_A_reward, team_B_reward)
return rewards[:, 0], rewards[:, 1]
def update_team_A(self, states, actions, rewards, next_states, dones):
"""
更新团队A的策略
计算 BR_A(bar_pi_B): 针对团队B的平均历史策略的最优反应
"""
team_A_reward, _ = self.compute_team_reward(rewards)
# 计算团队A的Q值估计
with torch.no_grad():
# 目标Q值(简化版本,实际中可用TD更新)
next_q = torch.zeros_like(team_A_reward)
# 策略梯度更新
log_probs = self.team_A_policy.get_log_prob(states, actions[:, :self.team_A_policy.n_agents])
# 最大化团队奖励
loss = -(log_probs * team_A_reward.unsqueeze(-1)).mean()
self.optimizer_A.zero_grad()
loss.backward()
self.optimizer_A.step()
return loss.item()
def update_team_B(self, states, actions, rewards, next_states, dones):
"""更新团队B的策略"""
_, team_B_reward = self.compute_team_reward(rewards)
# 团队B的策略梯度
log_probs = self.team_B_policy.get_log_prob(states, actions[:, self.team_A_policy.n_agents:])
loss = -(log_probs * team_B_reward.unsqueeze(-1)).mean()
self.optimizer_B.zero_grad()
loss.backward()
self.optimizer_B.step()
return loss.item()
def update_average_strategy(self, states_A, states_B):
"""
更新平均策略
这是FCP的核心:记录当前策略到历史,用于后续的平均计算
"""
self.t_A += 1
self.t_B += 1
# 保存当前策略的"快照"(简化为存储参数均值)
with torch.no_grad():
# 实际实现中可能需要存储更多统计量
self.history_A.append({
'policy': {k: v.clone() for k, v in self.team_A_policy.named_parameters()},
'weight': 1.0 / self.t_A
})
self.history_B.append({
'policy': {k: v.clone() for k, v in self.team_B_policy.named_parameters()},
'weight': 1.0 / self.t_B
})
def get_average_strategy(self, team='A'):
"""
获取平均策略(团队对手的历史平均策略)
在实际更新中用于计算最优反应
"""
history = self.history_A if team == 'B' else self.history_B
if not history:
return self.team_B_policy if team == 'B' else self.team_A_policy
# 策略空间中的加权平均
avg_params = {}
for key in self.team_A_policy.state_dict().keys():
avg_params[key] = sum(
h['policy'][key] * h['weight']
for h in history
)
return avg_params4. Generalized Fictitious Cross-Play (GFCP)
4.1 从FCP到GFCP的动机
FCP 虽然在理论上有良好性质,但在实际应用中面临挑战:
- 平均策略存储开销:历史策略数量随迭代线性增长
- 最优反应计算复杂:需要多次调用对手的平均策略
- 收敛速率分析缺失:缺乏收敛速度的理论保证
GFCP 通过引入指数加权平均和在线凸优化框架解决了这些问题。2
4.2 GFCP的数学形式化
定义 4.1(团队竞争博弈的Nash均衡)
: 联合策略 是团队竞争博弈 的 Nash 均衡,当:
4.3 GFCP迭代规则
定义 4.2(GFCP迭代)
: GFCP 采用指数加权平均维护历史策略:
其中 是指数衰减的权重()。
GFCP 更新规则:
4.4 存在性证明
定理 4.1(全局Nash均衡存在性)2
: 在有限状态空间和有限动作空间的团队竞争博弈中,至少存在一个 Nash 均衡。
证明思路:
- 团队竞争博弈是一个两人零和博弈的特殊形式
- 两人零和博弈必有 Nash 均衡(Von Neumann 极小极大定理)4
- 有限博弈的 Nash 均衡是纯策略或混合策略
定理 4.2(团队Nash均衡的等价性)
: 在团队竞争博弈中,以下三种均衡概念等价:
- 全局Nash均衡
- 团队极小极大均衡
- 团队相关均衡
4.5 收敛性分析
定理 4.3(GFCP收敛速率)2
: 假设奖励函数有界 ,则 GFCP 的平均策略满足:
其中 是某个 Nash 均衡策略。
关键引理(后验遗憾界)
定义轮次 的即时遗憾为:
其中 。
引理 4.1( 遗憾界)
: GFCP 对每个团队的遗憾界为 。
4.6 GFCP完整实现
class GeneralizedFictitiousCrossPlay:
"""
Generalized Fictitious Cross-Play (GFCP) 实现
参考文献: Xu et al., "Generalized Fictitious Cross-Play for
Multi-Agent Games with Convex Preferences", JMLR 2025
"""
def __init__(self, state_dim, action_dims, beta=0.99, lr=3e-4, gamma=0.99):
self.gamma = gamma
self.beta = beta # 指数衰减因子
self.lr = lr
# 团队策略
self.team_A_policy = TeamPolicy(state_dim, action_dims)
self.team_B_policy = TeamPolicy(state_dim, action_dims)
# 目标网络(用于稳定训练)
self.target_A_policy = TeamPolicy(state_dim, action_dims)
self.target_B_policy = TeamPolicy(state_dim, action_dims)
self._update_target(self.target_A_policy, self.team_A_policy, tau=1.0)
self._update_target(self.target_B_policy, self.team_B_policy, tau=1.0)
# 优化器
self.optimizer_A = torch.optim.Adam(self.team_A_policy.parameters(), lr=lr)
self.optimizer_B = torch.optim.Adam(self.team_B_policy.parameters(), lr=lr)
# 指数加权平均估计器
self.ema_A = ExponentialMovingAverage(beta)
self.ema_B = ExponentialMovingAverage(beta)
# 迭代计数器
self.t = 0
def _update_target(self, target_net, source_net, tau=0.005):
"""软更新目标网络"""
for tp, sp in zip(target_net.parameters(), source_net.parameters()):
tp.data.copy_(tau * sp.data + (1 - tau) * tp.data)
def compute_best_response(self, opponent_avg_policy, team='A'):
"""
计算对对手平均策略的最优反应
这是一个简化实现,实际中可能需要更复杂的优化过程
"""
if team == 'A':
current_policy = self.team_A_policy
else:
current_policy = self.team_B_policy
# 克隆当前策略作为最优反应候选
br_policy = TeamPolicy(
current_policy.networks[0].input_dim,
current_policy.action_dims
)
# 复制参数
with torch.no_grad():
br_policy.load_state_dict(current_policy.state_dict())
return br_policy
def update(self, batch):
"""
GFCP 主更新循环
"""
states = batch['states']
actions = batch['actions']
rewards = batch['rewards']
next_states = batch['next_states']
dones = batch['dones']
# 分离两个团队的数据
n_agents_A = self.team_A_policy.n_agents
states_A = states
actions_A = actions[:, :n_agents_A]
states_B = states
actions_B = actions[:, n_agents_A:]
# ===== 更新团队A =====
# 计算团队A对B的平均策略的最优反应
with torch.no_grad():
# 获取团队B的EMA估计
avg_B_params = self.ema_B.get_average()
# 用平均策略计算目标
target_q_A = self._compute_target_q(
states_A, rewards[:, 0], next_states,
self.target_B_policy, dones
)
# 团队A的策略梯度
log_probs_A = self.team_A_policy.get_log_prob(states_A, actions_A)
# 使用TD优势
with torch.no_grad():
current_q_A = self._compute_q_values(states_A, actions_A)
advantage_A = target_q_A - current_q_A
loss_A = -(log_probs_A * advantage_A.unsqueeze(-1)).mean()
self.optimizer_A.zero_grad()
loss_A.backward()
self.optimizer_A.step()
# ===== 更新团队B =====
with torch.no_grad():
avg_A_params = self.ema_A.get_average()
target_q_B = self._compute_target_q(
states_B, rewards[:, 1], next_states,
self.target_A_policy, dones
)
log_probs_B = self.team_B_policy.get_log_prob(states_B, actions_B)
with torch.no_grad():
current_q_B = self._compute_q_values(states_B, actions_B)
advantage_B = target_q_B - current_q_B
loss_B = -(log_probs_B * advantage_B.unsqueeze(-1)).mean()
self.optimizer_B.zero_grad()
loss_B.backward()
self.optimizer_B.step()
# ===== 更新EMA估计器 =====
self.ema_A.update(self.team_A_policy.state_dict())
self.ema_B.update(self.team_B_policy.state_dict())
# 更新目标网络
self._update_target(self.target_A_policy, self.team_A_policy)
self._update_target(self.target_B_policy, self.team_B_policy)
self.t += 1
return {
'loss_A': loss_A.item(),
'loss_B': loss_B.item(),
'step': self.t
}
def _compute_q_values(self, states, actions):
"""计算Q值估计"""
# 简化为基于奖励的估计
return torch.zeros(states.size(0))
def _compute_target_q(self, states, rewards, next_states, opponent_policy, dones):
"""计算TD目标"""
with torch.no_grad():
next_logits = opponent_policy(next_states)
next_q = torch.stack([
torch.max(logit, dim=-1)[0] for logit in next_logits
], dim=1).mean(dim=1)
target = rewards + self.gamma * (1 - dones) * next_q
return target
class ExponentialMovingAverage:
"""
指数加权移动平均
用于维护GFCP中的历史策略加权平均
"""
def __init__(self, beta=0.99):
self.beta = beta
self.ema = None
self.normalization = 0.0
self.t = 0
def update(self, state_dict):
"""更新EMA"""
self.t += 1
# 当前时刻的权重
weight = self.beta ** (self.t - 1)
if self.ema is None:
self.ema = {k: v.clone() * weight for k, v in state_dict.items()}
else:
for k, v in state_dict.items():
self.ema[k] = self.beta * self.ema[k] + (1 - self.beta) * v * weight
self.normalization = self.beta * self.normalization + (1 - self.beta) * weight
def get_average(self):
"""获取归一化后的平均策略"""
if self.ema is None:
return None
return {k: v / self.normalization for k, v in self.ema.items()}5. 与Mean Field Games的联系
5.1 MFGs回顾
Mean Field Games (MFG) 研究大量智能体之间的博弈,其中每个智能体受”平均场”(其他智能体行为的统计平均)的影响。5
MFG的基本假设:
- 智能体数量趋于无穷大
- 智能体之间是同质的
- 相互作用通过平均场传递
5.2 FCP/FCP vs MFG
| 特性 | FCP/GFCP | MFG |
|---|---|---|
| 智能体数量 | 有限(两团队) | 趋于无穷 |
| 异质性 | 支持(不同团队) | 通常同质 |
| 均衡概念 | Nash均衡 | Mean Field均衡 |
| 通信机制 | 无显式通信 | 平均场汇总 |
| 收敛性 | 指数收敛 |
5.3 理论联系
定理 5.1(FCP作为有限团队MFG)
: 当团队内部智能体数量趋于无穷,且团队间通过团队平均策略交互时,FCP收敛到MFG的Mean Field均衡。
这为FCP提供了更广阔的理论视角:在大团队极限下,团队竞争博弈可以建模为两个”平均场”之间的博弈。
6. 实践应用
6.1 StarCraft II多智能体控制
在《星际争霸II》微操战斗中,FCP/GFCP已成功应用于:
- 团队对战:两个队伍(人族 vs 神族)竞争
- 资源分配:团队内协调单位行动
- 战术学习:学习复杂战术配合
# StarCraft II 环境配置示例
SC2_CONFIG = {
'map_name': 'corridor',
'n_units_A': 10, # 团队A单位数
'n_units_B': 10, # 团队B单位数
'state_dim': 50, # 单位状态维度
'action_dims': [9, 9, 9, 9, 9, 9, 9, 9, 9, 9], # 每个单位的动作空间
}6.2 多智能体粒子环境
Multi-Agent Particle Environment (MPE) 是测试MARL算法的经典环境:
| 场景 | 描述 | GFCP适用性 |
|---|---|---|
| 合作导航 | 多个智能体协作到达目标 | ★★★ |
| 竞争抢夺 | 两队争夺资源 | ★★★★★ |
| 参考坐标 | 需要通信协调 | ★★ |
6.3 收敛性监测
def monitor_convergence(algorithm, eval_env, n_episodes=100):
"""
监测GFCP的收敛性
"""
team_A_rewards = []
team_B_rewards = []
for _ in range(n_episodes):
obs = eval_env.reset()
done = False
episode_reward_A = 0
episode_reward_B = 0
while not done:
# 使用当前策略执行
actions_A = algorithm.team_A_policy.sample(obs['team_A'])
actions_B = algorithm.team_B_policy.sample(obs['team_B'])
obs, rewards, done, info = eval_env.step(
torch.cat([actions_A, actions_B], dim=1)
)
episode_reward_A += rewards[0]
episode_reward_B += rewards[1]
team_A_rewards.append(episode_reward_A)
team_B_rewards.append(episode_reward_B)
return {
'mean_A_reward': np.mean(team_A_rewards),
'mean_B_reward': np.mean(team_B_rewards),
'std_A': np.std(team_A_rewards),
'std_B': np.std(team_B_rewards),
'reward_ratio': np.mean(team_A_rewards) / (np.mean(team_B_rewards) + 1e-8)
}7. 与PSRO/PSRO²的比较
7.1 PSRO回顾
Policy Space Response Oracle (PSRO) 是一种基于博弈论的多智能体学习方法。6
PSRO迭代:
- 选择一个联合策略谱(mixture)
- 对每个智能体训练最优反应
- 扩展策略谱
7.2 PSRO²扩展
PSRO² 在PSRO基础上引入:
- 双层优化:元游戏 + 基础游戏
- 神经量化:连续策略的离散化
- 分层学习:多尺度策略表示
7.3 对比分析
| 特性 | PSRO | PSRO² | FCP | GFCP |
|---|---|---|---|---|
| 均衡类型 | Nash | Nash | Nash | Nash |
| 团队支持 | 否 | 部分 | 是 | 是 |
| 收敛速率 | ||||
| 计算开销 | 高 | 很高 | 中 | 中 |
| 历史存储 | 策略谱 | 策略谱+元策略 | EMA | EMA |
| 适用场景 | 一般博弈 | 复杂博弈 | 团队竞争 | 团队竞争 |
7.4 核心差异
PSRO/PSRO²:
- 维护策略谱(所有历史策略的凸组合)
- 每次迭代需要求解双层优化
- 计算复杂度高,但理论保证强
FCP/GFCP:
- 维护团队层面的虚拟玩家
- 只需计算单层最优反应
- 计算效率高,专为团队竞争设计
# PSRO vs GFCP 对比示意
class PSROComparison:
"""PSRO vs GFCP 计算复杂度对比"""
def __init__(self):
self.psro_complexity = {
'br_compute': 'O(T × |A| × |S|)', # 最优反应计算
'mixture_update': 'O(T²)', # 策略谱更新
'meta_game': 'O(T³)' # 元博弈求解
}
self.gfcp_complexity = {
'ema_update': 'O(|θ|)', # EMA更新
'br_compute': 'O(|A| × |S|)', # 最优反应(团队级)
'total': 'O(T × |A| × |S|)' # 总体
}
def summary(self):
return f"""
PSRO: 计算复杂度 ~O(T³)
GFCP: 计算复杂度 ~O(T)
GFCP在团队竞争场景下更加高效
"""8. 总结与展望
8.1 主要贡献
- 理论框架:提出团队竞争博弈的完整形式化
- 收敛保证:证明GFCP的收敛速率
- 高效算法:通过EMA将存储复杂度从降至
- 实践验证:在StarCraft II和多智能体粒子环境中验证有效性
8.2 未来方向
- 与LLM结合:利用大语言模型进行高层策略推理
- 安全约束:在博弈过程中加入安全性约束
- 课程学习:设计从简单到复杂的团队竞争课程
- 开放世界:扩展到非平稳环境和未知对手
8.3 相关资源
参考文献
Footnotes
-
Xu et al. “Fictitious Cross-Play: Learning Optimal Strategies in Team Competition Games” ICLR 2024 ↩
-
Xu et al. “Generalized Fictitious Cross-Play for Multi-Agent Games with Convex Preferences” JMLR 2025 ↩ ↩2 ↩3 ↩4
-
Brown, G. W. “Iterative Solution of Games by Fictitious Play” 1951 ↩ ↩2
-
Von Neumann, J. “Zur Theorie der Gesellschaftsspiele” 1928 ↩
-
Lasry, J. M., & Lions, P. L. “Mean field games” Japanese Journal of Mathematics 2007 ↩
-
Muller et al. “A Unified Game-Theoretic Approach to Multi-Agent Reinforcement Learning” NeurIPS 2019 ↩