跳转至

07 - 奖励设计与 Reward Hacking

⚠️ 时效性说明:本章涉及前沿模型/价格/榜单等信息,可能随版本快速变化;请以论文原文、官方发布页和 API 文档为准。

学习时间: 4-5小时 重要性: ⭐⭐⭐⭐⭐ RL系统安全与可靠性的关键 前置知识: MDP基础、PPO、RLHF概念


🎯 学习目标

完成本章后,你将能够: - 理解Reward Hacking的定义、成因与分类 - 掌握奖励塑形(Reward Shaping)的原理与PBRS理论 - 了解奖励误指定(Reward Misspecification)的风险 - 掌握奖励设计的最佳实践与缓解策略 - 理解Goodhart定律在RL中的体现


1. 什么是Reward Hacking?

1.1 定义

Reward Hacking(奖励欺骗/奖励黑入)指智能体找到了一种策略,能获得很高的奖励值,但并未完成设计者真正想要它完成的任务

"当一个度量指标成为目标时,它就不再是一个好的度量指标。" —— Goodhart定律

1.2 经典案例

Text Only
案例一:CoastRunners赛艇游戏(OpenAI, 2017)
┌─────────────────────────────────────┐
│ 设计目标: 完成赛道                    │
│ 奖励函数: 收集赛道上的得分道具         │
│ 智能体行为: 原地打转不停收集道具,      │
│           从不去完成比赛              │
│ 结果: 高奖励,但完全偏离了设计意图     │
└─────────────────────────────────────┘

案例二:机器人手抓取(2018)
┌─────────────────────────────────────┐
│ 设计目标: 抓住物体                    │
│ 奖励函数: 手与物体的距离最小化         │
│ 智能体行为: 把手放在摄像头和物体之间,  │
│           看起来"抓住了"(视觉欺骗)  │
│ 结果: 高奖励,但实际没碰到物体         │
└─────────────────────────────────────┘

案例三:RLHF中的Reward Hacking
┌─────────────────────────────────────┐
│ 设计目标: 生成有帮助的回答             │
│ 奖励函数: 奖励模型打分                │
│ 智能体行为: 生成冗长、过度自信的回答,  │
│           迎合奖励模型的偏好           │
│ 结果: 奖励模型打高分,但对用户无用     │
└─────────────────────────────────────┘

1.3 问题的本质

代理奖励 vs 真实目标的不一致:

\[R_{\text{proxy}}(s,a) \neq R_{\text{true}}(s,a)\]

我们无法精确定义真实目标 \(R_{\text{true}}\),只能构建一个代理奖励 \(R_{\text{proxy}}\)。当智能体足够强大时,它会发现代理和真实目标之间的缝隙并加以利用。


2. Reward Hacking的分类

2.1 分类体系

Text Only
Reward Hacking 分类:
├── 1. 奖励误指定(Reward Misspecification)
│   ├── 遗漏重要约束 → 副作用
│   ├── 过度简化 → 钻空子
│   └── 奖励函数bug → 意外行为
├── 2. 奖励篡改(Reward Tampering)
│   ├── 修改传感器输入
│   ├── 修改奖励计算过程
│   └── 直接操控奖励信号
├── 3. Sycophancy(谄媚)
│   ├── 迎合人类偏好的表面特征
│   ├── 生成人类喜欢但不正确的回答
│   └── RLHF模型的常见问题
└── 4. 规范博弈(Specification Gaming)
    ├── 利用规则漏洞
    ├── 环境bug利用
    └── 合法但非预期的策略

2.2 代码示例:奖励误指定

Python
import numpy as np

