当前位置: 首页 > news >正文

一文详解策略梯度算法(REINFORCE)—强化学习(8)

目录

1、什么是基于价值的方法?什么是基于策略的方法?

1.1、基于价值的方法(Value-based Methods):先算 “好处”,再选行动

1.1.1、 核心逻辑

1.1.2、 关键概念:价值函数的类型

1.1.3、 动作选择逻辑

1.1.4、 典型算法与例子

1.1.5、优缺点

1.2、基于策略的方法(Policy-based Methods):直接学 “怎么做”,不管后果

1.2.1、核心逻辑

1.2.2、 动作选择逻辑

1.2.3、典型算法与例子

1.2.4、优缺点

1.3、核心区别对比

2、核心思想:直接优化策略

2.1、 什么是 “策略”?

2.2、 与基于价值的方法对比

3、数学基础:策略梯度的推导

3.1、 目标函数:累积奖励的期望

3.2、 策略梯度公式推导

3.3、 核心结论

4、经典算法:REINFORCE(基础策略梯度)

4.1、 算法流程

4.2、 代码核心逻辑(对应之前的 REINFORCE 实现)

4.3、 特点与问题

5、详解代码

6、实验结果


1、什么是基于价值的方法?什么是基于策略的方法?

1.1、基于价值的方法(Value-based Methods):先算 “好处”,再选行动

想象你在玩一款闯关游戏,每一步选择(比如往左走、往右走、打怪)都会影响你最终能不能通关、能拿多少分。
基于价值的方法会先给每个 “局面” 打分 —— 这个分数代表 “在这个局面下,只要好好玩,最后能得到的好处有多大”。
然后,它的决策逻辑很简单:在当前局面下,选那些能让你进入 “更高分局面” 的行动

 

比如:

  • 现在你面前有两条路,左边的路对应的 “未来好处分” 是 80 分,右边是 60 分。
  • 那它就会选左边的路。

核心:先判断 “每个选择的最终价值”,再跟着高价值走。

基于价值的方法的核心是学习 “价值函数”,通过价值函数间接指导动作选择。

1.1.1、 核心逻辑

价值函数(Value Function)是强化学习中的核心概念,它量化了 “在某个状态下采取动作(或遵循策略)能获得的长期累积奖励的期望”。基于价值的方法通过学习价值函数,最终根据价值函数的输出选择 “最优动作”(即能带来最高价值的动作)。

1.1.2、 关键概念:价值函数的类型

  • 状态价值函数 V^\pi(s):在状态 s 下,遵循策略 \pi 能获得的累积奖励的期望。
  • 动作价值函数 Q^\pi(s,a):在状态 s 下采取动作 a,之后遵循策略 \pi 能获得的累积奖励的期望(更常用,因为直接关联 “状态 - 动作对”)。

基于价值的方法通常聚焦于学习 Q 函数(动作价值函数),因为它能直接告诉我们 “在某个状态下做什么动作更好”。

1.1.3、 动作选择逻辑

当学习到最优的 Q 函数(记为 Q^\pi(s,a))后,在任意状态 s 下,只需选择使 Q^*(s,a)最大的动作 a 即可,即:a^* = \arg\max_a Q^*(s,a)

1.1.4、 典型算法与例子

  • Q-learning:直接学习最优动作价值函数Q^*(s,a),不依赖当前策略,属于 “异策略”(Off-policy)方法。
  • SARSA:学习当前策略下的动作价值函数 Q^\pi(s,a),属于 “同策略”(On-policy)方法。
  • Deep Q-Networks(DQN):用深度神经网络近似 Q(s,a),解决高维状态空间(如 Atari 游戏画面)的问题。

1.1.5、优缺点

  • 优点
    • 思路直观,通过价值函数可直接判断动作的优劣;
    • 在离散动作空间中表现稳定(因为选最大价值动作简单)。
  • 缺点
    • 难以处理连续动作空间(因为连续空间中 “找最大价值动作” 需要复杂的优化,如数值搜索);
    • 价值函数的估计可能存在偏差(如 DQN 中因经验回放和目标网络更新延迟导致的偏差)。

