通用游戏 AI:面向多款游戏的单一智能体训练

FreeGuideOnline 最新 2026-06-25

伪代码:多任务 A3C 的循环

for game in game_sampler: observation = game.reset() game_embedding = embedding(game.id) while not done: action = policy(observation, game_embedding) next_obs, reward = game.step(action) store_transition(...)


### 2. 渐进式神经网络(Progressive Neural Networks)
每学习一个新任务,就固定住以前任务的网络列,并横向添加新的隐藏层,利用旧列的特征来加速新任务学习。

- **优点**:完全避免遗忘,新任务可以直接利用已学到的低级特征。
- **缺点**:参数量随任务数量线性增长,且无法进行跨游戏的知识压缩。

### 3. 模型无关元学习(MAML)
训练一个初始参数,使它在面对新游戏时,仅用少量梯度更新就能快速适应。内层循环模拟适应新游戏的过程,外层循环最大化适应后的表现。

- **优点**:天然为少样本迁移设计,适合处理零样本泛化。
- **缺点**:需要大量不同游戏分布来训练元学习器,计算开销大,二阶导数可能导致训练不稳定。

### 4. 基于 Transformer 的序列决策模型
将游戏环境视为一个黑箱交互序列:过去帧序列 → 动作 → 奖励 → 新帧。使用 Transformer 或决策大模型直接对多游戏数据建模,类似于语言模型处理多种语言。

- **优点**:表达力强,容易扩展,可利用离线数据。
- **缺点**:计算资源需求高,推理速度可能不满足实时游戏。

### 5. 世界模型与内在激励
训练一个通用世界模型(world model)来预测不同游戏的下一帧和奖励,并在其隐空间中进行规划。同时利用好奇心驱动让智能体主动探索每一款游戏的环境结构。

- **优点**:一旦世界模型学到通用动力学,规划就变得非常通用。
- **缺点**:世界模型的训练本身需要巨大的多样数据,且长期预测容易累积误差。

## 动手实践:训练你的第一个通用游戏代理

我们将使用 **OpenAI Gym / Gymnasium** 和多个 **Atari 游戏** 作为环境,用稳定基线3 (Stable-Baselines3) 实现一个多任务 PPO 智能体的小型示例。

### 环境准备
确保安装以下依赖:
```bash
pip install gymnasium[atari] ale-py stable-baselines3 numpy matplotlib

步骤 1:构建多游戏包装器

Atari 游戏默认图像尺寸 210×160,需统一缩放到 84×84 并转换为灰度。另外,不同游戏的动作数不同,我们需要统一动作空间大小(通常取所有游戏中动作数的最大值,并对缺失动作填充 NOOP)。

import gymnasium as gym
import numpy as np

class MultiAtariEnv(gym.Wrapper):
    def __init__(self, game_list, max_actions=18):
        self.game_list = game_list
        self.max_actions = max_actions
        self.current_game = None
        self.env = None

    def reset(self, game_id=None):
        if game_id is None:
            game_id = np.random.choice(len(self.game_list))
        self.current_game = self.game_list[game_id]
        self.env = gym.make(self.current_game, render_mode='rgb_array')
        obs, info = self.env.reset()
        return self._preprocess(obs), info

    def step(self, action):
        if action >= self.env.action_space.n:
            action = 0  # NOOP for invalid indices
        obs, reward, term, trunc, info = self.env.step(action)
        return self._preprocess(obs), reward, term, trunc, info

    def _preprocess(self, obs):
        from PIL import Image
        img = Image.fromarray(obs).resize((84,84)).convert('L')
        return np.array(img) / 255.0

步骤 2:多任务策略网络

使用 SB3 的 MultiInputPolicy 或自定义网络,接收游戏 ID 作为额外输入。简单做法是将游戏 ID 转为 one-hot 后拼接到 CNN 展平后的特征上。

import torch
from stable_baselines3 import PPO
from stable_baselines3.common.torch_layers import BaseFeaturesExtractor

class GameConditionedCNN(BaseFeaturesExtractor):
    def __init__(self, observation_space, num_games=3, features_dim=256):
        super().__init__(observation_space, features_dim)
        self.cnn = torch.nn.Sequential(
            torch.nn.Conv2d(1, 32, 8, stride=4), torch.nn.ReLU(),
            torch.nn.Conv2d(32, 64, 4, stride=2), torch.nn.ReLU(),
            torch.nn.Conv2d(64, 64, 3, stride=1), torch.nn.ReLU(),
            torch.nn.Flatten()
        )
        # 计算CNN输出尺寸 (84x84 -> 7x7x64)
        cnn_output = 64 * 7 * 7
        self.game_embed = torch.nn.Linear(num_games, 64)
        self.linear = torch.nn.Sequential(
            torch.nn.Linear(cnn_output + 64, features_dim), torch.nn.ReLU()
        )

    def forward(self, observations):
        # observations 包含 'obs' 和 'game_id' 键
        obs = observations['obs'].float() / 255.0
        game_id = observations['game_id']
        cnn_feat = self.cnn(obs)
        game_feat = self.game_embed(game_id)
        combined = torch.cat([cnn_feat, game_feat], dim=1)
        return self.linear(combined)

步骤 3:训练循环

随机采样游戏,收集 rollout 并交替更新。

games = ['Pong-v4', 'Breakout-v4', 'SpaceInvaders-v4']
env = MultiAtariEnv(games, max_actions=18)

# 包装以提供字典观察
from gymnasium import spaces
env = gym.wrappers.TransformObservation(
    env, lambda obs: {'obs': obs, 'game_id': torch.eye(len(games))[env.current_game_idx]}
)
model = PPO(
    policy='MultiInputPolicy',
    env=env,
    policy_kwargs=dict(features_extractor_class=GameConditionedCNN),
    verbose=1
)
model.learn(total_timesteps=1_000_000)

步骤 4:评估泛化能力

训练完成后,在未见游戏中测试(例如《Asterix》),观察智能体是否表现出合理的探索行为。

test_game = 'Asterix-v4'
env_test = gym.make(test_game)
obs, _ = env_test.reset()
done = False
total_reward = 0
while not done:
    # 输入需要包含 game_id,可使用一个未出现过的 one-hot 或学习到的嵌入
    action, _ = model.predict({'obs': obs, 'game_id': torch.randn(3)})
    obs, reward, done, trunc, _ = env_test.step(action)
    total_reward += reward
print(f"Test reward on {test_game}: {total_reward}")