news 2026/10/2 2:02:56

PyTorch强化学习实战(27)——进化策略在强化学习中的应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PyTorch强化学习实战(27)——进化策略在强化学习中的应用

PyTorch强化学习实战(27)——进化策略在强化学习中的应用

    • 0. 前言
    • 1. 黑盒优化方法
    • 2. 进化策略
    • 3. 在 CartPole 环境中实现进化策略
    • 小结
    • 系列链接

0. 前言

在本节中,我们将改变对强化学习 (Reinforcement Learning, RL) 训练的视角,转而讨论黑盒优化方法,它们在大规模RL问题中具有适用性,并能与价值迭代和策略梯度方法竞争。尽管存在时间较长,这类方法在某些情境下仍然更具效率。具体而言,本节将介绍黑盒优化方法:进化策略。

1. 黑盒优化方法

首先,我们探讨整个黑盒方法类别及其与其他方法的区别。黑盒优化方法是解决优化问题的通用途径,它将待优化目标视为黑盒,无需考虑可微性、价值函数、目标平滑度等任何假设。这类方法唯一的要求是能够计算适应度函数,该函数应能评估当前优化实体特定实例的适用性。此类方法中最简单的例子是随机搜索:随机采样寻找目标(在强化学习 (Reinforcement Learning, RL) 中即策略π ( a ∣ s ) π(a|s)π(a∣s)),检验该候选方案的适应度,若结果足够好(符合某些奖励标准)则完成优化,否则持续重复此过程。尽管这种方法简单甚至略显朴素——尤其与复杂方法相比——但它能很好地阐释黑盒方法的核心思想。
更重要的是,通过一些改进,这种简单方法在效率与最终策略质量方面可与深度深度Q网络 (Deep Q-Network, DQN) 和策略梯度方法相媲美。此外,黑盒方法还具有若干显著优势:

  • 速度快:速度至少比基于梯度的方法快两倍,因为无需执行反向传播计算梯度
  • 对优化目标和策略的假设非常少:对被视为黑盒的优化目标和策略几乎不做任何假设,传统方法难以处理奖励函数非平滑或策略包含随机选择步骤的情况,而这对黑盒方法完全不构成问题,因为它们对黑盒内部机制没有要求
  • 易于并行化:例如前述随机搜索能轻松扩展到数千个中央处理器 (Central Processing Unit,CPU) 或图形处理器 (Graphics Processing Unit,GPU) 并行工作,且彼此间无任何依赖。相比之下,DQN或策略梯度方法需要累积梯度并将当前策略传播至所有并行工作节点,这会降低并行效率

前述方法的主要缺点通常是样本效率较低。特别是对于参数规模达50万的神经网络策略而言,朴素随机搜索的成功概率极低。

2. 进化策略

进化策略 (Evolution Strategies,ES) 是黑盒优化方法的一个子集,其灵感源于进化过程。在ES中,最成功的个体对整体搜索方向具有最大影响力。该类包含多种不同方法,本节我们将重点讨论Salimans等人于2017年发表的论文《Evolution strategies as a scalable alternative to reinforcement learning》中采用的方法。
ES方法的基本思想是:在每次迭代中,对当前策略参数执行随机扰动,并评估所得策略的适应度函数值。然后根据相对适应度函数值按比例调整策略权重。
Salimans等人采用的具体方法称为协方差矩阵自适应进化策略 (Covariance Matrix Adaptation Evolution Strategy,CMA-ES)。该方法通过从均值为零、方差为单位矩阵的正态分布中采样随机噪声作为扰动,计算原始策略权重加上缩放噪声后的新策略适应度函数值,随后根据所得值调整原始策略权重——将噪声乘以适应度函数值后加至原权重,使策略向适应度函数值更高的权重方向移动。为了提高稳定性,权重的更新通过对包含不同随机噪声的批次步骤取平均值来实现。更形式化地,该方法可表述为以下步骤:

  1. 初始化学习率α αα、噪声标准差σ σσ和初始策略参数𝜃 0 𝜃_0𝜃0​
  2. 对于t = 0, 1, … 循环执行:
    • 从均值为0、方差为1的正态分布中采样与权重形状相同的噪声批次:ε 1 , … , ε n ∼ N ( 0 , 1 ) ε_1, …, ε_n\sim N(0, 1)ε1​,…,εn​∼N(0,1)
    • 计算返回值:F i = F ( θ t + σ ε i ) F_i = F(θ_t + σε_i)Fi​=F(θt​+σεi​),其中i = 1 , … , n i = 1, …, ni=1,…,n
    • 更新权重:
      θ t + 1 ← θ t + α 1 n σ ∑ i = 1 n F i ε i \theta_{t+1}\leftarrow\theta_t+\alpha\frac 1{n\sigma}\sum_{i=1}^nF_iε_iθt+1​←θt​+αnσ1​i=1∑n​Fi​εi​

