1. 为什么需要Policy Gradient在强化学习领域我们通常会把算法分为Value-Based和Policy-Based两大类。你可能已经熟悉了DQN、DDQN这些基于价值函数的算法它们确实在很多场景下表现不错。但我在实际项目中发现当遇到连续动作空间或者需要随机策略的场景时这些方法就显得力不从心了。举个例子在开发一个简单的机械臂控制程序时我发现传统的DQN根本无法处理机械臂关节的连续转动角度。每次尝试用离散化的动作空间要么控制精度不够要么动作空间爆炸式增长。这时候Policy Gradient的优势就显现出来了——它可以直接输出动作的概率分布完美适配连续控制场景。另一个常见误区是很多人认为强化学习必须通过价值函数才能找到最优策略。其实不然就像我们学骑自行车不需要计算每个动作的价值而是直接学习如何保持平衡。Policy Gradient正是采用了这种更直接的思路通过参数化的策略函数直接优化策略。2. Policy Gradient核心原理剖析2.1 策略函数的数学表示Policy Gradient的核心在于策略函数的参数化表示。对于离散动作我们通常使用Softmax策略def softmax_policy(obs, theta): features extract_features(obs) scores np.dot(features, theta) exp_scores np.exp(scores - np.max(scores)) # 数值稳定性处理 return exp_scores / np.sum(exp_scores)而对于连续动作空间Gaussian策略更为常见。我在自动驾驶项目中就采用了这种形式def gaussian_policy(obs, theta): mean np.dot(extract_features(obs), theta) return np.random.normal(mean, sigma) # sigma可以是固定值或可学习参数2.2 策略梯度的推导关键理解策略梯度的关键在于score function技巧。这个技巧的精妙之处在于它把策略梯度的计算转化为对策略函数对数梯度的期望∇J(θ) E[∇logπ(a|s) * Q(s,a)]这个公式看似简单却蕴含着一个重要特性即使我们不知道环境模型状态转移概率也能通过采样来估计梯度。在实际编码时这个特性让我们能够处理真实世界中的复杂环境。我曾在机器人路径规划项目中使用这个技巧即使不知道障碍物的精确物理特性算法也能通过试错学习到有效的避障策略。3. 实战CartPole的Policy Gradient实现3.1 环境搭建与策略网络让我们以经典的CartPole环境为例实现一个完整的Policy Gradient算法。首先定义策略网络import numpy as np import gym class PolicyNetwork: def __init__(self, n_features, n_actions, lr0.01): self.theta np.random.randn(n_features, n_actions) * 0.1 self.lr lr def get_action(self, obs): score np.dot(obs, self.theta) probs np.exp(score) / np.sum(np.exp(score)) return np.random.choice(len(probs), pprobs), probs这个简单的线性策略网络已经足够解决CartPole问题。在实际项目中对于更复杂的环境我通常会使用多层神经网络作为策略函数。3.2 梯度计算与参数更新Policy Gradient的核心在于如何计算梯度并更新参数。以下是实现代码def train_episode(env, policy, gamma0.99): obs env.reset() rewards [] grads [] probs_history [] while True: action, probs policy.get_action(obs) next_obs, reward, done, _ env.step(action) # 存储梯度信息 grad np.zeros_like(policy.theta) grad[:, action] obs grad - np.outer(obs, probs) grads.append(grad) rewards.append(reward) probs_history.append(probs[action]) if done: break obs next_obs # 计算折扣回报 discounted_rewards [] running_add 0 for r in rewards[::-1]: running_add running_add * gamma r discounted_rewards.insert(0, running_add) # 归一化奖励 discounted_rewards (discounted_rewards - np.mean(discounted_rewards)) / np.std(discounted_rewards) # 更新参数 for grad, reward in zip(grads, discounted_rewards): policy.theta policy.lr * grad * reward return sum(rewards)这里有几个关键点需要注意梯度计算采用了score function技巧使用了折扣回报并进行了归一化处理参数更新方向是梯度上升4. 高级技巧与实战经验4.1 基线技巧降低方差原始的Policy Gradient算法方差很大我在实际项目中发现添加基线可以显著提升稳定性。最常见的基线是状态价值函数V(s)# 修改梯度更新部分 advantage discounted_rewards - baseline_values for grad, adv in zip(grads, advantage): policy.theta policy.lr * grad * adv4.2 连续动作空间处理对于连续动作空间比如自动驾驶的转向控制Gaussian策略特别有用。以下是实现示例class GaussianPolicy: def __init__(self, n_features, action_dim, lr0.01): self.theta np.random.randn(n_features, action_dim) * 0.1 self.sigma 1.0 # 可以设为可学习参数 self.lr lr def get_action(self, obs): mean np.dot(obs, self.theta) action np.random.normal(mean, self.sigma) return action, mean, self.sigma在机器人控制项目中这种参数化方式让我能够精确控制关节角度而离散动作方法根本无法达到这种精度。4.3 实际项目中的调参经验经过多个项目的实践我总结出几个关键调参技巧学习率设置通常从1e-3开始尝试观察训练曲线调整折扣因子γ对于回合制任务0.9-0.99比较合适批次大小使用小批次(10-100个episode)更新更稳定策略初始化参数初始值不宜过大避免过早陷入局部最优在开发工业机械臂控制系统时这些经验帮助我将训练时间缩短了约40%。特别是在学习率的选择上采用余弦退火策略取得了不错的效果。
Policy Gradient实战:从理论推导到代码实现
1. 为什么需要Policy Gradient在强化学习领域我们通常会把算法分为Value-Based和Policy-Based两大类。你可能已经熟悉了DQN、DDQN这些基于价值函数的算法它们确实在很多场景下表现不错。但我在实际项目中发现当遇到连续动作空间或者需要随机策略的场景时这些方法就显得力不从心了。举个例子在开发一个简单的机械臂控制程序时我发现传统的DQN根本无法处理机械臂关节的连续转动角度。每次尝试用离散化的动作空间要么控制精度不够要么动作空间爆炸式增长。这时候Policy Gradient的优势就显现出来了——它可以直接输出动作的概率分布完美适配连续控制场景。另一个常见误区是很多人认为强化学习必须通过价值函数才能找到最优策略。其实不然就像我们学骑自行车不需要计算每个动作的价值而是直接学习如何保持平衡。Policy Gradient正是采用了这种更直接的思路通过参数化的策略函数直接优化策略。2. Policy Gradient核心原理剖析2.1 策略函数的数学表示Policy Gradient的核心在于策略函数的参数化表示。对于离散动作我们通常使用Softmax策略def softmax_policy(obs, theta): features extract_features(obs) scores np.dot(features, theta) exp_scores np.exp(scores - np.max(scores)) # 数值稳定性处理 return exp_scores / np.sum(exp_scores)而对于连续动作空间Gaussian策略更为常见。我在自动驾驶项目中就采用了这种形式def gaussian_policy(obs, theta): mean np.dot(extract_features(obs), theta) return np.random.normal(mean, sigma) # sigma可以是固定值或可学习参数2.2 策略梯度的推导关键理解策略梯度的关键在于score function技巧。这个技巧的精妙之处在于它把策略梯度的计算转化为对策略函数对数梯度的期望∇J(θ) E[∇logπ(a|s) * Q(s,a)]这个公式看似简单却蕴含着一个重要特性即使我们不知道环境模型状态转移概率也能通过采样来估计梯度。在实际编码时这个特性让我们能够处理真实世界中的复杂环境。我曾在机器人路径规划项目中使用这个技巧即使不知道障碍物的精确物理特性算法也能通过试错学习到有效的避障策略。3. 实战CartPole的Policy Gradient实现3.1 环境搭建与策略网络让我们以经典的CartPole环境为例实现一个完整的Policy Gradient算法。首先定义策略网络import numpy as np import gym class PolicyNetwork: def __init__(self, n_features, n_actions, lr0.01): self.theta np.random.randn(n_features, n_actions) * 0.1 self.lr lr def get_action(self, obs): score np.dot(obs, self.theta) probs np.exp(score) / np.sum(np.exp(score)) return np.random.choice(len(probs), pprobs), probs这个简单的线性策略网络已经足够解决CartPole问题。在实际项目中对于更复杂的环境我通常会使用多层神经网络作为策略函数。3.2 梯度计算与参数更新Policy Gradient的核心在于如何计算梯度并更新参数。以下是实现代码def train_episode(env, policy, gamma0.99): obs env.reset() rewards [] grads [] probs_history [] while True: action, probs policy.get_action(obs) next_obs, reward, done, _ env.step(action) # 存储梯度信息 grad np.zeros_like(policy.theta) grad[:, action] obs grad - np.outer(obs, probs) grads.append(grad) rewards.append(reward) probs_history.append(probs[action]) if done: break obs next_obs # 计算折扣回报 discounted_rewards [] running_add 0 for r in rewards[::-1]: running_add running_add * gamma r discounted_rewards.insert(0, running_add) # 归一化奖励 discounted_rewards (discounted_rewards - np.mean(discounted_rewards)) / np.std(discounted_rewards) # 更新参数 for grad, reward in zip(grads, discounted_rewards): policy.theta policy.lr * grad * reward return sum(rewards)这里有几个关键点需要注意梯度计算采用了score function技巧使用了折扣回报并进行了归一化处理参数更新方向是梯度上升4. 高级技巧与实战经验4.1 基线技巧降低方差原始的Policy Gradient算法方差很大我在实际项目中发现添加基线可以显著提升稳定性。最常见的基线是状态价值函数V(s)# 修改梯度更新部分 advantage discounted_rewards - baseline_values for grad, adv in zip(grads, advantage): policy.theta policy.lr * grad * adv4.2 连续动作空间处理对于连续动作空间比如自动驾驶的转向控制Gaussian策略特别有用。以下是实现示例class GaussianPolicy: def __init__(self, n_features, action_dim, lr0.01): self.theta np.random.randn(n_features, action_dim) * 0.1 self.sigma 1.0 # 可以设为可学习参数 self.lr lr def get_action(self, obs): mean np.dot(obs, self.theta) action np.random.normal(mean, self.sigma) return action, mean, self.sigma在机器人控制项目中这种参数化方式让我能够精确控制关节角度而离散动作方法根本无法达到这种精度。4.3 实际项目中的调参经验经过多个项目的实践我总结出几个关键调参技巧学习率设置通常从1e-3开始尝试观察训练曲线调整折扣因子γ对于回合制任务0.9-0.99比较合适批次大小使用小批次(10-100个episode)更新更稳定策略初始化参数初始值不宜过大避免过早陷入局部最优在开发工业机械臂控制系统时这些经验帮助我将训练时间缩短了约40%。特别是在学习率的选择上采用余弦退火策略取得了不错的效果。