HBBC部署到代码中
This commit is contained in:
439
docs/HBBC_Deploy_guied.md
Normal file
439
docs/HBBC_Deploy_guied.md
Normal file
@@ -0,0 +1,439 @@
|
||||
# HBBC 策略部署指南
|
||||
|
||||
本文档说明如何将 `weights/hbbc.pt` 部署到 MetaDrive 项目中的**背景车辆**上,作为车辆控制策略使用。
|
||||
|
||||
---
|
||||
|
||||
## 0. 本仓库适配说明(MAGAIL4AutoDrive)
|
||||
|
||||
本仓库已落地一套可直接使用的 HBBC 背景车接入实现,核心代码:
|
||||
|
||||
- `Env/hbbc_actor_critic.py`:HBBC 所需 `ActorCritic` 最小推理网络
|
||||
- `Env/hbbc_background_policy.py`:模型加载、18 维观测构建、latent 管理(含 JSON 覆盖)
|
||||
- `Env/bc_env.py`:`BCScenarioEnv` 动态背景车 HBBC 接入(静态背景车保持不变)
|
||||
- `Env/bc_ego_replay_env.py`:`BCEgoReplayEnv` 动态背景车 HBBC 接入(ego-only 评估兼容)
|
||||
|
||||
与原文档示例不同点:
|
||||
|
||||
1. 当前仓库 `BaseVehicle` 没有 `pos_buffer/rot_buffer/action_buffer`,因此 8 维 `base_state` 使用当前可得车辆状态重建;
|
||||
2. 仅动态背景车使用 HBBC,静态背景车仍作为占位/邻居车辆;
|
||||
3. 支持通过 JSON 手动指定场景中某些车辆的 latent(`object_id` / `agent_id` 双 key)。
|
||||
|
||||
---
|
||||
|
||||
## 1. 概述
|
||||
|
||||
### 1.1 HBBC 是什么
|
||||
|
||||
**HBBC**(Hierarchical Behavior-Based Controller)是一个低层驾驶策略网络,输入车辆状态和行为条件,输出连续控制动作 `[steering, acceleration]`,可直接用于 MetaDrive 的车辆控制。
|
||||
|
||||
### 1.2 依赖
|
||||
|
||||
- **PyTorch**
|
||||
- **NumPy**
|
||||
- **MetaDrive**(需包含 `BaseVehicle`、`BasePolicy` 等基础组件)
|
||||
|
||||
---
|
||||
|
||||
## 2. 模型加载
|
||||
|
||||
### 2.1 模型架构
|
||||
|
||||
HBBC 对应 `ActorCritic` 网络,需按以下参数实例化:
|
||||
|
||||
```python
|
||||
import torch
|
||||
from algorithms.modules import ActorCritic # 或复制 actor_critic.py 到目标项目
|
||||
|
||||
hbbc = ActorCritic(
|
||||
num_actor_obs=18,
|
||||
num_critic_obs=18,
|
||||
num_actions=2,
|
||||
latent_c_dim=4, # 行为模式数
|
||||
latent_eps_dim=6, # 风格向量维度
|
||||
use_style_latent=True,
|
||||
).to(device)
|
||||
|
||||
# 加载权重
|
||||
checkpoint = torch.load("path/to/hbbc.pt", map_location=device, weights_only=False)
|
||||
hbbc.load_state_dict(checkpoint['actor_critic'])
|
||||
hbbc.eval()
|
||||
```
|
||||
|
||||
### 2.2 推理接口
|
||||
|
||||
```python
|
||||
with torch.no_grad():
|
||||
actions = hbbc.act_inference(obs_tensor) # obs_tensor: (batch, 18), 输出: (batch, 2)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 输入规格(18 维)
|
||||
|
||||
HBBC 的输入为 `hbbc_obs`,维度 18,由三部分拼接:
|
||||
|
||||
```
|
||||
hbbc_obs = [base_state(8) | latent_eps(6) | latent_c(4)]
|
||||
```
|
||||
|
||||
### 3.1 base_state(8 维)
|
||||
|
||||
从车辆对象构建,需按**精确顺序**拼接。实现如下(需配合 `relative_pos_local`、`rot_matrix_inv`、`clip` 等工具函数):
|
||||
|
||||
```python
|
||||
import numpy as np
|
||||
|
||||
def build_hbbc_base_state(vehicle):
|
||||
"""
|
||||
从 MetaDrive 车辆对象构建 HBBC 的 8 维 base_state。
|
||||
要求 vehicle 具有: position, pos_buffer, rot_buffer, heading_buffer,
|
||||
speed_km_h, max_speed_km_h, eps_step, acceleration, yaw_rate, action_buffer
|
||||
"""
|
||||
from metadrive.utils.math import clip # 或 np.clip
|
||||
|
||||
veh_pos = list(vehicle.position) + [0]
|
||||
init_veh_rot = np.array([vehicle.rot_buffer[0][0], vehicle.rot_buffer[0][1], vehicle.rot_buffer[0][2]])
|
||||
init_veh_pos = list(vehicle.pos_buffer[0]) + [0]
|
||||
init_veh_heading = vehicle.heading_buffer[0]
|
||||
|
||||
# 局部位置(本实现中置 0)
|
||||
veh_pos_local = relative_pos_local(init_veh_pos, veh_pos, init_veh_rot)[:2]
|
||||
veh_pos_local[0] /= 10
|
||||
veh_pos_local[1] /= 2
|
||||
|
||||
# 局部航向(本实现中置 0)
|
||||
veh_heading = vehicle.heading
|
||||
cross = np.cross(init_veh_heading, veh_heading)
|
||||
dot = np.dot(init_veh_heading, veh_heading)
|
||||
veh_heading_local = np.arctan2(cross, dot)
|
||||
|
||||
veh_vel = clip((vehicle.speed_km_h + 1) / (vehicle.max_speed_km_h + 1), 0.0, 1.0)
|
||||
veh_acc = vehicle.acceleration / 5 if vehicle.eps_step > 1 else 0
|
||||
yaw_rate = vehicle.yaw_rate
|
||||
last_action_0 = vehicle.action_buffer[-1][0]
|
||||
last_action_1 = vehicle.action_buffer[-1][1]
|
||||
|
||||
# 8 维,顺序固定
|
||||
obs = np.concatenate((
|
||||
veh_pos_local * 0, # 2 维,置 0
|
||||
[veh_heading_local * 0], # 1 维,置 0
|
||||
[veh_vel], # 1 维
|
||||
[veh_acc * 0], # 1 维,置 0
|
||||
[yaw_rate * 0.5], # 1 维
|
||||
[last_action_0], [last_action_1] # 2 维
|
||||
)).astype(np.float32)
|
||||
return obs
|
||||
```
|
||||
|
||||
### 3.2 latent_eps(6 维)
|
||||
|
||||
风格向量,需 **L2 归一化** 且在 `[-1, 1]` 内:
|
||||
|
||||
```python
|
||||
# 随机采样(每个 episode 或每辆车可固定/随机)
|
||||
latent_eps = np.random.randn(6).astype(np.float32)
|
||||
latent_eps = latent_eps / (np.linalg.norm(latent_eps) + 1e-8)
|
||||
latent_eps = np.clip(latent_eps, -1.0, 1.0)
|
||||
```
|
||||
|
||||
### 3.3 latent_c(4 维)
|
||||
|
||||
行为模式 one-hot,4 选 1:
|
||||
|
||||
```python
|
||||
# 随机选一个模式 (0~3)
|
||||
mode = np.random.randint(0, 4)
|
||||
latent_c = np.zeros(4, dtype=np.float32)
|
||||
latent_c[mode] = 1.0
|
||||
```
|
||||
|
||||
### 3.4 完整观测拼接
|
||||
|
||||
```python
|
||||
def build_hbbc_obs(vehicle, latent_eps, latent_c):
|
||||
base = build_hbbc_base_state(vehicle)
|
||||
return np.concatenate([base, latent_eps, latent_c], axis=-1) # shape: (18,)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 必需工具函数
|
||||
|
||||
若目标项目无以下函数,需自行实现或从 styledrive 的 `envs/utils.py` 拷贝:
|
||||
|
||||
```python
|
||||
def rot_matrix(t):
|
||||
"""t: [roll, pitch, yaw], 返回 3x3 旋转矩阵"""
|
||||
roll, pitch, yaw = t[0], t[1], t[2]
|
||||
sr, cr = np.sin(roll), np.cos(roll)
|
||||
sp, cp = np.sin(pitch), np.cos(pitch)
|
||||
sy, cy = np.sin(yaw), np.cos(yaw)
|
||||
r_roll = np.array([[1, 0, 0], [0, cr, -sr], [0, sr, cr]])
|
||||
r_pitch = np.array([[cp, 0, sp], [0, 1, 0], [-sp, 0, cp]])
|
||||
r_yaw = np.array([[cy, -sy, 0], [sy, cy, 0], [0, 0, 1]])
|
||||
return np.dot(np.dot(r_yaw, r_pitch), r_roll)
|
||||
|
||||
def rot_matrix_inv(t):
|
||||
return rot_matrix(t).T
|
||||
|
||||
def relative_pos_local(coord, coord_t, veh_rot):
|
||||
"""将 coord_t 从世界坐标变换到以 coord 为原点、veh_rot 为姿态的局部坐标"""
|
||||
r_pos_global = np.array(coord_t) - np.array(coord)
|
||||
rot_mat_inv = rot_matrix_inv(veh_rot)
|
||||
return rot_mat_inv @ r_pos_global
|
||||
```
|
||||
|
||||
`clip` 可用 `np.clip` 或 `metadrive.utils.math.clip`。
|
||||
|
||||
---
|
||||
|
||||
## 5. 车辆属性要求
|
||||
|
||||
使用 HBBC 的车辆需继承或兼容 MetaDrive 的 `BaseVehicle`,并具备:
|
||||
|
||||
| 属性 | 说明 |
|
||||
|------|------|
|
||||
| `position` | 当前位置 (x, y) 或 (x, y, z) |
|
||||
| `heading` | 航向单位向量 |
|
||||
| `heading_theta` | 航向角(弧度) |
|
||||
| `pos_buffer` | `deque`,至少 1 个元素,`pos_buffer[0]` 为 episode 起始位姿 |
|
||||
| `rot_buffer` | `deque`,`(roll, pitch, yaw)`,`rot_buffer[0]` 为起始姿态 |
|
||||
| `heading_buffer` | `deque`,`heading_buffer[0]` 为起始航向 |
|
||||
| `action_buffer` | `deque`,`action_buffer[-1]` 为上一时刻动作 `(steering, acc)` |
|
||||
| `speed_km_h` | 当前速度 km/h |
|
||||
| `max_speed_km_h` | 最大速度 km/h |
|
||||
| `acceleration` | 当前加速度 |
|
||||
| `yaw_rate` | 偏航角速度 (rad/s) |
|
||||
| `eps_step` | 本 episode 的步数 |
|
||||
| `last_heading_theta` | 上一帧航向角(用于 yaw_rate) |
|
||||
|
||||
`BaseVehicle` 在 `before_step` 中会更新 `pos_buffer`、`rot_buffer`、`heading_buffer`、`action_buffer`,只要在配置中设置 `veh_obs_len >= 1`(建议 3–10)即可。
|
||||
|
||||
---
|
||||
|
||||
## 6. 输出动作格式
|
||||
|
||||
HBBC 输出 2 维连续动作,与 MetaDrive 动作空间一致:
|
||||
|
||||
```python
|
||||
# actions: (2,) 或 (batch, 2)
|
||||
# actions[0]: steering ∈ [-1, 1]
|
||||
# actions[1]: acceleration ∈ [-1, 1],正=油门,负=刹车
|
||||
```
|
||||
|
||||
环境会在 `_preprocess_actions` 中做限幅与平滑,无需在策略内再次裁剪。
|
||||
|
||||
---
|
||||
|
||||
## 7. 部署为 MetaDrive 策略(背景车)
|
||||
|
||||
### 7.1 自定义 Policy
|
||||
|
||||
实现一个继承 `BasePolicy` 的策略,在 `act` 中调用 HBBC:
|
||||
|
||||
```python
|
||||
from metadrive.policy.base_policy import BasePolicy
|
||||
import torch
|
||||
import numpy as np
|
||||
|
||||
class HBBCPolicy(BasePolicy):
|
||||
def __init__(self, control_object, random_seed=None, hbbc_path="weights/hbbc.pt", device="cpu"):
|
||||
super().__init__(control_object, random_seed)
|
||||
self.device = torch.device(device)
|
||||
self.hbbc = self._load_hbbc(hbbc_path)
|
||||
self.latent_eps = None
|
||||
self.latent_c = None
|
||||
self._resample_latent()
|
||||
|
||||
def _load_hbbc(self, path):
|
||||
from algorithms.modules import ActorCritic # 根据实际路径调整
|
||||
model = ActorCritic(
|
||||
num_actor_obs=18, num_critic_obs=18, num_actions=2,
|
||||
latent_c_dim=4, latent_eps_dim=6, use_style_latent=True
|
||||
).to(self.device)
|
||||
ckpt = torch.load(path, map_location=self.device, weights_only=False)
|
||||
model.load_state_dict(ckpt['actor_critic'])
|
||||
model.eval()
|
||||
return model
|
||||
|
||||
def _resample_latent(self):
|
||||
self.latent_eps = np.random.randn(6).astype(np.float32)
|
||||
self.latent_eps = self.latent_eps / (np.linalg.norm(self.latent_eps) + 1e-8)
|
||||
self.latent_eps = np.clip(self.latent_eps, -1.0, 1.0)
|
||||
mode = np.random.randint(0, 4)
|
||||
self.latent_c = np.zeros(4, dtype=np.float32)
|
||||
self.latent_c[mode] = 1.0
|
||||
|
||||
def act(self, agent_id=None):
|
||||
vehicle = self.control_object
|
||||
base_state = build_hbbc_base_state(vehicle)
|
||||
obs = np.concatenate([base_state, self.latent_eps, self.latent_c], axis=-1)
|
||||
obs_t = torch.tensor(obs, dtype=torch.float32, device=self.device).unsqueeze(0)
|
||||
with torch.no_grad():
|
||||
actions = self.hbbc.act_inference(obs_t).cpu().numpy().squeeze()
|
||||
self.action_info["action"] = actions.tolist()
|
||||
return [float(actions[0]), float(actions[1])]
|
||||
|
||||
def reset(self):
|
||||
super().reset()
|
||||
self._resample_latent()
|
||||
```
|
||||
|
||||
### 7.2 配置背景车使用 HBBC
|
||||
|
||||
在环境配置中为背景车辆指定 `HBBCPolicy`:
|
||||
|
||||
```python
|
||||
config = {
|
||||
# ...
|
||||
"agent_configs": {
|
||||
"agent0": {
|
||||
"policy": HBBCPolicy,
|
||||
"policy_kwargs": {"hbbc_path": "path/to/hbbc.pt", "device": "cuda:0"},
|
||||
}
|
||||
},
|
||||
# 若使用 traffic 的 policy 配置方式,则需在 traffic 管理逻辑中
|
||||
# 将部分或全部背景车的 policy 替换为 HBBCPolicy
|
||||
}
|
||||
```
|
||||
|
||||
若背景车由 TrafficManager 等模块统一管理,需在该模块的 policy 选择逻辑中加入对 `HBBCPolicy` 的分配。
|
||||
|
||||
### 7.3 与 TrafficManager 集成
|
||||
|
||||
若背景车由 `PGTrafficManager` 等生成,需在添加策略时改为使用 `HBBCPolicy`:
|
||||
|
||||
```python
|
||||
# 原代码通常为:
|
||||
# self.add_policy(random_v.id, IDMPolicy, random_v, self.generate_seed())
|
||||
|
||||
# 改为:
|
||||
from your_policy_module import HBBCPolicy
|
||||
self.add_policy(random_v.id, HBBCPolicy, random_v, self.generate_seed(),
|
||||
hbbc_path="path/to/hbbc.pt", device="cuda:0")
|
||||
```
|
||||
|
||||
`add_policy` 的额外参数会传给 Policy 的 `__init__`。若接口不支持传参,可修改 `HBBCPolicy` 从全局配置读取路径,或使用自定义 TrafficManager 子类。
|
||||
|
||||
**注意**:HBBC 在 styledrive 中基于 scenario 轨迹训练,不包含路由逻辑。背景车若需要沿车道/路线行驶,可能需:
|
||||
- 在项目中为 HBBC 车辆配置 `navigation`,或
|
||||
- 仅对部分背景车使用 HBBC(如混合 IDM + HBBC),或
|
||||
- 在目标项目中验证 HBBC 在开放道路上的表现后决定是否全量使用。
|
||||
|
||||
### 7.4 注意事项
|
||||
|
||||
1. **latent 生命周期**:可为每辆车在 spawn 时采样一次,或在每个 episode reset 时重采样。
|
||||
2. **首帧 action_buffer**:首步 `action_buffer[-1]` 通常为 `(0, 0)`,由 `BaseVehicle` 初始化保证。
|
||||
3. **同步更新 buffer**:车辆必须在每步调用 `before_step` 之类接口,更新 `pos_buffer`、`action_buffer` 等,否则观测会错位。
|
||||
4. **veh_obs_len**:车辆配置中设置 `veh_obs_len >= 3`(建议 10),确保 buffer 长度足够。
|
||||
|
||||
---
|
||||
|
||||
## 8. ActorCritic 网络定义(可移植)
|
||||
|
||||
若目标项目无法导入 styledrive 的 `algorithms`,可把以下简化版 `ActorCritic` 放到本项目中单独使用:
|
||||
|
||||
```python
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
def get_activation(name):
|
||||
return getattr(nn, name)()
|
||||
|
||||
class ActorCritic(nn.Module):
|
||||
def __init__(self, num_actor_obs=18, num_critic_obs=18, num_actions=2,
|
||||
latent_c_dim=4, latent_eps_dim=6, use_style_latent=True,
|
||||
actor_hidden_dims=[512, 256, 128], activation='elu'):
|
||||
super().__init__()
|
||||
act_fn = getattr(nn, activation.upper())()
|
||||
self.latent_c_dim = latent_c_dim
|
||||
self.latent_eps_dim = latent_eps_dim
|
||||
self.use_style_latent = use_style_latent
|
||||
|
||||
layers = []
|
||||
layers.append(nn.Linear(num_actor_obs, actor_hidden_dims[0]))
|
||||
layers.append(act_fn)
|
||||
for i in range(len(actor_hidden_dims) - 1):
|
||||
layers.append(nn.Linear(actor_hidden_dims[i], actor_hidden_dims[i + 1]))
|
||||
layers.append(act_fn)
|
||||
self.actor_trunk = nn.Sequential(*layers)
|
||||
self.actor_head = nn.Linear(actor_hidden_dims[-1], num_actions)
|
||||
|
||||
if use_style_latent:
|
||||
style_layers = [nn.Linear(latent_eps_dim, 512), act_fn,
|
||||
nn.Linear(512, 256), act_fn, nn.Linear(256, 128), act_fn]
|
||||
self.style_trunk = nn.Sequential(*style_layers)
|
||||
self.style_head = nn.Linear(128, latent_eps_dim)
|
||||
self.style_activation = torch.tanh
|
||||
|
||||
def act_inference(self, observations):
|
||||
if self.use_style_latent:
|
||||
obs = observations[..., :-(self.latent_c_dim + self.latent_eps_dim)]
|
||||
eps = observations[..., -self.latent_c_dim - self.latent_eps_dim:-self.latent_c_dim]
|
||||
c = observations[..., -self.latent_c_dim:]
|
||||
eps = self.style_activation(self.style_head(self.style_trunk(eps)))
|
||||
observations = torch.cat([obs, eps, c], dim=-1)
|
||||
embedding = self.actor_trunk(observations)
|
||||
return self.actor_head(embedding)
|
||||
```
|
||||
|
||||
加载与调用方式与前面一致。
|
||||
|
||||
---
|
||||
|
||||
## 9. 简要检查清单
|
||||
|
||||
- [ ] 正确加载 `hbbc.pt` 的 `actor_critic` 权重
|
||||
- [ ] `build_hbbc_base_state` 输出 8 维,顺序与文档一致
|
||||
- [ ] `latent_eps` 6 维、L2 归一化
|
||||
- [ ] `latent_c` 4 维 one-hot
|
||||
- [ ] 车辆具备 `pos_buffer`、`rot_buffer`、`heading_buffer`、`action_buffer` 等属性
|
||||
- [ ] 策略返回 `[steering, acceleration]`,范围 [-1, 1]
|
||||
- [ ] 每步更新上述 buffer,保证观测连续
|
||||
|
||||
---
|
||||
|
||||
## 10. 本仓库配置项与 JSON 示例
|
||||
|
||||
可通过环境配置控制 HBBC 背景车行为:
|
||||
|
||||
- `enable_hbbc_background`:是否启用动态背景车 HBBC(`True/False`)
|
||||
- `hbbc_model_path`:模型路径(默认 `models/hbbc/hbbc.pt`)
|
||||
- `hbbc_inference_device`:推理设备(如 `cpu` / `cuda:0`)
|
||||
- `hbbc_latent_mode`:`per_vehicle_fixed` 或 `per_episode_reset`
|
||||
- `hbbc_latent_json_path`:可选,手动 latent JSON 路径
|
||||
|
||||
`hbbc_latent_json_path` 内容格式(优先按 `object_id` 匹配,失败回退 `agent_id`):
|
||||
|
||||
```json
|
||||
{
|
||||
"global": {
|
||||
"latent_eps": [0.35, -0.12, 0.28, 0.46, -0.22, 0.18],
|
||||
"latent_c": [0, 0, 1, 0]
|
||||
},
|
||||
"object_id": {
|
||||
"12345": {
|
||||
"latent_eps": [0.2, -0.1, 0.3, 0.4, -0.2, 0.1],
|
||||
"latent_c": [0, 1, 0, 0]
|
||||
}
|
||||
},
|
||||
"agent_id": {
|
||||
"controlled_abcde": {
|
||||
"latent_eps": [0.5, 0.1, -0.1, 0.2, -0.3, 0.4],
|
||||
"latent_c": [1, 0, 0, 0]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
匹配优先级为:`object_id` > `agent_id` > `global` > 随机采样。
|
||||
`latent_eps` 会做 L2 归一化,`latent_c` 会强制 one-hot;非法输入会告警并回退随机采样。
|
||||
|
||||
---
|
||||
|
||||
## 11. 参考来源
|
||||
|
||||
- 策略与观测:`envs/ad_hbbc_gym.py` 中的 `ADObservation.vehicle_state`
|
||||
- 模型:`algorithms/modules/actor_critic.py` 中 `ActorCritic`
|
||||
- 工具:`envs/utils.py` 中的 `relative_pos_local`、`rot_matrix`、`rot_matrix_inv`
|
||||
498
docs/TRAINING_ARCHITECTURE.md
Normal file
498
docs/TRAINING_ARCHITECTURE.md
Normal file
@@ -0,0 +1,498 @@
|
||||
# MAGAIL 训练方案架构文档
|
||||
|
||||
## 目录
|
||||
1. [训练数据结构](#1-训练数据结构)
|
||||
2. [多智能体训练机制](#2-多智能体训练机制)
|
||||
3. [完整训练流程](#3-完整训练流程)
|
||||
4. [当前项目问题](#4-当前项目问题)
|
||||
5. [TensorBoard 日志问题](#5-tensorboard-日志问题)
|
||||
|
||||
---
|
||||
|
||||
## 1. 训练数据结构
|
||||
|
||||
### 1.1 数据维度
|
||||
|
||||
**观测空间 (Observation Space)**
|
||||
- **维度**: 45维
|
||||
- **组成**:
|
||||
- **Ego状态** (5维): `[position_x, position_y, velocity_x, velocity_y, heading_theta]`
|
||||
- **邻居信息** (40维): 最多10个邻居,每个邻居4维特征
|
||||
- 每个邻居: `[relative_x, relative_y, velocity_x, velocity_y]`
|
||||
- 如果邻居数量 < 10,用零填充
|
||||
|
||||
**动作空间 (Action Space)**
|
||||
- **维度**: 2维
|
||||
- **组成**: `[steering, accel]`
|
||||
- **范围**: 归一化到 `[-1, 1]`
|
||||
|
||||
### 1.2 数据格式
|
||||
|
||||
**专家数据文件结构** (`.pkl` 文件):
|
||||
```python
|
||||
# 每个 .pkl 文件包含一个列表,每个元素是一条车辆轨迹
|
||||
trajectories = [
|
||||
{
|
||||
'obs': np.array, # Shape: (T, 45) - T为轨迹长度(可变)
|
||||
'acts': np.array, # Shape: (T, 2) - 对应的动作序列
|
||||
'agent_id': str, # 车辆ID
|
||||
'scenario_id': int # 场景ID
|
||||
},
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
**数据特点**:
|
||||
- 轨迹长度 `T` 是**可变的**,取决于车辆在场景中的存活时间
|
||||
- 最小轨迹长度过滤: 只保留长度 > 10 的轨迹
|
||||
- 数据已通过静态车辆过滤(移动距离 < 5m 且最大速度 < 1m/s 的车辆被过滤)
|
||||
|
||||
### 1.3 数据生成流程
|
||||
|
||||
**脚本**: `scripts/generate_expert_data.py`
|
||||
|
||||
**流程**:
|
||||
1. 从 Waymo 数据 (`data/exp_filtered`) 加载场景
|
||||
2. 使用 `ExpertReplayEnv` 回放专家轨迹
|
||||
3. 通过逆动力学 (`Env/inverse_dynamics.py`) 计算动作
|
||||
4. 构建45维观测(Ego + 10个最近邻居)
|
||||
5. 过滤无效轨迹(长度 < 10)
|
||||
6. 保存为 `.pkl` 文件到 `data/training_data/`
|
||||
|
||||
**关键代码位置**:
|
||||
- 观测构建: `Env/expert_replay_env.py` 的 `_get_all_obs()` 方法
|
||||
- 动作计算: `Env/inverse_dynamics.py` 的 `compute_action()` 方法
|
||||
|
||||
---
|
||||
|
||||
## 2. 多智能体训练机制
|
||||
|
||||
### 2.1 可变长度处理
|
||||
|
||||
**问题**: 不同场景中智能体数量不同,每个智能体的轨迹长度也不同。
|
||||
|
||||
**解决方案**:
|
||||
|
||||
1. **数据层面** (`dataset/magail_dataset.py`):
|
||||
- 将轨迹**展平**为独立的 `(state, action)` 对
|
||||
- 每个样本是独立的,不保留序列信息
|
||||
- 这样所有轨迹可以统一处理,不受长度限制
|
||||
|
||||
```python
|
||||
# MAGAILExpertDataset 的处理方式
|
||||
for traj in self.trajectories:
|
||||
obs = traj['obs'] # (T, 45)
|
||||
acts = traj['acts'] # (T, 2)
|
||||
# 展平为独立样本
|
||||
for i in range(len(obs)):
|
||||
self.flat_data.append((obs[i], acts[i])) # 每个样本: (45,), (2,)
|
||||
```
|
||||
|
||||
2. **训练环境层面** (`train_magail.py`):
|
||||
- 每个 episode 动态处理不同数量的智能体
|
||||
- 在 rollout 循环中,为每个活跃智能体独立收集数据
|
||||
- 所有智能体的数据合并到一个 `memory` 中
|
||||
|
||||
```python
|
||||
# Rollout 循环
|
||||
for agent_id, obs in obs_dict.items():
|
||||
act, logprob = ppo_agent.select_action(obs)
|
||||
actions[agent_id] = act
|
||||
# 所有智能体的数据都存入同一个 memory
|
||||
memory['states'].append(obs)
|
||||
memory['actions'].append(actions[agent_id])
|
||||
...
|
||||
```
|
||||
|
||||
3. **观测维度固定**:
|
||||
- 通过 `MAGAILScenarioEnv` 确保观测维度始终为45维
|
||||
- 邻居数量不足时用零填充,保证维度一致
|
||||
|
||||
### 2.2 多智能体交互
|
||||
|
||||
**环境设置**:
|
||||
- 使用 `MAGAILScenarioEnv` (继承自 `MultiAgentScenarioEnv`)
|
||||
- 自定义 `_get_all_obs()` 方法,确保观测格式与专家数据一致
|
||||
- 每个智能体独立选择动作,环境统一执行
|
||||
|
||||
**关键点**:
|
||||
- 所有智能体共享同一个策略网络(参数共享)
|
||||
- 每个智能体独立计算动作和奖励
|
||||
- 数据收集时将所有智能体的经验合并
|
||||
|
||||
---
|
||||
|
||||
## 3. 完整训练流程
|
||||
|
||||
### 3.1 数据准备阶段
|
||||
|
||||
**步骤 1: 生成专家数据**
|
||||
```bash
|
||||
python scripts/generate_expert_data.py \
|
||||
--data_dir data/exp_filtered \
|
||||
--output_dir data/training_data \
|
||||
--num_scenarios 100 \
|
||||
--start_index 0
|
||||
```
|
||||
|
||||
**输出**: `data/training_data/expert_data_*.pkl`
|
||||
|
||||
### 3.2 模型初始化
|
||||
|
||||
**网络架构**:
|
||||
|
||||
1. **Actor (策略网络)**:
|
||||
- 输入: 45维状态
|
||||
- 输出: 2维动作(连续)
|
||||
- 结构: MLP (45 → 256 → 256 → 2)
|
||||
- 输出分布: 高斯分布(均值 + 可学习标准差)
|
||||
|
||||
2. **Critic (价值网络)**:
|
||||
- 输入: 45维状态
|
||||
- 输出: 标量价值
|
||||
- 结构: MLP (45 → 256 → 256 → 1)
|
||||
|
||||
3. **Discriminator (鉴别器)**:
|
||||
- 输入: 45维状态 + 2维动作 = 47维
|
||||
- 输出: 标量(0-1之间,表示专家概率)
|
||||
- 结构: MLP (47 → 256 → 256 → 1) + Sigmoid
|
||||
|
||||
### 3.3 训练循环
|
||||
|
||||
**主循环** (`train_magail.py` 的 `train()` 函数):
|
||||
|
||||
```
|
||||
For each episode:
|
||||
1. 收集 Rollout
|
||||
- 重置环境(随机选择场景)
|
||||
- 运行策略收集轨迹
|
||||
- 存储 (state, action, logprob, next_state, done)
|
||||
|
||||
2. 训练 Discriminator
|
||||
- 采样专家批次
|
||||
- 采样策略批次
|
||||
- 更新鉴别器:
|
||||
- Expert loss: BCE(D(s_e, a_e), 1)
|
||||
- Policy loss: BCE(D(s_p, a_p), 0)
|
||||
- Total: L_d = L_expert + L_policy
|
||||
|
||||
3. 计算 GAIL 奖励
|
||||
- 对所有策略状态-动作对:
|
||||
reward = -log(1 - D(s, a) + ε)
|
||||
- 替换环境奖励
|
||||
|
||||
4. 更新策略 (PPO)
|
||||
- 计算 GAE (Generalized Advantage Estimation)
|
||||
- PPO 更新 (K epochs):
|
||||
- 计算优势函数
|
||||
- 计算策略损失(带clip)
|
||||
- 计算价值损失
|
||||
- 更新 Actor 和 Critic
|
||||
```
|
||||
|
||||
### 3.4 训练目标
|
||||
|
||||
**Discriminator 目标**:
|
||||
```
|
||||
L_D = E_{(s,a)~π_E}[-log(D(s,a))] + E_{(s,a)~π_θ}[-log(1-D(s,a))]
|
||||
```
|
||||
- 最大化区分专家数据和策略数据的能力
|
||||
|
||||
**Policy (Generator) 目标**:
|
||||
```
|
||||
L_π = E_{(s,a)~π_θ}[-log(D(s,a))] - λ_H(π_θ)
|
||||
```
|
||||
- 通过 PPO 优化,使用 GAIL 奖励作为信号
|
||||
- 最大化鉴别器给出的"专家概率"
|
||||
- 同时保持策略熵(探索)
|
||||
|
||||
**PPO 更新**:
|
||||
```python
|
||||
# 优势函数 (GAE)
|
||||
advantages = compute_gae(rewards, values, next_values, dones, gamma, lambda)
|
||||
|
||||
# 策略损失
|
||||
ratios = exp(log_probs - old_log_probs)
|
||||
surr1 = ratios * advantages
|
||||
surr2 = clip(ratios, 1-ε, 1+ε) * advantages
|
||||
policy_loss = -min(surr1, surr2) + 0.01 * entropy
|
||||
|
||||
# 价值损失
|
||||
value_loss = MSE(critic(states), returns)
|
||||
|
||||
# 总损失
|
||||
total_loss = policy_loss + 0.5 * value_loss
|
||||
```
|
||||
|
||||
### 3.5 关键代码位置
|
||||
|
||||
- **训练主循环**: `train_magail.py:278-505`
|
||||
- **PPO 更新**: `train_magail.py:90-146`
|
||||
- **Discriminator 更新**: `train_magail.py:429-462`
|
||||
- **GAIL 奖励计算**: `train_magail.py:472-477`
|
||||
|
||||
---
|
||||
|
||||
## 4. 当前项目问题
|
||||
|
||||
### 4.1 环境重置问题
|
||||
|
||||
**问题描述**:
|
||||
- MetaDrive 环境在快速重置时可能出现对象清理不完整的问题
|
||||
- 错误信息: "You should clear all generated objects..."
|
||||
|
||||
**当前处理**:
|
||||
- 代码中已有异常处理机制(`train_magail.py:288-342`)
|
||||
- 重置失败时会尝试关闭并重新创建环境
|
||||
- 但可能导致训练不稳定
|
||||
|
||||
**建议修复**:
|
||||
- 在每次重置前显式清理所有对象
|
||||
- 增加重置间隔,避免过于频繁的重置
|
||||
- 考虑使用环境池(Environment Pool)复用环境实例
|
||||
|
||||
### 4.2 观测维度对齐
|
||||
|
||||
**问题描述**:
|
||||
- 原始 `MultiAgentScenarioEnv` 返回108维观测(包含Lidar)
|
||||
- 专家数据使用45维观测
|
||||
- 维度不匹配会导致训练失败
|
||||
|
||||
**当前解决方案**:
|
||||
- 通过 `MAGAILScenarioEnv` 重写 `_get_all_obs()` 方法
|
||||
- 确保训练环境与专家数据使用相同的观测格式
|
||||
|
||||
**代码位置**: `train_magail.py:223-262`
|
||||
|
||||
### 4.3 数据收集效率
|
||||
|
||||
**问题描述**:
|
||||
- 每个 episode 都需要完整运行环境收集数据
|
||||
- 可变长度轨迹导致 batch 大小不一致
|
||||
- 可能影响训练稳定性
|
||||
|
||||
**当前处理**:
|
||||
- 使用展平的数据集,每个样本独立
|
||||
- 在 rollout 时收集所有智能体的数据,合并处理
|
||||
|
||||
**潜在改进**:
|
||||
- 考虑使用经验回放缓冲区
|
||||
- 实现轨迹级别的采样(保留序列信息)
|
||||
|
||||
### 4.4 内存管理
|
||||
|
||||
**问题描述**:
|
||||
- 长时间训练可能导致内存泄漏
|
||||
- 环境对象可能没有完全释放
|
||||
|
||||
**当前处理**:
|
||||
- 代码中有显式的 `gc.collect()` 和 `torch.cuda.empty_cache()`
|
||||
- 但可能不够彻底
|
||||
|
||||
**建议**:
|
||||
- 定期检查内存使用
|
||||
- 考虑限制 rollout 长度
|
||||
- 使用更激进的清理策略
|
||||
|
||||
### 4.5 训练稳定性
|
||||
|
||||
**问题描述**:
|
||||
- Discriminator 可能过早收敛,导致策略无法学习
|
||||
- GAIL 奖励可能不稳定
|
||||
|
||||
**当前处理**:
|
||||
- 使用标准的 GAIL 奖励公式: `-log(1 - D(s,a) + ε)`
|
||||
- PPO 的 clip 机制提供稳定性
|
||||
|
||||
**潜在改进**:
|
||||
- 考虑使用 WGAN-GP 或 LSGAN 损失
|
||||
- 实现 Discriminator 的预训练
|
||||
- 添加奖励归一化
|
||||
|
||||
---
|
||||
|
||||
## 5. TensorBoard 日志问题
|
||||
|
||||
### 5.1 问题分析
|
||||
|
||||
**现象**:
|
||||
- `runs/magail_0112/` 目录下只有模型文件(`.pth`),没有 TensorBoard 事件文件(`events.out.tfevents.*`)
|
||||
- 其他目录(`magail_full`, `magail_production`)有事件文件
|
||||
|
||||
**可能原因**:
|
||||
|
||||
1. **TensorBoard 未安装**:
|
||||
- 代码中有 try-except 处理(`train_magail.py:269-274`)
|
||||
- 如果 TensorBoard 未安装,`writer` 会被设置为 `None`
|
||||
- 训练会继续,但不会写入日志
|
||||
|
||||
2. **日志写入失败**:
|
||||
- 即使 `SummaryWriter` 创建成功,如果写入时出错,可能不会生成文件
|
||||
- 需要检查是否有异常被静默捕获
|
||||
|
||||
3. **训练中断**:
|
||||
- 如果训练在写入第一个日志前中断,可能没有事件文件
|
||||
- 但模型文件已保存,说明训练至少运行了一段时间
|
||||
|
||||
### 5.2 检查方法
|
||||
|
||||
**步骤 1: 检查 TensorBoard 安装**
|
||||
```bash
|
||||
python -c "import tensorboard; print(tensorboard.__version__)"
|
||||
```
|
||||
|
||||
**步骤 2: 检查训练脚本中的日志写入**
|
||||
查看 `train_magail.py:493-496`:
|
||||
```python
|
||||
if writer:
|
||||
writer.add_scalar('Loss/Discriminator', disc_loss.item(), i_episode)
|
||||
writer.add_scalar('Loss/Policy', ppo_loss, i_episode)
|
||||
writer.add_scalar('Reward/Mean_GAIL', np.mean(all_gail_rewards), i_episode)
|
||||
```
|
||||
|
||||
**步骤 3: 检查日志目录权限**
|
||||
```bash
|
||||
ls -la runs/magail_0112/
|
||||
```
|
||||
|
||||
### 5.3 解决方案
|
||||
|
||||
**方案 1: 确保 TensorBoard 已安装**
|
||||
```bash
|
||||
pip install tensorboard
|
||||
```
|
||||
|
||||
**方案 2: 添加显式刷新**
|
||||
在训练循环结束后,显式调用 `writer.flush()`:
|
||||
```python
|
||||
if writer:
|
||||
writer.flush() # 确保数据写入磁盘
|
||||
```
|
||||
|
||||
**方案 3: 添加日志验证**
|
||||
在训练开始时检查日志目录:
|
||||
```python
|
||||
if writer:
|
||||
# 测试写入
|
||||
writer.add_scalar('Test/Initialization', 0.0, 0)
|
||||
writer.flush()
|
||||
print(f"TensorBoard logging enabled. Log dir: {args.log_dir}")
|
||||
else:
|
||||
print("WARNING: TensorBoard not available. Logging disabled.")
|
||||
```
|
||||
|
||||
**方案 4: 使用文件日志作为备份**
|
||||
即使 TensorBoard 不可用,也可以写入文本日志:
|
||||
```python
|
||||
import logging
|
||||
logging.basicConfig(
|
||||
filename=os.path.join(args.log_dir, 'training.log'),
|
||||
level=logging.INFO
|
||||
)
|
||||
```
|
||||
|
||||
### 5.4 代码修复建议
|
||||
|
||||
**在 `train_magail.py` 中添加以下改进**:
|
||||
|
||||
1. **确保 disc_loss 在 CPU 上**:
|
||||
```python
|
||||
# 第425行附近
|
||||
disc_loss = torch.tensor(0.0).cuda() # 改为 .cuda() 或保持 CPU
|
||||
# 或者在使用时转换
|
||||
if writer:
|
||||
disc_loss_value = disc_loss.item() if isinstance(disc_loss, torch.Tensor) else disc_loss
|
||||
writer.add_scalar('Loss/Discriminator', disc_loss_value, i_episode)
|
||||
```
|
||||
|
||||
2. **添加显式刷新**:
|
||||
```python
|
||||
# 第496行后添加
|
||||
if writer:
|
||||
writer.flush() # 确保数据写入磁盘
|
||||
```
|
||||
|
||||
3. **添加初始化验证**:
|
||||
```python
|
||||
# 第271行后添加
|
||||
if writer:
|
||||
# 测试写入
|
||||
writer.add_scalar('Test/Initialization', 0.0, 0)
|
||||
writer.flush()
|
||||
print(f"✓ TensorBoard logging enabled. Log dir: {args.log_dir}")
|
||||
# 检查文件是否创建
|
||||
import glob
|
||||
event_files = glob.glob(os.path.join(args.log_dir, "events.out.tfevents.*"))
|
||||
if event_files:
|
||||
print(f"✓ TensorBoard event file created: {event_files[0]}")
|
||||
else:
|
||||
print("⚠ WARNING: TensorBoard not available. Logging disabled.")
|
||||
```
|
||||
|
||||
4. **在训练结束时确保关闭**:
|
||||
```python
|
||||
# 第505行后添加
|
||||
if writer:
|
||||
writer.flush() # 最后一次刷新
|
||||
writer.close()
|
||||
print(f"TensorBoard logs saved to {args.log_dir}")
|
||||
```
|
||||
|
||||
### 5.5 验证修复
|
||||
|
||||
**重新训练测试**:
|
||||
```bash
|
||||
python train_magail.py \
|
||||
--expert_data_dir data/training_data \
|
||||
--data_dir data/exp_filtered \
|
||||
--batch_size 1024 \
|
||||
--max_episodes 10 \
|
||||
--log_dir runs/test_tensorboard
|
||||
```
|
||||
|
||||
**检查输出**:
|
||||
```bash
|
||||
# 应该看到事件文件
|
||||
ls runs/test_tensorboard/events.out.tfevents.*
|
||||
|
||||
# 启动 TensorBoard
|
||||
tensorboard --logdir runs/test_tensorboard
|
||||
```
|
||||
|
||||
**对于 magail_0112 训练**:
|
||||
由于该训练已经完成且没有日志文件,建议:
|
||||
1. 检查训练时的控制台输出,确认是否有 "TensorBoard not installed" 消息
|
||||
2. 如果确实没有 TensorBoard,可以重新运行少量 episode 来验证修复
|
||||
3. 或者查看是否有其他日志文件(如 `training.log`)
|
||||
|
||||
---
|
||||
|
||||
## 附录: 关键文件清单
|
||||
|
||||
### 核心训练文件
|
||||
- `train_magail.py`: 主训练脚本
|
||||
- `dataset/magail_dataset.py`: 专家数据集加载
|
||||
- `Env/expert_replay_env.py`: 专家回放环境
|
||||
- `Env/scenario_env.py`: 多智能体场景环境
|
||||
- `Env/inverse_dynamics.py`: 逆动力学计算
|
||||
|
||||
### 数据生成文件
|
||||
- `scripts/generate_expert_data.py`: 专家数据生成
|
||||
- `scripts/visualize_replay.py`: 数据可视化
|
||||
- `scripts/analyze_expert_data.py`: 数据分析
|
||||
|
||||
### 配置文件
|
||||
- `README.md`: 项目说明
|
||||
- `TRAINING_ARCHITECTURE.md`: 本文档
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
本项目的 MAGAIL 训练方案通过以下方式处理多智能体可变长度问题:
|
||||
|
||||
1. **数据层面**: 将轨迹展平为独立样本,统一处理
|
||||
2. **环境层面**: 动态处理不同数量的智能体,合并经验
|
||||
3. **网络层面**: 固定输入维度(45维),通过零填充处理邻居不足的情况
|
||||
|
||||
训练流程遵循标准的 GAIL 框架,使用 PPO 作为策略优化算法。当前主要问题集中在环境稳定性和日志记录方面,需要进一步优化。
|
||||
18
docs/examples/hbbc_latent_example.json
Normal file
18
docs/examples/hbbc_latent_example.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"global": {
|
||||
"latent_eps": [0.35, -0.12, 0.28, 0.46, -0.22, 0.18],
|
||||
"latent_c": [0, 1,1, 0]
|
||||
},
|
||||
"object_id": {
|
||||
"12345": {
|
||||
"latent_eps": [0.2, -0.1, 0.3, 0.4, -0.2, 0.1],
|
||||
"latent_c": [0, 1, 0, 0]
|
||||
}
|
||||
},
|
||||
"agent_id": {
|
||||
"controlled_abcde": {
|
||||
"latent_eps": [0.5, 0.1, -0.1, 0.2, -0.3, 0.4],
|
||||
"latent_c": [1, 0, 0, 0]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user