Paddle-NEAT——飞桨进化神经网络组件

Paddle-NEAT——飞桨进化神经网络组件

写在前面:

最近自己写了个把 neat-python 和 paddlepaddle 深度学习框架相结合的套件,取名叫 Paddle-NEAT。链接会在下面的安装方式处给出。

该套件涉及到了机器学习的另一个方向——遗传算法与进化策略。但本文的重点是介绍其中的一个分支——NEAT(NeuroEvolution of Augmenting Topologies)

遗传算法与进化策略可以跳脱出梯度的限制(因为不以梯度作为更新的依据),在高并行的计算条件下,说不定会有超越深度学习的效果。

噢对了,现在已经有将遗传算法与 NAS 相结合的文章了——Genetic CNN

注意:本文的代码环境基于 Jupyter Notebook

NEAT 简介

遗传算法是个通用的框架,因此需要根据具体的问题来定义遗传算法

整体框架可分为三部分:交叉、变异与适应度

我们的 NEAT 将神经元及其连接定义成基因组

基因组的表示

从上图可以看出,节点变量分为节点变量与连接变量两种。其中节点变量包括了节点属性等变量,连接变量则包括了输入节点、输出节点、权重与连接是否可行等属性。

通过将节点与连接各列成一张链表,网络计算时查询即可得到输出。

基因组的变异

同样的,对应节点与连接两种基因组组成,变异类型也有结点变异和链接变异两种

节点变异

在连接列表中随机选择一个连接,然后中间加入一个节点

连接变异

随机选择两个不同的节点,增加一段连接

基因组的交叉

我们的基因组如何交叉呢?

首先,我们要把父母双方的基因先按顺序铺开对齐,如果是双方都有的连接id(称为 innovation),那就随机选择一个;

如果有不匹配的基因,那么继承具有更好 fitness 的一方;

如果双方的 fitness 相同,那就随便选了。

最后稍微介绍一下 NEAT 的两种改进形式:

HyperNEAT

HyperNEAT是对NEAT的扩展,它用一个单独的网络(称为CPPN,用于合成图案生成网络)间接地编码网络(称为衬底)的权重

Adaptive HyperNEAT

Adaptive HyperNEAT是HyperNEAT的一个扩展,它间接地对初始权值和权值的更新规则进行编码,以便在网络的“生命周期”内进行一些学习

运行Paddle-NEAT

安装

!pip install git+https://github.com/AgentMaker/Paddle-NEAT.git
!pip install neat-python

本套件底层仍然是 neat-python,但某些算子可以借由 paddle2.0 实现加速

网络配置与节点信息参照下面的 neat-python 中的介绍

neat-python 链接:
https://github.com/CodeReclaimers/neat-python

继续来立我们的棍子吧

%%writefile cart_pole.py
import os

import click
import gym
import neat


from paddle_neat.multi_env_eval import MultiEnvEvaluator
from paddle_neat.neat_reporter import LogReporter
from paddle_neat.recurrent_net import RecurrentNet

max_env_steps = 200

# 创建环境
def make_env():
    return gym.make("CartPole-v0")

# 创建网络
def make_net(genome, config, bs):
    return RecurrentNet.create(genome, config, bs)

# 定义输出层
def activate_net(net, states):
    outputs = net.activate(states).numpy()
    return outputs[:, 0] > 0.5


@click.command()
@click.option("--n_generations", type=int, default=100)
def run(n_generations):
    config_path = os.path.join(os.path.dirname(__file__), "neat_cart_pole.cfg")
    config = neat.Config(
        neat.DefaultGenome,
        neat.DefaultReproduction,
        neat.DefaultSpeciesSet,
        neat.DefaultStagnation,
        config_path,
    )

    evaluator = MultiEnvEvaluator(
        make_net, activate_net, make_env=make_env, max_env_steps=max_env_steps
    )

    def eval_genomes(genomes, config):
        for _, genome in genomes:
            genome.fitness = evaluator.eval_genome(genome, config)

    pop = neat.Population(config)
    stats = neat.StatisticsReporter()
    pop.add_reporter(stats)
    reporter = neat.StdOutReporter(True)
    pop.add_reporter(reporter)
    logger = LogReporter("neat.log", evaluator.eval_genome)
    pop.add_reporter(logger)

    pop.run(eval_genomes, n_generations)


