autowzry-agent

Label和Buffer架构重构

日期: 2025-11-22 类型: 架构重构 状态: 已完成


📋 开发内容

1. 模块重命名与重构

问题

解决方案

2. 统一接口设计

新增接口

两个模块都提供相同的接口模式:

####GameState 模块

def get_frame_state(self, frame1, frame2) -> dict:
    # 返回: {'in_battle': True, 'dead': False, 'alive': True, 'kill': False, 'assist': False}

def sum_reward(self, state_dict: dict) -> float:
    # 累加所有 True 状态的得分

ActionSpace 模块

def get_frame_action(self, frame1, frame2) -> Dict[str, List[float]]:
    # 返回: {'move': [0,0,0,1], 'attack': [0]*10}

def sum_reward(self, action_dict: Dict[str, List[float]]) -> float:
    # 计算点积:action_values * weights

3. 配置驱动的启用控制

新增配置项

实现细节

4. Label 流程重构

旧实现

def label(filepath, compute_actions=True, compute_states=True, overwrite=False):
    for i in range(num_frames):
        img = grp['image'][()]
        # 单独处理 kill/assist 累计计数
        prev_kill_count = ...
        curr_kill_count = compat.detect_kill_count(frame=img)

新实现

def label(filepath, overwrite=False):
    for i in range(num_frames):
        img = grp['image'][()]
        next_img = f[frame_list[i + 1]]['image'][()] if i < num_frames - 1 else img

        # 统一传两帧
        state_dict = self.game_state.get_frame_state(img, next_img)
        action_dict = self.action.get_frame_action(img, next_img)

        # 保存到 HDF5
        for key, value in state_dict.items():
            grp.create_dataset(f'state_{key}', data=value)
        for key, values in action_dict.items():
            grp.create_dataset(f'action_{key}', data=values)

5. Buffer 流程重构

旧实现

def load(filepath, max_frames=None):
    # 调用 compat 进行状态检测
    if not self.compat.is_in_battle(state):
        continue
    if self.compat.is_dead(state):
        continue

    # 读取预计算的 reward
    reward = frames[i].get('reward', 0.0)
    reward_extra = frames[i].get('reward_extra', 0.0)

新实现

def load(filepath, max_frames=None):
    # 从 HDF5 读取状态进行筛选
    in_battle = frame.get('state_in_battle', False)
    is_dead = frame.get('state_dead', False)
    if not in_battle or is_dead:
        skipped += 1
        continue

    # 读取状态并计算奖励
    reward = 0.0
    if self.game_state:
        state_dict = {...}  # 从 HDF5 读取 state_*
        reward += self.game_state.sum_reward(state_dict)
    if self.action:
        action_dict = {...}  # 从 HDF5 读取 action_*
        reward += self.action.sum_reward(action_dict)

6. 脚本参数简化

旧命令

python scripts/label_data.py --file xxx.hdf5 --actions --states

新命令

python scripts/label_data.py --file xxx.hdf5 --config config.yaml
# enabled_states 和 enabled_actions 从配置文件读取

📊 技术细节

HDF5 数据格式变化

旧格式

frame_000000/
  ├── image (array)
  ├── timestamp (float)
  ├── reward (float)          # 预计算的奖励
  ├── reward_extra (float)
  ├── move (array)            # 直接的动作字段
  └── attack (array)

新格式

frame_000000/
  ├── image (array)
  ├── timestamp (float)
  ├── state_in_battle (bool)  # 状态字段
  ├── state_dead (bool)
  ├── state_alive (bool)
  ├── state_kill (bool)
  ├── state_assist (bool)
  ├── action_move (array)     # 动作字段
  └── action_attack (array)

配置文件结构

# config/agent.config.yaml
enabled_states:
  - in_battle
  - dead
  - alive
  - kill
  - assist

enabled_actions:
  - move
  - attack

奖励计算逻辑

# 状态奖励
state_scores = {
    'in_battle': 0,    # 不参与得分,只用于筛选
    'dead': -2,
    'alive': 0.01,
    'kill': 1,
    'assist': 1
}

# 动作奖励
action_weights = {
    'move': [0.0, 0.0, 0.0, 0.1],  # 上, 下, 左, 右
    'attack': [0.0] * 10
}

# 总奖励 = sum(state_scores[state]) + sum(action_values * action_weights)

🎯 影响范围

修改的文件

删除的文件

向后兼容性


🔧 关键设计决策

1. 为什么 get_frame_state 接受两帧?

kill/assist 需要比较两帧的计数变化,与 get_frame_action 保持一致的接口。所有比较逻辑封装在模块内部,label 不需要关心实现细节。

2. 为什么 in_battle 权重为 0?

in_battle 只用于筛选数据,不参与得分计算。但仍然作为 enabled_states 的一部分保存,保持接口统一性。筛选逻辑:in_battle=True 且 dead=False。

3. 为什么不在 label 阶段计算 reward?

状态(True/False)是客观事实,reward 是主观策略。分离后可以方便调整奖励权重而不需要重新 label。buffer 加载时动态计算,支持不同的训练策略实验。

4. state_dict 中 dead 和 alive 的关系?

这样设计避免了非战斗帧被错误标记为 alive 而获得奖励。


✅ 测试建议

测试 label 流程

# 收集原始数据
python scripts/collect_from_video.py --video test.mp4 --output test.hdf5

# 运行 label
python scripts/label_data.py --file test.hdf5

# 检查 HDF5 字段
python -c "import h5py; f = h5py.File('test.hdf5', 'r'); print(list(f['frame_000000'].keys()))"
# 应该看到:state_in_battle, state_dead, state_alive, state_kill, state_assist, action_move, action_attack

测试 buffer 流程

# 加载数据到 buffer
python scripts/check_buffer.py --files test.hdf5

# 验证 reward 计算正确

测试端到端

# 完整流程
python scripts/test_pipeline.py --video test.mp4

📝 文档更新


🎯 成果

  1. 职责清晰: label 存储状态,buffer 计算奖励
  2. 接口统一: 两个模块都有 get_frame_xxx 和 sum_reward 方法
  3. 配置驱动: enabled_states 和 enabled_actions 从配置读取
  4. 代码简化: 移除重复的状态检测逻辑
  5. 灵活性提升: 调整奖励权重不需要重新标记数据