该算法是论文所述方法的核心,但正如RL领域的常见情况,仅靠算法本身不足以获得良好结果。因此论文还包含若干改进技巧,但核心思想保持不变。

3. 在 CartPole 环境中实现进化策略

接下来,我们在CartPole环境中实现并测试论文中的方法。完整代码参见 cartpole_es.py。本节将使用单一环境来检验扰动后网络权重的适应度。我们的适应度函数将采用回合的未折扣总奖励。

(1)首先导入必要的库:

importgymnasiumasgymimporttimeimportnumpyasnpimporttypingasttimporttorchimporttorch.nnasnnfromtorch.utils.tensorboard.writerimportSummaryWriterimportcommon

由于完全不执行反向传播,我们不需要使用PyTorch优化器。事实上,我们完全可以避免使用PyTorch而仅依赖NumPy,因为使用PyTorch的唯一目的是执行前向传播和计算网络输出。

(2)接下来,定义超参数:

MAX_BATCH_EPISODES=100MAX_BATCH_STEPS=10000NOISE_STD=0.001LEARNING_RATE=0.001TNoise=tt.List[torch.Tensor]

超参数数量也很少,包含以下值:

  • MAX_BATCH_EPISODES和MAX_BATCH_STEPS:训练使用的回合数和步数上限
  • NOISE_STD:用于权重扰动的噪声标准差σ σσ
  • LEARNING_RATE:训练步骤中调整权重的系数

我们还为包含权重噪声的张量列表定义了类型别名,这将简化代码处理噪声的逻辑。

(3)定义网络:

classNet(nn.Module):def__init__(self,obs_size:int,action_size:int):super(Net,self).__init__()self.net=nn.Sequential(nn.Linear(obs_size,32),nn.ReLU(),nn.Linear(32,action_size),nn.Softmax(dim=1))defforward(self,x:torch.Tensor)->torch.Tensor:returnself.net(x)

我们使用的模型是一个简单的单隐藏层神经网络,它根据观测状态给出要采取的动作。此处使用PyTorch神经网络模块仅出于便利性考虑,因为我们只需要前向传播过程,但这完全可以被矩阵乘法和非线性变换所替代。

(3)evaluate()函数使用给定策略运行完整回合,并返回总奖励和步数:

defevaluate(env:gym.Env,net:nn.Module,get_max_action:bool=True,device:torch.device=torch.device('cpu'))->tt.Tuple[float,int]:obs,_=env.reset()reward=0.0steps=0whileTrue:obs_v=torch.FloatTensor(np.expand_dims(obs,0)).to(device)act_v=net(obs_v)ifget_max_action:act=act_v.max(dim=1)[1].data.numpy()[0]else:act=act_v.data.cpu().numpy()[0]obs,r,done,is_tr,_=env.step(act)reward+=r steps+=1ifdoneoris_tr:breakreturnreward,steps

奖励值将作为适应度值使用,而步数计数用于限制我们组建批次所花费的时间。动作选择通过计算网络输出的argmax以确定性方式执行。原则上,我们可以从分布中进行随机采样,但通过对网络参数添加噪声已经实现了探索过程,因此这里使用确定性动作选择是可行的。

(4)在sample_noise()函数中,我们创建与网络参数形状相同的零均值单位方差随机噪声:

defsample_noise(net:nn.Module,device:torch.device=torch.device('cpu'))->tt.Tuple[TNoise,TNoise]:pos=[]neg=[]forpinnet.parameters():noise=np.random.normal(size=p.data.size())pos.append(torch.FloatTensor(noise).to(device))neg.append(torch.FloatTensor(-noise).to(device))returnpos,neg

该函数返回两组噪声张量:一组为正噪声,另一组为相同随机值但取负号。这两个样本将作为独立样本在批次中使用。这种技术称为镜像采样,用于提高收敛稳定性。事实上,若没有负噪声,收敛会变得非常不稳定,因为正噪声会将权重推向单一方向。

(5)eval_with_noise()函数接收由sample_noise()创建的噪声数组,并评估添加噪声后的网络表现:

