Files
MAGAIL4AutoDrive/docs/HBBC_Deploy_guied.md
2026-03-02 10:58:20 +08:00

440 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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_state8 维)
从车辆对象构建,需按**精确顺序**拼接。实现如下(需配合 `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_eps6 维)
风格向量,需 **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_c4 维)
行为模式 one-hot4 选 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`(建议 310即可。
---
## 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`