create NEAT app repo
This commit is contained in:
commit
9cd2d19fb9
34 files changed
+4454
No files matched your search
Executable
+142
@@ -0,0 +1,142 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require 'pp'
|
||||
|
||||
# a single neuron
|
||||
class Node
|
||||
attr_accessor :activation, :input_nodes
|
||||
attr_reader :id
|
||||
|
||||
def initialize(id)
|
||||
@id = id
|
||||
@input_nodes = Array.new
|
||||
@activation = 0.0
|
||||
end
|
||||
|
||||
def update
|
||||
# activate unless this is an input node
|
||||
@activation = activate(sum_input) unless /i[0-9]+/.match(@id)
|
||||
end
|
||||
|
||||
def <=>(other)
|
||||
@id <=> other.id
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sum_input
|
||||
@input_nodes.inject(0.0) { |sum,node| sum + node.activation }
|
||||
end
|
||||
|
||||
def activate(x)
|
||||
Math.tanh(x)
|
||||
end
|
||||
end
|
||||
|
||||
# a basic ANN (assumes all weights are 1.0)
|
||||
class Network
|
||||
attr_accessor :nodes
|
||||
|
||||
def initialize(n_in, n_hidden, n_out)
|
||||
@nodes = Hash.new
|
||||
n_in.times { |i| @nodes["i#{i}"] = Node.new("i#{i}") }
|
||||
n_hidden.times { |i| @nodes["h#{i}"] = Node.new("h#{i}") }
|
||||
n_out.times { |i| @nodes["o#{i}"] = Node.new("o#{i}") }
|
||||
end
|
||||
|
||||
def connect(from_id, to_id)
|
||||
@nodes[to_id].input_nodes << @nodes[from_id]
|
||||
@order = nil # mark activation order as being dirty
|
||||
end
|
||||
|
||||
def update
|
||||
# calculate activation order if it's 'nil'
|
||||
@order ||= activation_order
|
||||
|
||||
# simply update each node in order
|
||||
@order.each do |layer|
|
||||
puts "Layer"
|
||||
layer.each { |node| puts "updating node #{node.id}"; node.update }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def activation_order
|
||||
# do the recursive search, starting with the output layer
|
||||
# then, add the input nodes to the front
|
||||
[input_nodes] + a_o_bfs(output_nodes)
|
||||
end
|
||||
|
||||
def find_nodes(re)
|
||||
# collect all of the output node ids, this is the end layer of the ANN
|
||||
node_ids = @nodes.keys.select { |k,v| re.match(k) }
|
||||
|
||||
# use id's to get array of nodes, then sort
|
||||
node_ids.collect { |id| @nodes[id] }.sort
|
||||
end
|
||||
|
||||
def input_nodes
|
||||
find_nodes(/i[0-9]+/)
|
||||
end
|
||||
|
||||
def hidden_nodes
|
||||
find_nodes(/h[0-9]+/)
|
||||
end
|
||||
|
||||
def output_nodes
|
||||
find_nodes(/o[0-9]+/)
|
||||
end
|
||||
|
||||
def a_o_bfs(current_layer, seen=nil, order=nil)
|
||||
# setup some state for this recursion
|
||||
seen ||= Hash.new
|
||||
order ||= Array.new
|
||||
|
||||
# mark current nodes as seen
|
||||
current_layer.each { |n| seen[n] = true }
|
||||
|
||||
# prepend current layer to final order
|
||||
order = [current_layer.sort] + order
|
||||
|
||||
# get next layer
|
||||
next_layer = current_layer.collect { |n| n.input_nodes }
|
||||
next_layer = next_layer.flatten.select { |n| not seen[n] and not /i[0-9]+/.match(n.id) }
|
||||
|
||||
if not next_layer.empty?
|
||||
# recurse into next layer if it exists
|
||||
a_o_bfs(next_layer, seen, order)
|
||||
else
|
||||
# finished
|
||||
order
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
# create a network
|
||||
n = Network.new(3,4,3)
|
||||
n.connect('i0','h0')
|
||||
n.connect('h0','o0')
|
||||
n.connect('i1','h2')
|
||||
n.connect('i2','h1')
|
||||
n.connect('h1','h0')
|
||||
n.connect('h2','h0')
|
||||
n.connect('h2','h3')
|
||||
n.connect('h3','o2')
|
||||
|
||||
# run some data through the network
|
||||
10.times do
|
||||
# give network some data
|
||||
n.nodes['i0'].activation = (2.0 * Kernel.rand) - 1.0
|
||||
n.nodes['i1'].activation = (2.0 * Kernel.rand) - 1.0
|
||||
|
||||
# update network
|
||||
n.update
|
||||
|
||||
# read outputs
|
||||
puts n.nodes['o0'].activation
|
||||
puts
|
||||
end
|
||||
|
||||
Reference in new issue
Block a user