news 2026/10/13 2:28:34

PyTorch强化学习实战(28)——遗传算法在强化学习中的应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PyTorch强化学习实战(28)——遗传算法在强化学习中的应用

PyTorch强化学习实战(28)——遗传算法在强化学习中的应用

    • 0. 前言
    • 1. 遗传算法
    • 2. 遗传算法在 CartPole 上的应用
    • 3. 遗传算法改进方案
      • 3.1 深度遗传算法
      • 3.2 新颖性搜索
    • 4. 遗传算法在 HalfCheetah 环境中的应用
      • 4.1 算法实现
      • 4.2 运行结果
    • 小结
    • 系列链接

0. 前言

我们已经学习了进化策略 (Evolution Strategies, ES) 这一黑盒优化方法在强化学习 (Reinforcement Learning, RL) 中的应用,黑盒优化方法在大规模RL问题中具有适用性,并能与价值迭代和策略梯度方法竞争。尽管存在时间较长,这类方法在某些情境下仍然更具效率。具体而言,本节将介绍另一类黑盒优化方法:遗传算法。

1. 遗传算法

遗传算法 (Genetic Algorithm,GA) 是另一类流行的黑盒优化方法,这类方法拥有超过二十年的发展历史,其核心思想简单明了:生成包含N个个体的种群(即具体的模型参数),每个个体都通过适应度函数进行评估。然后,选取一些表现最好的个体,利用它们生成下一代种群。这个过程会重复进行,直到种群性能满足要求为止。
遗传算法包含多种不同方法,例如如何执行下一代个体的突变,或者如何对个体进行排序。本节中,我们将探讨Such等人论文《Deep neuroevolution: Genetic algorithms are a competitive alternative for training deep neural networks for reinforcement learning》中提出的带扩展的简单GA方法。
该论文分析了一种简单GA方法,该方法通过对父代权重进行高斯噪声扰动来实现突变。每次迭代时,表现最优的个体会被直接复制到下一代。用算法形式表示,简单GA方法的步骤可表述如下:

  1. 初始化突变强度σ σσ、种群规模N NN、选择的个体数量T TT和初始种群P 0 P_0P0​,其中P 0 P_0P0​是N NN个随机初始化的策略及其适应度:F 0 = F ( P i 0 ) ∣ i = 1 … N F₀ = {F(P_i^0) | i = 1 … N}F0​=F(Pi0​)∣i=1…N
  2. 对世代g = 1 … G g = 1 … Gg=1…G循环:
    • 根据适应度函数值F g − 1 F^{g-1}Fg−1降序排序上一代种群P n − 1 P^{n-1}Pn−1
    • 复制精英个体P 1 g = P 1 g − 1 , F 1 g = F 1 g − 1 P_1^g = P_1^{g-1},F_1^g = F_1^{g-1}P1g​=P1g−1​,F1g​=F1g−1​
    • 对个体i = 2 … N i = 2 … Ni=2…N循环:
      • 从1 … T 1…T1…T中随机选择k kk个父代
      • 采样噪声ϵ ∼ N ( 0 , I ) \epsilon ∼ N(0, I)ϵ∼N(0,I)
      • 进行突变:P i g = P g − 1 i + σ 𝜖 P_i^g = P^i_{g-1} + σ𝜖Pig​=Pg−1i​+σ𝜖
      • 计算其适应度:F i g = F ( P i g ) F_i^g = F(P_i^g)Fig​=F(Pig​)

论文对该基础方法提出了若干改进,我们将在后续学习中进行讨论,接下来,我们先来查看核心算法的实现。

2. 遗传算法在 CartPole 上的应用

源代码位于 cartpole_ga.py 中,与进化策略 (Evolution Strategies, ES) 示例有许多共同之处。区别在于缺少梯度上升代码,而是被网络突变函数所替代:

defmutate_parent(net:Net)->Net:new_net=copy.deepcopy(net)forpinnew_net.parameters():noise=np.random.normal(size=p.data.size())noise_t=torch.FloatTensor(noise)p.data+=NOISE_STD*noise_treturnnew_net

该函数的目标通过对所有权重添加随机噪声,创建给定策略的突变副本。父代权重保持原样,因为父代是采用替换方式随机选择的,该网络后续可能被再次使用。
超参数数量比ES更少,主要包括添加到突变中的噪声的标准差、种群大小以及用于产生下一代的最优个体数量:

