rubyneat 0.3.5.alpha.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.directory +4 -0
- data/.gitignore.orig +20 -0
- data/.idea/.name +1 -0
- data/.idea/.rakeTasks +7 -0
- data/.idea/dictionaries/trader.xml +3 -0
- data/.idea/encodings.xml +5 -0
- data/.idea/misc.xml +5 -0
- data/.idea/modules.xml +9 -0
- data/.idea/rubyneat.iml +197 -0
- data/.idea/runConfigurations/invpend_neat.xml +26 -0
- data/.idea/runConfigurations/sigdebug_neat.xml +24 -0
- data/.idea/runConfigurations/xor_neat.xml +26 -0
- data/.idea/runConfigurations/xordebug_neat.xml +24 -0
- data/.idea/runConfigurations/xorsin_neat.xml +24 -0
- data/.idea/scopes/scope_settings.xml +5 -0
- data/.idea/vcs.xml +7 -0
- data/.idea/workspace.xml +1124 -0
- data/.semver +5 -0
- data/.yardoc/checksums +11 -0
- data/.yardoc/object_types +0 -0
- data/.yardoc/objects/root.dat +0 -0
- data/.yardoc/proxy_types +0 -0
- data/Gemfile +32 -0
- data/Gemfile.lock +135 -0
- data/Gemfile.lock.orig +147 -0
- data/Guardfile +8 -0
- data/Rakefile +61 -0
- data/bin/neat +83 -0
- data/config/application.rb +5 -0
- data/doc/ControllerPoint.html +125 -0
- data/doc/CuteA.html +286 -0
- data/doc/CuteB.html +297 -0
- data/doc/DSL.html +883 -0
- data/doc/NEAT/BasicNeuronTypes/BiasNeuron.html +518 -0
- data/doc/NEAT/BasicNeuronTypes/CosineNeuron.html +274 -0
- data/doc/NEAT/BasicNeuronTypes/InputNeuron.html +366 -0
- data/doc/NEAT/BasicNeuronTypes/SigmoidNeuron.html +275 -0
- data/doc/NEAT/BasicNeuronTypes/SineNeuron.html +274 -0
- data/doc/NEAT/BasicNeuronTypes/TanhNeuron.html +274 -0
- data/doc/NEAT/BasicNeuronTypes.html +136 -0
- data/doc/NEAT/Controller/NeatSettings.html +3985 -0
- data/doc/NEAT/Controller.html +2490 -0
- data/doc/NEAT/Critter/Genotype/Gene.html +979 -0
- data/doc/NEAT/Critter/Genotype.html +1601 -0
- data/doc/NEAT/Critter/Phenotype.html +603 -0
- data/doc/NEAT/Critter.html +1037 -0
- data/doc/NEAT/DSL.html +1255 -0
- data/doc/NEAT/Evaluator.html +420 -0
- data/doc/NEAT/Evolver/CritterOp.html +551 -0
- data/doc/NEAT/Evolver.html +602 -0
- data/doc/NEAT/Expressor.html +327 -0
- data/doc/NEAT/Graph/DependencyResolver.html +478 -0
- data/doc/NEAT/Graph/GraphException.html +123 -0
- data/doc/NEAT/Graph.html +402 -0
- data/doc/NEAT/NeatException.html +123 -0
- data/doc/NEAT/NeatOb.html +567 -0
- data/doc/NEAT/Neuron.html +1067 -0
- data/doc/NEAT/Operator.html +162 -0
- data/doc/NEAT/Population.html +1961 -0
- data/doc/NEAT/Trait.html +169 -0
- data/doc/NEAT.html +588 -0
- data/doc/_index.html +373 -0
- data/doc/class_list.html +54 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +57 -0
- data/doc/css/style.css +339 -0
- data/doc/file_list.html +53 -0
- data/doc/frames.html +26 -0
- data/doc/index.html +373 -0
- data/doc/js/app.js +219 -0
- data/doc/js/full_list.js +178 -0
- data/doc/js/jquery.js +4 -0
- data/doc/method_list.html +1415 -0
- data/doc/top-level-namespace.html +164 -0
- data/foo/foo_aquarium_example.rb +38 -0
- data/foo/foo_gosu.rb +99 -0
- data/foo/foo_rubygoo.rb +104 -0
- data/foo/foo_sdl.rb +34 -0
- data/foo/icon.png +0 -0
- data/lib/rubyneat/critter.rb +374 -0
- data/lib/rubyneat/default_neat.rb +10 -0
- data/lib/rubyneat/dsl.rb +130 -0
- data/lib/rubyneat/evaluator.rb +51 -0
- data/lib/rubyneat/evolver.rb +315 -0
- data/lib/rubyneat/expressor.rb +110 -0
- data/lib/rubyneat/graph.rb +95 -0
- data/lib/rubyneat/neuron.rb +152 -0
- data/lib/rubyneat/population.rb +227 -0
- data/lib/rubyneat/rubyneat.rb +429 -0
- data/lib/rubyneat.rb +8 -0
- data/neater/invpend_neat.rb +150 -0
- data/neater/rnlib/inverted_pendulum.rb +380 -0
- data/neater/rnlib/xor.rb +10 -0
- data/neater/sigdebug_neat.rb +136 -0
- data/neater/xor_neat.rb +137 -0
- data/neater/xoranalog_neat.rb +138 -0
- data/neater/xorsin_neat.rb +143 -0
- data/projectFilesBackup/.idea/rubyneat.iml +180 -0
- data/public/.directory +4 -0
- data/public/background.png +0 -0
- data/public/background.xcf +0 -0
- data/public/cart.png +0 -0
- data/public/cart.xcf +0 -0
- data/public/metalpoles_molton_ball_l.jpg +0 -0
- data/public/old_background.png +0 -0
- data/public/pointer.png +0 -0
- data/public/pointer.xcf +0 -0
- data/public/pole.kra +0 -0
- data/public/pole.png +0 -0
- data/public/pole.xcf +0 -0
- data/public/wheel-of-year-stone-DD-131-WOYS.jpg +0 -0
- data/public/wheel.png +0 -0
- data/public/wheel.xcf +0 -0
- data/public/wood-planks-texture.jpg +0 -0
- data/rdoc/ControllerPoint.html +116 -0
- data/rdoc/CuteA.html +177 -0
- data/rdoc/CuteB.html +178 -0
- data/rdoc/DSLSetup.html +177 -0
- data/rdoc/GameTestWindow.html +242 -0
- data/rdoc/GameWindow.html +292 -0
- data/rdoc/Gemfile.html +215 -0
- data/rdoc/Gemfile_lock.html +327 -0
- data/rdoc/GraphTest.html +210 -0
- data/rdoc/Guardfile.html +198 -0
- data/rdoc/InvertedPendulum/Cart.html +668 -0
- data/rdoc/InvertedPendulum/DSL.html +259 -0
- data/rdoc/InvertedPendulum/InvPendWindow.html +402 -0
- data/rdoc/InvertedPendulum.html +198 -0
- data/rdoc/Logger.html +98 -0
- data/rdoc/NEAT/BasicNeuronTypes/BiasNeuron.html +265 -0
- data/rdoc/NEAT/BasicNeuronTypes/CosineNeuron.html +162 -0
- data/rdoc/NEAT/BasicNeuronTypes/InputNeuron.html +206 -0
- data/rdoc/NEAT/BasicNeuronTypes/SigmoidNeuron.html +162 -0
- data/rdoc/NEAT/BasicNeuronTypes/SineNeuron.html +162 -0
- data/rdoc/NEAT/BasicNeuronTypes/TanhNeuron.html +161 -0
- data/rdoc/NEAT/BasicNeuronTypes.html +107 -0
- data/rdoc/NEAT/Controller/NeatSettings.html +880 -0
- data/rdoc/NEAT/Controller.html +729 -0
- data/rdoc/NEAT/Critter/Genotype/Gene.html +457 -0
- data/rdoc/NEAT/Critter/Genotype.html +735 -0
- data/rdoc/NEAT/Critter/Phenotype.html +330 -0
- data/rdoc/NEAT/Critter.html +489 -0
- data/rdoc/NEAT/DSL.html +729 -0
- data/rdoc/NEAT/Evaluator.html +256 -0
- data/rdoc/NEAT/Evolver/CritterOp.html +349 -0
- data/rdoc/NEAT/Evolver.html +891 -0
- data/rdoc/NEAT/Expressor.html +402 -0
- data/rdoc/NEAT/Graph/DependencyResolver.html +291 -0
- data/rdoc/NEAT/Graph/GraphException.html +105 -0
- data/rdoc/NEAT/Graph.html +263 -0
- data/rdoc/NEAT/NeatException.html +105 -0
- data/rdoc/NEAT/NeatOb.html +325 -0
- data/rdoc/NEAT/Neuron.html +481 -0
- data/rdoc/NEAT/Operator.html +109 -0
- data/rdoc/NEAT/Population.html +935 -0
- data/rdoc/NEAT/Trait.html +117 -0
- data/rdoc/NEAT.html +422 -0
- data/rdoc/Object.html +384 -0
- data/rdoc/Phi.html +98 -0
- data/rdoc/Player.html +383 -0
- data/rdoc/Rakefile.html +254 -0
- data/rdoc/RubyNEAT/Application.html +105 -0
- data/rdoc/RubyNEAT.html +98 -0
- data/rdoc/SDL/Event2.html +98 -0
- data/rdoc/SDL.html +98 -0
- data/rdoc/Vector.html +195 -0
- data/rdoc/created.rid +125 -0
- data/rdoc/doc/ControllerPoint_html.html +299 -0
- data/rdoc/doc/CuteA_html.html +438 -0
- data/rdoc/doc/CuteB_html.html +436 -0
- data/rdoc/doc/DSL_html.html +992 -0
- data/rdoc/doc/NEAT/BasicNeuronTypes/BiasNeuron_html.html +617 -0
- data/rdoc/doc/NEAT/BasicNeuronTypes/CosineNeuron_html.html +413 -0
- data/rdoc/doc/NEAT/BasicNeuronTypes/InputNeuron_html.html +498 -0
- data/rdoc/doc/NEAT/BasicNeuronTypes/SigmoidNeuron_html.html +413 -0
- data/rdoc/doc/NEAT/BasicNeuronTypes/SineNeuron_html.html +413 -0
- data/rdoc/doc/NEAT/BasicNeuronTypes/TanhNeuron_html.html +412 -0
- data/rdoc/doc/NEAT/BasicNeuronTypes_html.html +310 -0
- data/rdoc/doc/NEAT/Controller/NeatSettings_html.html +3324 -0
- data/rdoc/doc/NEAT/Controller_html.html +2212 -0
- data/rdoc/doc/NEAT/Critter/Genotype/Gene_html.html +997 -0
- data/rdoc/doc/NEAT/Critter/Genotype_html.html +1556 -0
- data/rdoc/doc/NEAT/Critter/Phenotype_html.html +687 -0
- data/rdoc/doc/NEAT/Critter_html.html +1037 -0
- data/rdoc/doc/NEAT/DSL_html.html +1349 -0
- data/rdoc/doc/NEAT/Evaluator_html.html +556 -0
- data/rdoc/doc/NEAT/Evolver/CritterOp_html.html +690 -0
- data/rdoc/doc/NEAT/Evolver_html.html +677 -0
- data/rdoc/doc/NEAT/Expressor_html.html +468 -0
- data/rdoc/doc/NEAT/Graph/DependencyResolver_html.html +598 -0
- data/rdoc/doc/NEAT/Graph/GraphException_html.html +299 -0
- data/rdoc/doc/NEAT/Graph_html.html +527 -0
- data/rdoc/doc/NEAT/NeatException_html.html +299 -0
- data/rdoc/doc/NEAT/NeatOb_html.html +671 -0
- data/rdoc/doc/NEAT/Neuron_html.html +1095 -0
- data/rdoc/doc/NEAT/Operator_html.html +337 -0
- data/rdoc/doc/NEAT/Population_html.html +1795 -0
- data/rdoc/doc/NEAT/Trait_html.html +344 -0
- data/rdoc/doc/NEAT_html.html +736 -0
- data/rdoc/doc/_index_html.html +559 -0
- data/rdoc/doc/class_list_html.html +369 -0
- data/rdoc/doc/css/common_css.html +188 -0
- data/rdoc/doc/css/full_list_css.html +243 -0
- data/rdoc/doc/css/style_css.html +530 -0
- data/rdoc/doc/file_list_html.html +240 -0
- data/rdoc/doc/frames_html.html +217 -0
- data/rdoc/doc/index_html.html +559 -0
- data/rdoc/doc/js/app_js.html +423 -0
- data/rdoc/doc/js/full_list_js.html +372 -0
- data/rdoc/doc/js/jquery_js.html +1536 -0
- data/rdoc/doc/method_list_html.html +1375 -0
- data/rdoc/doc/top-level-namespace_html.html +317 -0
- data/rdoc/fonts/Lato-Light.ttf +0 -0
- data/rdoc/fonts/Lato-LightItalic.ttf +0 -0
- data/rdoc/fonts/Lato-Regular.ttf +0 -0
- data/rdoc/fonts/Lato-RegularItalic.ttf +0 -0
- data/rdoc/fonts/SourceCodePro-Bold.ttf +0 -0
- data/rdoc/fonts/SourceCodePro-Regular.ttf +0 -0
- data/rdoc/fonts.css +167 -0
- data/rdoc/images/add.png +0 -0
- data/rdoc/images/arrow_up.png +0 -0
- data/rdoc/images/brick.png +0 -0
- data/rdoc/images/brick_link.png +0 -0
- data/rdoc/images/bug.png +0 -0
- data/rdoc/images/bullet_black.png +0 -0
- data/rdoc/images/bullet_toggle_minus.png +0 -0
- data/rdoc/images/bullet_toggle_plus.png +0 -0
- data/rdoc/images/date.png +0 -0
- data/rdoc/images/delete.png +0 -0
- data/rdoc/images/find.png +0 -0
- data/rdoc/images/loadingAnimation.gif +0 -0
- data/rdoc/images/macFFBgHack.png +0 -0
- data/rdoc/images/package.png +0 -0
- data/rdoc/images/page_green.png +0 -0
- data/rdoc/images/page_white_text.png +0 -0
- data/rdoc/images/page_white_width.png +0 -0
- data/rdoc/images/plugin.png +0 -0
- data/rdoc/images/ruby.png +0 -0
- data/rdoc/images/tag_blue.png +0 -0
- data/rdoc/images/tag_green.png +0 -0
- data/rdoc/images/transparent.png +0 -0
- data/rdoc/images/wrench.png +0 -0
- data/rdoc/images/wrench_orange.png +0 -0
- data/rdoc/images/zoom.png +0 -0
- data/rdoc/index.html +282 -0
- data/rdoc/js/darkfish.js +140 -0
- data/rdoc/js/jquery.js +18 -0
- data/rdoc/js/navigation.js +142 -0
- data/rdoc/js/search.js +109 -0
- data/rdoc/js/search_index.js +1 -0
- data/rdoc/js/searcher.js +228 -0
- data/rdoc/rdoc.css +580 -0
- data/rdoc/rubyneat_gemspec.html +387 -0
- data/rdoc/table_of_contents.html +2502 -0
- data/rdoc/xordebug_log.html +170598 -0
- data/rdoc/xorsin_log.html +22569 -0
- data/rubyneat.gemspec +347 -0
- data/rubyneat.gemspec.orig +375 -0
- data/spec/lib/rubyneat/rubyneat_spec.rb +132 -0
- metadata +555 -0
@@ -0,0 +1,315 @@
|
|
1
|
+
require 'rubyneat'
|
2
|
+
require 'distribution'
|
3
|
+
module NEAT
|
4
|
+
#= Evolver -- Basis of all evolvers.
|
5
|
+
# All evolvers shall derive from this basic evolver (or this one can be
|
6
|
+
# used as is). Here, we'll have many different evolutionary operators
|
7
|
+
# that will perform operations on the various critters in the population.
|
8
|
+
#
|
9
|
+
|
10
|
+
class Evolver < Operator
|
11
|
+
attr_reader :npop
|
12
|
+
|
13
|
+
def initialize(c)
|
14
|
+
super
|
15
|
+
@critter_op = CritterOp.new self
|
16
|
+
end
|
17
|
+
|
18
|
+
# Generate the initial genes for a given genotype.
|
19
|
+
# We key genes off their innovation numbers.
|
20
|
+
def gen_initial_genes!(genotype)
|
21
|
+
genotype.genes = {}
|
22
|
+
genotype.neural_inputs.each do |s1, input|
|
23
|
+
genotype.neural_outputs.each do |s2, output|
|
24
|
+
g = Critter::Genotype::Gene[genotype, input, output, NEAT::controller.gaussian]
|
25
|
+
genotype.genes[g.innovation] = g
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Here we mutate the population.
|
31
|
+
def mutate!(population)
|
32
|
+
@npop = population
|
33
|
+
|
34
|
+
if @controller.parms.mate_only_prob.nil? or rand > @controller.parms.mate_only_prob
|
35
|
+
log.debug "[[[ Neuron and Gene Giggling!"
|
36
|
+
mutate_perturb_gene_weights!
|
37
|
+
mutate_change_gene_weights!
|
38
|
+
mutate_add_neurons!
|
39
|
+
mutate_change_neurons!
|
40
|
+
mutate_add_genes!
|
41
|
+
mutate_disable_genes!
|
42
|
+
mutate_reenable_genes!
|
43
|
+
log.debug "]]] End Neuron and Gene Giggling!\n"
|
44
|
+
else
|
45
|
+
log.debug "*** Mating only!"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Here we clone the population and then evolve it
|
50
|
+
# on the basis of fitness and novelty, etc.
|
51
|
+
#
|
52
|
+
# Returns the newly-evolved population.
|
53
|
+
def evolve(population)
|
54
|
+
@npop = population.dclone
|
55
|
+
|
56
|
+
# Population sorting and evaluation for breeding, mutations, etc.
|
57
|
+
prepare_speciation!
|
58
|
+
prepare_fitness!
|
59
|
+
prepare_novelty!
|
60
|
+
|
61
|
+
mate!
|
62
|
+
|
63
|
+
return @npop
|
64
|
+
end
|
65
|
+
|
66
|
+
# Here we specify evolutionary operators.
|
67
|
+
protected
|
68
|
+
|
69
|
+
def prepare_speciation!
|
70
|
+
@npop.speciate!
|
71
|
+
log.debug "SPECIES:"
|
72
|
+
NEAT::dpp @npop.species
|
73
|
+
end
|
74
|
+
|
75
|
+
# Sort species within the basis of fitness.
|
76
|
+
# Think of the fitness as an error / cost function.
|
77
|
+
# The better fit, the closer to zero the fitness parameter will be.
|
78
|
+
#
|
79
|
+
# If a compare block is specified in the DSL, then that function is called
|
80
|
+
# with the *fitness values* from critters c1 and c2. The default valuation
|
81
|
+
# is c1.fitness <=> c2.fitness. You may elect to evaluate them differently.
|
82
|
+
def prepare_fitness!
|
83
|
+
@npop.species.each do |k, sp|
|
84
|
+
sp.sort!{|c1, c2|
|
85
|
+
unless @controller.compare_func.nil?
|
86
|
+
@controller.compare_func.(c1.fitness, c2.fitness)
|
87
|
+
else
|
88
|
+
c1.fitness <=> c2.fitness
|
89
|
+
end
|
90
|
+
}
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
#TODO: write novelty code
|
95
|
+
def prepare_novelty!
|
96
|
+
end
|
97
|
+
|
98
|
+
# Perturb existing gene weights by adding a guassian to them.
|
99
|
+
def mutate_perturb_gene_weights!
|
100
|
+
@gperturb = Distribution::Normal::rng(0, @controller.parms.mutate_perturb_gene_weights_sd) if @gperturb.nil?
|
101
|
+
@npop.critters.each do |critter|
|
102
|
+
critter.genotype.genes.each { |innov, gene|
|
103
|
+
if rand < @controller.parms.mutate_perturb_gene_weights_prob
|
104
|
+
gene.weight += per = @gperturb.()
|
105
|
+
log.debug { "Peturbed gene #{gene}.#{innov} by #{per}" }
|
106
|
+
end
|
107
|
+
}
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Totally change weights to something completely different
|
112
|
+
def mutate_change_gene_weights!
|
113
|
+
@gchange = Distribution::Normal::rng(0, @controller.parms.mutate_change_gene_weights_sd) if @gchange.nil?
|
114
|
+
@npop.critters.each do |critter|
|
115
|
+
critter.genotype.genes.each { |innov, gene|
|
116
|
+
if rand < @controller.parms.mutate_change_gene_weights_prob
|
117
|
+
gene.weight = chg = @gchange.()
|
118
|
+
log.debug { "Change gene #{gene}.#{innov} by #{chg}" }
|
119
|
+
end
|
120
|
+
}
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def mutate_add_genes!
|
125
|
+
@npop.critters.each do |critter|
|
126
|
+
if rand < @controller.parms.mutate_add_gene_prob
|
127
|
+
log.debug "mutate_add_genes! for #{critter}"
|
128
|
+
@critter_op.add_gene! critter
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def mutate_disable_genes!
|
134
|
+
@npop.critters.each do |critter|
|
135
|
+
if rand < @controller.parms.mutate_gene_disable_prob
|
136
|
+
log.debug "mutate_disable_genes! for #{critter}"
|
137
|
+
@critter_op.disable_gene! critter
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def mutate_reenable_genes!
|
143
|
+
@npop.critters.each do |critter|
|
144
|
+
if rand < @controller.parms.mutate_gene_reenable_prob
|
145
|
+
log.debug "mutate_reenable_genes! for #{critter}"
|
146
|
+
@critter_op.reenable_gene! critter
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def mutate_add_neurons!
|
152
|
+
@npop.critters.each do |critter|
|
153
|
+
if rand < @controller.parms.mutate_add_neuron_prob
|
154
|
+
log.debug "mutate_add_neurons! for #{critter}"
|
155
|
+
@critter_op.add_neuron! critter
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# TODO Finish mutate_change_neurons!
|
161
|
+
def mutate_change_neurons!
|
162
|
+
log.error "mutate_change_neurons! NIY"
|
163
|
+
end
|
164
|
+
|
165
|
+
# Here we select candidates for mating. We must look at species and fitness
|
166
|
+
# to make the selection for mating.
|
167
|
+
def mate!
|
168
|
+
parm = @controller.parms
|
169
|
+
popsize = parm.population_size
|
170
|
+
surv = parm.survival_threshold
|
171
|
+
survmin = parm.survival_mininum_per_species
|
172
|
+
mlist = [] # list of chosen mating pairs of critters [crit1, crit2], or [:carryover, crit]
|
173
|
+
|
174
|
+
# species list already sorted in descending order of fitness.
|
175
|
+
# We will generate the approximate number of pairs that correspond
|
176
|
+
# to the survivial_threshold percentage of the population,
|
177
|
+
# then backfill with the most fit out of the top original population.
|
178
|
+
@npop.species.each do |k, sp|
|
179
|
+
crem = [(sp.size * surv).ceil, survmin].max
|
180
|
+
log.warn "Minumum per species hit -- #{survmin}" unless crem > survmin
|
181
|
+
spsel = sp[0, crem]
|
182
|
+
spsel = sp if spsel.empty?
|
183
|
+
crem.times do
|
184
|
+
mlist << [spsel[rand spsel.size], spsel[rand spsel.size]]
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# And now for the backfilling
|
189
|
+
unless mlist.size >= @npop.critters.size
|
190
|
+
mlist += @npop.critters[0, @npop.critters.size - mlist.size].map{|crit| [:carryover, crit]}
|
191
|
+
end
|
192
|
+
|
193
|
+
@npop.critters = mlist.map do |crit1, crit2|
|
194
|
+
(crit1 == :carryover) ? crit2 : sex(crit1, crit2)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
protected
|
199
|
+
# Mate the given critters and return a baby.
|
200
|
+
# This is rather involved, and relies heavily on the Innovation Numbers.
|
201
|
+
#
|
202
|
+
# Some definitions:
|
203
|
+
#
|
204
|
+
## Matching Gene
|
205
|
+
### 2 genes with matching innovation numbers.
|
206
|
+
#
|
207
|
+
## Disjoint Gene
|
208
|
+
### A gene in one has an innovation number in the range of innovation numbers
|
209
|
+
### of the other.
|
210
|
+
#
|
211
|
+
## Excess Gene
|
212
|
+
### Gene in one critter that has an innovation number outside of the range
|
213
|
+
### of innovation numbers of the other.
|
214
|
+
#
|
215
|
+
## Neurons
|
216
|
+
### Distinct Neurons from both crit1 and crit2 must be present in
|
217
|
+
### the baby.
|
218
|
+
#
|
219
|
+
# Excess and Disjoint genes are always included from the more fit parent.
|
220
|
+
# Matching genes are randomly chosen. For now, we make it 50/50.
|
221
|
+
def sex(crit1, crit2)
|
222
|
+
Critter.new(@npop, true) do |baby|
|
223
|
+
fitcrit = if crit1.fitness > crit2.fitness
|
224
|
+
crit1
|
225
|
+
elsif crit2.fitness > crit1.fitness
|
226
|
+
crit2
|
227
|
+
else
|
228
|
+
(rand(2) == 1) ? crit1 : crit2
|
229
|
+
end
|
230
|
+
a = crit1.genotype.genes.keys.to_set
|
231
|
+
b = crit2.genotype.genes.keys.to_set
|
232
|
+
disjoint = (a - b) + (b - a)
|
233
|
+
joint = (a + b) - disjoint
|
234
|
+
baby.genotype.neucleate { |gtype|
|
235
|
+
joint.map { |innov|
|
236
|
+
g1 = crit1.genotype.genes[innov]
|
237
|
+
g2 = crit2.genotype.genes[innov]
|
238
|
+
Critter::Genotype::Gene[gtype,
|
239
|
+
g1.in_neuron, g1.out_neuron,
|
240
|
+
(rand(2) == 1) ? g1.weight : g2.weight,
|
241
|
+
innov]
|
242
|
+
} + disjoint.map { |innov|
|
243
|
+
fitcrit.genotype.genes[innov].clone unless fitcrit.genotype.genes[innov].nil?
|
244
|
+
}.reject{|i| i.nil? }
|
245
|
+
}
|
246
|
+
baby.genotype.innervate! crit1.genotype.neurons, crit2.genotype.neurons
|
247
|
+
baby.genotype.prune!
|
248
|
+
baby.genotype.wire!
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
# A set of Critter Genotype operators.
|
253
|
+
class CritterOp < NeatOb
|
254
|
+
def initialize(evol)
|
255
|
+
super evol.controller
|
256
|
+
@evolver = evol
|
257
|
+
@npop = evol.npop
|
258
|
+
end
|
259
|
+
|
260
|
+
#= Add a neuron to given critter
|
261
|
+
# Here, we add a neuron by randomly picking a
|
262
|
+
# gene, and split it into two genes with an intervening
|
263
|
+
# neuron. The old gene is not replaced, but disabled. 2 new genes are
|
264
|
+
# created along with the new neuron.
|
265
|
+
def add_neuron!(crit)
|
266
|
+
gene = crit.genotype.genes.values.sample
|
267
|
+
neu = controller.neural_hidden.values.sample.new(controller)
|
268
|
+
g1 = Critter::Genotype::Gene[crit.genotype, gene.in_neuron, neu.name, gene.weight]
|
269
|
+
g2 = Critter::Genotype::Gene[crit.genotype, neu.name, gene.out_neuron, gene.weight]
|
270
|
+
gene.enabled = false
|
271
|
+
crit.genotype.add_neurons neu
|
272
|
+
crit.genotype.add_genes g1, g2
|
273
|
+
log.debug "add_neuron!: neu #{neu}, g1 #{g1}, g2 #{g2}"
|
274
|
+
end
|
275
|
+
|
276
|
+
#= Add a gene to the genome
|
277
|
+
# Unlike adding a new neuron, adding a new gene
|
278
|
+
# could result in a circular dependency. If so,
|
279
|
+
# and if recurrency is switched off, we must detect
|
280
|
+
# this condition and switch off the offending neurons.
|
281
|
+
#
|
282
|
+
# Obviously, this might result in a loss of functionality, but
|
283
|
+
# oh well.
|
284
|
+
#
|
285
|
+
# An easy and obvious check is to make sure we don't
|
286
|
+
# accept any inputs from output neurons, and we don't
|
287
|
+
# do any outputs to input neurons.
|
288
|
+
#
|
289
|
+
# Constructs for handling recurrency are present in Expressor.
|
290
|
+
def add_gene!(crit)
|
291
|
+
n1 = crit.genotype.neurons.values.sample # input
|
292
|
+
n2 = crit.genotype.neurons.values.sample # output
|
293
|
+
|
294
|
+
# Sanity checks!
|
295
|
+
unless n1 == n2 or n1.output? or n2.input?
|
296
|
+
gene = Critter::Genotype::Gene[crit.genotype, n1.name, n2.name, NEAT::controller.gaussian]
|
297
|
+
crit.genotype.add_genes gene
|
298
|
+
log.debug "add_gene! Added gene #{gene}(#{n1.name} -> #{n2.name}) to #{crit}"
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
# Pick an enabled gene at random and disable it.
|
303
|
+
def disable_gene!(crit)
|
304
|
+
gene = crit.genotype.genes.values.reject{|gene| gene.disabled? }.sample
|
305
|
+
gene.enabled = false unless gene.nil?
|
306
|
+
end
|
307
|
+
|
308
|
+
# Pick a disabled gene at random and reenable it.
|
309
|
+
def reenable_gene!(crit)
|
310
|
+
gene = crit.genotype.genes.values.reject{|gene| gene.enabled? }.sample
|
311
|
+
gene.enabled = true unless gene.nil?
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'rubyneat/rubyneat'
|
2
|
+
require 'pp'
|
3
|
+
|
4
|
+
module NEAT
|
5
|
+
#= Basis of all expressors.
|
6
|
+
# Expressor object turn genotypes into phenotypes.
|
7
|
+
class Expressor < Operator
|
8
|
+
def initialize(c)
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
# Take the genotype of the critter and
|
13
|
+
# create a phenotype from the genotype.
|
14
|
+
#
|
15
|
+
# In the phenotype, it creates a function called stimulate(),
|
16
|
+
# which is called with the input parameters and returns a response
|
17
|
+
# in the form of a response hash (which corresponds directly to the
|
18
|
+
# output neurons).
|
19
|
+
#
|
20
|
+
# This implementation assumes an acyclic graph (feed forward)
|
21
|
+
# and cannot handle cycles at all. Later we may fix this or create
|
22
|
+
# a type of Expressor that can.
|
23
|
+
def express!(critter)
|
24
|
+
critter.ready_for_expression!
|
25
|
+
express_neurons! critter
|
26
|
+
express_genes! critter
|
27
|
+
express_expression! critter
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
# Express Neurons as methods
|
32
|
+
def express_neurons!(critter)
|
33
|
+
critter.genotype.neurons.each do |name, neuron|
|
34
|
+
neuron.express(critter.phenotype) unless neuron.input? and not neuron.bias?
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
#= Expression of the Genotype as a Phenotype.
|
39
|
+
# What this really does is create the function that calls
|
40
|
+
# all the functions.
|
41
|
+
#
|
42
|
+
# This makes use of the Graph plugin for Neurons.
|
43
|
+
#= Recurrency and Expression of Genes
|
44
|
+
# A simple approach has been taken here to allow for recurrency in
|
45
|
+
# our Critters. Basically, a looping construct has been put around the
|
46
|
+
# activation of the neurons so that recurrency can be done in 2 ways:
|
47
|
+
## 1) Via yielding, thus treating the stimulus function as a enumerable.
|
48
|
+
### In this approach, one would call the Critter's phenotype with a block of
|
49
|
+
### code that would accept the output of the net. It would return 'true' to
|
50
|
+
### continue the iteration, or 'false' to end the iteration.
|
51
|
+
## 2) Via multiple calls to the Pheontype instance:
|
52
|
+
### Since the value of the prior activation is preserved in the instance variables
|
53
|
+
### of the phenotype, subsequent activations will iterate the network.
|
54
|
+
#== Cavets to recurrent activation
|
55
|
+
# For (2) above, the input neurons would be overwritten on each subsequent call.
|
56
|
+
# Since we do not allow recurrent connections to input neurons anyway, this should
|
57
|
+
# not be an issue, though we may allow for this at a future date.
|
58
|
+
def express_genes!(critter)
|
59
|
+
g = critter.genotype
|
60
|
+
p = critter.phenotype
|
61
|
+
|
62
|
+
init_code = "\n def initialize_neurons\n"
|
63
|
+
|
64
|
+
# 'stimulate' function call (really should be 'activate', but we'll reserve this for something else)
|
65
|
+
p.code += " def #{NEAT::STIMULUS}("
|
66
|
+
p.code += g.neural_inputs.reject{ |sym| g.neural_inputs[sym].bias? }.map{|sym, neu| sym}.join(", ")
|
67
|
+
p.code += ")\n"
|
68
|
+
|
69
|
+
# Assign all the parameters to instance variables.
|
70
|
+
p.code += g.neural_inputs.map{|sym, neu| " @#{sym} = #{sym}\n"}.join("")
|
71
|
+
p.code += " loop {\n"
|
72
|
+
|
73
|
+
# Resolve the order in which we shall call the neurons
|
74
|
+
# TODO handle the dependency list if it comes back!
|
75
|
+
@resolved, @dependencies = NEAT::Graph::DependencyResolver[g.neural_outputs.map{|s, neu| neu}].resolve
|
76
|
+
|
77
|
+
# And now call them in that order!
|
78
|
+
@resolved.each do |neu|
|
79
|
+
unless neu.input?
|
80
|
+
init_code += " @#{neu.name} = 0\n"
|
81
|
+
if g.neural_gene_map.member? neu.name
|
82
|
+
p.code += " @#{neu.name} = #{neu.name}("
|
83
|
+
p.code += g.neural_gene_map[neu.name].map{ |gene|
|
84
|
+
"%s * @%s" % [gene.weight, gene.in_neuron]
|
85
|
+
}.join(", ") + ")\n"
|
86
|
+
else
|
87
|
+
g.dangling_neurons = true
|
88
|
+
log.debug "Dangling neuron in critter #{critter} -- #{neu}"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
init_code += " end\n"
|
93
|
+
|
94
|
+
# And now return the result as a vector of outputs.
|
95
|
+
p.code += " @_outvec = [" + g.neural_outputs.map{|sym, neu| "@#{sym}"}.join(',') + "]\n"
|
96
|
+
p.code += " break unless block_given?\n"
|
97
|
+
p.code += " break unless yield @_outvec\n"
|
98
|
+
p.code += " }\n"
|
99
|
+
p.code += " @_outvec\n"
|
100
|
+
p.code += " end\n"
|
101
|
+
p.code += init_code
|
102
|
+
log.debug p.code
|
103
|
+
p.express!
|
104
|
+
end
|
105
|
+
|
106
|
+
def express_expression!(critter)
|
107
|
+
critter.phenotype.express!
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'rubyneat'
|
2
|
+
|
3
|
+
module NEAT
|
4
|
+
# General graph representation
|
5
|
+
# (mainly used for Neurons, but could be used
|
6
|
+
# for other structures.)
|
7
|
+
#
|
8
|
+
# This is a mixin for Neuron and whatever else you'd like.
|
9
|
+
# the contained class is for evaluation, and may be instantiated separately.
|
10
|
+
module Graph
|
11
|
+
class GraphException < Exception
|
12
|
+
end
|
13
|
+
|
14
|
+
# clear and initialize the graph.
|
15
|
+
def clear_graph
|
16
|
+
@g_inputs = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def << (input)
|
20
|
+
@g_inputs << input
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
# Add a single input
|
25
|
+
def add (input)
|
26
|
+
@g_inputs << input
|
27
|
+
end
|
28
|
+
|
29
|
+
# Get list of inputs
|
30
|
+
def inputs
|
31
|
+
raise GraphException.new "Graph Failure -- input is nil" if @g_inputs.nil?
|
32
|
+
@g_inputs
|
33
|
+
end
|
34
|
+
|
35
|
+
# Create an instantiation of this and pass it a list of nodes to resolve.
|
36
|
+
class DependencyResolver < NeatOb
|
37
|
+
|
38
|
+
# Given a list of output nodes, we shall work backwards
|
39
|
+
# from them to resolve their dependencies.
|
40
|
+
def initialize(outputs, &block)
|
41
|
+
@outputs = outputs
|
42
|
+
super
|
43
|
+
block.(self) unless block.nil?
|
44
|
+
end
|
45
|
+
|
46
|
+
# Create a DependencyResolver from either
|
47
|
+
# an array of outputs or a parameter list of outputs.
|
48
|
+
def self.[](*outs)
|
49
|
+
outs = outs.first if outs.first.kind_of? Array
|
50
|
+
DependencyResolver.new outs
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
# Resolve dependencies, and return [dependency_list, circular_ref_node_list]
|
55
|
+
# Note that circular_ref_node_list shall be nil if there are no dependencies!
|
56
|
+
def resolve
|
57
|
+
@resolved = []
|
58
|
+
@unresolved = []
|
59
|
+
@circular = []
|
60
|
+
@outputs.each do |onode|
|
61
|
+
rdep onode
|
62
|
+
end
|
63
|
+
[@resolved, @circular.empty? ? nil : @circular]
|
64
|
+
end
|
65
|
+
|
66
|
+
# Throw an exception if dependencies are found.
|
67
|
+
# We only return the dependency list since we throw an exception on circular
|
68
|
+
# dependencies.
|
69
|
+
def resolve!
|
70
|
+
dl, cl = resolve
|
71
|
+
raise GraphException("Circular Dependency Detected: %s" % cl) unless cl.nil?
|
72
|
+
dl
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
# recursive resolution of nodes
|
77
|
+
def rdep(node)
|
78
|
+
@unresolved << node
|
79
|
+
node.inputs.each { |inode|
|
80
|
+
if not @resolved.member? inode
|
81
|
+
unless @unresolved.member? inode
|
82
|
+
rdep inode
|
83
|
+
else
|
84
|
+
# we found a circular reference.
|
85
|
+
@circular << inode
|
86
|
+
#log.warn "Dependency found: %s" % inode
|
87
|
+
end
|
88
|
+
end
|
89
|
+
}
|
90
|
+
@resolved << node
|
91
|
+
@unresolved.delete node
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'rubyneat'
|
2
|
+
require 'rubyneat/graph'
|
3
|
+
|
4
|
+
=begin rdoc
|
5
|
+
= Neuron Types
|
6
|
+
|
7
|
+
We create all the neuron types for this system here.
|
8
|
+
|
9
|
+
=end
|
10
|
+
module NEAT
|
11
|
+
#= Neuron -- Basis of all Neat Neuron types.
|
12
|
+
# Normally contains primatives which aids in its
|
13
|
+
# own expression, but the details of this remains to be worked out.
|
14
|
+
class Neuron < NeatOb
|
15
|
+
include Math
|
16
|
+
include Graph
|
17
|
+
|
18
|
+
# Genotype to which we belong
|
19
|
+
attr_reader :genotype
|
20
|
+
attr_accessor :trait
|
21
|
+
|
22
|
+
# (assigned by wire!) Heirarchy number in the Genome / Critter
|
23
|
+
# We need this to assure we add genes in the proper order.
|
24
|
+
# This will be recalculated every time a new neuron is added.
|
25
|
+
attr_accessor :heirarchy_number
|
26
|
+
|
27
|
+
# This is true if this is an output neuron.
|
28
|
+
attr_accessor :output
|
29
|
+
|
30
|
+
# List of neuron types defined.
|
31
|
+
@@neuron_types = []
|
32
|
+
|
33
|
+
# Class is is of Input type?
|
34
|
+
def self.input? ; false ; end
|
35
|
+
def input? ; self.class.input? ; end
|
36
|
+
|
37
|
+
def self.bias? ; false ; end
|
38
|
+
def bias? ; self.class.bias? ; end
|
39
|
+
|
40
|
+
# Instantiation is of outout type?
|
41
|
+
def output? ; !!@output ; end
|
42
|
+
|
43
|
+
def self.inherited(clazz)
|
44
|
+
@@neuron_types << clazz
|
45
|
+
end
|
46
|
+
|
47
|
+
# List of distinct neuron types (classes)
|
48
|
+
def self.neuron_types; @@neuron_types ; end
|
49
|
+
|
50
|
+
# Function must be implemented by subclasses for phenotype
|
51
|
+
# generation. Basically, an instance is passed to this function
|
52
|
+
# and it will add a function to sum all inputs
|
53
|
+
# and a apply an operator to the sum.
|
54
|
+
def express(instance)
|
55
|
+
raise NeatException.new "express() must be implemented by subclass."
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
|
61
|
+
=begin rdoc
|
62
|
+
= Basic Neuron Types
|
63
|
+
|
64
|
+
Basically, the neurons (nodes) will have an instantiation to represent their places in the
|
65
|
+
neural net, and way to spin up the phenotypic representation.
|
66
|
+
|
67
|
+
The basic types to RubyNEAT are represented here.
|
68
|
+
=end
|
69
|
+
module BasicNeuronTypes
|
70
|
+
#= Special class of Neuron that takes input from the "real world"
|
71
|
+
# Name of this neuron equates to the parameter name of the input.
|
72
|
+
#
|
73
|
+
# All inputs are handled with this neuron. This type of
|
74
|
+
# neuron only has one input -- from the outside world.
|
75
|
+
class InputNeuron < NEAT::Neuron
|
76
|
+
def self.input? ; true ; end
|
77
|
+
|
78
|
+
# Takes a single input and passes it as is.
|
79
|
+
def express(instance)
|
80
|
+
instance.define_singleton_method(@name) {|input|
|
81
|
+
input
|
82
|
+
}
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
#= Special class of neuron that provides a bias signal.
|
87
|
+
# FIXME: The bias value is not behaving as expected because
|
88
|
+
# FIXME: the instance is not the neuron, but the phenotype.
|
89
|
+
class BiasNeuron < InputNeuron
|
90
|
+
def self.bias? ; true ; end
|
91
|
+
attr_accessor :neu_bias
|
92
|
+
|
93
|
+
def initialize(c=nil, n=nil)
|
94
|
+
super
|
95
|
+
@neu_bias = 1.00
|
96
|
+
end
|
97
|
+
|
98
|
+
# Just provides a bias signal
|
99
|
+
# FIXME: we had to hard-code the value here for now. Not a biggie,
|
100
|
+
# FIXME: but really should be @neu_bias
|
101
|
+
def express(instance)
|
102
|
+
instance.define_singleton_method(@name) { 1.00 }
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# The most commonly-used neuron for the hidden and output layers.
|
107
|
+
# We use the Logistic Function for the Sigmoid.
|
108
|
+
class SigmoidNeuron < Neuron
|
109
|
+
# create a function on the instance with our name
|
110
|
+
# that sums all inputs and produce a sigmoid output (using tanh)
|
111
|
+
def express(instance)
|
112
|
+
instance.define_singleton_method(@name) {|*inputs|
|
113
|
+
1.0 / (1.0 + exp(-4.9 * inputs.reduce {|p, q| p + q}))
|
114
|
+
}
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# An alternative Sigmoid Function, but ranges -1 to +1
|
119
|
+
class TanhNeuron < Neuron
|
120
|
+
# create a function on the instance with our name
|
121
|
+
# that sums all inputs and produce a sigmoid output (using tanh)
|
122
|
+
def express(instance)
|
123
|
+
instance.define_singleton_method(@name) {|*inputs|
|
124
|
+
tanh(2.4 * inputs.reduce {|p, q| p + q})
|
125
|
+
}
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Sin function (CPPN) -- adjusted to have its +1 and -1 near TanhNeuron
|
130
|
+
class SineNeuron < Neuron
|
131
|
+
# create a function on the instance with our name
|
132
|
+
# that sums all inputs and produce a sigmoid output (using tanh)
|
133
|
+
def express(instance)
|
134
|
+
instance.define_singleton_method(@name) {|*inputs|
|
135
|
+
sin(1.6 * inputs.reduce {|p, q| p + q})
|
136
|
+
}
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Cosine function (CPPN) -- adjusted to have its +1 and -1 near TanhNeuron
|
141
|
+
class CosineNeuron < Neuron
|
142
|
+
# create a function on the instance with our name
|
143
|
+
# that sums all inputs and produce a sigmoid output (using tanh)
|
144
|
+
def express(instance)
|
145
|
+
instance.define_singleton_method(@name) {|*inputs|
|
146
|
+
cos(1.6 * inputs.reduce {|p, q| p + q})
|
147
|
+
}
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
end
|