当前位置: 首页 > news >正文

深度理解用于多智能体强化学习的单调价值函数分解QMIX算法:基于python从零实现

引言:合作式多智能体强化学习与功劳分配

在合作式多智能体强化学习(MARL)中,多个智能体携手合作,共同达成一个目标,通常会收到一个团队共享的奖励。在这种场景下,一个关键的挑战就是功劳分配:一个单独的智能体如何仅凭全局奖励信号来判断自己对团队成功或失败的贡献呢?简单的独立学习方法(比如每个智能体都运行 DQN)往往行不通,因为它把其他智能体当作了非静态环境的一部分,而且在功劳分配上也搞不定。

价值分解方法,像 QMIX,为应对合作式 MARL 中的这一挑战提供了一种结构化的解决方案。

QMIX 是啥玩意儿?

QMIX(Q 值混合)是一种用于合作任务的离线策略、基于价值的 MARL 算法。它遵循集中式训练、分散式执行的范式。它的核心创新之处在于学习各个智能体的效用函数(表示为 Q i Q_i Qi),并通过一个单调混合网络将它们组合起来,以估计联合行动价值函数( Q t o t Q_{tot} Qtot)。

核心思想:价值函数分解

QMIX 假定团队的联合行动价值函数 Q t o t Q_{tot} Qtot 可以分解或者表示为各个智能体效用函数 Q i Q_i Qi 的单调组合。每个 Q i ( o i , a i ) Q_i(o_i, a_i) Qi(oi,ai) 只依赖于智能体 i i i 的局部观测 o i o_i oi 和行动 a i a_i ai

混合网络

一个单独的混合网络以每个智能体的个体网络产生的值( Q 1 , . . . , Q N Q_1, ..., Q_N Q1,...,QN)作为输入,并输出估计的联合行动价值 Q t o t Q_{tot} Qtot。关键的是,这个混合网络还以全局状态 x x x 作为输入,使得个体效用与团队价值之间的关系能够依赖于整体情况。通常会使用超网络根据全局状态来生成混合网络层的权重和偏置。

单调性约束(IQL 原则)

为了确保分散式执行(每个智能体根据 Q i Q_i Qi 选择最佳行动)与集中式训练(优化 Q t o t Q_{tot} Qtot)之间的一致性,QMIX 强制执行单调性约束:
∂ Q t o t ∂ Q i ≥ 0 ∀ i \frac{\partial Q_{tot}}{\partial Q_i} \ge 0 \quad \forall i QiQtot0i
这意味着,增加一个单独智能体的效用 Q i Q_i Qi 绝对不能降低团队的总价值 Q t o t Q_{tot} Qtot。这保证了智能体在执行阶段最大化其局部 Q i Q_i Qi 时,会对最大化全局 Q t o t Q_{tot} Qtot 做出积极贡献。QMIX 通过确保混合网络的权重为非负值来实现这一约束。
这个约束体现了个体 - 全局最大(IQL)原则:最大化 Q t o t Q_{tot} Qtot 的联合行动与最大化每个智能体 Q i Q_i Qi 的个体行动是一样的。

QMIX 为啥这么牛?优势所在

  • 搞定功劳分配:通过学习单调地贡献于 Q t o t Q_{tot} Qtot 的个体 Q i Q_i Qi 函数,它比只用全局奖励的方法更好地隐式处理功劳分配问题。
  • 可扩展性(行动空间):与直接学习联合 Q 函数的方法(随着智能体和行动数量呈指数级增长)不同,QMIX 学习个体 Q i Q_i Qi 函数和一个混合网络,使其在联合行动空间方面更具可扩展性。
  • 分散式执行:智能体在训练后可以仅使用其局部 Q i Q_i Qi 函数来行动。
  • 依赖状态的混合:使用全局状态可以让混合函数捕捉到依赖于整体上下文的智能体效用之间的复杂非线性关系。

QMIX 在哪儿用?怎么用?

QMIX 是一种流行的用于合作式 MARL 任务的算法,特别是在具有离散行动空间的任务中:

  1. 星际争霸多智能体挑战(SMAC):QMIX 及其变体在这些基准微观管理任务上表现出色。
  2. 协调游戏:需要智能体同步或协调行动的网格世界或其他任务。
  3. 多机器人协作:协作导航、编队控制。

QMIX 的数学基础