if__name__=="__main__":env=gym.make("CartPole-v1")writer=SummaryWriter(comment="-cartpole-ga")gen_idx=0nets=[Net(env.observation_space.shape[0],env.action_space.n)for_inrange(POPULATION_SIZE)]population=[(net,common.evaluate(env,net))fornetinnets]

在训练循环开始前,我们创建随机初始化的网络种群并获取其适应度:

whileTrue:population.sort(key=lambdap:p[1],reverse=True)rewards=[p[1]forpinpopulation[:PARENTS_COUNT]]reward_mean=np.mean(rewards)reward_max=np.max(rewards)reward_std=np.std(rewards)writer.add_scalar("reward_mean",reward_mean,gen_idx)writer.add_scalar("reward_std",reward_std,gen_idx)writer.add_scalar("reward_max",reward_max,gen_idx)print("%d: reward_mean=%.2f, reward_max=%.2f, reward_std=%.2f"%(gen_idx,reward_mean,reward_max,reward_std))ifreward_mean>199:print("Solved in %d steps"%gen_idx)break

在每个世代开始时,我们根据适应度对上一代进行排序,并记录关于未来父代的统计信息:

prev_population=population population=[population[0]]for_inrange(POPULATION_SIZE-1):parent_idx=np.random.randint(0,PARENTS_COUNT)parent=prev_population[parent_idx][0]net=mutate_parent(parent)fitness=common.evaluate(env,net)population.append((net,fitness))gen_idx+=1

在生成新个体的独立循环中,我们随机采样一个父代,进行突变,并评估其适应度得分。
启动实现后,可以看到以下输出(具体输出和步数可能因执行随机性而有所不同):

$ python3 cartpole_ga.py

可以看到,GA方法比ES方法更高效。

3. 遗传算法改进方案

Such等人提出了两种对基本遗传算法 (Genetic Algorithm,GA) 算法的改进:

  • 第一项称为深度GA,旨在提升实现的可扩展性
  • 第二项称为新颖性搜索,尝试用不同的回合评估指标替代奖励目标

接下来的“遗传算法在 HalfCheetah 环境中的应用”部分中,我们将实现第一个改进,而第二个则作为可选练习。

3.1 深度遗传算法

作为一种无梯度方法,GA在速度方面比ES方法具有更强的可扩展性潜力,尤其是在使用更多CPU参与优化时。然而,基础GA算法存在与ES方法类似的瓶颈:策略参数必须在工作节点之间交换。Such等人提出了一种与共享种子方法类似但更极端的技巧(使用种子来追踪数千次变异),称之为深度GA。其核心思想是将策略参数表示为用于创建该特定策略权重的随机种子列表。
实际上,初始网络权重在首代种群中随机生成,因此列表中的第一个种子就定义了这种初始化。在每一代种群中,变异也完全由每个变异的随机种子指定。因此,我们重建权重唯一需要的就是种子本身。这种方法需要在每个工作节点上重建权重,但通常这种开销远小于通过网络传输完整权重的开销。

3.2 新颖性搜索

对基本GA方法的另一项改进是新颖性搜索 (Novelty Search,NS),它由Lehman和Stanley在论文《Abandoning objectives: Evolution through the search for novelty alone》中提出。
NS的核心思想是改变优化目标:我们不再试图提高环境中的总奖励,而是奖励智能体探索前所未见的行为(即,新颖行为)。
根据作者在迷宫导航问题上的实验(其中设置了许多陷阱),NS的表现远优于其他奖励驱动方法。
要实现NS,我们需要定义所谓的行为特征 (Behavior Characteristic,BC)π ππ——用于描述策略行为,以及两个BC之间的距离。然后使用k近邻方法检查新策略的新奇性,并据此距离驱动GA进化。在Such等人的论文中,智能体需要进行充分探索,而NS方法在RL问题上的表现显著超越了ES、GA和其他传统方法。

4. 遗传算法在 HalfCheetah 环境中的应用