1.2、基于策略的方法(Policy-based Methods):直接学 “怎么做”,不管后果

还是玩闯关游戏,但这种方法不先算 “每个局面的价值”,而是直接学一套 “行动规则”。
比如通过大量练习,它总结出:“看到 A 怪物就后退,看到 B 道具就捡,在 C 路口就往右拐”—— 这套规则就叫 “策略”。
哪怕有时候按这套规则做,短期看可能吃亏(比如绕了点路),但它还是会坚持按规则来,因为这套规则是长期试错后 “大概率能赢” 的总结。

 

核心:直接记住 “在什么情况下该做什么”,不纠结 “这么做能有多少好处”。

基于策略的方法的核心是直接学习 “策略函数”,通过策略函数直接输出动作。

1.2.1、核心逻辑

策略函数(Policy Function)是从 “状态” 到 “动作” 的映射,分为两种类型:

  • 确定性策略a = \pi(s)(给定状态 s,直接输出唯一动作 a);
  • 随机性策略\pi(a|s)(给定状态 s,输出动作 a 的概率分布)。

基于策略的方法通过优化策略函数的参数,使策略对应的累积奖励期望最大化,最终得到最优策略 \pi^*

1.2.2、 动作选择逻辑

  • 对于确定性策略:直接输出动作a = \pi(s)
  • 对于随机性策略:从概率分布 \pi(a|s)中采样动作(兼顾探索与利用)。

1.2.3、典型算法与例子

  • REINFORCE:通过 “蒙特卡洛采样” 估计策略的梯度,用梯度上升法优化策略参数(同策略方法)。
  • Proximal Policy Optimization(PPO):通过限制策略更新的幅度(“信任域”),解决 REINFORCE 中梯度估计方差大的问题,是目前最常用的强化学习算法之一。
  • Deterministic Policy Gradient(DPG):针对确定性策略的梯度优化方法,适合连续动作空间。

1.2.4、优缺点

  • 优点
    • 天然适合连续动作空间(随机性策略可直接输出概率分布,无需搜索最大价值动作);
    • 策略更新更直接,避免了价值函数估计的偏差问题。
  • 缺点
    • 梯度估计的方差较大(需通过 “基线函数” 或 “ Actor-Critic 框架” 缓解);
    • 在离散动作空间中,可能不如基于价值的方法高效。

1.3、核心区别对比

维度基于价值的方法基于策略的方法
学习对象动作价值函数 Q(s,a)策略函数 \pi(a|s) 或 \pi(s)
动作选择依据选择 Q(s,a) 最大的动作策略直接输出动作(或采样动作)
连续动作空间适配性较差(需额外优化搜索最大 Q 的动作)较好(随机性策略可直接输出概率分布)
探索方式依赖 epsilon-贪心(如 DQN)随机性策略天然支持探索(从分布中采样)
典型应用场景离散动作(如 Atari 游戏、围棋)连续动作(如机器人控制、自动驾驶)

2、核心思想:直接优化策略

在强化学习中,智能体的目标是学习一个 “策略”(Policy),即从 “状态” 到 “动作” 的映射,使长期累积奖励最大化。策略梯度算法的核心逻辑可概括为: 通过计算 “累积奖励对策略参数的梯度”,用梯度上升法不断调整参数,最终得到最优策略。

2.1、 什么是 “策略”?

策略是强化学习的核心概念,定义为智能体在给定状态下选择动作的规则,分为两种形式:

  • 确定性策略a = \pi_\theta(s)(给定状态s,直接输出唯一动作a,\theta为策略参数)。
  • 随机性策略\pi_\theta(a|s)(给定状态s,输出动作a的概率分布,策略参数\theta决定分布形状)。

策略梯度算法通常聚焦于随机性策略,因为随机性策略天然支持 “探索”(从概率分布中采样动作),更适合强化学习的试错过程。

2.2、 与基于价值的方法对比