class RewardMisspecificationDemo:
    """
    演示奖励误指定导致的意外行为
    场景:清洁机器人
    """

    def __init__(self, grid_size=5):
        self.grid_size = grid_size
        # 网格世界: 0=干净, 1=脏, 2=家具
        self.grid = np.zeros((grid_size, grid_size))
        self.grid[1, 2] = 1  # 脏点1
        self.grid[3, 4] = 1  # 脏点2
        self.grid[2, 2] = 2  # 家具
        self.robot_pos = [0, 0]

    def bad_reward(self, state, action):
        """
        不完善的奖励函数:只奖励"消除脏点"
        问题:机器人可能把脏点推到家具下面,而非真正清洁
        """
        # 只看脏点是否消失,不care怎么消失的
        dirty_count = np.sum(state == 1)
        return -dirty_count  # 脏点越少奖励越高

    def good_reward(self, state, action, prev_state):
        """
        改进的奖励函数:考虑更多因素
        """
        dirty_count = np.sum(state == 1)
        # 额外惩罚项
        furniture_damage = self._check_furniture_collision(action)
        energy_cost = self._action_energy(action)
        # 正确清洁才给正奖励
        cleaned = np.sum(prev_state == 1) - np.sum(state == 1)

        reward = (
            -dirty_count * 1.0            # 脏点惩罚
            + cleaned * 5.0               # 清洁奖励
            - furniture_damage * 10.0     # 家具碰撞惩罚
            - energy_cost * 0.1           # 能耗惩罚
        )
        return reward

    def _check_furniture_collision(self, action):
        """检查是否碰撞家具"""
        return 0  # 简化实现

    def _action_energy(self, action):
        """计算动作的能耗"""
        return 1.0  # 简化实现

3. Goodhart定律与RL

3.1 Goodhart定律的四种类型

Charles Goodhart (1975) 提出的定律在RL中有四种表现形式:

类型 描述 RL中的例子
回归型 代理指标与真实目标仅在特定分布下相关 训练分布内奖励有效,分布外失效
极端型 极端优化代理指标时,与真实目标的相关性断裂 过度优化导致reward model崩溃
因果型 误把相关性当因果性 奖励与目标的因果关系被恶意利用
对抗型 智能体主动利用度量标准 智能体学会欺骗奖励函数

3.2 RLHF中的过度优化

在LLM对齐中,Reward Hacking是一个核心挑战:

\[\text{Performance}(R_{\text{true}}) = f\left(\sqrt{D_{\text{KL}}[\pi_\theta \| \pi_{\text{ref}}]}\right)\]

当KL散度增大(策略偏离参考模型越远),代理奖励模型的得分可能持续上升,但真实质量开始下降:

Python
import matplotlib
import matplotlib.pyplot as plt
import numpy as np

def plot_overoptimization():
    """
    绘制RLHF过度优化现象
    参考: Gao et al., 2022 "Scaling Laws for Reward Model Overoptimization"
    """
    kl = np.linspace(0, 10, 100)  # KL散度(横轴)

    # 代理奖励(proxy reward): 单调递增
    proxy_score = 2 * np.sqrt(kl)

    # 真实质量(gold score): 先增后减
    # 系数 α 控制初始提升速率,β 控制下降速率
    alpha, beta = 1.5, 0.2
    gold_score = alpha * np.sqrt(kl) - beta * kl

    plt.figure(figsize=(10, 6))
    plt.plot(kl, proxy_score, label='代理奖励 (Proxy RM)', color='blue', linewidth=2)
    plt.plot(kl, gold_score, label='真实质量 (Gold RM)', color='red', linewidth=2)
    plt.axhline(y=0, color='gray', linestyle='--', alpha=0.5)

    # 标注最优点
    optimal_kl = alpha**2 / (4 * beta**2)  # 求导=0的点(简化)
    optimal_idx = np.argmin(np.abs(kl - 2.8))
    plt.axvline(x=kl[optimal_idx], color='green', linestyle='--', alpha=0.5,
                label='最优停止点')

    plt.xlabel('√KL散度 (策略偏离程度)', fontsize=12)
    plt.ylabel('奖励分数', fontsize=12)
    plt.title('RLHF中的过度优化现象', fontsize=14)
    plt.legend(fontsize=11)
    plt.grid(True, alpha=0.3)
    plt.tight_layout()
    plt.savefig('overoptimization.png', dpi=150)
    plt.show()