在本节中,我们将在HalfCheetah环境中实现并行化深度遗传算法 (Genetic Algorithm,GA)。完整代码位于 cheetah_ga.py 中。其架构与并行进化策略 (Evolution Strategies, ES) 版本非常接近,包含一个主进程和若干工作进程。每个工作进程的目标是评估一批网络并将结果返回给主进程,主进程将部分结果合并为完整种群,根据获得的奖励对个体进行排序,并生成下一代种群供工作进程评估。
每个个体通过随机种子列表进行编码,这些种子用于初始化网络权重及所有后续突变。这种表示法可以实现非常紧凑的网络编码,即使策略参数数量不大时也是如此。例如,在我们具有64个神经元隐藏层的网络中,共有1542个浮点值(输入为17个值,动作为6个浮点数,计算公式为:17×64 + 64 + 64×6 + 6 = 1542)。每个浮点数占用4字节,与随机种子的大小相同。因此,论文提出的深度GA表示法在优化过程中最多可节省1542代的数据量。

4.1 算法实现

在本节中,我们将在本地CPU上执行并行化,因此数据的传输量不会对性能产生太大影响;但是,如果有数百个核心可用,这种表示法可能会成为重要考量因素。

(1)超参数集与CartPole示例相同,区别在于种群规模更大:

NOISE_STD=0.01POPULATION_SIZE=2000PARENTS_COUNT=10WORKERS_COUNT=6SEEDS_PER_WORKER=POPULATION_SIZE//WORKERS_COUNT MAX_SEED=2**32-1

(2)有两个函数用于根据给定种子构建网络。第一个函数对已创建的策略网络执行一次突变:

defmutate_net(net:Net,seed:int,copy_net:bool=True)->Net:new_net=copy.deepcopy(net)ifcopy_netelsenet np.random.seed(seed)forpinnew_net.parameters():noise=np.random.normal(size=p.data.size())noise_t=torch.FloatTensor(noise)p.data+=NOISE_STD*noise_treturnnew_net

上述函数可根据参数选择原地执行突变或通过复制目标网络执行(第一代需要复制操作)。

(3)第二个函数使用种子列表从头创建网络:

defbuild_net(env:gym.Env,seeds:tt.List[int])->Net:torch.manual_seed(seeds[0])net=Net(env.observation_space.shape[0],env.action_space.shape[0])forseedinseeds[1:]:net=mutate_net(net,seed,copy_net=False)returnnet

此处第一个种子传递给PyTorch以影响网络初始化,后续种子用于实施网络突变。
工作进程函数获取待评估的种子列表,并为每个获得的结果输出一个个体OutputItem数据类项目:

@dataclassclassOutputItem:seeds:tt.List[int]reward:floatsteps:intdefworker_func(input_queue:mp.Queue,output_queue:mp.Queue):env=gym.make("HalfCheetah-v4")cache={}whileTrue:parents=input_queue.get()ifparentsisNone:breaknew_cache={}fornet_seedsinparents:iflen(net_seeds)>1:net=cache.get(net_seeds[:-1])ifnetisnotNone:net=mutate_net(net,net_seeds[-1])else:net=build_net(env,net_seeds)else:net=build_net(env,net_seeds)new_cache[net_seeds]=net reward,steps=common.evaluate(env,net,get_max_action=False)output_queue.put(OutputItem(seeds=net_seeds,reward=reward,steps=steps))cache=new_cache

该函数维护网络缓存以最小化从种子列表重建参数所耗时间。缓存每代清空一次,因为每个新世代都由当前世代的优胜者创建,旧网络从缓存中复用的概率极低。

(4)主进程代码同样简明直接:

batch_steps=0population=[]whilelen(population)<SEEDS_PER_WORKER*WORKERS_COUNT:out_item=output_queue.get()population.append((out_item.seeds,out_item.reward))batch_steps+=out_item.stepsifeliteisnotNone:population.append(elite)population.sort(key=lambdap:p[1],reverse=True)rewards=[p[1]forpinpopulation[:PARENTS_COUNT]]reward_mean=np.mean(rewards)reward_max=np.max(rewards)reward_std=np.std(rewards)speed=batch_steps/(time.time()-t_start)writer.add_scalar("speed",speed,gen_idx)print("%d: reward_mean=%.2f, reward_max=%.2f, reward_std=%.2f, speed=%.2f f/s"%(gen_idx,reward_mean,reward_max,reward_std,speed))elite=population[0]forworker_queueininput_queues:seeds=[]for_inrange(SEEDS_PER_WORKER):parent=np.random.randint(PARENTS_COUNT)next_seed=np.random.randint(MAX_SEED)s=list(population[parent][0])+[next_seed]seeds.append(tuple(s))

