Files
neat/network_sort.rb
2010-09-30 15:23:17 -07:00

143 lines
3.0 KiB
Ruby
Executable File

#!/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