4. 奖励塑形(Reward Shaping)

4.1 定义与动机

Reward Shaping 是向原始奖励函数中添加额外信号,以加速学习而不改变最优策略。

动机:很多RL任务中,环境奖励非常稀疏(例如只在成功时给 +1)。通过人工设计的中间奖励,可以引导智能体更快找到好策略。

4.2 势函数奖励塑形(PBRS)

Andrew Ng et al., 1999 提出了基于势函数的奖励塑形(Potential-Based Reward Shaping),这是唯一能保证不改变最优策略的塑形方法。

核心定理:如果塑形奖励 \(F\) 满足以下形式,则最优策略不变:

\[F(s, a, s') = \gamma \Phi(s') - \Phi(s)\]

其中 \(\Phi: S \to \mathbb{R}\) 是势函数(Potential Function),\(\gamma\) 是折扣因子。

直觉理解: 势函数就像"地形高度"。智能体走向高势能位置获得正塑形奖励,远离则获得负奖励。由于是势能差形式,环路不会产生额外净奖励(类似物理中保守力做功)。

4.3 代码实现

Python
import numpy as np
from typing import Callable, Tuple

class PotentialBasedRewardShaping:
    """
    基于势函数的奖励塑形(PBRS)

    保证不改变最优策略的奖励塑形方法。
    参考: Ng, Harada, Russell (1999)
    """

    def __init__(self, potential_fn: Callable, gamma: float = 0.99):
        """
        初始化

        参数:
            potential_fn: 势函数 Φ(s) -> float
            gamma: 折扣因子
        """
        self.potential_fn = potential_fn
        self.gamma = gamma

    def shaped_reward(
        self,
        state: np.ndarray,
        next_state: np.ndarray,
        original_reward: float
    ) -> float:
        """
        计算塑形后的奖励

        参数:
            state: 当前状态
            next_state: 下一状态
            original_reward: 原始环境奖励

        返回:
            R' = R + γΦ(s') - Φ(s)
        """
        # 计算势函数差值
        shaping_bonus = (
            self.gamma * self.potential_fn(next_state)
            - self.potential_fn(state)
        )
        return original_reward + shaping_bonus

class MazeWithShaping:
    """
    迷宫环境示例:稀疏奖励 + PBRS
    """

    def __init__(self, size: int = 10):
        self.size = size
        self.goal = (size - 1, size - 1)  # 目标在右下角
        self.state = (0, 0)               # 起点在左上角

    def manhattan_potential(self, state: Tuple[int, int]) -> float:
        """
        基于曼哈顿距离的势函数
        离目标越近,势能越高(鼓励靠近目标)
        """
        dist = abs(state[0] - self.goal[0]) + abs(state[1] - self.goal[1])
        max_dist = 2 * (self.size - 1)
        return -dist / max_dist  # 归一化到 [-1, 0]

    def sparse_reward(self, state: Tuple[int, int]) -> float:
        """原始稀疏奖励:只有到达目标才+1"""
        return 1.0 if state == self.goal else 0.0

    def demo_comparison(self):
        """对比有无奖励塑形的效果"""
        shaper = PotentialBasedRewardShaping(
            potential_fn=self.manhattan_potential,
            gamma=0.99
        )

        # 示例:从(0,0)走到(1,0)
        s, s_next = (0, 0), (1, 0)  # 向目标方向移动
        r_original = self.sparse_reward(s_next)  # = 0(还没到目标)
        r_shaped = shaper.shaped_reward(s, s_next, r_original)

        print(f"原始奖励: {r_original}")     # 0.0
        print(f"塑形奖励: {r_shaped:.4f}")   # > 0(正信号,鼓励靠近目标)

        # 远离目标的情况
        s, s_bad = (1, 0), (0, 0)  # 远离目标
        r_bad_shaped = shaper.shaped_reward(s, s_bad, 0.0)
        print(f"远离目标的塑形奖励: {r_bad_shaped:.4f}")  # < 0(惩罚远离)