个体智能体 Q 函数( Q i Q_i Qi

每个智能体 i i i 有一个网络(如果需要历史信息,通常是 DRQN - 深度循环 Q 网络,或者是一个 MLP),它以智能体的局部观测历史 τ i \tau_i τi 作为输入,并输出其可能行动 a i a_i ai 的 Q 值: Q i ( τ i , a i ; θ i ) Q_i(\tau_i, a_i; \theta_i) Qi(τi,ai;θi)。为了简化 Markovian 状态,我们使用 Q i ( o i , a i ; θ i ) Q_i(o_i, a_i; \theta_i) Qi(oi,ai;θi)

联合行动价值函数( Q t o t Q_{tot} Qtot

混合网络 f m i x f_{mix} fmix 将个体 Q 值和全局状态 x x x 结合起来,产生联合行动价值:
Q t o t ( x , a ) = f m i x ( Q 1 ( o 1 , a 1 ) , . . . , Q N ( o N , a N ) ; x ; ϕ m i x ) Q_{tot}(x, \mathbf{a}) = f_{mix}(Q_1(o_1, a_1), ..., Q_N(o_N, a_N); x; \phi_{mix}) Qtot(x,a)=fmix(Q1(o1,a1),...,QN(oN,aN);x;ϕmix)
其中 a = ( a 1 , . . . , a N ) \mathbf{a} = (a_1, ..., a_N) a=(a1,...,aN)

混合网络架构与单调性

混合网络强制执行 ∂ Q t o t ∂ Q i ≥ 0 \frac{\partial Q_{tot}}{\partial Q_i} \ge 0 QiQtot0。这通常是通过以下方式实现的:

  1. 在混合层中使用非负权重。
  2. 使用单调激活函数(比如 ReLU 或线性层,其中权重受到限制)。
    原始 QMIX 论文通过使用超网络生成混合层的权重和偏置,这些超网络以全局状态为条件。

超网络用于依赖状态

为了使混合依赖于状态,使用超网络。例如,对于一个混合层 Q t o t = W 1 Q + b 1 Q_{tot} = W_1 \mathbf{Q} + b_1 Qtot=W1Q+b1(其中 Q = [ Q 1 , . . . , Q N ] T \mathbf{Q} = [Q_1, ..., Q_N]^T Q=[Q1,...,QN]T):

  • 一个超网络以全局状态 x x x 作为输入,并输出权重矩阵 W 1 W_1 W1。通过取 abs() 或使用 ELU 激活函数 + 1 来限制 W 1 W_1 W1 的元素为非负值。
  • 另一个超网络以 x x x 作为输入并输出偏置 b 1 b_1 b1。(偏置不需要正性约束。)
    如果混合网络有多个层,就会重复这个过程。

损失函数( Q t o t Q_{tot} Qtot 的 TD 误差)

QMIX 使用来自回放缓冲区 D \mathcal{D} D 的离线策略数据。损失函数旨在最小化联合行动价值函数的 TD 误差,类似于 DQN:
L ( θ 1 , . . . , θ N , ϕ m i x ) = E ( x , a , r , x ′ ) ∼ D [ ( y − Q t o t ( x , a ) ) 2 ] L(\theta_1, ..., \theta_N, \phi_{mix}) = \mathbb{E}_{(x, \mathbf{a}, r, x') \sim \mathcal{D}} [ (y - Q_{tot}(x, \mathbf{a}))^2 ] L(θ1,...,θN,ϕmix)=E(x,a,r,x)D[(yQtot(x,a))2]
目标 y y y 是使用目标智能体网络( Q i ′ Q'_i Qi)和目标混合网络( f m i x ′ f'_{mix} fmix)计算的:
y = r + γ Q t o t ′ ( x ′ , a ′ ) y = r + \gamma Q'_{tot}(x', \mathbf{a}') y=r+γQtot(x,a)
其中  a ′ = ( a 1 ′ , . . . , a N ′ ) 且  a i ′ = arg ⁡ max ⁡ a i Q i ′ ( o i ′ , a i ) \text{其中 } \mathbf{a}' = (a'_1, ..., a'_N) \text{ 且 } a'_i = \arg\max_{a_i} Q'_i(o'_i, a_i) 其中 a=(a1,...,aN)  ai=argaimaxQi(oi,ai)
梯度 ∇ L \nabla L L 会反向传播到主混合网络以及所有主智能体网络 Q i Q_i Qi

目标网络

目标网络(每个智能体的 Q i ′ Q'_i Qi,以及目标混合网络 f m i x ′ f'_{mix} fmix)用于稳定 TD 目标计算。它们会定期或通过软更新从主网络更新。

QMIX 的逐步解释

  1. 初始化:对于每个智能体 i i i:智能体网络 Q i ( θ i ) Q_i(\theta_i) Qi(θi),目标智能体网络 Q i ′ ( θ i ′ ) Q'_i(\theta'_i) Qi(θi) 并且 θ i ′ ← θ i \theta'_i \leftarrow \theta_i θiθi
  2. 初始化:混合网络 f m i x ( ϕ m i x ) f_{mix}(\phi_{mix}) fmix(ϕmix),目标混合网络 f m i x ′ ( ϕ m i x ′ ) f'_{mix}(\phi'_{mix}) fmix(ϕmix) 并且 ϕ m i x ′ ← ϕ m i x \phi'_{mix} \leftarrow \phi_{mix} ϕmixϕmix
  3. 初始化:回放缓冲区 D \mathcal{D} D。超参数(缓冲区大小、批量大小、 γ \gamma γ τ \tau τ、学习率、 ϵ \epsilon ϵ)。
  4. 对于每个剧集
    a. 获取初始联合观测 o = ( o 1 , . . . , o N ) o=(o_1, ..., o_N) o=(o1,...,oN) 和全局状态 x x x
    b. 对于每个步骤 t t t
    i. 对于每个智能体 i i i,使用 ϵ \epsilon ϵ-贪婪在 Q i ( o i , ⋅ ) Q_i(o_i, \cdot) Qi(oi,) 上选择行动 a i a_i ai
    ii. 执行联合行动 a = ( a 1 , . . . , a N ) \mathbf{a}=(a_1, ..., a_N) a=(a1,...,aN),观察共享奖励 r r r,下一个联合观测 o ′ o' o,下一个全局状态 x ′ x' x,以及完成标志 d d d
    iii. 将转换 ( o , a , r , o ′ , x , x ′ , d ) (o, \mathbf{a}, r, o', x, x', d) (o,a,r,o,x,x,d) 存储到 D \mathcal{D} D 中。
    iv. o ← o ′ , x ← x ′ o \leftarrow o', x \leftarrow x' oo,xx
    v. 更新步骤(如果缓冲区有足够的样本)
    1. 从 D \mathcal{D} D 中采样一个大小为 B B B 的小批量转换。
    2. 对于批次中的每个转换 j j j
    - 计算目标 Q t o t , j ′ Q'_{tot,j} Qtot,j
    - 对于每个智能体 i i i,找到 a i , j ′ = arg ⁡ max ⁡ a Q i ′ ( o i , j ′ , a ) a'_{i,j} = \arg\max_{a} Q'_{i}(o'_{i,j}, a) ai,j=argmaxaQi(oi,j,a)
    - 获取 Q i , j ′ = Q i ′ ( o i , j ′ , a i , j ′ ) Q'_{i,j} = Q'_{i}(o'_{i,j}, a'_{i,j}) Qi,j=Qi(oi,j,ai,j)
    - 计算 Q t o t , j ′ = f m i x ′ ( Q 1 , j ′ , . . . , Q N , j ′ ; x j ′ ) Q'_{tot,j} = f'_{mix}(Q'_{1,j}, ..., Q'_{N,j}; x'_{j}) Qtot,j=fmix(Q1,j,...,QN,j;xj)
    - 计算 TD 目标 y j = r j + γ ( 1 − d j ) Q t o t , j ′ y_j = r_j + \gamma (1-d_j) Q'_{tot,j} yj=rj+γ(1dj)Qtot,j
    - 计算当前 Q t o t , j Q_{tot,j} Qtot,j
    - 对于每个智能体 i i i,获取 Q i , j = Q i ( o i , j , a i , j ) Q_{i,j} = Q_{i}(o_{i,j}, a_{i,j}) Qi,j=Qi(oi,j,ai,j)(使用缓冲区中的行动 a i , j a_{i,j} ai,j)。
    - 计算 Q t o t , j = f m i x ( Q 1 , j , . . . , Q N , j ; x j ) Q_{tot,j} = f_{mix}(Q_{1,j}, ..., Q_{N,j}; x_{j}) Qtot,j=fmix(Q1,j,...,QN,j;xj)
    3. 计算损失 L = 1 B ∑ j ( y j − Q t o t , j ) 2 L = \frac{1}{B} \sum_j (y_j - Q_{tot,j})^2 L=B1j(yjQtot,j)2
    4. 对 L L L 关于 θ 1 , . . . , θ N , ϕ m i x \theta_1, ..., \theta_N, \phi_{mix} θ1,...,θN,ϕmix 执行梯度下降步骤。
    5. 通过软更新(或定期硬更新)更新目标网络( Q i ′ Q'_i Qi f m i x ′ f'_{mix} fmix)。
    vi. 如果完成,则中断步骤循环。
  5. 重复:直到收敛。

QMIX 的关键组成部分

智能体网络( Q i Q_i Qi

  • 基于局部观测学习个体效用 / Q 函数。
  • 可以是 MLP 或 DRQN(如果历史重要)。

混合网络

  • 将个体 Q i Q_i Qi 值和全局状态 x x x 结合起来产生 Q t o t Q_{tot} Qtot
  • 强制执行单调性约束( ∂ Q t o t / ∂ Q i ≥ 0 \partial Q_{tot} / \partial Q_i \ge 0 Qtot/Qi0)。

超网络

  • 生成混合网络的权重 / 偏置,以全局状态 x x x 为条件。
  • 确保混合依赖于状态,且权重为非负值。

回放缓冲区(共享)

  • 存储联合转换以供离线策略学习。

目标网络(智能体和混合器)

  • 稳定 TD 目标计算。

行动选择(分散式, ϵ \epsilon ϵ-贪婪)

  • 每个智能体仅根据自己的 Q i Q_i Qi 贪婪地(或 ϵ \epsilon ϵ-贪婪地)行动。

集中式训练

  • 更新使用联合信息(全局状态 x x x,所有 Q i Q_i Qi 值)通过混合网络。

超参数

  • 标准 RL 参数( γ \gamma γ ϵ \epsilon ϵ,学习率, τ \tau τ,缓冲区 / 批量大小)。
  • 混合网络架构,超网络架构。

实践示例:自定义多智能体网格世界

环境设计理由

这个合作式 2 智能体网格世界非常适合在离散设置中展示 QMIX 的价值分解。

环境细节:

  • 全局状态( x x x:两个智能体和两个目标的归一化位置的连接( r 1 , c 1 , r 2 , c 2 , g 1 r , g 1 c , g 2 r , g 2 c r1, c1, r2, c2, g1r, g1c, g2r, g2c r1,c1,r2,c2,g1r,g1c,g2r,g2c)。
  • 局部观测( o i o_i oi:智能体 i i i 的归一化位置,另一个智能体的归一化位置,智能体 i i i 的目标归一化位置( s e l f _ r , s e l f _ c , o t h e r _ r , o t h e r _ c , g o a l _ i _ r , g o a l _ i _ c self\_r, self\_c, other\_r, other\_c, goal\_i\_r, goal\_i\_c self_r,self_c,other_r,other_c,goal_i_r,goal_i_c)。
  • 行动 / 奖励:如之前所定义。

设置环境

导入库。

# 导入必要的库
import numpy as np
import matplotlib.pyplot as plt
import random
import math
from collections import namedtuple, deque, defaultdict
from itertools import count
from typing import List, Tuple, Dict, Optional, Callable, Any
import copy# 导入 PyTorch
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F# 设置设备
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"使用设备:{device}")# 设置随机种子
seed = 42
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
if torch.cuda.is_available():torch.cuda.manual_seed_all(seed)%matplotlib inline
使用设备:cpu

创建自定义多智能体环境

使用来自 MADDPG 笔记本的 MultiAgentGridEnv,添加一个获取全局状态的方法。

class MultiAgentGridEnv:"""简单的 2 智能体合作网格世界。智能体必须同时到达各自的终点。观测包括智能体位置和终点位置。奖励是共享的。"""def __init__(self, size: int = 5, num_agents: int = 2):self.size: int = sizeself.num_agents: int = num_agentsself.action_dim: int = 4 # 上,下,左,右self.action_map: Dict[int, Tuple[int, int]] = {0: (-1, 0), 1: (1, 0), 2: (0, -1), 3: (0, 1)}self.actions: List[int] = list(self.action_map.keys())# 为简单起见,定义固定的起始位置和终点位置self.start_positions: List[Tuple[int, int]] = [(0, 0), (size - 1, size - 1)]self.goal_positions: List[Tuple[int, int]] = [(size - 1, 0), (0, size - 1)]if num_agents != 2:raise NotImplementedError("目前仅支持 2 个智能体。")self.agent_positions: List[Tuple[int, int]] = list(self.start_positions)# 智能体 i 的观测空间:(self_r, self_c, other_r, other_c, goal_i_r, goal_i_c)# 在 0 和 1 之间进行归一化self.observation_dim_per_agent: int = 6 self.max_coord = float(self.size - 1)def _normalize_pos(self, pos: Tuple[int, int]) -> Tuple[float, float]:""" 对网格位置进行归一化。 """r, c = posreturn (r / self.max_coord, c / self.max_coord) if self.max_coord > 0 else (0.0, 0.0)def _get_observation(self, agent_id: int) -> np.ndarray:""" 获取特定智能体的归一化局部观测。 """obs = []# 自身位置self_pos_norm = self._normalize_pos(self.agent_positions[agent_id])obs.extend(self_pos_norm)# 另一个智能体的位置other_id = 1 - agent_idother_pos_norm = self._normalize_pos(self.agent_positions[other_id])obs.extend(other_pos_norm)# 自身终点位置goal_pos_norm = self._normalize_pos(self.goal_positions[agent_id])obs.extend(goal_pos_norm)return np.array(obs, dtype=np.float32)def get_joint_observation(self) -> List[np.ndarray]:""" 获取所有智能体的观测列表。 """return [self._get_observation(i) for i in range(self.num_agents)]def reset(self) -> List[np.ndarray]:""" 重置环境,返回初始观测列表。 """self.agent_positions = list(self.start_positions)return self.get_joint_observation()def step(self, actions: List[int]) -> Tuple[List[np.ndarray], List[float], bool]:"""执行所有智能体的一个步骤。参数:- actions (List[int]): 每个智能体的行动列表。返回:- Tuple[List[np.ndarray], List[float], bool]: - 每个智能体的下一个观测列表。- 每个智能体的奖励列表(共享奖励)。- 全局完成标志。"""if len(actions) != self.num_agents:raise ValueError(f"期望有 {self.num_agents} 个行动,但得到了 {len(actions)} 个")next_positions: List[Tuple[int, int]] = list(self.agent_positions) # 从当前开始total_dist_reduction = 0.0hit_wall_penalty = 0.0for i in range(self.num_agents):current_pos = self.agent_positions[i]if current_pos == self.goal_positions[i]: # 如果智能体已经在终点,则不移动continue action = actions[i]dr, dc = self.action_map[action]next_r, next_c = current_pos[0] + dr, current_pos[1] + dc# 检查边界并更新位置if 0 <= next_r < self.size and 0 <= next_c < self.size:next_positions[i] = (next_r, next_c)else:hit_wall_penalty -= 0.5 # 碰墙惩罚next_positions[i] = current_pos # 保持原位# 简单的碰撞处理:如果智能体移动到同一个位置,则反弹if self.num_agents == 2 and next_positions[0] == next_positions[1]:next_positions = list(self.agent_positions) # 恢复到之前的位置hit_wall_penalty -= 0.5 # 视为惩罚(就像碰墙一样)self.agent_positions = next_positions# 计算奖励和完成标志done = all(self.agent_positions[i] == self.goal_positions[i] for i in range(self.num_agents))if done:reward = 10.0 # 合作成功获得大奖励else:# 基于距离的奖励(终点距离的负和)dist_reward = 0.0for i in range(self.num_agents):dist = abs(self.agent_positions[i][0] - self.goal_positions[i][0]) + \abs(self.agent_positions[i][1] - self.goal_positions[i][1])dist_reward -= dist * 0.1 # 缩放后的负距离reward = -0.05 + dist_reward + hit_wall_penalty # 小步惩罚 + 距离 + 碰墙惩罚next_observations = self.get_joint_observation()# 共享奖励rewards = [reward] * self.num_agents return next_observations, rewards, done
class MultiAgentGridEnv_QMIX(MultiAgentGridEnv):""" 为 MA 网格环境添加全局状态方法。 """def __init__(self, size: int = 5, num_agents: int = 2):super().__init__(size=size, num_agents=num_agents)# 全局状态:(r1,c1, r2,c2, g1r,g1c, g2r,g2c) 归一化self.global_state_dim = self.num_agents * 4 def get_global_state(self) -> np.ndarray:""" 返回归一化的全局状态。 """state = []# 智能体位置for i in range(self.num_agents):state.extend(self._normalize_pos(self.agent_positions[i]))# 目标位置for i in range(self.num_agents):state.extend(self._normalize_pos(self.goal_positions[i]))return np.array(state, dtype=np.float32)def reset_qmix(self) -> Tuple[List[np.ndarray], np.ndarray]:""" 重置并返回观测列表和全局状态。 """obs_list = super().reset()global_state = self.get_global_state()return obs_list, global_statedef step_qmix(self, actions: List[int]) -> Tuple[List[np.ndarray], np.ndarray, float, bool]:""" 执行环境步骤,返回观测列表、全局状态、共享奖励和完成标志。 """next_obs_list, rewards, done = super().step(actions)next_global_state = self.get_global_state()shared_reward = rewards[0] # 使用共享奖励return next_obs_list, next_global_state, shared_reward, done

实例化并测试环境。

qmix_env = MultiAgentGridEnv_QMIX(size=5, num_agents=2)
obs_list_qmix, global_state_qmix = qmix_env.reset_qmix()
n_agents_qmix = qmix_env.num_agents
obs_dim_qmix = qmix_env.observation_dim_per_agent
global_state_dim_qmix = qmix_env.global_state_dim
action_dim_qmix = qmix_env.action_dimprint(f"QMIX 环境大小:{qmix_env.size}x{qmix_env.size}")
print(f"智能体数量:{n_agents_qmix}")
print(f"每个智能体的观测维度:{obs_dim_qmix}")
print(f"全局状态维度:{global_state_dim_qmix}")
print(f"每个智能体的行动维度:{action_dim_qmix}")
print(f"初始观测列表:{obs_list_qmix}")
print(f"初始全局状态:{global_state_qmix}")
QMIX 环境大小:5x5
智能体数量:2
每个智能体的观测维度:6
全局状态维度:8
每个智能体的行动维度:4
初始观测列表:[array([0., 0., 1., 1., 1., 0.], dtype=float32), array([1., 1., 0., 0., 0., 1.], dtype=float32)]
初始全局状态:[0. 0. 1. 1. 1. 0. 0. 1.]

实现 QMIX 算法

定义智能体网络(DRQN 或 MLP)

输出所有行动的 Q i ( o i , a ) Q_i(o_i, a) Qi(oi,a)。我们这里使用一个简单的 MLP。对于具有部分可观测性的环境,使用 LSTM 或 GRU 的 DRQN(深度循环 Q 网络)会更常见。

class AgentQNetwork(nn.Module):""" QMIX 中的个体智能体 Q 网络。输出所有行动的 Q 值。 """def __init__(self, obs_dim: int, action_dim: int):super(AgentQNetwork, self).__init__()self.fc1 = nn.Linear(obs_dim, 64) # 个体智能体的较小网络self.fc2 = nn.Linear(64, 64)self.fc3 = nn.Linear(64, action_dim) # 为每个行动输出 Q 值def forward(self, obs: torch.Tensor) -> torch.Tensor:""" 将观测映射到所有行动的 Q 值。 """x = F.relu(self.fc1(obs))x = F.relu(self.fc2(x))q_values = self.fc3(x)return q_values

定义混合网络(带超网络)

将个体 Q i Q_i Qi 值以单调的方式组合起来,以全局状态为条件。

class QMixer(nn.Module):""" QMIX 的混合网络,带超网络。 """def __init__(self, num_agents: int, global_state_dim: int, mixing_embed_dim: int = 32):super(QMixer, self).__init__()self.num_agents = num_agentsself.state_dim = global_state_dimself.embed_dim = mixing_embed_dim# 为第一层混合网络生成权重的超网络# 输入:全局状态,输出:权重形状(num_agents * embed_dim)self.hyper_w1 = nn.Sequential(nn.Linear(self.state_dim, 64),nn.ReLU(),nn.Linear(64, self.num_agents * self.embed_dim))# 为第一层混合网络生成偏置的超网络# 输入:全局状态,输出:偏置形状(embed_dim)self.hyper_b1 = nn.Linear(self.state_dim, self.embed_dim)# 为第二层混合网络(可选,可以是单层)生成权重的超网络# 输入:全局状态,输出:权重形状(embed_dim * 1)self.hyper_w2 = nn.Sequential(nn.Linear(self.state_dim, 64),nn.ReLU(),nn.Linear(64, self.embed_dim) # 输出大小为 embed_dim)# 为最终偏置(标量)生成的超网络# 输入:全局状态,输出:偏置(标量)self.hyper_b2 = nn.Sequential(nn.Linear(self.state_dim, 32),nn.ReLU(),nn.Linear(32, 1))def forward(self, agent_qs: torch.Tensor, global_state: torch.Tensor) -> torch.Tensor:"""将个体智能体 Q 值混合以产生 $Q_{tot}$。参数:- agent_qs (torch.Tensor): 个体 Q 值张量,形状为 (batch_size, num_agents)。- global_state (torch.Tensor): 全局状态张量,形状为 (batch_size, global_state_dim)。返回:- torch.Tensor: 估计的 $Q_{tot}$ 值,形状为 (batch_size, 1)。"""batch_size = agent_qs.size(0)# 将 agent_qs 重塑为 (batch_size, 1, num_agents),以便进行批量矩阵乘法agent_qs_reshaped = agent_qs.view(batch_size, 1, self.num_agents)# --- 第一层混合网络 --- # 生成权重并确保非负性w1 = torch.abs(self.hyper_w1(global_state)) # abs() 确保权重非负# 将权重重塑为 (batch_size, num_agents, embed_dim)w1 = w1.view(batch_size, self.num_agents, self.embed_dim)# 生成偏置b1 = self.hyper_b1(global_state)# 将偏置重塑为 (batch_size, 1, embed_dim)b1 = b1.view(batch_size, 1, self.embed_dim)# 应用第一层:$Q_{hidden} = Q_{agents} \times W1 + b1$hidden = F.elu(torch.bmm(agent_qs_reshaped, w1) + b1)# 形状:(batch_size, 1, embed_dim)# --- 第二层混合网络 --- # 生成权重并确保非负性w2 = torch.abs(self.hyper_w2(global_state))# 将权重重塑为 (batch_size, embed_dim, 1)w2 = w2.view(batch_size, self.embed_dim, 1)# 生成偏置b2 = self.hyper_b2(global_state)# 将偏置重塑为 (batch_size, 1, 1)b2 = b2.view(batch_size, 1, 1)# 应用第二层:$Q_{tot} = Q_{hidden} \times W2 + b2$q_tot = torch.bmm(hidden, w2) + b2# 形状:(batch_size, 1, 1)return q_tot.view(batch_size, 1) # 返回形状 (batch_size, 1)

定义回放记忆

存储联合转换,包括全局状态。

# 定义 QMIX 缓冲区的转换结构
QMIXTransition = namedtuple('QMIXTransition', ('obs_list', 'actions_list', 'reward', 'next_obs_list', 'global_state', 'next_global_state', 'done'))class QMIXReplayBuffer:def __init__(self, capacity: int):self.memory = deque([], maxlen=capacity)def push(self, obs_list: List[np.ndarray], actions_list: List[int], reward: float, next_obs_list: List[np.ndarray], global_state: np.ndarray,next_global_state: np.ndarray, done: bool) -> None:""" 保存一个联合转换。 """# 以 numpy 数组或原始类型存储self.memory.append(QMIXTransition(obs_list, actions_list, reward, next_obs_list, global_state,next_global_state, done))def sample(self, batch_size: int) -> Optional[QMIXTransition]:""" 采样一批经验并转换为张量。 """if len(self.memory) < batch_size:return Nonetransitions = random.sample(self.memory, batch_size)# 解包并先转换为 numpy 数组以便于堆叠obs_l, act_l, rew_l, next_obs_l, gs_l, next_gs_l, done_l = zip(*transitions)num_agents = len(obs_l[0])obs_dim = obs_l[0][0].shape[0]global_dim = gs_l[0].shape[0]# 堆叠观测:列表的列表 -> (batch, agent, dim)obs_arr = np.array(obs_l, dtype=np.float32).reshape(batch_size, num_agents, obs_dim)next_obs_arr = np.array(next_obs_l, dtype=np.float32).reshape(batch_size, num_agents, obs_dim)# 堆叠行动、奖励、完成标志、全局状态act_arr = np.array(act_l, dtype=np.int64) # 行动索引rew_arr = np.array(rew_l, dtype=np.float32)gs_arr = np.array(gs_l, dtype=np.float32).reshape(batch_size, global_dim)next_gs_arr = np.array(next_gs_l, dtype=np.float32).reshape(batch_size, global_dim)done_arr = np.array(done_l, dtype=np.float32)# 转换为张量return QMIXTransition(torch.from_numpy(obs_arr).to(device),torch.from_numpy(act_arr).to(device),torch.from_numpy(rew_arr).unsqueeze(1).to(device),torch.from_numpy(next_obs_arr).to(device),torch.from_numpy(gs_arr).to(device),torch.from_numpy(next_gs_arr).to(device),torch.from_numpy(done_arr).unsqueeze(1).to(device))def __len__(self) -> int:return len(self.memory)

软更新函数

标准的软更新。

def soft_update(target_net: nn.Module, main_net: nn.Module, tau: float) -> None:for target_param, main_param in zip(target_net.parameters(), main_net.parameters()):target_param.data.copy_(tau * main_param.data + (1.0 - tau) * target_param.data)

QMIX 更新步骤

执行集中式的 QMIX 更新的函数。

def update_qmix(memory: QMIXReplayBuffer,batch_size: int,agent_networks: List[AgentQNetwork],target_agent_networks: List[AgentQNetwork],mixer: QMixer,target_mixer: QMixer,optimizer: optim.Optimizer, # 单个优化器用于所有智能体 + 混合器参数gamma: float,tau: float,num_agents: int,action_dim: int) -> float:"""为所有智能体和混合器执行一次 QMIX 更新步骤。返回:- float: TD 损失值。"""if len(memory) < batch_size:return 0.0batch = memory.sample(batch_size)if batch is None: return 0.0obs_b, act_b, rew_b, next_obs_b, gs_b, next_gs_b, done_b = batch# obs_b 形状:(batch_size, num_agents, obs_dim)# act_b 形状:(batch_size, num_agents) -> 行动索引# rew_b 形状:(batch_size, 1)# gs_b 形状:(batch_size, global_state_dim)# done_b 形状:(batch_size, 1)# --- 计算目标 $Q_{tot}'$ --- target_agent_qs_list = []with torch.no_grad():for i in range(num_agents):# 从目标智能体网络获取下一个观测的 Q 值target_q_values_next = target_agent_networks[i](next_obs_b[:, i, :])# 选择使目标 $Q_i$ 最大化的行动max_actions_next = target_q_values_next.argmax(dim=1, keepdim=True) # 形状:(batch_size, 1)# 获取对应于该最大行动的 Q 值max_target_q_next = torch.gather(target_q_values_next, 1, max_actions_next)target_agent_qs_list.append(max_target_q_next)# 堆叠目标智能体 Q 值:(batch_size, num_agents)target_agent_qs = torch.cat(target_agent_qs_list, dim=1)# 使用目标混合器计算目标 $Q_{tot}'$q_tot_target = target_mixer(target_agent_qs, next_gs_b)# 计算 TD 目标 $y = r + \gamma \times Q_{tot}' \times (1 - done)$y = rew_b + gamma * (1.0 - done_b) * q_tot_target# --- 计算当前 $Q_{tot}$ --- current_agent_qs_list = []for i in range(num_agents):# 从主智能体网络获取当前观测的 Q 值q_values_current = agent_networks[i](obs_b[:, i, :])# 选择缓冲区中采取的行动对应的 Q 值action_i = act_b[:, i].unsqueeze(1) # 形状:(batch_size, 1)q_current_i = torch.gather(q_values_current, 1, action_i)current_agent_qs_list.append(q_current_i)# 堆叠当前智能体 Q 值:(batch_size, num_agents)current_agent_qs = torch.cat(current_agent_qs_list, dim=1)# 使用主混合器计算当前 $Q_{tot}$q_tot_current = mixer(current_agent_qs, gs_b)# --- 计算损失并优化 --- loss = F.mse_loss(q_tot_current, y)optimizer.zero_grad()loss.backward()# 可选的梯度裁剪# total_norm = torch.nn.utils.clip_grad_norm_([param for net in agent_networks for param in net.parameters()] + #                                            list(mixer.parameters()), max_norm=1.0)optimizer.step()# --- 更新目标网络 --- for i in range(num_agents):soft_update(target_agent_networks[i], agent_networks[i], tau)soft_update(target_mixer, mixer, tau)return loss.item()

运行 QMIX 算法

超参数设置

# QMIX 在自定义多智能体网格世界中的超参数
BUFFER_SIZE_QMIX = int(5e4)  # 对于简单环境,较小的缓冲区可能就足够了
BATCH_SIZE_QMIX = 64
GAMMA_QMIX = 0.99
TAU_QMIX = 1e-3           # 软更新因子
LR_QMIX = 5e-4            # 智能体网络和混合器的学习率
EPSILON_QMIX_START = 1.0
EPSILON_QMIX_END = 0.05
EPSILON_QMIX_DECAY = 100000 # 在 N 步内衰减
MIXING_EMBED_DIM = 32     # 混合网络中的嵌入维度
HYPERNET_HIDDEN = 64      # 超网络的隐藏大小NUM_EPISODES_QMIX = 600
MAX_STEPS_PER_EPISODE_QMIX = 100
UPDATE_EVERY_QMIX = 4      # 每 N 个环境步骤执行一次更新
TARGET_UPDATE_FREQ_QMIX = 100 # 目标网络软更新的步数(替代方案)
USE_SOFT_UPDATE = True       # 使用软更新(True)或定期硬更新(False)

初始化

# 初始化环境
env_qmix = MultiAgentGridEnv_QMIX(size=5, num_agents=2)
num_agents_qmix = env_qmix.num_agents
obs_dim_qmix = env_qmix.observation_dim_per_agent
global_state_dim_qmix = env_qmix.global_state_dim
action_dim_qmix = env_qmix.action_dim# 初始化智能体网络和目标网络
agent_networks = [AgentQNetwork(obs_dim_qmix, action_dim_qmix).to(device) for _ in range(num_agents_qmix)]
target_agent_networks = [AgentQNetwork(obs_dim_qmix, action_dim_qmix).to(device) for _ in range(num_agents_qmix)]
for i in range(num_agents_qmix):target_agent_networks[i].load_state_dict(agent_networks[i].state_dict())for p in target_agent_networks[i].parameters(): p.requires_grad = False# 初始化混合网络和目标混合网络
mixer = QMixer(num_agents_qmix, global_state_dim_qmix, MIXING_EMBED_DIM).to(device)
target_mixer = QMixer(num_agents_qmix, global_state_dim_qmix, MIXING_EMBED_DIM).to(device)
target_mixer.load_state_dict(mixer.state_dict())
for p in target_mixer.parameters(): p.requires_grad = False# 收集所有参数用于单个优化器
all_params = list(mixer.parameters())
for net in agent_networks:all_params.extend(list(net.parameters()))# 初始化优化器
optimizer_qmix = optim.Adam(all_params, lr=LR_QMIX)# 初始化回放缓冲区
memory_qmix = QMIXReplayBuffer(BUFFER_SIZE_QMIX)# 用于绘图的列表
qmix_episode_rewards = []
qmix_episode_losses = []
qmix_episode_epsilons = []

训练循环

print("开始 QMIX 训练...")total_steps_qmix = 0
epsilon = EPSILON_QMIX_STARTfor i_episode in range(1, NUM_EPISODES_QMIX + 1):obs_list_np, global_state_np = env_qmix.reset_qmix()episode_reward = 0episode_loss = 0num_updates_ep = 0for t in range(MAX_STEPS_PER_EPISODE_QMIX):# --- 行动选择(分散式 $\epsilon$-贪婪) --- actions_list = []for i in range(num_agents_qmix):obs_tensor = torch.from_numpy(obs_list_np[i]).float().to(device)agent_networks[i].eval() # 评估模式用于选择with torch.no_grad():q_values = agent_networks[i](obs_tensor)agent_networks[i].train() # 恢复训练模式if random.random() < epsilon:action = random.randrange(action_dim_qmix)else:action = q_values.argmax().item()actions_list.append(action)# --- 环境交互 --- next_obs_list_np, next_global_state_np, reward, done = env_qmix.step_qmix(actions_list)# --- 存储经验 --- memory_qmix.push(obs_list_np, actions_list, reward, next_obs_list_np, global_state_np, next_global_state_np, done)# 更新当前状态 / 观测obs_list_np = next_obs_list_npglobal_state_np = next_global_state_npepisode_reward += rewardtotal_steps_qmix += 1# --- 更新网络 --- if len(memory_qmix) > BATCH_SIZE_QMIX and total_steps_qmix % UPDATE_EVERY_QMIX == 0:loss = update_qmix(memory_qmix, BATCH_SIZE_QMIX,agent_networks, target_agent_networks,mixer, target_mixer,optimizer_qmix,GAMMA_QMIX, TAU_QMIX if USE_SOFT_UPDATE else 1.0, # 如果是硬更新,则 Tau=1.0num_agents_qmix, action_dim_qmix)episode_loss += lossnum_updates_ep += 1# --- 目标网络更新逻辑 --- if not USE_SOFT_UPDATE and total_steps_qmix % TARGET_UPDATE_FREQ_QMIX == 0:# 定期硬更新for i in range(num_agents_qmix):target_agent_networks[i].load_state_dict(agent_networks[i].state_dict())target_mixer.load_state_dict(mixer.state_dict())# 如果 USE_SOFT_UPDATE 为 True,则在 update_qmix 中进行软更新# 衰减 epsilon(基于步数的衰减)epsilon = max(EPSILON_QMIX_END, EPSILON_QMIX_START - total_steps_qmix / EPSILON_QMIX_DECAY * (EPSILON_QMIX_START - EPSILON_QMIX_END))if done:break# --- 剧集结束 --- qmix_episode_rewards.append(episode_reward)qmix_episode_losses.append(episode_loss / num_updates_ep if num_updates_ep > 0 else 0)qmix_episode_epsilons.append(epsilon)# 打印进度if i_episode % 50 == 0:avg_reward = np.mean(qmix_episode_rewards[-50:])avg_loss = np.mean(qmix_episode_losses[-50:])print(f"剧集 {i_episode}/{NUM_EPISODES_QMIX} | 步骤数:{total_steps_qmix} | 平均奖励:{avg_reward:.2f} | 平均损失:{avg_loss:.4f} | Epsilon:{epsilon:.3f}")print("QMIX 训练完成。")
开始 QMIX 训练...
剧集 50/600 | 步骤数:3981 | 平均奖励:-70.69 | 平均损失:0.3158 | Epsilon:0.962
剧集 100/600 | 步骤数:7651 | 平均奖励:-50.70 | 平均损失:0.0936 | Epsilon:0.927
剧集 150/600 | 步骤数:10213 | 平均奖励:-32.85 | 平均损失:0.0371 | Epsilon:0.903
剧集 200/600 | 步骤数:12701 | 平均奖励:-27.53 | 平均损失:0.0442 | Epsilon:0.879
剧集 250/600 | 步骤数:15222 | 平均奖励:-24.79 | 平均损失:0.0468 | Epsilon:0.855
剧集 300/600 | 步骤数:17356 | 平均奖励:-19.04 | 平均损失:0.0500 | Epsilon:0.835
剧集 350/600 | 步骤数:19198 | 平均奖励:-14.75 | 平均损失:0.0539 | Epsilon:0.818
剧集 400/600 | 步骤数:20969 | 平均奖励:-13.01 | 平均损失:0.0618 | Epsilon:0.801
剧集 450/600 | 步骤数:22741 | 平均奖励:-13.86 | 平均损失:0.0642 | Epsilon:0.784
剧集 500/600 | 步骤数:24157 | 平均奖励:-7.86 | 平均损失:0.0676 | Epsilon:0.771
剧集 550/600 | 步骤数:25939 | 平均奖励:-13.20 | 平均损失:0.0675 | Epsilon:0.754
剧集 600/600 | 步骤数:27128 | 平均奖励:-5.17 | 平均损失:0.0629 | Epsilon:0.742
QMIX 训练完成。

可视化学习过程

绘制剧集奖励、TD 损失和 epsilon 衰减的图表。

# 绘制 QMIX 的结果
plt.figure(figsize=(18, 4))# 剧集奖励(共享)
plt.subplot(1, 3, 1)
plt.plot(qmix_episode_rewards)
plt.title('QMIX 网格世界:剧集奖励(共享)')
plt.xlabel('剧集')
plt.ylabel('总奖励')
plt.grid(True)
if len(qmix_episode_rewards) >= 50:rewards_ma_qmix = np.convolve(qmix_episode_rewards, np.ones(50)/50, mode='valid')plt.plot(np.arange(len(rewards_ma_qmix)) + 49, rewards_ma_qmix, label='50 剧集移动平均', color='orange')plt.legend()# TD 损失 / 剧集
plt.subplot(1, 3, 2)
plt.plot(qmix_episode_losses)
plt.title('QMIX 网格世界:每剧集平均 TD 损失')
plt.xlabel('剧集')
plt.ylabel('平均 MSE 损失')
plt.yscale('log') # 如果损失变化很大,则使用对数刻度
plt.grid(True, which='both')
if len(qmix_episode_losses) >= 50:loss_ma_qmix = np.convolve(qmix_episode_losses, np.ones(50)/50, mode='valid')# 避免对数刻度下绘制 0 或负值(如果损失非常低)valid_indices = np.where(loss_ma_qmix > 1e-8)[0] if len(valid_indices) > 0:plt.plot(np.arange(len(loss_ma_qmix))[valid_indices] + 49, loss_ma_qmix[valid_indices], label='50 剧集移动平均', color='orange')plt.legend()# Epsilon
plt.subplot(1, 3, 3)
plt.plot(qmix_episode_epsilons)
plt.title('QMIX:Epsilon 衰减')
plt.xlabel('剧集')
plt.ylabel('Epsilon')
plt.grid(True)plt.tight_layout()
plt.show()

在这里插入图片描述

QMIX 学习曲线分析(网格世界 - 共享奖励):

  1. 剧集奖励(共享):

    • 观察结果: 学习过程显示出明显的、尽管缓慢且嘈杂的改进趋势。每剧集的总共享奖励起初非常低(大约 -120),并且极其不稳定。50 剧集移动平均值(橙色线)显示从第 100 剧集开始有一个明显的向上爬升趋势,最终在第 500-600 剧集时稳定在 -10 到 -20 之间。尽管平均值有所改善,但原始剧集奖励(蓝色线)在整个 600 剧集期间保持很高的方差,经常在接近零的值和显著的负值之间大幅波动。
    • 解释: 这表明 QMIX 智能体团队正在学习协调以实现更高的共享奖励,明显地远离了最初的糟糕表现。移动平均值的逐渐爬升证实了在更长时间尺度上的成功学习。然而,持续的高方差是多智能体协调挑战的特征。QMIX 依赖于个体智能体根据其局部 Q 值贪婪行动,这些值通过一个单调的混合网络组合。尽管这种结构有助于学习,但找到一致的最优联合行动仍然很困难,导致了奖励的波动。
  2. 每剧集平均 TD 损失:

    • 观察结果: 在对数刻度上绘制,混合网络的平均 TD(时间差分)损失在最初的 100 剧集内急剧下降,大约减少了两个数量级。在这一快速的初始下降之后,损失稳定在一个较低的平均水平(大约 0.01),在剩余的训练期间表现出适度的波动。移动平均值确认了这种收敛模式。
    • 解释: 这个图表至关重要,因为它反映了集中式混合网络在基于个体智能体的当前估计学习团队价值函数( Q t o t Q_{tot} Qtot)方面的表现。急剧的初始下降表明混合网络很快就能熟练地根据智能体的 Q 值表示团队价值函数。随后在低水平的稳定表明价值函数学习部分的 QMIX 运行良好,为选择联合行动提供了一个一致的基础,即使智能体的策略(因此它们的 Q i Q_i Qi 值)可能仍在演变。
  3. Epsilon 衰减:

    • 观察结果: 探索参数 epsilon 遵循预定义的衰减计划,从 1.0 开始,到第 600 剧集时大约减少到 0.74。衰减看起来相对缓慢,可能是线性或轻微指数型的。
    • 解释: 这显示了基于 Q 学习的方法中使用的标准 epsilon-greedy 探索策略。从 epsilon=1.0 开始,智能体逐渐减少采取随机行动的倾向,越来越多地利用学习到的 Q 值。在第 600 剧集时仍然相对较高的 epsilon 值表明在整个展示的训练期间保持了相当程度的探索。这种持续的探索可能有助于发现更好的策略,但它也可能阻碍更快地收敛到一个完全稳定的、利用性的策略。

总体结论:
QMIX 在多智能体网格世界任务中成功地进行了学习,在 600 剧集内显著提高了共享团队奖励。其核心机制,学习个体 Q 函数并通过一个收敛良好的混合网络进行组合(由低 TD 损失所表明),被证明是估计价值的有效方法。然而,与 MADDPG 类似,团队的整体表现仍然高度不稳定,这表明基于分解的价值函数实现一致的最优协调是具有挑战性的。缓慢的 epsilon 衰减可能有助于这种持续的方差,因为它在整个训练期间维持了相当程度的探索。

分析学习到的策略(测试)

使用从它们学习到的 Q i Q_i Qi 网络派生出的贪婪策略,测试智能体以分散式的方式行动。

def test_qmix_agents(agent_nets: List[AgentQNetwork], env_instance: MultiAgentGridEnv_QMIX, num_episodes: int = 5, seed_offset: int = 6000) -> None:""" 测试训练好的 QMIX 智能体以分散式方式(贪婪策略)行动。 """print(f"\n--- 测试 QMIX 智能体({num_episodes} 剧集) ---")all_episode_rewards = []for i in range(num_episodes):obs_list_np, _ = env_instance.reset_qmix()episode_reward = 0done = Falset = 0while not done and t < MAX_STEPS_PER_EPISODE_QMIX:actions_list = []for agent_id in range(env_instance.num_agents):obs_tensor = torch.from_numpy(obs_list_np[agent_id]).float().to(device)agent_nets[agent_id].eval() # 设置为评估模式with torch.no_grad():q_values = agent_nets[agent_id](obs_tensor)action = q_values.argmax().item() # 贪婪行动actions_list.append(action)# 执行环境步骤next_obs_list_np, _, reward, done = env_instance.step_qmix(actions_list)# 更新观测obs_list_np = next_obs_list_npepisode_reward += rewardt += 1print(f"测试剧集 {i+1}: 奖励 = {episode_reward:.2f}, 长度 = {t}")all_episode_rewards.append(episode_reward)print(f"--- 测试完成。平均奖励:{np.mean(all_episode_rewards):.2f} ---")# 运行测试剧集
test_qmix_agents(agent_networks, env_qmix, num_episodes=5)
--- 测试 QMIX 智能体(5 剧集) ---
测试剧集 1: 奖励 = 8.65, 长度 = 4
测试剧集 2: 奖励 = 8.65, 长度 = 4
测试剧集 3: 奖励 = 8.65, 长度 = 4
测试剧集 4: 奖励 = 8.65, 长度 = 4
测试剧集 5: 奖励 = 8.65, 长度 = 4
--- 测试完成。平均奖励:8.65 ---

QMIX 的常见挑战和扩展

挑战:有限的表示能力

  • 问题: 单调性约束( ∂ Q t o t ∂ Q i ≥ 0 \frac{\partial Q_{tot}}{\partial Q_i} \ge 0 QiQtot0)限制了 QMIX 可以表示的联合行动价值函数的类别。它无法表示某些状态下需要牺牲一个智能体的高效用以实现团队更大利益的情况(需要非单调混合)。
  • 解决方案
    • QTRAN: 扩展 QMIX 以处理更广泛的分解类别。
    • 加权 QMIX: 引入依赖于状态的权重,可以为负值,从而放宽严格的单调性约束。
    • QPLEX: 使用 dueling 架构实现更通用的价值分解。

挑战:全局状态需求

  • 问题: 混合网络在集中式训练期间需要访问全局状态 x x x。这可能并不总是可用的,或者可能非常高维。
  • 解决方案
    • 近似全局状态: 使用聚合信息或智能体之间的通信来近似全局状态。
    • 替代方法: 使用依赖于紧凑全局状态表示的方法。

挑战:多智能体环境中的探索

  • 问题: 协调探索可能很困难。在个体智能体上简单地使用 ϵ \epsilon ϵ-greedy 可能不足以发现复杂的联合策略。
    解决方案
    • 更复杂的探索: 技术如 MAVEN 通过引入潜在变量来鼓励多样化的联合探索策略。
    • 参数噪声: 给智能体网络参数添加噪声。

挑战:超参数敏感性

  • 问题: 与许多 DRL 算法一样,QMIX 的性能取决于对学习率、目标更新频率 / 率、缓冲区 / 批量大小和探索参数等的精心调整。
    解决方案:系统地调整,并使用常见的成功设置作为起始点。

扩展:

  • VDN(价值分解网络): 一个更简单的前身,其中 Q t o t = ∑ i Q i Q_{tot} = \sum_i Q_i Qtot=iQi$。缺乏依赖于状态的混合。
  • QTRAN、QPLEX、加权 QMIX: 放宽单调性约束以获得更大的表示能力。
  • MAVEN: 通过学习潜在探索空间来改进探索。

结论

QMIX 是一种用于合作式多智能体强化学习的极具影响力的基于价值的算法。其核心贡献是单调价值函数分解,使得集中式训练稳定,分散式执行高效。通过学习个体智能体 Q 函数,并通过一个依赖于状态的混合网络以单调的方式将它们组合起来,QMIX 有效地解决了许多合作任务中的功劳分配问题。

尽管其表示能力受到单调性约束的限制,QMIX 在可扩展性方面具有显著优势(与学习完整的联合 Q 函数相比),并且为许多后续的 MARL 价值分解方法提供了坚实的基础。它在诸如 SMAC 等基准测试中的成功,突显了结构化价值函数分解在合作式多智能体问题中的强大能力。

相关文章:

深度理解用于多智能体强化学习的单调价值函数分解QMIX算法:基于python从零实现

引言&#xff1a;合作式多智能体强化学习与功劳分配 在合作式多智能体强化学习&#xff08;MARL&#xff09;中&#xff0c;多个智能体携手合作&#xff0c;共同达成一个目标&#xff0c;通常会收到一个团队共享的奖励。在这种场景下&#xff0c;一个关键的挑战就是功劳分配&a…...

C语言经典笔试题目分析(持续更新)

1. 描述下面代码中两个static 各自的含义 static void func (void) {static unsigned int i; }static void func(void) 中的 static 作用对象&#xff1a;函数 func。 含义&#xff1a; 限制函数的作用域&#xff08;链接属性&#xff09;&#xff0c;使其仅在当前源文件&…...

射击游戏demo11

完善地图&#xff0c;加载出装饰品&#xff0c;检测人员与地面的碰撞&#xff0c;检测子弹与岩壁的碰撞&#xff0c;检测手雷与地面的碰撞。 import pygame import sys import os import random import csv # 初始化Pygame pygame.init()# 屏幕宽度 SCREEN_WIDTH 1200 # 屏幕高…...

多智能体Multi-Agent应用实战与原理分析

一:Agent 与传统工具调用的对比 在当今的开发环境中,Agent 的出现极大地简化了工作流程。其底层主要基于提示词、模型和工具。用户只需向 Agent 输入需求,Agent 便会自动分析需求,并利用工具获取最终答案。而传统方式下,若没有 Agent,我们则需要手动编码来执行工具,还要…...

专项智能练习(定义判断)_DA_01

1. 单选题 热传导是介质内无宏观运动时的传热现象&#xff0c;其在固体、液体和气体中均可发生。但严格而言&#xff0c;只有在固体中才是纯粹的热传导&#xff0c;在流体&#xff08;泛指液体和气体&#xff09;中又是另外一种情况&#xff0c;流体即使处于静止状态&#xff0…...

关于NLP自然语言处理的简单总结

参考&#xff1a; 什么是自然语言处理&#xff1f;看这篇文章就够了&#xff01; - 知乎 (zhihu.com) 所谓自然语言理解&#xff0c;就是研究如何让机器能够理解我们人类的语言并给出一些回应。 自然语言处理&#xff08;Natural Language Processing&#xff0c;NLP&#xff0…...

SLAM定位与地图构建

SLAM介绍 SLAM全称Simultaneous Localization And Mapping&#xff0c;中文名称同时定位与地图构建。旨在让移动设备在未知环境中同时完成以下两个任务&#xff08;定位需要地图&#xff0c;而建图又依赖定位信息&#xff0c;两者互为依赖&#xff09;&#xff1a; 定位&#…...

REST架构风格介绍

一.REST&#xff08;表述性状态转移&#xff09; 1.定义 REST&#xff08;Representational State Transfer&#xff09;是由 Roy Fielding 在 2000 年提出的一种软件架构风格&#xff0c;用于设计网络应用的通信模式。它基于 HTTP 协议&#xff0c;强调通过统一的接口&#…...

前端流行框架Vue3教程:16. 组件事件配合`v-model`使用

组件事件配合v-model使用 如果是用户输入&#xff0c;我们希望在获取数据的同时发送数据配合v-model 来使用&#xff0c;帮助理解组件间的通信和数据绑定。 &#x1f9e9; 第一步&#xff1a;创建子组件&#xff08;SearchComponent.vue&#xff09; 这个组件用于处理用户的搜…...

5月15日day26打卡

函数专题1 知识点回顾&#xff1a; 函数的定义变量作用域&#xff1a;局部变量和全局变量函数的参数类型&#xff1a;位置参数、默认参数、不定参数传递参数的手段&#xff1a;关键词参数传递参数的顺序&#xff1a;同时出现三种参数类型时 作业&#xff1a; 题目1&#xff1a;…...

Java中的深拷贝与浅拷贝

什么是拷贝 在Java中&#xff0c;拷贝是指创建一个对象的副本。拷贝主要分为两种类型&#xff1a;浅拷贝(Shallow Copy)和深拷贝(Deep Copy)。理解这两种拷贝的区别对于编写正确的Java程序非常重要&#xff0c;特别是在处理对象引用时。 浅拷贝(Shallow Copy) 浅拷贝是指创建…...

springboot AOP中,通过解析SpEL 表达式动态获取参数值

切面注解 import com.bn.document.constants.FmDeptCatalogueConstants;import java.lang.annotation.*;Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface FmDeptCatalogueAopAnnotation {/*** 权限类型*/FmDeptCatalogueConstants value();/…...

【论信息系统项目的合同管理】

论信息系统项目的合同管理 论文要求写作要点正文前言一、合同的签订管理二、合同履行管理三、合同变更管理四、合同档案管理五、合同违约索赔管理结语 论文要求 项目合同管理通过对项目合同的全生命周期进行管理&#xff0c;来回避和减轻可识别的项目风险。 请以“论信息系统项…...

redis持久化方式

一、RDB redis database&#xff1a;快照&#xff0c;某一时刻将内存中的数据&#xff0c;写入磁盘中生成1个dump.rdb文件RDB的触发方式&#xff1a; 手动触发&#xff1a;save&#xff08;阻塞主进程&#xff0c;阻塞其它指令&#xff0c;保证数据一致性&#xff09;、bgsave…...

free void* 指令

https://stackoverflow.com/questions/2182103/is-it-ok-to-free-void free(ptr) 仅释放指针指向的内存&#xff0c;不会修改指针变量本身的值。调用后&#xff0c;ptr 仍然指向原来的地址&#xff08;称为 "悬空指针"&#xff09;&#xff0c;但该地址对应的内存已…...

ADS1220高精度ADC(TI)——应用 源码

文章目录 德州仪器ADS1220概述资料引脚&封装布线寄存器配置寄存器0&#xff08;00h&#xff09;配置寄存器1&#xff08;01h&#xff09;配置寄存器2&#xff08;02h&#xff09;配置寄存器3&#xff08;03h&#xff09; 连续转换流程驱动源码ads1220.cads1220.h 德州仪器A…...

mysql-Java手写分布式事物提交流程

准备 innodb存储引擎开启支持分布式事务 set global innodb_support_axon分布式的流程 详细流程&#xff1a; XA START ‘a’; 作用&#xff1a;开始一个新的XA事务&#xff0c;并分配一个唯一的事务ID ‘a’。 说明&#xff1a;在这个命令之后&#xff0c;所有后续的SQL操…...

红黑树:数据世界的平衡守护者

在 C 算法的神秘森林里&#xff0c;红黑树是一棵充满智慧的 “魔法树”。它既不像普通二叉搜索树那样容易失衡&#xff0c;也不像 AVL 树对平衡要求那么苛刻。作为 C 算法小白&#xff0c;今天就和大家一起深入探索红黑树的奥秘&#xff0c;看看它是如何成为数据世界的平衡守护…...

哈夫曼树完全解析:从原理到应用

目录 一、核心概念 二、构造全流程解析 三、编码生成机制 四、C语言实现关键代码 五、核心特性深度解读 六、现代应用场景 七、压缩实战演示 一、核心概念 哈夫曼树&#xff08;最优二叉树&#xff09;是带权路径长度&#xff08;WPL&#xff09;最短的树形结构&#x…...

如何在 Windows 命令提示符中创建多个文件夹和多个文件

如何在 Windows 命令提示符中创建多个文件夹和多个文件 虽然大多数用户习惯使用 Windows 图形界面来创建文件夹&#xff0c;但如果你需要一次性创建多个文件夹或文件&#xff0c;如同在类Unix系统中可以使用mkdir和touch命令一样&#xff0c;windows下也有创建目录和文件的对应…...

【Java】Spring的声明事务在多线程场景中失效问题。

大家都知道Spring的声明式事务在多线程当中会失效&#xff0c;来看一下如下案例。 按照如下方式&#xff0c;b()方法抛出异常,由于父子线程导致事务失效&#xff0c;a()会成功插入,但是b()不会。 因此成功插入一条数据&#xff0c;事务失效。 Component public class UserServ…...

多平台图标设计与管理的终极解决方案

IconWorkshop Pro 是一款由Axialis团队开发的专业图标设计与制作软件&#xff0c;专注于为设计师、开发者及企业用户提供高效且灵活的图标创作解决方案。该软件凭借其强大的功能与跨平台适配性&#xff0c;成为Windows、macOS、iOS、Android等多系统图标设计的首选工具之一。 …...

【搭建Node-RED + MQTT Broker实现AI大模型交互】

搭建Node-RED MQTT Broker实现AI大模型交互 搭建Node-RED MQTT Broker实现AI大模型交互一、系统架构二、环境准备与安装1. 安装Node.js2. 安装Mosquitto MQTT Broker3. 配置Mosquitto4. 安装Node-RED5. 配置Node-RED监听所有网络接口6. 启动Node-RED 三、Node-RED流程配置1. …...

小结: js 在浏览器执行原理

浏览器多进程与多线程 现代浏览器的标签环境隔离主要通过多进程架构和多线程机制实现&#xff0c;以确保安全、性能和稳定性。以下是浏览器实现标签环境隔离的多进程和多线程交互架构的详细解析&#xff1a; ------------------- ------------------- -----------…...

C++核心编程--2 引用

引用就是给变量起别名&#xff0c;操作引用就等于操作原始变量。 2.1 引用基本用法 int var 10; int & r_var var; 2.2 注意事项 声明时必须初始化不允许更改引用指向的原始变量 2.3 引用作为函数参数传递 简化指针修饰函数参数 2.4 引用作为函数返回值 不要返回…...

音频/AI/BLE/WIFI/玩具/商业等方向的论坛网站总结

我爱音频网 我爱音频网 - 我们只谈音频&#xff0c;丰富的TWS真无线蓝牙耳机拆解报告 (52audio.com) 中国人工智能学会 中国人工智能学会 (caai.cn) AIIA人工智能网 https://www.aiiaw.com/ 世界人工智能论坛 世界人工智能论坛 - (amtbbs.org) 36氪 36氪_让一部分人先…...

告别碎片化!MCP 带来 AI Agent 开发生态的革命性突破

引言&#xff1a; 在当今的智能客服系统开发中&#xff0c;开发者常常面临一个棘手的挑战&#xff1a;需要整合用户的 CRM 数据、知识库和实时聊天记录。然而&#xff0c;由于缺乏统一的标准&#xff0c;每个团队都不得不手动实现这些集成。这不仅延长了开发周期&#xff0c;还…...

centos7部署mysql5.7

1.下载mysql的官方yum源 wget http://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm2.安装yum源 yum -y install mysql57-community-release-el7-11.noarch.rpm3.安装秘钥文件 rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-20224.安装mysql5.7…...

linux dbus

Linux D-Bus(Desktop Bus)是一种进程间通信(IPC)机制,主要用于Linux桌面环境和系统服务之间的消息传递。它允许不同的应用程序或系统组件以高效、安全的方式相互通信,是现代Linux桌面(如GNOME、KDE)的核心基础设施之一。 1. D-Bus 的核心概念 消息总线(Message Bus):…...

计量——异方差的检验及其修正

目录 1.异方差的检验 1 BP检验 2white检验 2.异方差的修正 1.异方差的检验 1 BP检验 选择检验方法&#xff1a;BP BP检验的实际步骤&#xff08;非机器&#xff09;&#xff1a; 1.y对所有x进行回归&#xff0c;得到残差u。计算残差的平方u^2 2.u^2对所有x进行回归&#…...

操作系统学习笔记第3章 内存管理(灰灰题库)

1. 单选题 某页式存储管理系统中&#xff0c;主存为 128KB&#xff0c;分成 32 块&#xff0c;块号为 0、1、2、3、…、31。某作业有 5 块&#xff0c;其页号为 0、1、2、3、4&#xff0c;被分别装入主存的 3、8、4、6、9 块中。有一逻辑地址为 [3, 70]&#xff08;其中方括号中…...

vue3项目中使用CanvasEditor开箱即用(组件的形式,组件封装好了)

canvas-editor-vue 这是canvas-editor项目的vue版本,我封装成组建了,当然了这是个已经把canvas-editor封装好的的Vue项目 项目地址GitHub地址:GitHub - aini-aini/canvas-editor-vue: this is a project than can be used in vue project as Componentthis is a project than…...

人体肢体工作识别-一步几个脚印从头设计数字生命——仙盟创梦IDE

人体肢体识别是借助计算机视觉、传感器等技术&#xff0c;对人体各肢体的位置、动作、姿态等进行检测与分析的技术。其在医疗健康、智能交互、运动训练、安全监控等多个领域具有重要价值&#xff0c; 示例代码 import cv2 import mediapipe as mp import numpy as np import c…...

【重磅】配电网智能软开关和储能联合规划

目录 1 主要内容 目标函数 数据说明 节点系统图 2 部分代码 3 程序结果 4 下载链接 1 主要内容 该程序复现《具有源荷不平衡特性的配电网智能软开关和储能联合规划》部分模型&#xff0c;未考虑聚类分析和分布鲁棒部分&#xff0c;就智能软开关和储能联合规划部分进行了…...

实验6 电子邮件

实验6 电子邮件 1、实验目的 理解电子邮件系统基本结构 理解客户端和服务器端&#xff0c;以及服务器之间的通信 分析理解SMTP&#xff0c;POP3协议 2、实验环境 硬件要求&#xff1a;阿里云云主机ECS 一台。 软件要求&#xff1a;Linux/ Windows 操作系统 3、实验内容…...

NHANES指标推荐:OBS

文章题目&#xff1a;Association between oxidative balance score and all-cause and cancer-specific mortality among cancer survivors DOI&#xff1a;10.3389/fimmu.2025.1541675 中文标题&#xff1a;癌症幸存者氧化平衡评分与全因死亡率和癌症特异性死亡率之间的关联 …...

python-修改图片背景色

在Python中&#xff0c;可以使用图像处理库&#xff08;如OpenCV或Pillow&#xff09;来修改图片的背景色。通常&#xff0c;修改背景色的流程包括以下步骤&#xff1a; 1、对图片进行分割&#xff0c;识别前景和背景。 2、对背景区域进行颜色替换。 下面是两种实现方法&#x…...

数据结构与算法--顺序表--单链表

一 顺序表 需要指向存储位置的基地址分配一段连续的内存用length记录实际的元素的个数&#xff0c;也即顺序表的长度&#xff0c;因为顺序表是允许删除和插入元素的不需要定义数组 1.1 普通结构体数组实现版本&#xff0c;实现白色的点以一定的步长移除画面&#xff0c;大量fo…...

MATLAB安装全攻略:常见问题与解决方案

MATLAB安装常见问题与解决方案 一、系统兼容性验证 安装前需确认操作系统满足MATLAB版本要求&#xff1a; Windows 10版本1903及以上&#xff08;64位&#xff09;macOS Monterey 12.6及以上Ubuntu 22.04 LTS及以上 验证命令示例&#xff1a; # Linux系统验证 lsb_release…...

constexpr 关键字的意义(入门)

author: hjjdebug date: 2025年 05月 15日 星期四 16:03:33 CST description: constexpr 关键字的意义(入门) constexpr 是c11 引入的一个关键字, 代表了一种属性. 文章目录 1. constexpr 修饰的变量, 在编译期间就可以得到其数值.2. constexpr 修饰的函数, 可以在编译期间被调…...

aptitude 深度教程:从基础到生产实践

目录 一、aptitude 基础:核心概念与环境准备 1.1 aptitude 是什么? 1.2 安装与环境配置 二、aptitude 核心操作:从命令行到交互式界面 2.1 命令行基础操作 2.2 交互式界面(TUI)入门 三、高级功能:依赖管理与版本控制 3.1 依赖冲突解决实战 3.2 版本锁定与降级 3…...

嵌入式开发学习日志(数据结构--双链表)Day21

一、双链表 1.定义 双向链表是在单链表的每个结点中&#xff0c;再设置一个指向其钱去节点的指针域。 2、声明文件 3、创建表头 4、头插 5、 遍历 6、尾插、 7、指定插 8、查找 9、修改 10.、删除 11、逆序 12、销毁链表 13、main.c 三、扩展&#xff1a;工程管理工具&#…...

抢购Python代码示例与技术解析

引言&#xff1a;抢购系统的技术挑战 在当今电子商务高度发达的时代&#xff0c;抢购活动已成为各大电商平台吸引用户的重要手段。然而&#xff0c;高并发、低延迟的抢购场景对系统设计提出了严峻挑战。本文将提供一个完整的Python抢购代码示例&#xff0c;并深入分析其技术实…...

undefined reference to CPUAllocatorSingleton::instance

它发生的原因是你声明了 CPUAllocatorSingleton 类中的 instance 变量&#xff0c;但没有提供它的定义。 这个错误是链接器无法找到 CPUAllocatorSingleton::instance 的定义。它发生的原因是你声明了 CPUAllocatorSingleton 类中的 instance 变量&#xff0c;但没有提供它的定…...

【c语言】动态内存分配

文章标题 一、为什么要进行动态内存管理二、malloc和free2.1. malloc2.2. free2.3. 举例 三、calloc和realloc3.1. calloc3.2. realloc 四、常见的动态内存错误4.1. 对NULL指针的解引用操作4.2. 对动态开辟空间的越界访问4.3. 对非动态开辟内存使用free释放4.4. 使用free释放⼀…...

深入理解JavaScript中的闭包:原理、应用与常见问题

引言 闭包(Closure)是JavaScript中一个既强大又容易让人困惑的概念。理解闭包对于成为一名优秀的JavaScript开发者至关重要。本文将深入探讨闭包的工作原理、实际应用场景以及常见问题&#xff0c;帮助你彻底掌握这一重要概念。 什么是闭包&#xff1f; 闭包是指那些能够访问…...

IPLOOK | 2025 MVNOs 世界大会:从Wi-Fi通话到卫星覆盖

2025 MVNOs 世界大会于5月12日至14日在奥地利维也纳举行&#xff0c;汇聚了来自50多个国家的550余位行业领袖&#xff0c;共同探讨移动虚拟网络运营商&#xff08;MVNO&#xff09;领域的变革趋势。本届大会聚焦数字化转型、技术创新与战略合作&#xff0c;其中IPLOOK凭借其创新…...

为什么elasticsearch配置文件JVM配置31G最佳

Elasticsearch的JVM堆内存配置为32GB被视为最佳实践&#xff0c;主要基于以下综合技术原理和性能优化考量&#xff1a; 1. ‌JVM指针压缩机制优化内存效率‌ 当堆内存≤32GB时&#xff0c;JVM启用‌对象指针压缩&#xff08;Compressed Ordinary Object Pointers, COOP&#…...

单片机开发软件

目录 纯编码 vscode Ardunio Keil 1. 集成化开发环境&#xff08;IDE&#xff09; 2. 多架构芯片支持 3. 高效的代码生成与优化 4. 强大的调试与仿真功能 5. 丰富的库函数与生态系统 6. 教育与企业级适用性 典型应用场景 半编码半图形化 STM32CUBEIED 1. 图形化配置…...

Java随机生成邀请码 (包含字母大小写+数字)

前言: 目前我们生成的是6位包含数字和大小写字母的随机邀请码, 并且代码中已经有了处理冲突的机制确保了邀请码的唯一性如(①生成随机邀请码后会检查数据库中是否已存在②如果存在冲突,会尝试最多10次重新生成③如果多次尝试仍失败&#xff0c;会使用"U"用户ID派生的…...