rubyneat 0.3.5.alpha.6 → 0.4.0.alpha.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.irbrc +2 -0
- data/.ruby-version +1 -0
- data/.semver +3 -3
- data/Gemfile +7 -8
- data/Gemfile.lock +43 -42
- data/Gemfile.lock.orig +161 -0
- data/Gemfile.orig +54 -0
- data/README.md +35 -4
- data/Rakefile +4 -3
- data/lib/rubyneat.rb +4 -3
- data/lib/rubyneat/cli.rb +3 -1
- data/lib/rubyneat/cli/console.rb +18 -0
- data/lib/rubyneat/cli/main.rb +3 -5
- data/lib/rubyneat/cli/templates/generate/.irbrc.tt +3 -0
- data/lib/rubyneat/critter.rb +2 -2
- data/lib/rubyneat/default_neat.rb +1 -1
- data/lib/rubyneat/dsl.rb +13 -12
- data/lib/rubyneat/evaluator.rb +10 -6
- data/lib/rubyneat/evolver.rb +2 -1
- data/lib/rubyneat/expressor.rb +1 -1
- data/lib/rubyneat/neuron.rb +1 -1
- data/lib/rubyneat/population.rb +12 -45
- data/lib/rubyneat/reporting.rb +113 -0
- data/lib/rubyneat/rubyneat.rb +189 -31
- data/rubyneat.gemspec +21 -4
- metadata +53 -4
data/Rakefile
CHANGED
@@ -25,6 +25,7 @@ Jeweler::Tasks.new do |gem|
|
|
25
25
|
gem.license = "MIT"
|
26
26
|
gem.summary = %Q{RubyNEAT NeuralEvolution of Augmenting Topologies}
|
27
27
|
gem.version = s_version
|
28
|
+
gem.required_ruby_version = '>= 2.0'
|
28
29
|
gem.description = %Q{
|
29
30
|
RubyNEAT -- Neural Evolution of Augmenting Topologies for Ruby.
|
30
31
|
By way of an enhanced form of Genetic Algorithms -- the NEAT algorithm,
|
@@ -42,15 +43,15 @@ Jeweler::Tasks.new do |gem|
|
|
42
43
|
because it's also extensible and modular. See http://rubyneat.com for the
|
43
44
|
details.
|
44
45
|
}
|
45
|
-
gem.email = "
|
46
|
+
gem.email = "lordalveric@yahoo.com"
|
46
47
|
gem.authors = ["Fred Mitchell"]
|
47
48
|
# dependencies defined in Gemfile
|
48
49
|
|
49
50
|
# Exclude the Neural Docs directory
|
50
51
|
gem.files.exclude 'Neural_Docs/*', 'foo/**/*', 'rdoc/*',
|
51
52
|
'.idea/**/*', '.idea/**/.*', '.yardoc/**/*',
|
52
|
-
'
|
53
|
-
'
|
53
|
+
'app/**/*', 'neater/**/*', 'doc/**/*',
|
54
|
+
'app/**/.*', 'Guardfile'
|
54
55
|
end
|
55
56
|
Jeweler::RubygemsDotOrgTasks.new
|
56
57
|
|
data/lib/rubyneat.rb
CHANGED
@@ -2,7 +2,8 @@
|
|
2
2
|
=RubyNEAT -- Neural Evolution of Augmenting Topologies
|
3
3
|
=end
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
require_relative 'rubyneat/rubyneat'
|
6
|
+
require_relative 'rubyneat/graph'
|
7
|
+
require_relative 'rubyneat/dsl'
|
8
|
+
require_relative 'rubyneat/reporting'
|
8
9
|
require 'set'
|
data/lib/rubyneat/cli.rb
CHANGED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'irb'
|
2
|
+
require 'irb/completion'
|
3
|
+
|
4
|
+
module RubyNEAT
|
5
|
+
module Cli
|
6
|
+
class Console < Thor
|
7
|
+
class << self
|
8
|
+
def default_command
|
9
|
+
# TODO: maybe dynamically set it in Rakefile and then retrieve from ENV?
|
10
|
+
# require "#{$calling_gem_name}"
|
11
|
+
ARGV.clear
|
12
|
+
IRB.start
|
13
|
+
end
|
14
|
+
alias_method :default_task, :default_command
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/rubyneat/cli/main.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'rubyneat/cli
|
1
|
+
require 'rubyneat/cli'
|
2
2
|
|
3
3
|
module RubyNEAT
|
4
4
|
module Cli
|
@@ -43,10 +43,7 @@ module RubyNEAT
|
|
43
43
|
end
|
44
44
|
|
45
45
|
desc 'console', 'Run RubyNEAT interactively'
|
46
|
-
|
47
|
-
#TODO: Finish the console
|
48
|
-
puts "Not Implemented Yet."
|
49
|
-
end
|
46
|
+
subcommand 'console', Console
|
50
47
|
|
51
48
|
desc 'run <neater> [<neater> <neater> ...] [OPTS]', 'Run a Neater'
|
52
49
|
option :log, type: :string, banner: 'info|warn|debug|error'
|
@@ -57,6 +54,7 @@ module RubyNEAT
|
|
57
54
|
neaters.map do |neater|
|
58
55
|
"#{neater}_neat.rb"
|
59
56
|
end.each do |file|
|
57
|
+
NEAT::controller.neater = file
|
60
58
|
load file
|
61
59
|
end
|
62
60
|
end
|
data/lib/rubyneat/critter.rb
CHANGED
@@ -222,7 +222,7 @@ module NEAT
|
|
222
222
|
end
|
223
223
|
|
224
224
|
#= Gene Specification
|
225
|
-
# The Gene
|
225
|
+
# The Gene specifies a singular input and
|
226
226
|
# output neuron, which represents a connection
|
227
227
|
# between them, along with the weight of that
|
228
228
|
# connection, which may be positive, negative, or zero.
|
@@ -236,7 +236,7 @@ module NEAT
|
|
236
236
|
attr_accessor :innovation
|
237
237
|
|
238
238
|
# input neuron's name (where our output goes)
|
239
|
-
#
|
239
|
+
# output neuron's name (neuron to be queried)
|
240
240
|
attr_accessor :in_neuron, :out_neuron
|
241
241
|
|
242
242
|
# weight of the connection
|
data/lib/rubyneat/dsl.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
require_relative 'rubyneat'
|
2
2
|
|
3
3
|
=begin rdoc
|
4
4
|
= RubyNEAT DSL
|
@@ -42,11 +42,11 @@ module NEAT
|
|
42
42
|
# to the input nodes. In the case of hash, the keys in the hash
|
43
43
|
# shall correspond to the names given to the input neurons.
|
44
44
|
def query(&block)
|
45
|
-
NEAT::controller.
|
45
|
+
NEAT::controller.query_func_add &block
|
46
46
|
end
|
47
47
|
|
48
48
|
def recurrence(&block)
|
49
|
-
NEAT::controller.
|
49
|
+
NEAT::controller.recurrence_func_set &block
|
50
50
|
end
|
51
51
|
|
52
52
|
# fitness function calls the block with 2 vectors or two hashes, input and output
|
@@ -54,26 +54,27 @@ module NEAT
|
|
54
54
|
# number that can be used to index what the actual output should be.
|
55
55
|
# |vin, vout, seq|
|
56
56
|
def fitness(&block)
|
57
|
-
NEAT::controller.
|
57
|
+
NEAT::controller.fitness_func_set &block
|
58
58
|
end
|
59
59
|
|
60
60
|
# Fitness ordering -- given 2 fitness numbers,
|
61
61
|
# use the <=> to compare them (or the equivalent, following
|
62
62
|
# the +1, 0, -1 that is in the sense of <=>)
|
63
63
|
def compare(&block)
|
64
|
-
NEAT::controller.
|
64
|
+
NEAT::controller.compare_func_set &block
|
65
65
|
end
|
66
66
|
|
67
67
|
# Calculation to add the cost to the fitness, resulting in a fitness
|
68
68
|
# that incorporates the cost for sorting purposes.
|
69
69
|
def cost(&block)
|
70
|
-
NEAT::controller.
|
70
|
+
NEAT::controller.cost_func_set &block
|
71
71
|
end
|
72
72
|
|
73
73
|
# Stop the progression once the fitness criteria is reached
|
74
|
-
# for the most fit critter
|
74
|
+
# for the most fit critter. We allow more than one stop
|
75
|
+
# function here.
|
75
76
|
def stop_on_fitness(&block)
|
76
|
-
NEAT::controller.
|
77
|
+
NEAT::controller.stop_on_fit_func_add &block
|
77
78
|
end
|
78
79
|
|
79
80
|
# Helper function to
|
@@ -101,12 +102,12 @@ module NEAT
|
|
101
102
|
|
102
103
|
# Report on evaluations
|
103
104
|
def report(&block)
|
104
|
-
NEAT::controller.
|
105
|
+
NEAT::controller.report_add &block
|
105
106
|
end
|
106
107
|
|
107
108
|
# Run the engine. The block is called on each generation.
|
108
109
|
def run_engine(&block)
|
109
|
-
NEAT::controller.
|
110
|
+
NEAT::controller.end_run_add &block
|
110
111
|
NEAT::controller.run
|
111
112
|
end
|
112
113
|
|
@@ -125,6 +126,6 @@ module NEAT
|
|
125
126
|
end
|
126
127
|
end
|
127
128
|
|
128
|
-
# FIXME: This needs to better specified for cases in which
|
129
|
-
# Controllers.
|
129
|
+
# FIXME: This needs to better specified for cases in which
|
130
|
+
# FIXME: there may be multiple Controllers.
|
130
131
|
require 'rubyneat/default_neat'
|
data/lib/rubyneat/evaluator.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
require_relative 'rubyneat'
|
2
2
|
|
3
3
|
module NEAT
|
4
4
|
#= Evaluator evaluates phenotype of critter for fitness, novelty, etc.
|
@@ -22,10 +22,14 @@ module NEAT
|
|
22
22
|
# FIXME: this should not really have to deal with an error.
|
23
23
|
# FIXME: the error should be handled upstream from here.
|
24
24
|
def evaluate!(critter)
|
25
|
-
vin = @controller.
|
25
|
+
vin = @controller.query_func_hook(@controller.seq_num)
|
26
26
|
@crit_hist[critter] = {} unless @crit_hist.member? critter
|
27
27
|
begin
|
28
|
-
vout =
|
28
|
+
vout = unless @controller.recurrence_func_none?
|
29
|
+
critter.phenotype.stimulate *vin, &@controller.recurrence_func_hook_itself
|
30
|
+
else
|
31
|
+
critter.phenotype.stimulate *vin
|
32
|
+
end
|
29
33
|
log.debug "Critter #{critter.name}: vin=#{vin}. vout=#{vout}"
|
30
34
|
@crit_hist[critter][@controller.seq_num] = [vin, vout]
|
31
35
|
rescue Exception => e
|
@@ -38,10 +42,10 @@ module NEAT
|
|
38
42
|
# Note that if cost_func is set, we call that to integrate the cost to
|
39
43
|
# the fitness average fitness calculated for the fitness vector.
|
40
44
|
def analyze_for_fitness!(critter)
|
41
|
-
fitvec = @crit_hist[critter].map{|seq, vio| @controller.
|
45
|
+
fitvec = @crit_hist[critter].map{|seq, vio| @controller.fitness_func_hook(vio[0], vio[1], seq) }
|
42
46
|
# Average the fitness vector to get a scalar fitness.
|
43
|
-
critter.fitness = unless @controller.
|
44
|
-
@controller.
|
47
|
+
critter.fitness = unless @controller.cost_func_none?
|
48
|
+
@controller.cost_func_hook(fitvec, critter.genotype.fitness_cost)
|
45
49
|
else
|
46
50
|
fitvec.reduce {|a,r| a+r} / fitvec.size.to_f + critter.genotype.fitness_cost
|
47
51
|
end
|
data/lib/rubyneat/evolver.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'rubyneat'
|
2
2
|
require 'distribution'
|
3
|
+
|
3
4
|
module NEAT
|
4
5
|
#= Evolver -- Basis of all evolvers.
|
5
6
|
# All evolvers shall derive from this basic evolver (or this one can be
|
@@ -83,7 +84,7 @@ module NEAT
|
|
83
84
|
@npop.species.each do |k, sp|
|
84
85
|
sp.sort!{|c1, c2|
|
85
86
|
unless @controller.compare_func.nil?
|
86
|
-
@controller.
|
87
|
+
@controller.compare_func_hook(c1.fitness, c2.fitness)
|
87
88
|
else
|
88
89
|
c1.fitness <=> c2.fitness
|
89
90
|
end
|
data/lib/rubyneat/expressor.rb
CHANGED
data/lib/rubyneat/neuron.rb
CHANGED
data/lib/rubyneat/population.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
require_relative 'rubyneat'
|
2
2
|
|
3
3
|
module NEAT
|
4
4
|
#= Population of NEAT Critters.
|
@@ -8,6 +8,11 @@ module NEAT
|
|
8
8
|
# expression, all the phenotypes shall be created individually.
|
9
9
|
#
|
10
10
|
class Population < NeatOb
|
11
|
+
# Generation number of the Population.
|
12
|
+
# Any newly-derivied population is always one greater
|
13
|
+
# than the former. Needs to be set, invalid if nil.
|
14
|
+
attr_neat :generation, default: nil
|
15
|
+
|
11
16
|
# Ordered list or hash of input neuron classes
|
12
17
|
# (all critters generated here shall have this)
|
13
18
|
attr_accessor :input_neurons
|
@@ -46,6 +51,7 @@ module NEAT
|
|
46
51
|
@critters = (0 ... c.parms.start_population_size || c.parms.population_size).map do
|
47
52
|
Critter.new(self)
|
48
53
|
end
|
54
|
+
|
49
55
|
block.(self) unless block.nil?
|
50
56
|
end
|
51
57
|
|
@@ -160,22 +166,12 @@ module NEAT
|
|
160
166
|
|
161
167
|
end
|
162
168
|
|
163
|
-
#== Generate a report on the state of this population.
|
164
|
-
#
|
165
|
-
def report
|
166
|
-
{
|
167
|
-
fitness: report_fitness,
|
168
|
-
fitness_species: report_fitness_species,
|
169
|
-
best_critter: report_best_fit,
|
170
|
-
worst_critter: report_worst_fit,
|
171
|
-
}
|
172
|
-
end
|
173
|
-
|
174
169
|
# The "best critter" is the critter with the lowest (closet to zero)
|
175
170
|
# fitness rating.
|
171
|
+
# TODO: DRY up best_critter and worst_critter
|
176
172
|
def best_critter
|
177
|
-
unless @controller.compare_func.
|
178
|
-
@critters.min {|a, b| @controller.
|
173
|
+
unless @controller.compare_func.empty?
|
174
|
+
@critters.min {|a, b| @controller.compare_func_hook(a.fitness, b.fitness) }
|
179
175
|
else
|
180
176
|
@critters.min {|a, b| a.fitness <=> b.fitness}
|
181
177
|
end
|
@@ -184,8 +180,8 @@ module NEAT
|
|
184
180
|
# The "worst critter" is the critter with the highest (away from zero)
|
185
181
|
# fitness rating.
|
186
182
|
def worst_critter
|
187
|
-
unless @controller.compare_func.
|
188
|
-
@critters.max {|a, b| @controller.
|
183
|
+
unless @controller.compare_func.empty?
|
184
|
+
@critters.max {|a, b| @controller.compare_func_hook(a.fitness, b.fitness) }
|
189
185
|
else
|
190
186
|
@critters.max {|a, b| a.fitness <=> b.fitness}
|
191
187
|
end
|
@@ -194,34 +190,5 @@ module NEAT
|
|
194
190
|
def dump_s
|
195
191
|
to_s + "\npopulation:\n" + @critters.map{|crit| crit.dump_s }.join("\n")
|
196
192
|
end
|
197
|
-
|
198
|
-
protected
|
199
|
-
# report on many fitness metrics
|
200
|
-
def report_fitness
|
201
|
-
{
|
202
|
-
overall: @critters.map{|critter| critter.fitness}.reduce{|m, f| m + f} / @critters.size,
|
203
|
-
best: best_critter.fitness,
|
204
|
-
worst: worst_critter.fitness,
|
205
|
-
}
|
206
|
-
end
|
207
|
-
|
208
|
-
# report on the best and worst species
|
209
|
-
def report_fitness_species
|
210
|
-
{
|
211
|
-
best: nil,
|
212
|
-
worst: nil,
|
213
|
-
}
|
214
|
-
end
|
215
|
-
|
216
|
-
# Find the best fit critter
|
217
|
-
def report_best_fit
|
218
|
-
best_critter.phenotype.code
|
219
|
-
end
|
220
|
-
|
221
|
-
# Find the worst fit critter
|
222
|
-
def report_worst_fit
|
223
|
-
worst_critter.phenotype.code
|
224
|
-
end
|
225
|
-
|
226
193
|
end
|
227
194
|
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
=begin rdoc
|
2
|
+
=RubyNEAT Reporting
|
3
|
+
Here we factor out all reporting-related functionality across the entire
|
4
|
+
RubyNEAT system to this one place, because reporting is not directly related
|
5
|
+
to RubyNEAT functionality. As such, it will make it much easier for forkers
|
6
|
+
to slim down RubyNEAT for some specific application where reporting may not
|
7
|
+
be so needed.
|
8
|
+
|
9
|
+
As far as plugins go, we could insist that all plugins do their own reporting.
|
10
|
+
However, we wish to insulate such activities from the internal structures of
|
11
|
+
the Population, Critters, etc. simply they are subject to change. This affords
|
12
|
+
us the one place to look to update the API in response to deep structural changes.
|
13
|
+
=end
|
14
|
+
|
15
|
+
module NEAT
|
16
|
+
#= Population Reporting
|
17
|
+
# The tangenial reporting needs for the Population module
|
18
|
+
# are extracted here simply because they do not directly relate
|
19
|
+
# to the operations of the Population.
|
20
|
+
class Population
|
21
|
+
# report on many fitness metrics
|
22
|
+
def report_fitness
|
23
|
+
{
|
24
|
+
overall: critters.map{|critter| critter.fitness}.reduce{|m, f| m + f} / critters.size,
|
25
|
+
best: best_critter.fitness,
|
26
|
+
worst: worst_critter.fitness,
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
# report on the best and worst species
|
31
|
+
def report_fitness_species
|
32
|
+
{
|
33
|
+
best: nil,
|
34
|
+
worst: nil,
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
# Find the best fit critter
|
39
|
+
def report_best_fit
|
40
|
+
best_critter.phenotype.code
|
41
|
+
end
|
42
|
+
|
43
|
+
# Find the worst fit critter
|
44
|
+
def report_worst_fit
|
45
|
+
worst_critter.phenotype.code
|
46
|
+
end
|
47
|
+
|
48
|
+
# Create a hash of critter names and fitness values
|
49
|
+
def report_critters
|
50
|
+
critters.inject({}){|memo, critter| memo[critter.name] = critter.fitness; memo }
|
51
|
+
end
|
52
|
+
|
53
|
+
#== Generate a report on the state of this population.
|
54
|
+
#
|
55
|
+
def report
|
56
|
+
[
|
57
|
+
self,
|
58
|
+
{
|
59
|
+
generation: generation,
|
60
|
+
fitness: report_fitness,
|
61
|
+
fitness_species: report_fitness_species,
|
62
|
+
best_critter: report_best_fit,
|
63
|
+
worst_critter: report_worst_fit,
|
64
|
+
all_critters: report_critters,
|
65
|
+
}
|
66
|
+
]
|
67
|
+
end
|
68
|
+
|
69
|
+
#TODO: we should probably provide a means to invalidate this cache.
|
70
|
+
#TODO: but in most reasonable use cases this would not be called until
|
71
|
+
#TODO: after all the critters have been created.
|
72
|
+
def critter_hash
|
73
|
+
@critter_hash ||= critters.inject({}){|memo, crit| memo[crit.name]=crit; memo}
|
74
|
+
end
|
75
|
+
|
76
|
+
# Retrive list of critters given from parameters given, names of critters.
|
77
|
+
# Return the results in an array. Names given must exist. Can be either
|
78
|
+
# strings or symbols or something that can be reduced to symbols, at least.
|
79
|
+
def find_critters(*names)
|
80
|
+
names.map{|name| critter_hash[name.to_sym]}
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
#= Critter Reporting
|
85
|
+
# The reporting functionality for critters are represented here,
|
86
|
+
# since this is only tangenial to the actual functionality of
|
87
|
+
# the critters themselves.
|
88
|
+
class Critter
|
89
|
+
def report_neuron_types
|
90
|
+
{
|
91
|
+
input: population.input_neurons.map {|n| n.name},
|
92
|
+
output: population.output_neurons.map{|n| n.name},
|
93
|
+
hidden: population.hidden_neurons.map{|n| n.name}
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
def report_genotype
|
98
|
+
genotype.genes.map{|innov, gene| {in: gene.in_neuron, out: gene.out_neuron, innov: innov}}
|
99
|
+
end
|
100
|
+
|
101
|
+
def report_phenotype
|
102
|
+
phenotype.code
|
103
|
+
end
|
104
|
+
|
105
|
+
def report
|
106
|
+
{
|
107
|
+
genotype: report_genotype,
|
108
|
+
phenotype: report_phenotype,
|
109
|
+
neuron_types: report_neuron_types
|
110
|
+
}
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|