NEAT-Python не может захватить экстремальные значения - PullRequest
0 голосов
/ 19 марта 2019

Я использую NEAT-Python для имитации хода обычной синусоидальной функции на основе абсолютной разницы кривой от 0. Файл конфигурации был почти полностью принят из базового примера XOR 1004 *, за исключением количества входов, установленного на 1. Направление смещения выводится из исходных данных сразу после фактического шага прогнозирования, так что это действительно все о прогнозировании смещений в диапазоне от [0, 1].

Функция пригодности и большая часть оставшегося кода также были перенесены со страниц справки, поэтому я достаточно уверен, что код является согласованным с технической точки зрения. Как видно из приведенной ниже визуализации смещений по сравнению с прогнозируемыми, модель в большинстве случаев дает довольно хорошие результаты. Тем не менее, он не может захватить нижний и верхний конец диапазона значений.

Буду очень признателен за любую помощь в улучшении производительности алгоритма, особенно на нижнем / верхнем краю. Или есть какие-то методические ограничения, которые я до сих пор не учел?


config-feedforward находится в текущем рабочем каталоге:

#--- parameters for the XOR-2 experiment ---#

[NEAT]
fitness_criterion     = max
fitness_threshold     = 3.9
pop_size              = 150
reset_on_extinction   = False

[DefaultGenome]
# node activation options
activation_default      = sigmoid
activation_mutate_rate  = 0.0
activation_options      = sigmoid

# node aggregation options
aggregation_default     = sum
aggregation_mutate_rate = 0.0
aggregation_options     = sum

# node bias options
bias_init_mean          = 0.0
bias_init_stdev         = 1.0
bias_max_value          = 30.0
bias_min_value          = -30.0
bias_mutate_power       = 0.5
bias_mutate_rate        = 0.7
bias_replace_rate       = 0.1

# genome compatibility options
compatibility_disjoint_coefficient = 1.0
compatibility_weight_coefficient   = 0.5

# connection add/remove rates
conn_add_prob           = 0.5
conn_delete_prob        = 0.5

# connection enable options
enabled_default         = True
enabled_mutate_rate     = 0.01

feed_forward            = True
initial_connection      = full

# node add/remove rates
node_add_prob           = 0.2
node_delete_prob        = 0.2

# network parameters
num_hidden              = 0
num_inputs              = 1
num_outputs             = 1

# node response options
response_init_mean      = 1.0
response_init_stdev     = 0.0
response_max_value      = 30.0
response_min_value      = -30.0
response_mutate_power   = 0.0
response_mutate_rate    = 0.0
response_replace_rate   = 0.0

# connection weight options
weight_init_mean        = 0.0
weight_init_stdev       = 1.0
weight_max_value        = 30
weight_min_value        = -30
weight_mutate_power     = 0.5
weight_mutate_rate      = 0.8
weight_replace_rate     = 0.1

[DefaultSpeciesSet]
compatibility_threshold = 3.0

[DefaultStagnation]
species_fitness_func = max
max_stagnation       = 20
species_elitism      = 2

[DefaultReproduction]
elitism            = 2
survival_threshold = 0.2

NEAT функции:

# . fitness function ----

def eval_genomes(genomes, config):
  for genome_id, genome in genomes:
    genome.fitness = 4.0
    net = neat.nn.FeedForwardNetwork.create(genome, config)
    for xi in zip(abs(x)):
      output = net.activate(xi)
      genome.fitness -= abs(output[0] - xi[0]) ** 2


# . neat run ----

def run(config_file, n = None):
  # load configuration
  config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction,
                       neat.DefaultSpeciesSet, neat.DefaultStagnation,
                       config_file)
  # create the population, which is the top-level object for a NEAT run
  p = neat.Population(config)
  # add a stdout reporter to show progress in the terminal
  p.add_reporter(neat.StdOutReporter(True))
  stats = neat.StatisticsReporter()
  p.add_reporter(stats)
  p.add_reporter(neat.Checkpointer(5))
  # run for up to n generations
  winner = p.run(eval_genomes, n)
  return(winner)

Код:

### ENVIRONMENT ====

### . packages ----

import os
import neat

import numpy as np
import matplotlib.pyplot as plt
import random


### . sample data ----

x = np.sin(np.arange(.01, 4000 * .01, .01))


### NEAT ALGORITHM ====

### . model evolution ----

random.seed(1899)
winner = run('config-feedforward', n = 25)


### . prediction ----

## extract winning model
config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction,
                     neat.DefaultSpeciesSet, neat.DefaultStagnation,
                     'config-feedforward')

winner_net = neat.nn.FeedForwardNetwork.create(winner, config)

## make predictions
y = []
for xi in zip(abs(x)):
  y.append(winner_net.activate(xi))

## if required, adjust signs
for i in range(len(y)):
  if (x[i] < 0):
    y[i] = [x * -1 for x in y[i]]

## display sample vs. predicted data
plt.scatter(range(len(x)), x, color='#3c8dbc', label = 'observed') # blue
plt.scatter(range(len(x)), y, color='#f39c12', label = 'predicted') # orange
plt.hlines(0, xmin = 0, xmax = len(x), colors = 'grey', linestyles = 'dashed')
plt.xlabel("Index")
plt.ylabel("Offset")
plt.legend(bbox_to_anchor = (0., 1.02, 1., .102), loc = 10,
           ncol = 2, mode = None, borderaxespad = 0.)
plt.show()
plt.clf()

observed vs. predicted offset

1 Ответ

2 голосов
/ 01 апреля 2019

Существуют различные реализации NEAT, поэтому детали могут отличаться.

Обычно NEAT обрабатывает смещения, включая специальный входной нейрон, который всегда активен (постактивация 1).Я подозреваю, что bias_max_value и bias_min_value определяют максимально допустимую силу соединений между этим нейроном смещения и скрытыми нейронами.В коде NEAT, который я использовал, эти два параметра не существовали, а соединения со смещением к скрытому обрабатывались как обычно (с собственным допустимым диапазоном, в нашем случае от -5 до 5).

Если выработая с функциями сигмоиды, ваши выходные нейроны будут работать в диапазоне от 0 до 1 (рассмотрите возможность более быстрой активации для скрытых нейронов, возможно, RELU).

Если вы пытаетесь предсказать значения, близкие к 0 или 1, этопроблема, так как вам действительно нужно подтолкнуть свои нейроны к пределу их диапазона, и сигмоиды приближаются к этим крайностям асимптотически (медленно!):

К счастью, существует очень простой способ выяснить, в этом ли проблема: просто измените масштаб своей продукции!Что-то вроде

out = raw_out * 1.2 - 0.1

Это позволит вашим теоретическим выходам находиться в диапазоне, выходящем за пределы ожидаемых результатов (от -0,1 до 1,1 в моем примере), и достижение 0 и 1 будет легче (и фактически возможно).строго говоря).

...