对于每一代,我们将当前种群的种子发送给工作进程进行评估并等待结果。随后对结果进行排序,并根据表现最优的个体生成下一代种群。在主进程端,突变仅需生成随机种子编号并将其附加到父代的种子列表中。

4.2 运行结果

本节使用MuJoCo的HalfCheetah环境,该环境内部没有任何健康度检测机制,因此每个回合都会运行2000步。经过300轮突变,最佳策略获得了6454分的奖励,这是一个非常出色的结果。虽然HalfCheetah环境挑战性有限,但这个成绩仍然非常优秀。相关曲线如下图所示。

小结

本节介绍了遗传算法 (Genetic Algorithm,GA) 在强化学习中的应用。GA是一种黑盒优化方法,通过维护种群、评估适应度、选择精英个体并进行突变来迭代进化策略。在CartPole环境中,基础GA通过对网络权重添加高斯噪声实现突变,每代保留最优个体并随机选择父代生成下一代。实验表明GA比进化策略 (Evolution Strategies, ES) 更高效。针对复杂环境,本节实现了深度GA的改进方案:使用随机种子列表编码网络参数,而非直接传递权重,大幅降低通信开销。在HalfCheetah环境中采用并行架构(主进程协调、工作进程评估),经过300代进化获得6454分,表现优异。

系列链接

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)——提升随机策略梯度稳定性
PyTorch强化学习实战(27)——进化策略在强化学习中的应用

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

Redis分页查询:从LRANGE到ZSET游标方案,深分页避坑指南

Redis分页查询&#xff1a;从LRANGE到游标方案&#xff0c;我把在这件事上踩过的坑一次说清楚最近在做社区Feed流的改造&#xff0c;用户在消息中心刷历史通知&#xff0c;一页10条&#xff0c;往下翻十几页之后明显变慢&#xff0c;查了下监控发现MySQL那边的大偏移量分页把磁…

作者头像 李华
网站建设 2026/10/13 2:27:28

【WebSocket】什么是 WebSocket?它和 HTTP 有什么本质区别?

WebSocket 协议本质:从“写信”到“打电话”的通信革命 用户问题原文:什么是 WebSocket?它和 HTTP 有什么本质区别? 在构建实时系统时,无数工程师都曾陷入一个困境:明明 TCP 是全双工的,为什么基于它的 HTTP 却只能“你问我答”?当你的金融行情推送因轮询延迟而错失交易…

作者头像 李华
网站建设 2026/10/13 2:26:55

PS5实战调校全攻略:散热、存储与网络优化指南

机器到手那天&#xff0c;我把它从箱子里捧出来时&#xff0c;第一反应不是激动&#xff0c;而是犯愁&#xff1a;这么大的家伙往哪儿放&#xff1f;后面那堆接口哪个跟哪个对应&#xff1f;系统里那么多选项&#xff0c;默认的和优化过的到底差多少&#xff1f;后来折腾了半年…

作者头像 李华
网站建设 2026/10/13 2:26:42

Czkawka 使用指南:用 14 个免费清理工具拿回磁盘空间

Czkawka 使用指南&#xff1a;用 14 个免费清理工具拿回磁盘空间 【免费下载链接】czkawka Multi functional app to find duplicates, empty folders, similar images etc. 项目地址: https://gitcode.com/GitHub_Trending/cz/czkawka Czkawka 是一款用 Rust 编写的磁盘…

作者头像 李华
网站建设 2026/10/13 2:25:12

Spring Boot 启动失败且日志空白?Nacos 配置加载顺序是关键

一次“静默失败”的排查&#xff1a;Spring Boot 应用启动不了&#xff0c;日志却一片空白这几天在做微服务改造时&#xff0c;遇到一个特别磨人的问题&#xff1a;本地启动 Spring Boot 服务&#xff0c;控制台只打印了几行 Spring 的 ASCII 图标&#xff0c;然后就一直卡在那…

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

OpenClaw部署全攻略:从安装到微信绑定,TaoToken统一Key接入实践

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

作者头像 李华