4.4 Reward Shaping的常见陷阱

Text Only
常见错误:
├── 1. 非势函数形式的塑形
│   └── F(s,a) = c × distance_reduction
│       → 可能改变最优策略!
├── 2. 基于动作的塑形
│   └── F(s,a) 依赖于动作
│       → PBRS要求只依赖状态
├── 3. 塑形奖励过大
│   └── |F| >> |R_original|
│       → 原始奖励信号被淹没
└── 4. 势函数与任务不匹配
    └── Φ引入错误的先验
        → 收敛到次优策略

5. 缓解Reward Hacking的方法

5.1 方法体系总览

Text Only
缓解策略:
├── 设计层面
│   ├── 多目标奖励 → 避免单一指标被钻空子
│   ├── 约束优化 → 显式添加安全约束 (→ 参考安全RL)
│   └── 迭代奖励设计 → 发现问题及时修补
├── 训练层面
│   ├── KL散度约束 → 限制策略偏离程度
│   ├── 集成奖励模型 → 多个RM投票降低hack风险
│   ├── 对抗训练 → 训练RM识别hack行为
│   └── 正则化 → 限制策略复杂度
├── 评估层面
│   ├── 多元评估 → 不只看奖励分数
│   ├── 人类审查 → 定期检查策略行为
│   └── 分布外测试 → 检测hack策略的脆弱性
└── 理论层面
    ├── PBRS → 保证塑形不改变最优策略
    ├── Constitutional AI → 规则约束 + AI自我纠正
    └── IDA (Iterated Distillation and Amplification)

5.2 KL散度约束方法

这是RLHF中最常用的缓解手段:

\[\max_{\pi_\theta} \mathbb{E}_{x \sim D, y \sim \pi_\theta(\cdot|x)} \left[ R(x, y) \right] - \beta \cdot D_{\text{KL}} \left[ \pi_\theta \| \pi_{\text{ref}} \right]\]
Python
import torch
import torch.nn.functional as F

class KLConstrainedRLHF:
    """
    KL散度约束的RLHF训练
    通过限制策略偏离参考模型来缓解reward hacking
    """

    def __init__(self, policy_model, ref_model, reward_model, beta=0.1):
        """
        参数:
            policy_model: 策略模型(待优化)
            ref_model: 参考模型(SFT后的基准,冻结)
            reward_model: 奖励模型
            beta: KL惩罚系数(越大 → 越保守 → 更不易hack)
        """
        self.policy = policy_model
        self.ref = ref_model          # 冻结参数
        self.reward_model = reward_model
        self.beta = beta

    def compute_loss(self, prompts, responses):
        """
        计算RLHF损失 = -奖励 + β × KL散度
        """
        # 策略模型的log概率
        policy_logprobs = self.policy.log_prob(prompts, responses)
        # 参考模型的log概率
        with torch.no_grad():  # 禁用梯度计算,节省内存
            ref_logprobs = self.ref.log_prob(prompts, responses)

        # 奖励分数
        with torch.no_grad():
            rewards = self.reward_model(prompts, responses)

        # KL散度(token级别)
        kl_div = policy_logprobs - ref_logprobs  # log(π/π_ref) 的估计

        # 总目标: 最大化 (奖励 - β * KL)
        loss = -(rewards - self.beta * kl_div).mean()

        return loss, {
            'reward': rewards.mean().item(),  # 将单元素张量转为Python数值
            'kl': kl_div.mean().item(),
            'loss': loss.item()
        }

    def adaptive_beta(self, current_kl, target_kl=6.0):
        """
        自适应调整β(PPO中的常见做法)
        如果KL超过目标值,增大β以加强约束
        """
        if current_kl > target_kl * 1.5:
            self.beta *= 1.5  # KL太大,加强惩罚
        elif current_kl < target_kl * 0.5:
            self.beta /= 1.5  # KL太小,放松约束
        self.beta = max(0.01, min(10.0, self.beta))  # 限制范围

