# Copied with little modifications from: https://github.com/jruby/rubybench/blob/master/time/bench_neural_net.rb class Synapse property weight : Float64 property prev_weight : Float64 property :source_neuron property :dest_neuron def initialize(@source_neuron : Neuron, @dest_neuron : Neuron) @prev_weight = @weight = rand * 2 - 1 end end class Neuron LEARNING_RATE = 1.0 MOMENTUM = 0.3 property :synapses_in property :synapses_out property threshold : Float64 property prev_threshold : Float64 property :error property :output def initialize @prev_threshold = @threshold = rand * 2 - 1 @synapses_in = [] of Synapse @synapses_out = [] of Synapse @output = 0.0 @error = 0.0 end def calculate_output activation = synapses_in.reduce(0.0) do |sum, synapse| sum + synapse.weight * synapse.source_neuron.output end activation -= threshold @output = 1.0 / (1.0 + Math.exp(-activation)) end def derivative output * (1 - output) end def output_train(rate, target) @error = (target - output) * derivative update_weights(rate) end def hidden_train(rate) @error = synapses_out.reduce(0.0) do |sum, synapse| sum + synapse.prev_weight * synapse.dest_neuron.error end * derivative update_weights(rate) end def update_weights(rate) synapses_in.each do |synapse| temp_weight = synapse.weight synapse.weight += (rate * LEARNING_RATE * error * synapse.source_neuron.output) + (MOMENTUM * (synapse.weight - synapse.prev_weight)) synapse.prev_weight = temp_weight end temp_threshold = threshold @threshold += (rate * LEARNING_RATE * error * -1) + (MOMENTUM * (threshold - prev_threshold)) @prev_threshold = temp_threshold end end class NeuralNetwork @input_layer : Array(Neuron) @hidden_layer : Array(Neuron) @output_layer : Array(Neuron) def initialize(inputs, hidden, outputs) @input_layer = (1..inputs).map { Neuron.new } @hidden_layer = (1..hidden).map { Neuron.new } @output_layer = (1..outputs).map { Neuron.new } @input_layer.each_cartesian(@hidden_layer) do |source, dest| synapse = Synapse.new(source, dest) source.synapses_out << synapse dest.synapses_in << synapse end @hidden_layer.each_cartesian(@output_layer) do |source, dest| synapse = Synapse.new(source, dest) source.synapses_out << synapse dest.synapses_in << synapse end end def train(inputs, targets) feed_forward(inputs) @output_layer.zip(targets) do |neuron, target| neuron.output_train(0.3, target) end @hidden_layer.each do |neuron| neuron.hidden_train(0.3) end end def feed_forward(inputs) @input_layer.zip(inputs) do |neuron, input| neuron.output = input.to_f64 end @hidden_layer.each do |neuron| neuron.calculate_output if neuron end @output_layer.each do |neuron| neuron.calculate_output if neuron end end def current_outputs @output_layer.map do |neuron| neuron.output end end end (ARGV[0]? || 5).to_i.times do xor = NeuralNetwork.new(2, 10, 1) 10000.times do xor.train([0, 0], [0]) xor.train([1, 0], [1]) xor.train([0, 1], [1]) xor.train([1, 1], [0]) end xor.feed_forward([0, 0]) puts xor.current_outputs xor.feed_forward([0, 1]) puts xor.current_outputs xor.feed_forward([1, 0]) puts xor.current_outputs xor.feed_forward([1, 1]) puts xor.current_outputs end