维度基于价值的方法(如 DQN)策略梯度算法(基于策略)
优化目标学习价值函数\(Q^*(s,a)\),间接选最优动作直接优化策略\(\pi_\theta\),输出动作分布
动作选择选价值最大的动作(\(\arg\max_a Q(s,a)\))从策略分布中采样动作(兼顾探索与利用)
连续动作适配性差(需额外优化搜索)好(直接输出概率分布)
训练稳定性较稳定(价值函数平滑)波动大(依赖轨迹采样,方差高)

3、数学基础:策略梯度的推导

策略梯度算法的核心是计算累积奖励期望对策略参数\theta的梯度,并通过梯度上升最大化这个期望。

3.1、 目标函数:累积奖励的期望

设智能体与环境交互产生的轨迹为\tau = (s_0,a_0,r_0,s_1,a_1,r_1,...,s_T,a_T,r_T),轨迹的累积奖励为R(\tau) = \sum_{t=0}^T \gamma^t r_t\gamma为折扣因子)。

策略梯度的目标是最大化 “策略\pi_\theta产生的累积奖励期望”:J(\theta) = \mathbb{E}_{\tau \sim \pi_\theta} [R(\tau)]其中\mathbb{E}_{\tau \sim \pi_\theta}表示对所有可能轨迹的期望(轨迹由策略\pi_\theta生成)。

3.2、 策略梯度公式推导

目标是求\nabla_\theta J(\theta)(策略梯度),关键步骤如下:

  • 步骤 1:展开期望 轨迹的概率P(\tau|\theta) = \mu(s_0) \prod_{t=0}^T \pi_\theta(a_t|s_t) P(s_{t+1}|s_t,a_t)(\mu(s_0)是初始状态分布,P(s_{t+1}|s_t,a_t)是环境动力学)。 目标函数可写为: J(\theta) = \sum_\tau P(\tau|\theta) R(\tau)

  • 步骤 2:求梯度 根据导数链式法则:\nabla_\theta J(\theta) = \sum_\tau \nabla_\theta P(\tau|\theta) R(\tau)

  • 步骤 3:对数导数技巧 利用\nabla_\theta P(\tau|\theta) = P(\tau|\theta) \nabla_\theta \log P(\tau|\theta)(对数导数性质),化简得: \nabla_\theta J(\theta) = \mathbb{E}_{\tau \sim \pi_\theta} [\nabla_\theta \log P(\tau|\theta) \cdot R(\tau)]

  • 步骤 4:简化轨迹概率的对数导数 轨迹概率的对数为\log P(\tau|\theta) = \log \mu(s_0) + \sum_{t=0}^T \log \pi_\theta(a_t|s_t) + \sum_{t=0}^T \log P(s_{t+1}|s_t,a_t)。 由于环境动力学P(s_{t+1}|s_t,a_t)\theta无关,其导数为 0,因此:\nabla_\theta \log P(\tau|\theta) = \sum_{t=0}^T \nabla_\theta \log \pi_\theta(a_t|s_t)

  • 最终策略梯度公式 合并上述结果,策略梯度为:\nabla_\theta J(\theta) = \mathbb{E}_{\tau \sim \pi_\theta} \left[ \left( \sum_{t=0}^T \nabla_\theta \log \pi_\theta(a_t|s_t) \right) \cdot R(\tau) \right]

3.3、 核心结论

策略梯度的直观含义是:对每个状态 - 动作对(s_t,a_t),用 “该动作的对数概率梯度” 乘以 “整个轨迹的累积奖励”,再求期望

实际计算中,用蒙特卡洛采样(即实际轨迹)近似期望,得到可计算的梯度:\nabla_\theta J(\theta) \approx \frac{1}{N} \sum_{i=1}^N \left( \sum_{t=0}^T \nabla_\theta \log \pi_\theta(a_{i,t}|s_{i,t}) \right) \cdot R(\tau_i) 其中N是采样的轨迹数量,\tau_i是第i条轨迹。

4、经典算法:REINFORCE(基础策略梯度)

REINFORCE 是最基础的策略梯度算法,用单条轨迹的累积奖励直接估计梯度,步骤如下:

4.1、 算法流程

  1. 采样轨迹:用当前策略\pi_\theta与环境交互,收集一条完整轨迹\tau = (s_0,a_0,r_0,...,s_T,a_T,r_T)
  2. 计算累积奖励:对每条轨迹计算折扣累积奖励G_t = \sum_{k=t}^T \gamma^{k-t} r_k(从时刻t到结束的总奖励)。
  3. 计算梯度:对每个时刻t,计算损失L_t = -\log \pi_\theta(a_t|s_t) \cdot G_t(负号将梯度上升转为梯度下降优化)。
  4. 更新参数:对所有时刻的损失求和,反向传播更新策略参数\theta

4.2、 代码核心逻辑(对应之前的 REINFORCE 实现)

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))):  # 逆序计算Greward = reward_list[i]state = torch.tensor([state_list[i]], dtype=torch.float).to(device)action = torch.tensor([action_list[i]]).view(-1, 1).to(device)# 动作的对数概率log_prob = torch.log(self.policy_net(state).gather(1, action))G = self.gamma * G + reward  # 折扣累积奖励# 损失:-log_prob * G(梯度上升→转为梯度下降)loss = -log_prob * Gloss.backward()  # 累积梯度self.optimizer.step()  # 梯度上升更新参数

4.3、 特点与问题

  • 优点:原理简单,直接优化目标函数,理论上可收敛到全局最优。
  • 问题
    • 高方差:单条轨迹的累积奖励波动大,导致梯度估计不稳定。
    • 样本效率低:需要大量轨迹才能得到可靠的梯度。

5、详解代码