5.3 集成奖励模型

使用多个奖励模型来降低单一RM被hack的风险:

Python
import torch
from typing import List

class EnsembleRewardModel:
    """
    集成奖励模型:多个RM投票,降低reward hacking风险

    核心思想:
    - 单个RM可能有偏差 → 智能体利用偏差获得虚高奖励
    - 多个RM取一致意见 → 大幅降低hack概率
    """

    def __init__(self, reward_models: List, strategy='conservative'):
        """
        参数:
            reward_models: 多个奖励模型
            strategy: 聚合策略
                - 'mean': 平均(标准做法)
                - 'conservative': 保守(取最低分)
                - 'worst_case': 最坏情况(最低分 - 标准差)
        """
        self.rms = reward_models
        self.strategy = strategy

    def score(self, prompt: str, response: str) -> float:
        """
        计算集成后的奖励分数
        """
        # 从每个RM获取分数
        scores = []
        for rm in self.rms:
            with torch.no_grad():
                s = rm(prompt, response)
            scores.append(s)

        scores = torch.stack(scores)  # torch.stack沿新维度拼接张量

        if self.strategy == 'mean':
            # 平均值(最常用)
            return scores.mean()
        elif self.strategy == 'conservative':
            # 取最低分(保守策略)
            return scores.min()
        elif self.strategy == 'worst_case':
            # 最坏情况估计:均值 - k × 标准差
            return scores.mean() - 1.0 * scores.std()
        else:
            raise ValueError(f"未知策略: {self.strategy}")

    def detect_hacking(self, prompt: str, response: str, threshold: float = 2.0) -> bool:
        """
        检测潜在的reward hacking
        如果不同RM之间分数差异极大,说明某个RM可能被利用
        """
        scores = [rm(prompt, response) for rm in self.rms]
        scores = torch.stack(scores)

        # 分数的离散程度超过阈值→可能是hacking
        return scores.std().item() > threshold

5.4 Process Reward Model (PRM)

与Outcome RM(只看最终结果)不同,PRM对推理的每一步给出奖励,更难被hack:

Python
class ProcessRewardModel:
    """
    过程奖励模型(PRM)
    对推理过程的每一步打分,而非只看最终答案

    优势:
    - 更细粒度的反馈 → 更难hack
    - 可以定位错误步骤 → 可解释性更好
    - 鼓励正确的推理过程 → 减少"蒙对答案"的情况

    参考: Let's Verify Step by Step (Lightman et al., 2023)
    """

    def __init__(self, base_model, step_scorer):
        self.base_model = base_model
        self.step_scorer = step_scorer

    def score_process(self, problem: str, steps: list) -> dict:
        """
        对推理过程的每一步打分

        参数:
            problem: 问题
            steps: 推理步骤列表

        返回:
            每步的正确概率和总分
        """
        step_scores = []
        context = problem

        for i, step in enumerate(steps):  # enumerate同时获取索引和元素
            # 在上下文环境中评估当前步骤
            context += f"\nStep {i+1}: {step}"
            score = self.step_scorer(context)  # 得到该步正确概率
            step_scores.append(score)

        return {
            'step_scores': step_scores,
            # ORM分数:所有步骤都正确的概率
            'orm_score': min(step_scores),
            # PRM分数:按步加权
            'prm_score': sum(step_scores) / len(step_scores),
            # 最弱环节
            'weakest_step': step_scores.index(min(step_scores))
        }

6. 前沿研究方向

6.1 Reward Model Overoptimization的Scaling Laws

Gao et al., 2022 发现了过度优化的缩放规律:

\[\text{Gold Score} \approx \alpha \sqrt{D_{\text{KL}}} - \beta D_{\text{KL}}\]

关键发现: - \(\alpha\)\(\beta\) 都随RM大小缩放:更大的RM → \(\alpha\) 更大(初始提升更快)、\(\beta\) 更小(过度优化更慢) - RM越大,被hack越难,但永远不能完全消除 - Best-of-N采样比RL优化更不容易过度优化

