在上一章中,我们讨论了 RLHF 的作用,以及如何通过多种方式将人类反馈纳入进来,以对齐 AI 策略并加速训练。其中一种非常实用的方法,是利用人类反馈训练一个奖励模型,再用这个奖励模型来引导智能体训练。本章将聚焦于奖励建模这一过程。这里的核心思想,是把“智能体应该实现什么目标”的目标规定过程,与“智能体如何实现该目标”的实现过程分离开来。
本章将覆盖以下几个主要主题:
- 奖励在强化学习中的重要性
- 奖励指定中的挑战
- 奖励建模技术
- 将人类反馈纳入奖励设计
到本章结束时,你将从动手实践层面,深入理解如何开发和训练一个奖励模型,并将其与强化学习算法集成,用于训练智能体。当奖励难以明确指定、但又可以利用人类反馈时,这将为你提供一种解决强化学习问题的新工具。
技术要求
本章依旧将实现相关依赖保持在最低限度。所有实现均使用 Python。推荐版本与最小依赖如下:
- Python 版本 >= 3.9
- NumPy == 1.26.4
- Matplotlib == 3.8.4
- PyTorch == 2.3.0
开始前,请克隆以下 Git 仓库,并切换到 chapter3 目录:
https://github.com/PacktPublishing/A-Practical-Guide-to-Reinforcement-Learning-from-Human-Feedback
购买附赠内容
购买本书后,你还将获得一本无 DRM 的 PDF 副本、Packt+ 资源库 7 天试用(无需信用卡),以及一些额外专属内容。请查看前言中的 Free benefits with your book 部分,以立即解锁这些权益并最大化你的学习收益。
奖励在强化学习中的重要性
在前几章中,我们介绍了 RL 以及状态—动作—奖励框架:智能体通过与环境交互来学习最优决策策略。现在,我们将把注意力更细致地放在奖励本身上。
智能体之所以会选择某个动作,是因为它在优化某个目标;而奖励的设计,就是为了帮助它到达目标或维持在目标状态。智能体一旦学会最大化奖励,它实现目标的概率也就随之提高。智能体训练效率,以及最终训练出来的智能体性能,都会取决于奖励设计得是否合理。
奖励设计为智能体提供了一种反馈机制,用来告诉它:基于当前采取的动作,它到底做得怎么样。正是这种反馈驱动了学习过程,因为它会激励智能体去探索环境,并不断调整行为,使其与人类期望的目标保持一致。通过在奖励赋值中清晰表达期望行为,奖励设计能够帮助智能体更高效地实现目标,并提升整体表现。
此外,对已采取动作给出清晰反馈,还能够加快智能体的学习过程。一个定义良好的奖励结构,可以让智能体更有效地适应其环境。例如,在迷宫导航任务中,如果奖励被设计得能够恰当地引导机器人动作,那么机器人更有可能成功到达目标,并避开障碍物。
外在奖励与内在奖励
外在奖励(Extrinsic rewards) 是指智能体因与环境交互所产生结果而获得的奖励,例如到达目标状态,或撞上障碍物。相对地,内在奖励(Intrinsic reward) 并不直接来自环境结果,而是用来激励智能体以某种特定方式行动。
以前面第 1 章中的 grid world 为例:
- 到达目标状态时给予
+10奖励 - 撞上障碍物时给予
-5奖励
这两者都属于基于环境交互结果的外在奖励。
同样地,如果每走一步都给予 -1 惩罚,以此让智能体倾向于用最少步数到达目标,那么由于这个奖励与“高效完成任务”直接相关,它也属于一种经过塑形的外在奖励。
相反,如果我们鼓励智能体去探索和保持好奇,例如:
- 访问一个新格子时给予
+0.5
这种奖励只是为了激励智能体的行为,本身并不依赖于环境结果,因此它属于内在奖励。
奖励塑形及其如何加速训练
奖励塑形(Reward shaping) ,是指对奖励函数进行修改,加入额外的中间奖励,以引导智能体更高效地学习。
其中一种奖励塑形技术,是提供中间奖励:也就是在任务过程中,当智能体实现某些子目标或阶段性里程碑时,就给予奖励,从而向它传递“正在朝最优策略前进”的信号。
另一种互补的奖励塑形技术,是赋予方向性奖励(directional rewards) 。方向性奖励会根据智能体朝主目标前进的进展或移动方向来发放。
中间奖励与方向性奖励都可以加速训练:
- 中间奖励更强调为达成子目标提供结构化反馈
- 方向性奖励则更强调根据智能体朝主目标推进的程度,持续给出反馈
把两种技术结合起来,往往能帮助智能体更有效地学习,因为在整个学习过程中,它会持续收到清晰而频繁的引导。
在一个 grid world 中,如果主目标是到达右下角,那么:
-
中间奖励 可以发放给某些中间格子或检查点,即使这些格子不是最靠近主目标的,但可能有助于绕开障碍
-
方向性奖励 则可以根据是否更接近目标来决定,例如:
- 每向目标靠近一步,奖励
+0.5 - 每远离目标一步,惩罚
-0.15
- 每向目标靠近一步,奖励
不过,中间奖励和方向性奖励主要关注的是性能与目标达成,也就是对已知知识的利用。对于存在未知区域的复杂环境而言,探索环境本身同样很重要。因此,鼓励智能体探索新的状态或动作、促进更全面的学习,也是合理策略。这可以通过给智能体加入探索奖励(exploration bonuses)来实现。
在训练智能体的奖励信号语境下,奖励的指定或设计会直接影响智能体训练表现。虽然奖励塑形能够加速训练,但在设计奖励函数时仍然需要相当程度的思考与谨慎。奖励信号必须具有信息性。这里的信息性,指的是:奖励信号向智能体提供了多少有助于改善其行为的有效信息。更有信息量的奖励,会给智能体更清晰的指导,帮助它分辨哪些动作是有益的,哪些不是。
与之相关的另一个概念是稀疏性(sparseness) ,指的是奖励发放得有多频繁:
- 稀疏奖励:很少发放,通常只在重大成就时给出
- 稠密奖励:更频繁发放,哪怕只是较小的进展
如果奖励过于稀疏,智能体可能会因为反馈不足,而难以学会策略;如果奖励过于稠密,但又缺乏信息性,智能体则可能学到次优行为,或者花更长时间才能搞清真正高效的策略。因此,我们需要在两者之间取得平衡。
通过在信息性与稀疏性之间做好平衡,奖励结构就能持续给出频繁而有意义的反馈,帮助智能体高效、有效地学习。虽然业界确实存在一些实现这种平衡的实践,但它们往往高度依赖具体场景,因此通常需要较强的技能和领域知识。接下来,我们将更详细地讨论奖励指定所面临的挑战。
奖励指定中的挑战
奖励指定与奖励设计对智能体训练至关重要,但这一过程本身也面临若干挑战。本节将讨论其中一些问题,并看看有哪些替代方案可以帮助缓解这些问题。
目标错位
智能体学到的目标与人类真实意图之间的错位,可能来自于模糊的奖励定义。模糊奖励会导致意外行为。因为要通过奖励精确表达期望的行为和结果,本身就是一件困难的事。另一个原因可能来自任务本身的复杂性:对于涉及多个目标的复杂任务,想要在一个单一奖励函数中准确刻画全部目标,往往非常困难。
稀疏奖励与延迟奖励
在很多环境中,奖励可能既稀疏,又延迟,或者两者兼有。
- 稀疏奖励 会带来探索难题,因为动作成功与否很少得到反馈
- 虽然奖励塑形能够缓解一部分稀疏性问题,而且似乎还会鼓励一种“短视但局部最优”的学习方式
- 但延迟奖励 会让当前动作和最终奖励之间的因果关系变得模糊,从而使学习变得困难
奖励黑客化
智能体可能会利用奖励函数中的漏洞,通过“刷高奖励”的方式获得高分,而不是表现出我们真正期望的行为。这种现象称为 reward hacking(奖励黑客化) 。
设计不良的奖励,可能会导致智能体采取虽然能最大化奖励、但实际上并不 desirable、安全或与目标一致的行为。
例如,设想一个清洁机器人,其代理奖励函数鼓励它“清扫尽可能多的区域”。那它可能会为了提高清扫分数,而把一个贵重花瓶撞倒。虽然这样能扩大可清扫面积,但显然打碎花瓶并不是我们希望发生的。这就说明,代理奖励没有考虑到这些非预期后果。
设计复杂度
设计一个奖励函数通常需要领域知识与专业经验,而且这一过程既费时,又容易出错。更麻烦的是,在动态环境或不断变化的环境中,固定奖励函数可能很快就不再适用,从而需要持续调整。
多目标权衡与平衡
将多个目标平衡到同一个奖励函数中,也是一个难点。例如,某个智能体可能必须同时兼顾速度与安全,而这两个目标往往是相互冲突的。
在 grid world 中,如果通向目标的最短路径上有障碍物,那么智能体就必须先绕开障碍再去到达目标。从短期动作来看,“避开障碍”这个目标与“尽快到达目标”这个目标就可能发生冲突。虽然这可以通过任务层级设计和奖励加权来处理,但在复杂环境中,要为奖励函数不同组成部分设定合适权重往往非常困难,而且通常需要大量调参。
可扩展性
在非常大的环境中,如果还同时包含多个目标,那么一个单一奖励函数往往很难良好扩展。在这种情况下,构建任务层级可能更适合。但对于具有层级结构的任务而言,如何在不同层级上设计奖励、使其能够合理地激励进展,本身又是一个复杂问题。
为了应对这些挑战,可以采用若干策略。虽然并不是每种策略在所有场景中都绝对有效,但它们通常都是值得尝试的思路。
例如:
-
对复杂任务,可以通过把任务拆分成更小、更简单、目标更明确的子任务来处理,这称为任务分解(task decomposition)
比如,一个抓取并放置物体的机器人任务,可以分解为三步:
- 在不撞上障碍物的情况下到达目标物体
- 抓取该物体
- 将物体移动到目标位置并释放
-
奖励定义中的其他问题,也可以通过反复设计、测试和修正来发现与改进。你可以使用仿真环境和受控环境来观察智能体行为,再据此调整奖励信号。
-
通过实现多个不同版本的奖励函数,并将其进行比较,我们还可以用 A/B 测试 来评估不同奖励设计在实现期望行为方面的有效性。
-
还可以利用逆强化学习(Inverse Reinforcement Learning, IRL) ,通过观察人类专家行为来推断奖励函数,以便捕捉专家在执行任务时所隐含使用的目标。
例如,在机器人抓取与放置场景中,可以观察专家的状态—动作轨迹,并尝试推断其对多个分解目标所赋予的权重,如:
- 避碰
- 达成目标的效率(耗时)
- 安全性与合规性(如速度或加速度限制)
这些都可以成为 IRL 的切入点。
不过,这些策略虽然在某些场景下有效,但都仍然依赖于人类专业知识与反复实验。将人类反馈更容易地纳入进来的一种方式,就是在学习过程中直接加入人类反馈,用来引导智能体并纠正与目标错位的行为。而实现这一点的一条路径,就是利用人类偏好,并把它们纳入奖励建模过程。
奖励建模技术
奖励建模技术形式多样,可以根据反馈来源、所涉及的学习类型,以及将人类偏好整合进奖励函数的具体方法来大致分类。人类反馈的来源有很多,具体取决于这些反馈可以如何被感知或接收。以下是几种常见来源:
专家反馈
由人类专家提供期望结果,或引导智能体朝特定目标前进。这在复杂任务中尤为有帮助,因为此时直接定义奖励函数可能非常困难。
环境反馈或试错反馈
智能体通过与环境交互来学习:
- 成功会带来正奖励
- 失败会带来负奖励
这是一种更偏探索性的方式,但在较简单环境中也可能非常有效。智能体从环境对其动作作出的响应中学习。环境反馈就是环境返回给智能体的原始、未经加工的信息,它可能包括:
- 成功/失败信号:智能体是否达成了目标?
- 状态变化:环境因为动作而发生了什么变化?
- 资源变化:智能体是否获得或失去了资源?
这些原始信息为奖励模型提供上下文。例如,它们可以作为数据点,用来学习:智能体动作、环境状态变化以及期望结果之间的关系。
举个例子:
在一个目标是收集金币的游戏中,原始信息可能会告诉模型:智能体撞到了墙(状态变化)。单凭这一点,还不足以判断它是好结果还是坏结果。但如果把这条信息与其他数据点结合起来,比如“它没有收集到金币”,模型就能逐渐学会:撞墙一般是不 desirable 的,应对应负奖励。
在 RLHF 语境下,人类反馈可以被用来补充或澄清环境信号。
组合反馈
这种方法同时利用人类反馈与环境反馈,来共同引导学习过程。
基于偏好的学习
这种技术通过人类对不同轨迹或动作的偏好,来学习奖励函数。整个过程通常包括三个步骤:
数据收集
收集成对的轨迹或动作,并让人类在二者之间表达偏好。
偏好建模
使用模型(通常是神经网络)来学习一个函数,用以预测人类偏好。
奖励函数学习
模型通过最大化人类提供偏好的似然,来学习一个与人类偏好保持一致的奖励函数。
假设有两条轨迹,分别记为 τ1 和 τ2。偏好 τ1 > τ2 表示轨迹 τ1 优于 τ2。模型训练的目标,是让“τ1 的预测奖励大于 τ2 的预测奖励”的概率尽可能高。
更详细的讨论可以参考:
- A Survey of Preference-Based Reinforcement Learning Methods
- Deep Reinforcement Learning From Human Preferences
- Rank Analysis of Incomplete Block Designs: I. The Method of Paired Comparisons
绝对评分模型
这类模型直接为单个输出估计一个分数,用来反映它与人类偏好的契合程度。这可以被看作一个回归任务。与依赖相对比较的偏好学习不同,绝对评分模型使用的是直接数值评分。
多目标奖励模型
在真实世界场景中,人类偏好往往涉及多个方面。这类模型会同时考虑多个目标来学习奖励赋值。例如文中提到的 Absolute-Rating Multi-Objective Reward Model (ArmoRM) 。
逆强化学习(IRL)
IRL 的目标,是根据观察到的专家行为,推断其背后的奖励函数。它基于这样一个前提:专家行为相对于某个未知奖励函数而言,是最优的。通过观察专家采取的动作,IRL 试图反向还原出专家隐式在优化的奖励函数。
这一过程通常分为两大步骤:
专家示范
收集专家执行任务时的轨迹,或状态与动作序列。这些轨迹代表专家行为,是 IRL 的核心数据来源。
奖励函数估计
使用优化技术,推断出一个奖励函数,使得观察到的专家行为在该奖励函数下看起来是最优的。这通常需要把问题形式化为一个优化任务,其目标是找到最能解释专家动作的奖励函数。
所有这些奖励建模方法,区别主要在于:
- 收集了哪些人类信号
- 如何利用这些信号来预测奖励
但它们通常都有一个共同点:都包含一个监督学习步骤,即利用输入数据训练一个模型,让模型学会预测某个输出,而这个输出就是奖励。这样的方法特别适合学习那些难以手工定义的复杂奖励函数,尤其是在大型或动态环境中。
此外,这种模型还可以利用额外数据进行微调,从而适应略有变化的环境或目标,因此在适应性与可扩展性方面更有优势。关于这一点,你还可以参考 Maximum Entropy Inverse Reinforcement Learning。
RewardModel 类
在仓库的 reward_models.py 文件中,RewardModel 类定义了一个基于神经网络的奖励预测模型架构。它继承自 nn.Module,并实现了一个将被训练的前馈神经网络。
nn.Module 是 PyTorch 中构建神经网络的基础类。通过继承 nn.Module,RewardModel 就能够使用 PyTorch 提供的各种神经网络层与相关功能。
下面这个函数会在模型创建时初始化其参数。它接收三个参数:
state_size:智能体状态表示的维度(例如机器人的位置)。这里设为2,也可根据具体问题调整hidden_size:隐藏层神经元个数,用于控制模型复杂度(这里设为64)output_size:模型输出的奖励值个数。在这个例子中设为3,很可能分别对应多个奖励类别,例如进展(progress) 、安全(safety)和目标效率(goal efficiency)
模型由两个全连接层组成:fc1 和 fc2。
fc1:接收拼接后的当前状态和下一状态(输入维度为state_size * 2),并经过 ReLU 激活函数。这个层负责从组合状态信息中提取有效特征。fc2:接收第一层输出,并把它映射成目标奖励维度(output_size)。
# RewardModel class definition
class RewardModel(nn.Module):
def __init__(self, state_size=2, hidden_size=64, output_size=3):
super(RewardModel, self).__init__()
# Adjusted to take both state and next_state as input:
self.fc1 = nn.Linear(state_size * 2, hidden_size)
# Output 3 rewards: progress, safety, and goal-efficiency,:
self.fc2 = nn.Linear(hidden_size, output_size)
def forward(self, state, next_state):
# Concatenate state and next_state:
x = torch.cat((state, next_state), dim=1)
x = torch.relu(self.fc1(x))
rewards = self.fc2(x)
return rewards
forward 函数定义了数据如何在网络中流动。它接收当前状态和下一状态作为输入:
- 首先把
state与next_state拼接,形成一个单一输入 - 然后将该输入送入第一层,并经过 ReLU 激活
- 最后把第一层输出送入第二层,得到预测奖励
通过使用人类反馈数据来训练这个 RewardModel,模型就能学会把特定状态转移与符合人类偏好的奖励对应起来。随后,这些信息又会被用来引导 RL 智能体朝着人类认为 desirable 的行为方向学习。
这只是 RLHF 集成的第一步。在本章后续部分,我们将进一步探讨:这个奖励模型如何用人类反馈数据进行训练,以及它又是如何被纳入 RL 智能体的学习过程中的。
在奖励设计中纳入人类反馈
在前面提到的几种奖励建模技术中,基于偏好的学习、绝对评分模型 和 多目标奖励模型,都适合把人类信号以直接反馈的方式纳入进来。
相比之下,在很多场景里,直接整合人类反馈 往往比单纯依赖 IRL 更受青睐,原因在于 IRL 在收集和利用人类反馈方面存在一些局限:
- 间接性:IRL 是通过观察来推断奖励函数,这个过程容易受到观测行为中的误差和偏差影响
- 复杂性:IRL 算法可能计算代价很高,尤其是在任务复杂、或人类示范有限时
- 覆盖范围有限:IRL 通常聚焦于从动作中推断奖励函数,因此可能遗漏那些没有直接体现在观测行为里的、更广泛的人类偏好或价值观
直接反馈整合的例子
为了把直接人类反馈整合进 RL 系统,至少有三种常见且实践中广泛使用的方法:
- 奖励标注(Reward labeling) :人类直接把不同状态或动作标成正向、负向或中性奖励
- 偏好学习(Preference learning) :人类在智能体生成的不同输出之间表达偏好,从而把智能体引向更 desirable 的行为
- 评价式反馈(Evaluative feedback) :人类通过书面或口头方式,对智能体输出进行评价,而这些评价随后可以被用来修正奖励信号
总的来说,RLHF 往往更偏向于使用直接反馈整合,因为这种方式能以更清晰、更高效、更灵活的方式,把人类价值观纳入训练过程。虽然 IRL 也能提供有价值的启发,但直接利用人类偏好通常更容易得到稳健、且与人类对齐程度更高的 RL 智能体。
当然,也要注意:在某些应用里,实时收集即时人类偏好并不可行,例如在高动态、实时性的场景中。这时,就需要依赖人类示范。比如在自动驾驶中,就可以通过采集人类驾驶行为与动作示范,来获得可用于训练的数据。
收集人类反馈
人类反馈的数据采集可能成本很高,而且通常需要一套针对具体应用量身定制的系统。为了方便本章演示,我们会构造一些模拟状态—动作场景,并用模拟奖励来表示人类反馈。
我们会复用第 1 章中的 GridWorld 模拟环境类,以及上一节中的 RewardModel 类,它们都位于仓库的 reward_modeling.py 文件中。与此同时,我们也会展示:为了便于讲解,人类反馈是可以被模拟出来的。
首先,导入奖励建模所需的依赖库:
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
from gridworld import GridWorld
from reward_models import RewardModel
接下来,我们来实现一些辅助函数,用于生成具有代表性的模拟人类反馈数据。
首先,我们编写一个函数:它接收智能体当前状态、执行动作后的下一状态,以及环境信息(目标位置与障碍物),然后计算三个奖励组成部分,用来模拟人类如何评价智能体的进展。
进展奖励(Progress reward)
这个奖励鼓励智能体朝目标靠近。它会计算下一状态与目标位置之间的距离,智能体越接近目标,奖励越高。实现方式是利用 np.linalg.norm 计算下一状态与目标位置之间的欧几里得距离,然后乘以 -1,因为越接近目标,距离值越小。
安全奖励(Safety reward)
这个奖励用于阻止智能体进入障碍物位置。如果下一状态正好是某个障碍物位置,就给予 -5 惩罚;如果下一状态是安全的(不在障碍物上),则奖励为 0。
目标效率奖励(Goal efficiency reward)
这个奖励鼓励智能体直接到达目标。如果智能体到达目标状态(使用 np.array_equal 判断),则给予较高奖励 +10;否则给予 -1 惩罚。
通过把这三类奖励结合起来,这个函数就会给出一个复合信号,用来反映对智能体而言哪些行为是 desirable 的:
- 更接近目标:正向进展奖励
- 避开障碍:正向安全奖励
- 高效到达目标:正向目标效率奖励
不过,奖励模型会把这三个奖励分别保留,而不是立刻合并,这样在更高层面上如何组合这些奖励时,就会有更大的控制空间。这一点很像多目标奖励模型,因为它允许我们对轨迹中每一步分别进行奖励评分。
def simulate_human_reward(
state, next_state, goal_position, obstacle_positions
):
next_state_array = np.array(next_state)
goal_position_array = np.array(goal_position)
progress_reward = -np.linalg.norm(
next_state_array - goal_position_array)
safety_reward = -5 if next_state in obstacle_positions else 0
goal_efficiency_reward = 10 if np.array_equal(
next_state_array, goal_position_array) else -1
return np.array([
progress_reward, safety_reward, goal_efficiency_reward])
generate_human_feedback 函数会为 grid world 中各种状态转移生成一个模拟人类反馈数据集。它的输入包括:
- 网格大小
- 目标位置
- 障碍物位置
- 所需样本数
num_samples
它还会接收智能体在 grid world 中可能采取的动作,例如上、下、左、右(例如 (0, 1) 表示向上,动作列表为 actions = [(0, 1), (0, -1), (1, 0), (-1, 0)]),并初始化一个空列表 data,用来存放生成出来的反馈样本。
随后,它会重复 num_samples 次,随机采样一个起始状态(也就是智能体位置),该位置位于 grid world 的边界之内;再从可选动作中选取一个动作,并计算执行这个动作后会到达的下一状态。
当它拿到当前状态、下一状态以及环境信息(目标与障碍物)后,就会调用 simulate_human_reward 函数,为这次状态转移生成对应的“类人奖励”。最后,它会把一个三元组追加到 data 列表中,这个三元组包含:
- 当前状态
- 下一状态
- 计算出来的奖励(进展、安全和目标效率)
该函数返回的数据集 data,会作为 RewardModel 的训练样本。
这个模拟反馈数据集能提供丰富多样的场景,帮助 RewardModel 学会把状态转移与人类偏好的结果关联起来。通过在训练过程中引入这些奖励,模型就会逐渐学到:哪些状态转移更符合给定环境中的人类期望,从而进一步引导 RL 智能体采取更 desirable 的行为。
训练奖励模型
train_reward_model 函数接收之前生成的模拟人类反馈数据 data,并训练 RewardModel 类,让它能够准确预测这些奖励。其实现位于仓库的 reward_modeling.py 文件中,代码如下:
def train_reward_model(
data, state_size=2, hidden_size=64,output_size = 2,
num_epochs=100, batch_size=64, learning_rate=0.001
):
reward_model = RewardModel(state_size, hidden_size, output_size)
optimizer = optim.Adam(reward_model.parameters(), lr=learning_rate)
criterion = nn.MSELoss()
for epoch in range(num_epochs):
np.random.shuffle(data)
batch_losses = []
for i in range(0, len(data), batch_size):
batch = data[i:i + batch_size]
states = torch.tensor(
[item[0] for item in batch], dtype=torch.float32)
next_states = torch.tensor(
[item[1] for item in batch], dtype=torch.float32)
rewards = torch.tensor(
[item[2] for item in batch], dtype=torch.float32)
optimizer.zero_grad()
predictions = reward_model(states, next_states)
loss = criterion(predictions, rewards)
loss.backward()
optimizer.step()
batch_losses.append(loss.item())
avg_loss = np.mean(batch_losses)
print(f"Epoch {epoch+1}/{num_epochs}, Loss: {avg_loss:.4f}")
return reward_model
训练过程可以分为三个步骤。
1)搭建模型与训练环境
首先,它创建一个 RewardModel 实例,并指定:
state_size:状态表示维度hidden_size:隐藏层大小,用于控制模型复杂度output_size:预测奖励的数量。这里是3,分别对应进展、安全和目标效率
然后,它定义一个优化器 Adam,用于在训练过程中更新模型权重。这个优化器使用学习率 learning_rate 来控制参数更新步长。
接着,它定义一个损失函数 MSELoss,也就是均方误差。这个函数用于衡量模型预测奖励 predictions 与人类反馈数据中真实奖励 rewards 之间的差距。最小化这个 MSE 损失,能够推动模型学习到尽可能贴近人类给定奖励的预测结果。
2)训练循环
训练会持续若干个 epoch,每个 epoch 都表示对完整训练数据集的一次遍历。
在每个 epoch 内部,它会先使用 np.random.shuffle(data) 把数据打乱,以避免训练偏置。这样一来,模型在不同 epoch 中都会以不同顺序看到整个数据集中的样本。
然后,数据会按 mini-batch 形式处理,也就是分批喂给模型。batching 有助于更高效地利用硬件资源。这里,数据被按 batch_size 大小切成若干批次。
对于每个 batch,函数会从中提取:
- 当前状态
states - 下一状态
next_states - 对应奖励
rewards
并把它们转换成 PyTorch tensor:
- 状态与下一状态转为浮点张量
torch.float32 - 奖励同样转成张量,便于高效计算
在每次参数更新前,它会调用 optimizer.zero_grad() 清空优化器中累计的梯度,以便开始当前 step 的反向传播。
随后,把状态与下一状态送入 RewardModel,得到模型对当前状态转移的奖励预测。然后利用损失函数 criterion 计算预测值与真实奖励之间的 MSE 损失,并执行反向传播:
loss.backward():计算损失相对于各模型参数的梯度optimizer.step():根据这些梯度更新模型权重,使损失下降、预测更准确
同时,它还会记录每个 batch 的 loss,存入 batch_losses 列表。等当前 epoch 的所有 batch 都处理完后,再计算该 epoch 的平均损失 avg_loss,并打印:
- 当前 epoch 序号
- 总 epoch 数
- 当前 epoch 的平均损失
这样就能方便地观察训练进展。
3)返回训练好的模型
当所有 epoch 都训练完成后,函数会返回训练好的 reward_model。这个模型已经学会了如何根据模拟人类反馈数据来预测奖励,后续就可以被 RL 智能体用来指导其在真实环境中的决策过程。
通过反复使用模拟人类反馈进行训练,RewardModel 会逐渐学会:状态转移与期望奖励之间的关系。有了这种知识,它就能对未见过的状态预测奖励,从而帮助 RL 智能体在环境中导航,并实现那些被人类认为 desirable 的目标。
下面是脚本主执行逻辑:
if __name__ == "__main__":
grid_size = (6, 6)
goal_position = (5, 5)
obstacle_positions = [(2, 3), (3, 1), (4, 3)]
gridworld = GridWorld(grid_size, obstacle_positions)
actions = gridworld.actions
# Generate human feedback and train the reward model
human_feedback_data = generate_human_feedback(
grid_size, actions, goal_position,
obstacle_positions, num_samples=500
)
reward_model = train_reward_model(
human_feedback_data, state_size=2,
hidden_size=64,output_size=3, num_epochs=5000
)
# Save the trained model
torch.save(reward_model.state_dict(), "reward_model.pth")
print("Reward model saved to reward_model.pth")
这段代码演示了如何使用前面定义好的 generate_human_feedback 和 train_reward_model 两个函数,为给定的 grid world 环境生成模拟人类反馈数据,并训练一个奖励模型。
其中,generate_human_feedback 函数可以写成如下形式,它会通过随机方式生成状态、下一状态以及对应奖励,而这些奖励是通过调用 simulate_human_reward 得到的:
def generate_human_feedback(grid_size, actions, goal_position, obstacle_positions, num_samples):
# actions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
data = []
for _ in range(num_samples):
state = (
np.random.randint(0, grid_size[0]),
np.random.randint(0, grid_size[1])
)
action = actions[np.random.choice(len(actions))]
next_state = (state[0] + action[0], state[1] + action[1])
rewards = simulate_human_reward(
state, next_state, goal_position, obstacle_positions)
data.append((state, next_state, rewards))
return data
在生成人类奖励并完成奖励模型训练之后,主程序会使用 torch.save 将训练好的 reward_model 内部状态字典 state_dict() 保存到名为 reward_model.pth 的文件中。这个文件保存了模型学到的权重与偏置,后续可以重新加载并使用。训练好的模型随后就可以交给 RL 智能体,在未来使用。
与 RL 策略训练集成
为了让智能体学到 desirable 的策略,我们会在前一节讨论的 Q-learning 算法基础上,把训练好的奖励模型加载进来,并与 grid world 示例中的 Q-learning 实现整合起来。
需要说明的是,这里使用 Q-learning 只是为了举例说明。在后续章节中,我们还会讨论更多适合这一任务的算法。
下面分步骤说明:上一节中训练好的奖励模型,如何与 Q-learning 这一 RL 算法集成,用于策略训练。
GridWorldWithNNReward 类继承自 GridWorld,并在此基础上增加了使用训练好奖励模型的能力。首先,它调用父类构造函数来初始化 grid world 环境。然后,它通过 load_reward_model 函数,从指定路径 reward_model_path 中加载预训练好的奖励模型。最后,把加载好的模型切换到评估模式 model.eval(),以便在预测时关闭 dropout 等仅训练阶段使用的机制(如果模型中用到了这些层的话)。
# Subclass with Neural Network-based Reward Model
class GridWorldWithNNReward(GridWorld):
def __init__(
self, grid_size, obstacle_positions, reward_model_path
):
super().__init__(grid_size, obstacle_positions)
self.reward_model = self.load_reward_model(
reward_model_path)
self.reward_model.eval()
load_reward_model 函数会创建一个与训练时架构相同的 RewardModel 实例(状态维度、隐藏层大小、输出维度都一致),然后使用 torch.load 从保存的模型文件 reward_model.pth 中加载权重和偏置,最后返回这个模型:
def load_reward_model(self, path):
model = RewardModel(state_size=2, hidden_size=64, output_size=3)
model.load_state_dict(torch.load(path))
return model
在原先的 q_learning 函数基础上,我们进一步加入神经网络奖励模型的整合版本,称为 q_learning_with_nn_reward。完整代码位于 chapter3 仓库中的 q_learning_rm.py 文件中。
它的核心变化是:
-
把当前状态和下一状态转换成 tensor
-
调用已经加载好的奖励模型,对这两个 tensor 进行推理
-
得到模型预测出的三类奖励:
- 进展奖励
- 安全奖励
- 目标效率奖励
-
把这三者合并为一个总奖励
-
再将总奖励裁剪到预设区间(例如
-5到10),以避免极端异常值
代码如下:
tensor_state = torch.tensor(
state, dtype=torch.float32).unsqueeze(0)
tensor_next_state = torch.tensor(
next_state, dtype=torch.float32).unsqueeze(0)
rewards_nn = self.reward_model(
tensor_state, tensor_next_state)
progress_reward, safety_reward, \
goal_efficiency_reward = \
rewards_nn.squeeze(0).detach().numpy()
reward = progress_reward + safety_reward + \
goal_efficiency_reward
reward = np.clip(reward, -5, 10) # Clip reward
下面是主执行逻辑。它会先定义 grid world 环境参数(大小、目标、障碍物)以及保存好的奖励模型路径。然后创建一个 GridWorldWithNNReward 实例,把预训练奖励模型整合进环境中。
接着,调用 q_learning_with_nn_reward,运行带有神经网络奖励模型的 Q-learning 训练过程。函数返回训练过程中收集到的数据:
- 奖励
- 每个 episode 的步数
- 策略演化过程
之后,它再调用绘图函数(与前文类似)来可视化:
- 学习曲线(奖励)
- 每个 episode 的步数
- 策略演化过程
if __name__ == "__main__":
grid_size = (6, 6)
goal_position = (5, 5)
obstacle_positions = [(2, 3), (3, 1), (4, 3)]
reward_model_path = "reward_model.pth"
# Initialize GridWorld with NN-based reward model
grid_world_nn = GridWorldWithNNReward(
grid_size, obstacle_positions, reward_model_path)
# Run Q-learning with neural network-based reward model
rewards, steps_per_episode, policy_progress = \
grid_world_nn.q_learning_with_nn_reward(
num_episodes=500, learning_rate=0.1,
discount_factor=0.95, epsilon=0.1
)
# Plot the learning curve, steps per episode, and policy progress
grid_world_nn.plot_rewards(rewards)
grid_world_nn.plot_steps_per_episode(steps_per_episode)
grid_world_nn.plot_policy_progress(policy_progress)
这一实现展示了:训练好的奖励模型如何在无缝集成到 Q-learning 算法中后,利用模拟奖励所表达的人类偏好来指导学习。相较于传统稀疏奖励,奖励模型能够提供更具信息量的奖励信号,从而更好地引导智能体训练过程。
读者可以进一步尝试修改:
- 网格大小
- 障碍物数量
- 初始位置和目标位置的变化范围
并观察这些变化对奖励模型训练的影响,例如:
- 需要多少训练 epoch
- 需要采集多少轨迹数据
需要注意的是,在前面的实现中,奖励模型的结构是按每一步预测奖励,而不是对整条轨迹进行评分。对于像 grid world 导航这样的系统来说,轨迹中的每一步都可能产生多种不同结果,因此如果只给出两个偏好选项,表达能力可能会受限,除非可选结果本身也被严格限制。
因此,对于这类问题,绝对评分式奖励建模往往更合适。后续章节中,我们还会重新回到基于偏好的建模,但那时偏好选项会由预训练语言模型生成,在那类任务中,给模型提供偏好选择会更加自然和合适。
总结
在本章中,我们讨论了奖励建模这一概念、奖励在 RL 中的重要性、传统 RL 中设计奖励函数所面临的挑战、若干奖励建模技术、如何把人类反馈纳入奖励模型,以及如何把奖励模型整合进 RL 中。
奖励建模通过预测状态转移之间的奖励值,来帮助训练 RL 智能体。这个奖励值不再只是像“到达目标”这样简单的信号,而是还能把人类对于进展、安全和效率等因素的偏好纳入进来。
训练奖励模型的过程,就是把模拟或真实的人类反馈数据喂给模型,让它学会状态转移与期望奖励之间的对应关系。有了来自奖励模型的这种更丰富的奖励信号,RL 智能体就能更好地被引导去执行那些能够最大化长期收益的动作,从而使其行为更贴近人类预期。
通过在 grid world 导航案例上的实现,我们也说明了:具体采用哪种奖励建模方式,取决于所面对的问题类型。
在下一章中,我们将讨论一个比 grid world 导航更复杂的用例,以及若干能够扩展到连续状态与连续动作空间的先进算法。我们还会展示如何通过行为克隆这一另一种人类反馈整合方式,利用人类指导来进一步加速训练。
参考资料
以下列出了一些可帮助你进一步深入该主题的书籍、课程、论文与资料:
- Explicable Reward Design for Reinforcement Learning Agents
- Defining and Characterizing Reward Hacking
- Maximum Entropy Inverse Reinforcement Learning
- Interpretable Preferences via Multi-Objective Reward Modeling and Mixture-of-Experts
- A Survey of Preference-Based Reinforcement Learning Methods
- Deep Reinforcement Learning from Human Preferences
- Rank Analysis of Incomplete Block Designs: I. The Method of Paired Comparisons
此外,本章代码示例中用到的库,也有相应文档可供参考,以帮助你更深入理解代码实现与可视化方式:
- Matplotlib 3.8.4 文档
- NumPy 用户文档