if __name__ == "__main__":
    run() 

运行一下

!python cart_pole.py

当然走一下迷宫也是可以滴

%%writefile maze.py
import multiprocessing
import os

import click
import neat

import paddle
import numpy as np

import sys 
sys.path.append("..") 

from paddle_neat import t_maze
from paddle_neat.activations import tanh_activation
from paddle_neat.adaptive_linear_net import AdaptiveLinearNet
from paddle_neat.multi_env_eval import MultiEnvEvaluator
from paddle_neat.neat_reporter import LogReporter

batch_size = 4
DEBUG = True


def make_net(genome, config, _batch_size):
    input_coords = [[-1.0, 0.0], [0.0, 0.0], [1.0, 0.0], [0.0, -1.0]]
    output_coords = [[-1.0, 0.0], [0.0, 0.0], [1.0, 0.0]]
    return AdaptiveLinearNet.create(
        genome,
        config,
        input_coords=input_coords,
        output_coords=output_coords,
        weight_threshold=0.4,
        batch_size=batch_size,
        activation=tanh_activation,
        output_activation=tanh_activation,
    )


def activate_net(net, states, debug=False, step_num=0):
    if debug and step_num == 1:
        print("\n" + "=" * 20 + " DEBUG " + "=" * 20)
        print(net.delta_w_node)
        print("W init: ", net.input_to_output.numpy()[0])
    outputs = net.activate(states).numpy()
    if debug and (step_num - 1) % 100 == 0:
        print("\nStep {}".format(step_num - 1))
        print("Outputs: ", outputs[0])
        print("Delta W: ", net.delta_w.numpy()[0])
        print("W: ", net.input_to_output.numpy()[0])
    return np.argmax(outputs, axis=1)


@click.command()
@click.option("--n_generations", type=int, default=10000)
@click.option("--n_processes", type=int, default=1)
def run(n_generations, n_processes):
    config_path = os.path.join(os.path.dirname(__file__), "neat_maze.cfg")
    config = neat.Config(
        neat.DefaultGenome,
        neat.DefaultReproduction,
        neat.DefaultSpeciesSet,
        neat.DefaultStagnation,
        config_path,
    )

    envs = [t_maze.TMazeEnv(init_reward_side=i, n_trials=100) for i in [1, 0, 1, 0]]

    evaluator = MultiEnvEvaluator(
        make_net, activate_net, envs=envs, batch_size=batch_size, max_env_steps=1000
    )

    if n_processes > 1:
        pool = multiprocessing.Pool(processes=n_processes)

        def eval_genomes(genomes, config):
            fitnesses = pool.starmap(
                evaluator.eval_genome, ((genome, config) for _, genome in genomes)
            )
            for (_, genome), fitness in zip(genomes, fitnesses):
                genome.fitness = fitness

    else:

        def eval_genomes(genomes, config):
            for i, (_, genome) in enumerate(genomes):
                try:
                    genome.fitness = evaluator.eval_genome(
                        genome, config, debug=DEBUG and i % 100 == 0
                    )
                except Exception as e:
                    print(genome)
                    raise e

    pop = neat.Population(config)
    stats = neat.StatisticsReporter()
    pop.add_reporter(stats)
    reporter = neat.StdOutReporter(True)
    pop.add_reporter(reporter)
    logger = LogReporter("log.json", evaluator.eval_genome)
    pop.add_reporter(logger)

    winner = pop.run(eval_genomes, n_generations)

    print(winner)
    final_performance = evaluator.eval_genome(winner, config)
    print("Final performance: {}".format(final_performance))
    generations = reporter.generation + 1
    return generations


if __name__ == "__main__":
    run()  

运行一下

!python maze.py
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值