defeval_with_noise(env:gym.Env,net:nn.Module,noise:TNoise,noise_std:float,get_max_action:bool=True,device:torch.device=torch.device("cpu"))->tt.Tuple[float,int]:old_params=net.state_dict()forp,p_ninzip(net.parameters(),noise):p.data+=noise_std*p_n r,s=evaluate(env,net,get_max_action=get_max_action,device=device)net.load_state_dict(old_params)returnr,s

为实现这一目标,我们将噪声添加到网络参数中,并调用evaluate函数获取奖励值和所采取步数。此后,需要通过加载网络的状态字典将权重恢复至原始状态。

(6)该方法最后也是核心的函数是train_step(),它接收包含噪声和对应奖励的批次,并通过以下公式计算网络参数的更新:
θ t + 1 ← θ t + α 1 n σ ∑ i = 1 n F i ε i \theta_{t+1}\leftarrow\theta_t+\alpha\frac 1{n\sigma}\sum_{i=1}^nF_iε_iθt+1​←θt​+αnσ1​i=1∑n​Fi​εi​
具体实现如下:

deftrain_step(net:Net,batch_noise:tt.List[common.TNoise],batch_reward:tt.List[float],writer:SummaryWriter,step_idx:int):weighted_noise=Nonenorm_reward=np.array(batch_reward)norm_reward-=np.mean(norm_reward)s=np.std(norm_reward)ifabs(s)>1e-6:norm_reward/=s

首先,我们对奖励进行归一化处理(使其具有零均值和单位方差),这有助于提升方法的稳定性。接着,遍历批次中的每对(噪声,奖励)数据,将噪声值与归一化后的奖励相乘,并对策略中每个参数的对应噪声进行累加:

fornoise,rewardinzip(batch_noise,norm_reward):ifweighted_noiseisNone:weighted_noise=[reward*p_nforp_ninnoise]else:forw_n,p_ninzip(weighted_noise,noise):w_n+=reward*p_n

最后,使用累积的缩放后噪声来调整网络参数:

m_updates=[]forp,p_updateinzip(net.parameters(),weighted_noise):update=p_update/(len(batch_reward)*NOISE_STD)p.data+=LEARNING_RATE*update m_updates.append(torch.norm(update))writer.add_scalar("update_l2",np.mean(m_updates),step_idx)

从技术角度看,我们执行的是梯度上升操作,尽管梯度并非通过反向传播获得,而是通过随机采样(也称为蒙特卡洛采样)得到。Salimans等人也证实了这一事实,他们指出CMA-ES与策略梯度方法非常相似,区别仅在于获取梯度估计的方式。

(7)创建环境和网络:

if__name__=="__main__":writer=SummaryWriter(comment="-cartpole-es")env=gym.make("CartPole-v1")net=Net(env.observation_space.shape[0],env.action_space.n)print(net)

**(8)**训练循环的每次迭代从创建批次开始,我们在此过程中采样噪声并获取正向和负向噪声对应的奖励:

step_idx=0whileTrue:t_start=time.time()batch_noise=[]batch_reward=[]batch_steps=0for_inrange(MAX_BATCH_EPISODES):noise,neg_noise=common.sample_noise(net)batch_noise.append(noise)batch_noise.append(neg_noise)reward,steps=common.eval_with_noise(env,net,noise,NOISE_STD)batch_reward.append(reward)batch_steps+=steps reward,steps=common.eval_with_noise(env,net,neg_noise,NOISE_STD)batch_reward.append(reward)batch_steps+=stepsifbatch_steps>MAX_BATCH_STEPS:break

当达到批次中的回合数上限或总步数上限时,我们停止收集数据并执行训练更新。

**(9)**为了执行网络的更新,调用train_step()函数:

step_idx+=1m_reward=float(np.mean(batch_reward))ifm_reward>199:print("Solved in %d steps"%step_idx)breaktrain_step(net,batch_noise,batch_reward,writer,step_idx)

train_step()函数的目标是根据总奖励对噪声进行缩放,然后沿着平均噪声的方向调整策略权重。

**(10)**训练循环中的最后,指标写入TensorBoard并在控制台显示训练进度:

writer.add_scalar("reward_mean",m_reward,step_idx)writer.add_scalar("reward_std",np.std(batch_reward),step_idx)writer.add_scalar("reward_max",np.max(batch_reward),step_idx)writer.add_scalar("batch_episodes",len(batch_reward),step_idx)writer.add_scalar("batch_steps",batch_steps,step_idx)speed=batch_steps/(time.time()-t_start)writer.add_scalar("speed",speed,step_idx)print("%d: reward=%.2f, speed=%.2f f/s"%(step_idx,m_reward,speed))