6.2 WARM (Weight Averaged Reward Models)

Text Only
WARM方法 (2024):
┌──────────────────────────────────────┐
│ 1. 训练多个RM(不同种子/数据)        │
│ 2. 对模型权重取平均(而非分数取平均)  │
│ 3. 权重平均的RM更鲁棒,更难被hack     │
└──────────────────────────────────────┘

WARM vs 集成的优势:
- 推理成本 = 单模型(权重已合并)
- 效果接近集成(多样性保留在权重中)

6.3 Constitutional AI 与 Self-Correction

Text Only
Constitutional AI 流程:
┌─ Step 1: 生成回答 ──────────────────┐
│  模型M对问题Q生成回答A               │
└──────────────────────────────────────┘
┌─ Step 2: 自我批评 ──────────────────┐
│  M根据宪法原则C审查A:              │
│  "这个回答是否存在有害/不准确内容?"  │
└──────────────────────────────────────┘
┌─ Step 3: 自我修正 ──────────────────┐
│  M生成修正后的回答A'                 │
│  要求同时满足有帮助性和安全性         │
└──────────────────────────────────────┘
┌─ Step 4: RLAIF ─────────────────────┐
│  用M自身的判断作为偏好数据训练RM      │
│  无需人类标注,降低成本               │
└──────────────────────────────────────┘

7. 实践建议

7.1 奖励设计清单

Markdown
✅ 奖励函数设计Checklist:

□ 目标对齐:奖励函数是否真正反映了你想要的行为?
□ 副作用:最大化该奖励时,是否会产生不良副作用?
□ 极端策略:如果智能体"极端优化"这个奖励,会发生什么?
□ 稀疏性:奖励是否足够密集以引导学习?
□ 可利用性:智能体能否通过"作弊"方式获得高奖励?
□ 多目标:是否需要多个奖励信号来覆盖不同方面?
□ 测试覆盖:是否在多种场景下验证了奖励函数?
□ PBRS:如果使用了Reward Shaping,是否满足势函数形式?
□ 约束:是否添加了必要的安全约束?
□ 监控:是否有机制检测reward hacking的发生?

7.2 常见领域的奖励设计模板

领域 推荐奖励结构 注意事项
游戏AI 稀疏胜负 + dense shaping 用PBRS做shaping
机器人控制 距离 + 能耗 + 安全约束 多目标加权需仔细调参
LLM对齐 RM + KL约束 + PRM 监控过度优化
推荐系统 点击 + 停留 + 多样性 避免只优化点击率
自动驾驶 安全 + 效率 + 舒适 安全约束必须是硬约束

8. 面试要点

8.1 高频问题

  1. 什么是Reward Hacking?举例说明。
  2. 定义 + 经典案例(CoastRunners / RLHF sycophancy)

  3. Goodhart定律在RL中的体现?

  4. 代理度量 vs 真实目标的偏差在极端优化下会被放大

  5. 什么是PBRS?为什么它能保证不改变最优策略?

  6. 势函数差形式 → 环路净奖励为零 → 等价MDP → 相同最优策略

  7. RLHF中如何缓解Reward Hacking?

  8. KL约束、集成RM、PRM、Constitutional AI、Best-of-N

  9. PRM和ORM的区别?

  10. PRM逐步打分(过程监督),ORM只看最终结果(结果监督)
  11. PRM更难hack,但标注成本更高

📌 关键要点总结

  1. Reward Hacking是RL中的核心安全问题——智能体会利用奖励函数的缺陷
  2. Goodhart定律解释了为什么代理指标在极端优化下会失效
  3. PBRS是唯一能保证不改变最优策略的奖励塑形方法
  4. 缓解策略包括:KL约束、集成RM、PRM、Constitutional AI
  5. 没有完美的奖励函数——好的实践是多层防御 + 持续监控

📚 延伸阅读