rubyneat 0.3.5.alpha.6 → 0.4.0.alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|