直接运行程序(无需参数)即可开始训练:

$ python3 cartpole_es.py

ES通常需要40-60个批次才能解决CartPole问题。上述运行的收敛动态如下图所示:

可以看到,智能体能够在30秒内解决环境问题,这与交叉熵方法性能相当。

小结

本节介绍了进化策略 (Evolution Strategies,ES) 这一黑盒优化方法在强化学习中的应用。与传统梯度方法不同,ES通过对策略参数添加随机噪声并评估适应度来估计更新方向,无需计算梯度,因此具有速度快、假设少、易于并行化等优势。我们在CartPole环境中实现了镜像采样与奖励归一化等改进技巧,实验表明ES能够在30秒内稳定解决该任务,效率与交叉熵方法相当。

系列链接

PyTorch强化学习实战(1)——强化学习(Reinforcement Learning,RL)详解
PyTorch强化学习实战(2)——强化学习环境库Gymnasium
PyTorch强化学习实战(3)——Gymnasium API扩展功能
PyTorch强化学习实战(4)——PyTorch基础
PyTorch强化学习实战(5)——PyTorch Ignite 事件驱动机制与实践
PyTorch强化学习实战(6)——交叉熵方法详解与实现
PyTorch强化学习实战(7)——表格学习与贝尔曼方程
PyTorch强化学习实战(8)——Q学习详解与实现
PyTorch强化学习实战(9)——深度Q学习
PyTorch强化学习实战(10)——强化学习高级组件
PyTorch强化学习实战(11)——N步DQN(N-step DQN)
PyTorch强化学习实战(12)——Double DQN(DDQN)
PyTorch强化学习实战(13)——噪声网络(NoisyNet-DQN)
PyTorch强化学习实战(14)——优先经验回放机制
PyTorch强化学习实战(15)——Dueling DQN
PyTorch强化学习实战(16)——Categorical DQN
PyTorch强化学习实战(17)——强化学习训练加速
PyTorch强化学习实战(18)——基于DQN处理股票交易问题
PyTorch强化学习实战(19)——策略梯度法
PyTorch强化学习实战(20)——优势演员-评论家(Advantage Actor-Critic, A2C)
PyTorch强化学习实战(21)——异步优势演员-评论家(Asynchronous Advantage Actor-Critic, A3C)
PyTorch强化学习实战(22)——将强化学习应用于TextWorld互动小说游戏
PyTorch强化学习实战(23)——强化学习在网页导航中的应用
PyTorch强化学习实战(24)——连续动作空间中的强化学习
PyTorch强化学习实战(25)——深度确定性策略梯度(DDPG)
PyTorch强化学习实战(26)——提升随机策略梯度稳定性

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 2:02:24

基于springboot + vue鲜花销售系统(源码+数据库+文档)

鲜花销售系统 目录 基于springboot vue鲜花销售系统 一、前言 二、系统功能演示 三、技术选型 四、其他项目参考 五、代码参考 六、测试参考 七、最新计算机毕设选题推荐 八、源码获取: 基于springboot vue鲜花销售系统 一、前言 博主介绍:✌…

作者头像 李华
网站建设 2026/10/2 2:02:03

KCF与卡尔曼滤波融合:视觉目标跟踪的观测预测互补方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/2 2:02:03

MFC对话框添加工具栏:原理、代码与常见问题

简介:这是一份面向Visual C与MFC开发者的完整示例工程,聚焦对话框窗体如何集成工具栏这一常见需求,覆盖CDialog派生类搭建、工具栏资源设计、控件关联以及按钮消息映射等关键环节,适合正在学习MFC界面开发或需要为对话框添加快捷工…

作者头像 李华
网站建设 2026/10/2 2:01:14

RandAugment图像增强原理与工程落地实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/2 2:00:15

基于Matlab的轴承式继电器无人机控制联合仿真与代码生成实践

1. 从标题拆解这个项目的真实意图1.1 为什么“轴承式继电器”和“无人机控制”会出现在同一个标题里第一次看到“轴承式继电器无人机控制Matlab实现”这个标题,我脑子里冒出来的第一个疑问是:轴承式继电器到底是个什么东西,它跟无人机控制是怎…

作者头像 李华