天天看點

Paddle-NEAT——飛槳進化神經網絡元件Paddle-NEAT——飛槳進化神經網絡元件

Paddle-NEAT——飛槳進化神經網絡元件

目錄

  • Paddle-NEAT——飛槳進化神經網絡元件
    • 寫在前面:
    • NEAT 簡介
      • 基因組的表示
      • 基因組的變異
        • 節點變異
        • 連接配接變異
    • 基因組的交叉
    • 最後稍微介紹一下 NEAT 的兩種改進形式:
      • HyperNEAT
      • Adaptive HyperNEAT
    • 運作Paddle-NEAT
      • 安裝
      • 繼續來立我們的棍子吧
      • 運作一下
      • 當然走一下迷宮也是可以滴
      • 運作一下

寫在前面:

最近自己寫了個把 neat-python 和 paddlepaddle 深度學習架構相結合的套件,取名叫 Paddle-NEAT。連結會在下面的安裝方式處給出。

該套件涉及到了機器學習的另一個方向——遺傳算法與進化政策。但本文的重點是介紹其中的一個分支——NEAT(NeuroEvolution of Augmenting Topologies)

遺傳算法與進化政策可以跳脫出梯度的限制(因為不以梯度作為更新的依據),在高并行的計算條件下,說不定會有超越深度學習的效果。

噢對了,現在已經有将遺傳算法與 NAS 相結合的文章了——Genetic CNN。

注意:本文的代碼環境基于 Jupyter Notebook

NEAT 簡介

遺傳算法是個通用的架構,是以需要根據具體的問題來定義遺傳算法

整體架構可分為三部分:交叉、變異與适應度

我們的 NEAT 将神經元及其連接配接定義成基因組

基因組的表示

Paddle-NEAT——飛槳進化神經網絡元件Paddle-NEAT——飛槳進化神經網絡元件

從上圖可以看出,節點變量分為節點變量與連接配接變量兩種。其中節點變量包括了節點屬性等變量,連接配接變量則包括了輸入節點、輸出節點、權重與連接配接是否可行等屬性。

通過将節點與連接配接各列成一張連結清單,網絡計算時查詢即可得到輸出。

基因組的變異

同樣的,對應節點與連接配接兩種基因組組成,變異類型也有結點變異和連結變異兩種

Paddle-NEAT——飛槳進化神經網絡元件Paddle-NEAT——飛槳進化神經網絡元件

節點變異

在連接配接清單中随機選擇一個連接配接,然後中間加入一個節點

連接配接變異

随機選擇兩個不同的節點,增加一段連接配接

基因組的交叉

Paddle-NEAT——飛槳進化神經網絡元件Paddle-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