"""
文件名: 9.1
作者: 墨尘
日期: 2025/7/22
项目名: d2l_learning
备注: 使用REINFORCE算法(蒙特卡洛策略梯度)解决CartPole-v0环境CartPole任务:通过左右移动小车,使杆子保持直立,每保持1步得1分,目标是最大化得分
"""
# 导入必要库
import gym  # 强化学习环境库
import torch  # 深度学习框架(用于构建神经网络)
import torch.nn.functional as F  # 神经网络功能函数(激活函数、损失函数等)
import numpy as np  # 数值计算库(处理数组和矩阵)
import matplotlib.pyplot as plt  # 绘图库(可视化训练结果)
from tqdm import tqdm  # 进度条库(显示训练进度)
import rl_utils  # 自定义强化学习工具库(包含经验回放、移动平均等功能)class PolicyNet(torch.nn.Module):"""策略网络:输入状态,输出动作的概率分布"""def __init__(self, state_dim, hidden_dim, action_dim):super(PolicyNet, self).__init__()  # 继承父类构造函数# 第一层全连接层:输入状态维度 -> 隐藏层维度(提取状态特征)self.fc1 = torch.nn.Linear(state_dim, hidden_dim)# 第二层全连接层:隐藏层维度 -> 动作维度(输出每个动作的"未归一化概率")self.fc2 = torch.nn.Linear(hidden_dim, action_dim)def forward(self, x):"""前向传播:输入状态张量,输出动作概率分布"""x = F.relu(self.fc1(x))  # 隐藏层用ReLU激活(引入非线性,增强拟合能力)# 输出层用softmax归一化,将未归一化概率转为概率分布(每行和为1)return F.softmax(self.fc2(x), dim=1)  # dim=1表示按行归一化class REINFORCE:"""REINFORCE算法实现:蒙特卡洛策略梯度算法"""def __init__(self,state_dim,  # 状态维度(CartPole为4:位置、速度、角度、角速度)hidden_dim,  # 策略网络隐藏层维度action_dim,  # 动作维度(CartPole为2:左移、右移)learning_rate,  # 学习率(控制参数更新速度)gamma,  # 折扣因子(权衡当前奖励和未来奖励)device):  # 计算设备(CPU或GPU)# 初始化策略网络(主网络,直接输出动作概率)self.policy_net = PolicyNet(state_dim, hidden_dim, action_dim).to(device)# 优化器(Adam优化器,用于更新策略网络参数)self.optimizer = torch.optim.Adam(self.policy_net.parameters(), lr=learning_rate)self.gamma = gamma  # 折扣因子(如0.98:未来奖励随时间衰减)self.device = device  # 计算设备def take_action(self, state):"""根据当前状态选择动作(ε-探索的替代方案:基于概率分布采样)"""# 将状态转换为张量(形状:[1, state_dim]),并移动到指定设备state = torch.tensor([state], dtype=torch.float).to(self.device)# 策略网络输出动作概率分布(形状:[1, action_dim])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()  # 清空优化器中的梯度(避免累积旧梯度)# 逆序遍历轨迹(从最后一步到第一步)# 原因:累积回报G_t = r_t + γ·G_{t+1},逆序计算更高效for i in reversed(range(len(reward_list))):# 当前步的奖励(r_t)reward = reward_list[i]# 当前步的状态(s_t),转换为张量state = torch.tensor([state_list[i]], dtype=torch.float).to(self.device)# 当前步的动作(a_t),转换为张量并调整形状为[1,1]action = torch.tensor([action_list[i]]).view(-1, 1).to(self.device)# 1. 计算当前动作的对数概率(log(π(a_t|s_t)))# policy_net(state)输出动作概率分布,gather(1, action)提取当前动作的概率# 例如:动作概率为[0.3, 0.7],动作是1(右移),则提取0.7,取对数为log(0.7)log_prob = torch.log(self.policy_net(state).gather(1, action))# 2. 计算累积回报G(逆序递推)# 公式:G_t = r_t + γ·G_{t+1}(最后一步G=0 + r_T)G = self.gamma * G + reward# 3. 计算策略梯度损失(核心公式)# 损失 = -log(π(a_t|s_t)) * G_t# 负号:因为PyTorch默认最小化损失,而我们需要最大化累积回报(梯度上升)# G_t:奖励越高,该动作的"重要性"越大,梯度更新幅度越大loss = -log_prob * G# 4. 反向传播计算梯度(累积所有时间步的梯度)# 注意:这里没有立即更新参数,而是累积所有步的梯度后统一更新loss.backward()# 5. 梯度上升更新策略网络参数(所有时间步的梯度累积后,统一更新一次)self.optimizer.step()if __name__ == '__main__':# 超参数设置learning_rate = 1e-3  # 学习率(1e-3:较常用的初始值)num_episodes = 1000   # 训练总回合数(越多越可能收敛,但耗时更长)hidden_dim = 128      # 策略网络隐藏层维度(128:兼顾表达能力和计算效率)gamma = 0.98          # 折扣因子(0.98:适度重视未来奖励)# 选择计算设备(优先GPU,无则用CPU)device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")# 创建CartPole环境(v0版本:最大步数200,超过则强制终止)env_name = "CartPole-v0"env = gym.make(env_name)  # 创建环境实例# 设置随机种子(保证实验可复现)state, _ = env.reset(seed=0)  # 初始化环境并设置种子,返回初始状态和信息torch.manual_seed(0)  # PyTorch随机种子np.random.seed(0)     # NumPy随机种子# 获取环境状态和动作维度state_dim = env.observation_space.shape[0]  # 状态维度(CartPole为4)action_dim = env.action_space.n  # 动作数量(CartPole为2)# 初始化REINFORCE智能体agent = REINFORCE(state_dim, hidden_dim, action_dim, learning_rate, gamma, device)# 训练循环return_list = []  # 记录每个回合的总奖励(用于评估训练效果)# 分10轮训练(每轮100个回合,便于显示进度)for i in range(10):# 进度条:显示当前轮次的训练进度with tqdm(total=int(num_episodes / 10), desc='Iteration %d' % i) as pbar:# 每轮训练100个回合for i_episode in range(int(num_episodes / 10)):episode_return = 0  # 记录当前回合的总奖励# 经验字典:存储当前回合的轨迹数据transition_dict = {'states': [],    # 状态列表'actions': [],   # 动作列表'next_states': [],  # 下一状态列表'rewards': [],   # 奖励列表'dones': []      # 终止标志列表}# 重置环境,获取初始状态(Gym v0.26+返回元组:(state, info))state, _ = env.reset(seed=i_episode)  # 每个回合用不同种子,增强泛化性done = False  # 回合终止标志(初始为False)# 循环执行动作,直到回合终止while not done:# 智能体根据当前状态选择动作action = agent.take_action(state)# 执行动作,获取下一状态、奖励等(Gym v0.26+返回5个值)# next_state:下一状态;reward:当前奖励;# terminated:是否因任务目标终止;truncated:是否因超时终止next_state, reward, terminated, truncated, _ = env.step(action)# 合并终止条件(任务结束或超时均视为回合结束)done = terminated or truncated# 存储当前步的经验到字典transition_dict['states'].append(state)transition_dict['actions'].append(action)transition_dict['next_states'].append(next_state)transition_dict['rewards'].append(reward)transition_dict['dones'].append(done)# 更新状态和总奖励state = next_stateepisode_return += reward  # 累积当前回合的奖励# 回合结束后,记录总奖励return_list.append(episode_return)# 用当前回合的轨迹数据更新策略网络agent.update(transition_dict)# 每10个回合显示一次平均奖励(监控训练效果)if (i_episode + 1) % 10 == 0:pbar.set_postfix({'episode':  # 当前总回合数'%d' % (num_episodes / 10 * i + i_episode + 1),'return':  # 最近10回合的平均奖励(平滑波动)'%.3f' % np.mean(return_list[-10:])})pbar.update(1)  # 进度条更新# 绘制训练结果:奖励曲线episodes_list = list(range(len(return_list)))  # 回合索引列表# 绘制原始奖励曲线plt.plot(episodes_list, return_list)plt.xlabel('Episodes')  # 横轴:回合数plt.ylabel('Returns')   # 纵轴:总奖励plt.title('REINFORCE on {}'.format(env_name))  # 标题:环境名称plt.show()# 绘制移动平均奖励曲线(平滑噪声,更易观察趋势)mv_return = rl_utils.moving_average(return_list, 9)  # 9点移动平均plt.plot(episodes_list, mv_return)plt.xlabel('Episodes')plt.ylabel('Returns')plt.title('REINFORCE on {}'.format(env_name))plt.show()

