NEAT 强化学习
学习资料:
- 本节的全部代码
- 我制作的 什么是神经进化 动画简介
- 什么是遗传算法
- 什么是进化策略
- NEAT 论文 (Evolving Neural Networks through Augmenting Topologies)
- NEAT Python 模块
要点¶
上节内容 里, 我们见到了使用 NEAT 来进化出一个类似于监督学习中的神经网络, 这次我们用 NEAT 来做强化学习 (Reinforcement Learning), 这个强化学习可是没有反向传播的神经网络哦, 有的只是一个不断进化 (还可能进化到主宰人类) 的神经网络!! (哈哈, 骗你的, 因为每次提到在电脑里进化, 联想到科幻片, 我就激动!)
立杆子的机器人最后学习的效果提前看:
这个机器人的神经网络长这样:
gym 模拟环境¶
OpenAI gym 应该算是当下最流行的 强化学习练手模块了吧. 它有超级多的虚拟环境可以让你 plugin 你的 python 脚本.
安装 gym 的方式也很简单, 大家可以直接参考我在之前做 强化学习 Reinforcement learning 教程中的这节内容, 简单的介绍了如何安装 Gym. 如果还是遇到了问题, 这里或许能够找到答案.
CartPole 进化吧¶
这次进化的框架系统大致是这样的:
在 neat 的 config
文件中, 我想提到的几个地方是:
fitness_criterion = max # 按照适应度最佳的模式选个体
# 为了一直立杆子下去, 这一个封顶值设置成永远达不到,
# 具体看我在 eval_genomes 中如何计算 fitness 的
fitness_threshold = 2.
activation_default = relu # 我挑选的 激活函数
# network 输入输出个数
num_hidden = 0
num_inputs = 4
num_outputs = 2
有了这个 config
文件里面的信息, 我们就能创建网络和评估网络了. 和上次一样, 下面的功能对每一个个体生成一个神经网络, 然后把这个网络放在立杆子游戏中玩, 一个 generation 中我们对每一个 genome
的 net
测试 GENERATION_EP
这么多回合, 然后最后挑选这么多回合中总 reward
最少的那个回合当成这个 net
的 fitness
(你可以想象这是木桶效应, 整体的效应取决于最差的那个结果). 然后要注意的是, net.activate()
output 的是动作的值. 然后我们挑选一个值最大的动作.
不知道大家看到这里有没有想过, 如果我们能并行运算该多好. 所以, 我亲测失败. 原因是, gym
+ neat
的环境不方便运行 multiprocessing
. 如果你想多线程的话, 可以考虑使用 threading
, 不过不保证效率有提高. 想知道为什么的话, 请看这里.
接下来我们就开始写 run
里面的内容了, 创建种群, 繁衍后代, 适者生存, 不适者淘汰.
那些可视化种群进化图的代码, 请在我的 github 中看全套代码吧.
最后我们挑选一下保存的 checkpoint
文件, 展示出最强神经网络的样子吧.
这串代码的结果就是这节内容最上面的那个视频效果啦. winner
的神经网络进化成这样了. 不过你的生成的神经网络可能并不是长这样. 有时候还可能某个 input
都没有使用到. 就说明这个 input
的效用可能并不大.
如果是实线, 如 B->1, B->2, 说明这个链接是 Enabled 的. 如果是虚线(点线), 如 B->A XOR B 就说明这个链接是 Disabled 的. 红色的线代表 weight <= 0, 绿色的线代表 weight > 0. 线的宽度和 weight 的大小有关.
Recurrent link 和 node¶
如果修改一下 config
文件里面的参数, 比如下面的 feed_forward = True
改成 False
, 我们就允许网络能产生 recurrent 节点或者链接. 这样的设置能使网络产生记忆功能. 就像循环神经网络那样. 神经网络的形式结构就能更加多种多样. 不过这里的 recurrent 貌似是和我们一般见到的 Recurrent Neural Network 有所不同, 我们通常说的 RNN 是通过一个 hidden state 来传递记忆, 而 NEAT 中的 Recurrent 是通过一种 延迟刷新的形式
(不知道这样说对不对, 我是细看了一遍 NEAT-python 的底层代码发现的), 每一个时间点每个节点只接收这一时刻传来的信息. 比如下面第一张图中, 现在所有节点都为0, 如果我先更新 node3
, 由于接收到了 act2=0
, node3
还是会为0. 但是如果是先更新 act2
, 等 act2
有值了再更新 node3
, 那 node3
这时刻也会有值. 如果这是一个 feedforward net, 更新 link/node 的顺序十分重要, 上述情况肯定会出问题的. 不过在这种版本中的 recurrent, 程序不知道顺序, 所以每次都 copy 一份所有 node 的值, 用上一步的 node 的值进行这一步的操作, 这样进行 recurrent 的操作.
feed_forward = False
将所有原来的 net = neat.nn.FeedForwardNetwork
改成 neat.nn.RecurrentNetwork
, 就能按上面所说的方式进行 recurrent 操作了.
这样我们就能发现, 产生的网络还能是这样, 注意箭头的方向和位置.
最后, 在这里提一下, 还有一些根据 NEAT 改良的算法. 比如 * HyperNEAT (A Hypercube-Based Encoding for Evolving Large-Scale Neural Networks), 使用 NEAT 形式生成 CPPN 的网络, 用 CPPN 进行 indirect encoding 生成更大更复杂的神经网络, 但是后者的网络结构的 capacity 不能改变; * ES-HyperNEAT (An Enhanced Hypercube-Based Encoding for Evolving the Placement, Density and Connectivity of Neurons), 解决上面提到的网络结构 capacity 不可变问题.
下一节我们会关注使用 Evolution Strategy 来做大规模强化学习.