2. 集中式训练分布式执行基础框架

任务目标

搭建一个最小可运行的CTDE(Centralized Training with Decentralized Execution)框架,包含集中式Critic与分布式Actor,用于多智能体协作任务。训练时Critic利用全局信息(所有智能体的观测与动作)评估联合动作价值;执行时每个Actor仅依赖自身局部观测独立决策。

环境与依赖

  • Python 3.8+
  • PyTorch 1.12+
  • NumPy

实现思路

采用两个智能体协作的简单环境:每个智能体观测自身位置(一维坐标),目标为同时到达同一目标点。奖励为两智能体与目标距离之和的负值,促使协作。Actor网络输入自身观测,输出动作(连续值,表示移动量);Critic网络输入拼接后的全局状态(两个观测)与两个动作,输出联合Q值。训练时使用集中式Critic计算TD误差更新两个Actor;执行时仅使用各自的Actor网络。

关键完整代码

import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np

# ---------- 环境定义 ----------
class CooperativeEnv:
    """两个智能体协作到达目标点。观测: 自身位置(1维)。动作: 移动量(连续)。"""
    def __init__(self):
        self.target = 5.0
        self.reset()
    def reset(self):
        self.pos = np.array([0.0, 0.0])
        return self._get_obs()
    def step(self, actions):
        self.pos += np.array(actions, dtype=np.float32)
        dist = np.abs(self.pos - self.target)
        reward = -float(np.sum(dist))  # 协作奖励: 距离越小越好
        done = bool(np.all(dist < 0.1))
        return self._get_obs(), reward, done
    def _get_obs(self):
        return self.pos.astype(np.float32).reshape(2, 1)

# ---------- 网络定义 ----------
class Actor(nn.Module):
    """分布式Actor: 输入自身观测, 输出动作。"""
    def __init__(self, obs_dim=1, act_dim=1):
        super().__init__()
        self.net = nn.Sequential(nn.Linear(obs_dim, 32), nn.ReLU(),
                                 nn.Linear(32, act_dim), nn.Tanh())
    def forward(self, obs):
        return self.net(obs) * 2.0  # 动作范围[-2, 2]

class Critic(nn.Module):
    """集中式Critic: 输入全局状态(2个观测)与2个动作, 输出联合Q值。"""
    def __init__(self, obs_dim=1, act_dim=1, n_agents=2):
        super().__init__()
        input_dim = obs_dim * n_agents + act_dim * n_agents
        self.net = nn.Sequential(nn.Linear(input_dim, 64), nn.ReLU(),
                                 nn.Linear(64, 64), nn.ReLU(),
                                 nn.Linear(64, 1))
    def forward(self, obs_all, act_all):
        x = torch.cat([obs_all.flatten(), act_all.flatten()])
        return self.net(x)

# ---------- 训练参数 ----------
n_agents = 2
actor_lr, critic_lr = 1e-3, 1e-3
gamma = 0.99
episodes = 300

actors = [Actor() for _ in range(n_agents)]
critic = Critic()
actor_optims = [optim.Adam(a.parameters(), lr=actor_lr) for a in actors]
critic_optim = optim.Adam(critic.parameters(), lr=critic_lr)
loss_fn = nn.MSELoss()

env = CooperativeEnv()

# ---------- 训练循环 ----------
for ep in range(episodes):
    obs = env.reset()
    obs_t = torch.tensor(obs, dtype=torch.float32)
    total_reward = 0.0
    done = False
    while not done:
        # 分布式执行: 每个Actor仅用自身观测
        acts = []
        for i in range(n_agents):
            with torch.no_grad():
                a = actors[i](obs_t[i].unsqueeze(0))
            acts.append(a)
        act_t = torch.cat(acts)  # shape [2,1]

        # 环境交互
        obs_next, reward, done = env.step(act_t.detach().numpy().flatten())
        obs_next_t = torch.tensor(obs_next, dtype=torch.float32)
        total_reward += reward

        # 集中式训练: Critic使用全局信息
        q = critic(obs_t, act_t)
        with torch.no_grad():
            if done:
                target = torch.tensor([reward], dtype=torch.float32)
            else:
                # 计算下一时刻动作(目标策略)
                next_acts = []
                for i in range(n_agents):
                    na = actors[i](obs_next_t[i].unsqueeze(0))
                    next_acts.append(na)
                next_act_t = torch.cat(next_acts)
                q_next = critic(obs_next_t, next_act_t)
                target = torch.tensor([reward], dtype=torch.float32) + gamma * q_next

        # 更新Critic
        critic_loss = loss_fn(q, target)
        critic_optim.zero_grad()
        critic_loss.backward()
        critic_optim.step()

        # 更新Actor (最大化Q值)
        for i in range(n_agents):
            actor_loss = -critic(obs_t, act_t)  # 梯度上升
            actor_optims[i].zero_grad()
            actor_loss.backward()
            actor_optims[i].step()

        obs_t = obs_next_t

    if (ep+1) % 50 == 0:
        print(f"Episode {ep+1}, Total Reward: {total_reward:.2f}")

# ---------- 验证: 分布式执行 ----------
print("\n验证分布式执行(仅用局部观测):")
obs = env.reset()
done = False
steps = 0
while not done and steps < 50:
    acts = []
    for i in range(n_agents):
        with torch.no_grad():
            a = actors[i](torch.tensor(obs[i], dtype=torch.float32).unsqueeze(0))
        acts.append(a.item())
    obs, _, done = env.step(acts)
    steps += 1
print(f"最终位置: {obs.flatten()}, 目标位置: {env.target}, 到达: {done}")

运行方式

python ctde_basic.py

验证方法与判定标准

  • 训练收敛指标:训练过程中每50个episode打印的Total Reward应呈上升趋势,最终稳定在接近0的负值(如-0.5~0),表示两个智能体已学会协作到达目标点。
  • 执行验证:训练结束后,代码自动运行一次分布式执行测试,若输出“到达: True”且最终位置接近[5.0, 5.0],则证明训练成功且Actor可独立决策。
  • 判定通过条件:满足上述两点即可判定框架搭建与训练成功。若训练不收敛,可调整学习率或增加episode数至500。

常见问题

  • 训练不收敛:检查Critic的target计算是否使用了detach()(已处理),或增大gamma至0.99以上。
  • 动作过大导致震荡:Tanh输出乘以2限制了动作范围,若仍震荡可减小至1.0。
  • 扩展至更多智能体:只需修改n_agents并确保环境返回对应维度的观测即可,网络结构无需改动。