一、序论
强化学习是一种机器学习方法,它通过智能体(agent)在与环境的交互中学习如何采取行动以最大化某种累积奖励。强化学习的核心思想是基于试错,智能体通过不断尝试不同的行动,观察结果,并根据结果调整策略,以逐步优化其行为。
强化学习的基本组成部分包括:
- 智能体(Agent):执行动作并从环境中接收反馈的实体。
- 环境(Environment):智能体所处的世界,它对智能体的动作做出反应并提供反馈。
- 状态(State):环境在某一时刻的描述,通常用一组特征表示。
- 动作(Action):智能体在某一状态下可以采取的操作。
- 奖励(Reward):环境对智能体动作的即时反馈,通常是一个数值,表示动作的好坏。
- 策略(Policy):智能体根据当前状态选择动作的规则或函数。
- 价值函数(Value Function):评估某一状态或状态-动作对长期累积奖励的期望值。
- 模型(Model):对环境的模拟,用于预测状态转移和奖励。
以围棋界大名鼎鼎的深蓝和Alpha go为例,背后的算法就是智能体,棋盘就是环境。每一次落子就是一个动作,落子完后就是一个状态。奖励就是赢或输,但围棋很难一开始就能判断输赢,所以奖励也会在探索的过程中更新。模型就是对整个棋盘、包括规则进行建模。由于我们很难在游戏结束前就对某一状态或动作进行价值判定,因此需要一个评估函数,这就是价值函数。
强化学习在许多领域都有广泛应用,如游戏AI、机器人控制、自动驾驶、推荐系统等。它能够处理复杂的决策问题,尤其是在环境动态变化且奖励信号稀疏的情况下,强化学习能够通过探索和利用的平衡,逐步学习到最优策略。大模型微调就会应用到人类反馈强化学习------RLHF。通过设定奖惩规则,让模型的输出更具有某种风格、更适用于某个领域。
二、强化学习的几种思想
强化学习的目标就是训练一个智能体,如这个老鼠,能够在任意状态(位置)都能找到最优策略(路线),以实现最大累计回报(如快速找到出口或吃到零食)。训练的过程中,老鼠前进一步就是在探索,遇到墙壁反馈就是负,需要回退。反之就是正,可以继续向下探索。如前所述,在彻底赢或输之前,每一个动作对应的反馈都不是最终的。如何评估某个状态下某个动作的价值,进而指导智能体做出正确的决策,下面是一些前人提出的想法。
1、蒙特卡洛
蒙特卡洛方法本质上是一种基于随机采样的数值计算方法,通常用于全局估计。通过采样完整的轨迹(episode)来估计价值函数(ValueFunction)或策略(Policy)。原理如下:
采样轨迹:从初始状态开始,根据当前策略与环境交互,生成完整的轨迹(episode)。
估计价值函数:使用轨迹的累积回报(Return)来估计状态或状态-动作对的价值。
策略改进:根据估计的价值函数,使用策略改进方法(如ε-greedy策略)来更新策略。
2、动态规划
动态规划方法通过利用环境的动态模型(状态转移概率和奖励函数)来计算最优策略。核心在于每一步求一个当前子问题的max,然后逐层往上迭代。缺点是需要环境已知,无需探索就知道每个动作对应的反馈。原理如下:
策略评估:根据当前策略,计算状态或状态-动作对的价值函数。
策略改进:根据价值函数,使用贪心策略或其他策略改进方法来更新策略。
迭代优化:通过反复进行策略评估和策略改进,逐步优化策略,直到收敛到最优策略。
关于动态规划解决实际问题的案例以及代码请移步我的另一篇总结:
3、时序差分
时序差分方法结合了蒙特卡洛方法和动态规划方法的特点,通过采样部分轨迹来估计价值函数或策略。原理如下:
采样部分轨迹:从初始状态开始,根据当前策略与环境交互,生成部分轨迹。
估计价值函数:使用时序差分更新公式(如TD(0)、TD(λ))来估计状态或状态-动作对的价值。
策略改进:根据估计的价值函数,使用策略改进方法(如ε-greedy策略)来更新策略。
4、关键概念与差别分析
无论是哪种思路,都涉及到了价值函数和策略。
(1)状态价值函数:v=f(s),表示从该状态s开始,遵循当前策略所能获得的期望累积回报。
(2)状态-动作价值函数:v=f(s, a),表示在状态s下采取动作a,并遵循当前策略所能获得的期望累积回报。
(3)确定性策略:π(s) = a,在确定性策略中,每个状态s对应一个确定的动作a。这意味着在状态s下,智能体总是选择动作a。
(4)随机性策略:π(a|s),在随机性策略中,每个状态s对应一个动作的概率分布,表示为π(a|s)。这意味着在状态s下,智能体根据概率分布选择动作a。比如在走迷宫游戏中,π(向上|(x, y)) = 0.7表示在位置(x, y)下,智能体有70%的概率选择向上移动。
蒙特卡洛不需要环境的动态模型,直接通过采样轨迹来学习。但需要完整的轨迹才能进行价值估计,适用于回合制任务。完整轨迹指的是,基于当前策略(如右移动),一直走到出口或走不动为止。然后才根据结果更新价值函数和策略。因此,可以看到,这类算法的反馈即使在后期也存在较大振荡。(我的理解是,前999步累积了很高的一个正反馈,最后一步是死胡同,一下全部变成负反馈了。)使用该思想的算法有DQN、Reinforce。动态规划需要环境的动态模型,即状态转移概率和奖励函数。需要计算所有状态和动作的值,计算复杂度较高。在动态规划中通常用于求解马尔可夫决策过程。时序差分不需要完整的轨迹,适用于连续任务。由于使用部分轨迹进行更新,估计结果的方差较低(振荡小)。代表算法PPO。
三、强化学习的几种实现方法
以一个游戏为例,展示几种强化学习的区别。模型需要学习如何移动下面的黑色物体从而使木棍不倒。这是一个动态交互的过程,非常适合使用强化学习来完成任务。
1、DQN(value-base,变种:DoubleDQN,DuelingDQN)
DQN的核心思想是将深度神经网络(Deep Neural Network, DNN)与Q-learning算法结合,以处理高维状态空间的问题。传统的Q-learning算法适用于离散状态和动作空间,但当状态空间非常大时(例如图像输入),传统的表格方法(Q-table)变得不可行。DQN通过使用深度神经网络来近似Q函数,从而解决了这个问题。
(1)DQN大致过程:
① 初始化
- 初始化主网络和目标网络的参数。
- 初始化经验回放缓冲区。
② 交互与采样
- 智能体在环境中执行动作,并记录经验(状态、动作、奖励、下一个状态)到经验回放缓冲区。
- 从经验回放缓冲区中随机采样一批经验用于训练。
③ 计算目标Q值
- 使用目标网络计算下一个状态的最大Q值:
max Q(s', a'; θ-)
,其中θ-
是目标网络的参数。 - 计算目标Q值:
y = r + γ * max Q(s', a'; θ-)
,其中r
是当前状态的奖励,γ
是折扣因子。
④ 更新主网络
- 使用目标Q值和主网络的预测Q值计算损失函数:
L = (y - Q(s, a; θ))^2
。 - 使用梯度下降法更新主网络的参数
θ
。
⑤ 更新目标网络
- 每隔一定步数,将主网络的参数
θ
复制到目标网络的参数θ-
。
核心代码:
python
class DQN:
def __init__(self, state_dim, hidden_dim, action_dim, lr, gama, epsilon, target_update, device, dqn_type='DQN'):
self.action_dim = action_dim
# 主网络和目标网络的结构是相同的
# 目标网络用于计算目标Q值,以减少Q值更新的波动性,提高训练的稳定性。
self.q_net = Qnet(state_dim, hidden_dim, action_dim).to(device)
self.target_q_net = Qnet(state_dim, hidden_dim, action_dim).to(device)
self.optimizer = torch.optim.Adam(self.q_net.parameters(), lr=lr)
self.gama = gama
self.epsilon = epsilon
self.target_update = target_update
self.count = 0
self.device = device
self.dqn_type = dqn_type
def take_action(self, state):
# 以ε的概率随机选择一个动作,可以探索新的动作
if np.random.random() < self.epsilon:
action = np.random.randint(self.action_dim)
else:
# 以1-ε的概率选择当前价值最高的动作,平衡探索与利用
# 也可以只选择当前价值最高的动作,前提是当前的价值函数估计比较准确
state = torch.tensor(state, dtype=torch.float).to(self.device)
action = self.q_net(state).argmax().item()
return action
def update(self, transition_dict):
states = torch.tensor(transition_dict['states'], dtype=torch.float).to(self.device)
actions = torch.tensor(transition_dict['actions']).view(-1, 1).to(self.device)
rewards = torch.tensor(transition_dict['rewards'], dtype=torch.float).view(-1, 1).to(self.device)
next_states = torch.tensor(transition_dict['next_states'], dtype=torch.float).to(self.device)
dones = torch.tensor(transition_dict['dones'], dtype=torch.float).view(-1, 1).to(self.device)
q_values = self.q_net(states).gather(1, actions)
max_next_q = self.target_q_net(next_states).max(1)[0].view(-1, 1)
q_target = rewards + self.gama * max_next_q * (1-dones)
dqn_loss = torch.mean(F.mse_loss(q_values, q_target))
self.optimizer.zero_grad()
dqn_loss.backward()
self.optimizer.step()
if self.count % self.target_update == 0:
# 传递网络参数,相当于目标网络的参数是主网络参数的延迟更新版本
self.target_q_net.load_state_dict(self.q_net.state_dict())
self.count += 1
(2)DoubleDQN
DoubleDQN是对DQN的一种改进,旨在解决DQN中的过估计(overestimation)问题。在DQN中,目标Q值的计算方式为y=r+γ*maxQ(s',a';θ-),
其中maxQ(s',a';θ-)
表示在下一个状态s'
下,使用目标网络参数θ-
计算的最大Q值。由于max
操作的存在,DQN容易高估下一个状态的Q值,尤其是在训练初期,Q值估计不准确时。这种高估可能导致次优策略,因为智能体可能会选择被高估的动作。
DoubleDQN通过引入两个独立的网络来计算目标Q值,从而减少过估计。具体说,DoubleDQN使用主网络(q_net)来选择动作,使用目标网络(tartget_q_net)来评估该动作的Q值。这也就是double的由来。
python
if self.dqn_type == 'DoubleDQN':
# 使用主网络的Q函数获取最大Q对应的动作
max_a = self.q_net(next_states).argmax(1).view(-1, 1)
# 使用目标网络评估Q值
max_next_q = self.target_q_net(next_states).gather(1, max_a).view(-1, 1)
else:
max_next_q = self.target_q_net(next_states).max(1)[0].view(-1, 1)
q_target = rewards + self.gama * max_next_q * (1 - dones)
(4)DuelingDQN
相对普通的DQN就是把神经网络由Qnet替换为VAnet,也就是替换价值函数
Q(s,a)=V(s)+A(s,a)
通过将Q值分解为状态值和优势值,DuelingDQN能够更好地表示状态的内在价值,从而提高对状态的区分能力。在某些情况下,状态值的变化比动作值的变化更为重要,DuelingDQN能够更好地捕捉这种变化。DuelingDQN通过优势值函数来表示动作的相对优势,从而提高对动作的区分能力。在动作空间较大的情况下,DuelingDQN能够更准确地选择最优动作。代码如下:
python
class Qnet(nn.Module):
def __init__(self, state_dim, hidden_dim, action_dim):
super().__init__()
self.fc1 = nn.Linear(state_dim, hidden_dim)
self.fc2 = nn.Linear(hidden_dim, action_dim)
def forward(self, x):
x = F.relu(self.fc1(x))
return self.fc2(x)
class VAnet(nn.Module):
def __init__(self, state_dim, hidden_dim, action_dim):
super().__init__()
self.fc1 = nn.Linear(state_dim, hidden_dim)
self.V = nn.Linear(hidden_dim, 1)
self.A = nn.Linear(hidden_dim, action_dim)
def forward(self, x):
if len(x.shape) == 1:
x = torch.unsqueeze(x, 0)
x = F.relu(self.fc1(x))
A = self.A(x)
V = self.V(x)
Q = V + A - A.mean(1).view(-1, 1)
return Q
2、Reinforce(policy_base)
REINFORCE算法是一种基于策略梯度的强化学习算法,通过直接优化策略函数来学习最优策略,适用于连续或离散的动作空间。具体来说,REINFORCE算法通过计算策略的梯度,并使用梯度上升法来更新策略参数,从而最大化期望回报。这个策略函数就是告诉你,在某个状态下最推荐某一个动作(概率最高的)。
算法优点是直接优化策略函数,适用于连续或离散的动作空间。原理简单,易于理解和实现。缺点是容易受到高方差问题的影响,导致训练不稳定。需要大量的样本进行训练,样本效率较低。
算法的流程可以概括为以下几个步骤:
①初始化 -初始化策略函数的参数θ
。
②交互与采样 -智能体在环境中执行动作,并记录轨迹(状态、动作、奖励)。-从轨迹中计算每个时间步的累积回报Gt
。
③计算策略梯度 -使用轨迹数据计算策略梯度:∇θJ(θ)=∑t=0T∇θlogπ(at|st;θ)Gt
。
④更新策略参数 -使用梯度上升法更新策略参数θ
:θ←θ+α∇θJ(θ),
其中α
是学习率。
⑤重复步骤2-4-重复步骤2-4,直到策略收敛或达到预定的迭代次数。
核心代码如下:
python
class REINFORCE:
def __init__(self, state_dim, hidden_dim, action_dim, lr, gama, device):
self.policy_net = PolicyNet(state_dim, hidden_dim, action_dim).to(device)
self.optimizer = torch.optim.Adam(self.policy_net.parameters(), lr=lr)
self.gama = gama
self.device = device
def take_action(self, state):
state = torch.tensor([state], dtype=torch.float).to(self.device)
# 经过网络后会输出每个类别(动作)的概率
probs = self.policy_net(state)
# 创建一个分类分布对象
action_dist = torch.distributions.Categorical(probs)
# 依据概率进行采样(多次采样后的概率满足提供的概率,但一次采样并不一定会选择概率较高的)
action = action_dist.sample()
return action.item()
def update(self, transition_dict):
reward_list = transition_dict['rewards']
state_list = transition_dict['states']
action_list = transition_dict['actions']
G = 0
self.optimizer.zero_grad()
# 采样到底,然后回填奖励,所以是逆序
for i in reversed(range(len(reward_list))):
r = reward_list[i]
state = torch.tensor([state_list[i]], dtype=torch.float).to(self.device)
action = torch.tensor([action_list[i]]).view(-1, 1).to(self.device)
log_prob = torch.log(self.policy_net(state).gather(1, action))
# 累计回报
G = self.gama * G + r
# 交叉熵
loss = -log_prob * G
loss.backward()
self.optimizer.step()
3、PPO(Actor-Critic,结合value和policy)
PPO(Proximal Policy Optimization,近端策略优化),旨在通过一种稳定且高效的方式来优化策略,避免传统策略梯度方法中可能出现的策略更新过大或不稳定的问题。
PPO算法的步骤如下:
① 收集数据
使用当前策略与环境交互,收集一批轨迹数据。每个轨迹包含状态、动作、奖励和下一个状态等信息。
② 计算优势函数
使用收集到的轨迹数据计算优势函数。优势函数可以通过多种方法计算,例如使用广义优势估计(GAE)。
③ 计算裁剪目标函数
使用当前策略和收集到的数据计算裁剪目标函数。
④ 更新策略
使用裁剪目标函数进行策略更新。通常使用梯度上升方法来最大化裁剪目标函数。
⑤ 重复步骤
重复上述步骤,直到策略收敛或达到预定的迭代次数。
核心代码如下:
python
class PPO:
def __init__(self, state_dim, hidden_dim, action_dim, actor_lr, critic_lr, lmbda, epochs, eps, gama, device):
# 学习策略
self.actor = PolicyNet(state_dim, hidden_dim, action_dim).to(device)
# 学习价值
self.critic = ValueNet(state_dim, hidden_dim).to(device)
self.opt_actor = torch.optim.Adam(self.actor.parameters(), lr=actor_lr)
self.opt_critic = torch.optim.Adam(self.critic.parameters(), lr=critic_lr)
self.gama = gama
self.lmbda = lmbda
self.epochs = epochs
self.eps = eps
self.device = device
def take_action(self, state):
state = torch.tensor([state], dtype=torch.float).to(self.device)
probs = self.actor(state)
action_dict = torch.distributions.Categorical(probs)
action = action_dict.sample()
return action.item()
def update(self, transition_dict):
states = torch.tensor(transition_dict['states'], dtype=torch.float).to(self.device)
actions = torch.tensor(transition_dict['actions']).view(-1, 1).to(self.device)
rewards = torch.tensor(transition_dict['rewards'], dtype=torch.float).view(-1, 1).to(self.device)
next_states = torch.tensor(transition_dict['next_states'], dtype=torch.float).to(self.device)
dones = torch.tensor(transition_dict['dones'], dtype=torch.float).view(-1, 1).to(self.device)
td_target = rewards + self.gama * self.critic(next_states) * (1 - dones)
td_delta = td_target - self.critic(states)
advantage = compute_advantage(self.gama, self.lmbda, td_delta.cpu()).to(self.device)
old_log_probs = torch.log(self.actor(states).gather(1, actions)).detach()
for _ in range(self.epochs):
log_probs = torch.log(self.actor(states).gather(1, actions))
ratio = torch.exp(log_probs - old_log_probs)
surr1 = ratio * advantage
# ppo的截断体现在这里, 限制梯度的范围
surr2 = torch.clamp(ratio, 1-self.eps, 1+self.eps) * advantage
actor_loss = torch.mean(-torch.min(surr1, surr2))
critic_loss = torch.mean(F.mse_loss(self.critic(states), td_target.detach()))
self.opt_actor.zero_grad()
self.opt_critic.zero_grad()
actor_loss.backward()
critic_loss.backward()
self.opt_actor.step()
self.opt_critic.step()
通过对比训练过程的反馈值趋势可知,PPO的效率很高,episode为100时就能达到较高的奖励。
四、人类反馈强化学习RLHF
RLHF(Reinforcement Learning from Human Feedback)是一种结合了人类反馈的强化学习方法。与传统的强化学习算法不同,RLHF通过引入人类专家或用户的反馈来增强学习过程,从而提高学习效率和策略质量。人类反馈可以是显式的(如评分、标签)或隐式的(如示范、纠正)。
以上知识都是掌握大模型所必须的基础,也是私有化部署所需要用到的技术。在GPT3.5以前,大模型并没有引起什么大的关注。主要就是它的回答太像机器人。而在3.5以后,我们突然发现,这个智能体居然能像人类一样同我们交流,机器的痕迹已经很淡了。虽然它回复的内容总体没有变化。另外,大模型一般只适用于通用场景,具体业务场景都需要给一些数据、示例来进行微调,机器人回复的语气也可以根据实际场景进行调整。这些都会涉及强化学习。
3种强化学习实现代码都放在了网盘里,代码包含关键点的注释。请扫码或关注同名公众号后,在资源获取栏选择【语言模型】获取。后续还会更新大模型微调的代码。