6、实验结果

 

http://www.dtcms.com/a/292196.html

相关文章:

  • 新手向:基于Python的剪贴板历史增强工具
  • Jiasou TideFlow AIGC SEO Agent:全自动外链构建技术重构智能营销新标准
  • 数据库 × 缓存双写策略深度剖析:一致性如何保障?
  • Apache Ignite缓存基本操作
  • Redis原理之缓存
  • uni-calendar自定义签到打卡颜色
  • Java-79 深入浅出 RPC Dubbo Dubbo 动态路由架构详解:从规则设计到上线系统集成
  • .NET 8.0 中有哪些新的变化?
  • 数据结构自学Day12-- 排序算法2
  • 前端面试专栏-工程化:29.微前端架构设计与实践
  • Vue 3 面试题全套题库
  • Vue项目中的AJAX请求与跨域问题解析
  • paddleocr微调训练学习笔记
  • 符号绑定详解:ES6模块中的“诡异”现象与内存机制
  • Java从入门到精通!第十一天(Java常见的数据结构)
  • vite+vue3自研框架:自定义本地运行端口、自动打开浏览器等
  • SecretFlow (3) --- 添加合作方并创建项目
  • 在 Linux 系统中基于 Nginx 搭建 openlab 网站及子页面
  • MySQL(151)什么是MySQL的二级索引?
  • 【Java SE】Object类
  • python小工具:测内网服务器网速和延迟
  • MySQL 8.0 中 LIMIT 优化新特性
  • 探索飞算JavaAI:AI赋能Java开发的新范式
  • haproxy的负载均衡集群搭建
  • 自研能管项目开发界面
  • 小白成长之路-部署Zabbix7
  • web登录页面
  • spring boot 异步线程@Async 传递 threadLocal数据
  • find / -name “ssl.h“ 2>/dev/null
  • Tailwind CSS快速上手 Tailwind CSS的安装、配置、使用