newral 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +278 -0
- data/Rakefile +10 -0
- data/lib/newral.rb +53 -0
- data/lib/newral/bayes.rb +39 -0
- data/lib/newral/classifier/dendogram.rb +68 -0
- data/lib/newral/classifier/k_means_cluster.rb +45 -0
- data/lib/newral/classifier/node.rb +58 -0
- data/lib/newral/classifier/node_distance.rb +19 -0
- data/lib/newral/data/base.rb +153 -0
- data/lib/newral/data/cluster.rb +37 -0
- data/lib/newral/data/cluster_set.rb +38 -0
- data/lib/newral/data/csv.rb +23 -0
- data/lib/newral/data/idx.rb +48 -0
- data/lib/newral/error_calculation.rb +28 -0
- data/lib/newral/functions/base.rb +102 -0
- data/lib/newral/functions/block.rb +34 -0
- data/lib/newral/functions/gaussian.rb +41 -0
- data/lib/newral/functions/line.rb +52 -0
- data/lib/newral/functions/polynomial.rb +48 -0
- data/lib/newral/functions/radial_basis_function_network.rb +54 -0
- data/lib/newral/functions/ricker_wavelet.rb +13 -0
- data/lib/newral/functions/vector.rb +59 -0
- data/lib/newral/genetic/tree.rb +70 -0
- data/lib/newral/graphs/a_star.rb +12 -0
- data/lib/newral/graphs/cheapest_first.rb +11 -0
- data/lib/newral/graphs/edge.rb +24 -0
- data/lib/newral/graphs/graph.rb +63 -0
- data/lib/newral/graphs/node.rb +11 -0
- data/lib/newral/graphs/path.rb +50 -0
- data/lib/newral/graphs/tree_search.rb +60 -0
- data/lib/newral/networks/backpropagation_network.rb +68 -0
- data/lib/newral/networks/layer.rb +28 -0
- data/lib/newral/networks/network.rb +146 -0
- data/lib/newral/networks/perceptron.rb +84 -0
- data/lib/newral/networks/sigmoid.rb +55 -0
- data/lib/newral/probability.rb +42 -0
- data/lib/newral/probability_set.rb +108 -0
- data/lib/newral/q_learning/base.rb +90 -0
- data/lib/newral/tools.rb +135 -0
- data/lib/newral/training/gradient_descent.rb +36 -0
- data/lib/newral/training/greedy.rb +36 -0
- data/lib/newral/training/hill_climbing.rb +77 -0
- data/lib/newral/training/linear_regression.rb +30 -0
- data/lib/newral/training/linear_regression_matrix.rb +32 -0
- metadata +147 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
module Newral
|
2
|
+
module Graphs
|
3
|
+
class Edge
|
4
|
+
attr_accessor :start_node, :end_node, :directed, :cost, :data
|
5
|
+
def initialize( key:nil, start_node: nil, end_node: nil, directed: false, cost: nil, data:nil )
|
6
|
+
@key = key
|
7
|
+
@start_node = start_node
|
8
|
+
@end_node = end_node
|
9
|
+
@directed = directed
|
10
|
+
@cost = cost
|
11
|
+
@data = data
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
def key
|
16
|
+
@key || "#{ @start_node }#{ directed ? '=>' : '<=>' }#{ @end_node }"
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
key
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Newral
|
2
|
+
module Graphs
|
3
|
+
module Errors
|
4
|
+
class UnknownNode < StandardError; end
|
5
|
+
end
|
6
|
+
class Graph
|
7
|
+
attr_reader :nodes, :edges
|
8
|
+
def initialize( nodes: [], edges: [] )
|
9
|
+
@nodes = nodes
|
10
|
+
@edges = edges
|
11
|
+
end
|
12
|
+
|
13
|
+
def add_edge( edge )
|
14
|
+
unless @nodes.member?( edge.start_node ) && @nodes.member?( edge.end_node )
|
15
|
+
# let´s try to find it
|
16
|
+
@nodes.each do |node|
|
17
|
+
edge.start_node = node if node.respond_to?( :name ) && node.name == edge.start_node
|
18
|
+
edge.end_node = node if node.respond_to?( :name ) && node.name == edge.end_node
|
19
|
+
end
|
20
|
+
raise Errors::UnkownNode unless @nodes.member?( edge.start_node ) && @nodes.member?( edge.end_node )
|
21
|
+
end
|
22
|
+
@edges << edge
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_node( node )
|
27
|
+
@nodes < node
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def add_nodes( nodes )
|
32
|
+
@nodes = @nodes+nodes
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def find_node_by_name( name )
|
37
|
+
@nodes.find{ |node| node.name == name }
|
38
|
+
end
|
39
|
+
|
40
|
+
# we can add also like this {1=> 2, 2 => 5 }
|
41
|
+
def add_edges( edges, directed: false )
|
42
|
+
if edges.kind_of?( Hash )
|
43
|
+
edges.each do |from,to|
|
44
|
+
@edges << Edge.new( start_node: from, end_node: to, directed: directed )
|
45
|
+
end
|
46
|
+
else
|
47
|
+
edges.each do |edge|
|
48
|
+
add_edge edge
|
49
|
+
end
|
50
|
+
end
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
def find_edges( node )
|
55
|
+
@edges.collect do |edge|
|
56
|
+
keep = edge.directed ? edge.start_node == node : edge.start_node == node || edge.end_node == node
|
57
|
+
edge if keep
|
58
|
+
end.compact
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Newral
|
2
|
+
module Graphs
|
3
|
+
module Errors
|
4
|
+
class CanOnlyConnectToLastEdge < ::StandardError; end
|
5
|
+
class CircularPath < ::StandardError; end
|
6
|
+
end
|
7
|
+
|
8
|
+
class Path
|
9
|
+
attr_reader :edges
|
10
|
+
def initialize( edges:[], allow_circular_paths: true )
|
11
|
+
@edges = edges.dup
|
12
|
+
@allow_circular_paths = allow_circular_paths
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_edge( edge )
|
16
|
+
last_edge = @edges.last
|
17
|
+
raise Errors::CanOnlyConnectToLastEdge,[last_edge,edge] unless @edges.empty? || last_edge.end_node == edge.start_node
|
18
|
+
raise Errors::CircularPath unless @allow_circular_paths && !@edges.index{|edge1| edge1.start_node == edge.end_node || edge1.end_node == edge.end_node }
|
19
|
+
@edges << edge
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def length
|
24
|
+
@edges.length
|
25
|
+
end
|
26
|
+
|
27
|
+
def cost
|
28
|
+
@edges.inject(0){ |value,edge| value+edge.cost }
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
def start_node
|
33
|
+
@edges.first.start_node
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
def end_node
|
38
|
+
@edges.last.end_node
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_s
|
42
|
+
@edges.join(', ')
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Newral
|
2
|
+
module Graphs
|
3
|
+
module Errors
|
4
|
+
class FrontierEmpty < StandardError; end
|
5
|
+
end
|
6
|
+
# the algorithms are heavily inspired by the Udacity course from
|
7
|
+
# Sebastian Thrun https://classroom.udacity.com/courses/cs271
|
8
|
+
class TreeSearch
|
9
|
+
attr_reader :frontier
|
10
|
+
def initialize( graph: nil, start_node: nil, end_node: nil )
|
11
|
+
@graph = graph
|
12
|
+
@start_node = start_node
|
13
|
+
@end_node = end_node
|
14
|
+
path = Path.new(edges:[ Edge.new( start_node: start_node, end_node: start_node, directed: true, cost:0 )])
|
15
|
+
@explored = {end_node: 0 }
|
16
|
+
@frontier = [ path ]
|
17
|
+
end
|
18
|
+
|
19
|
+
def run
|
20
|
+
|
21
|
+
while @frontier.length > 0
|
22
|
+
path = remove_choice
|
23
|
+
return path if path.end_node == @end_node
|
24
|
+
edges = @graph.find_edges( path.end_node)
|
25
|
+
puts "no edges found for #{path.end_node.name} #{@graph.edges.length}" unless edges.length > 0
|
26
|
+
edges.each do |edge|
|
27
|
+
begin
|
28
|
+
end_node = edge.start_node == path.end_node ? edge.end_node : edge.start_node
|
29
|
+
new_edge = Edge.new( start_node: path.end_node, end_node: end_node, directed: true, cost: edge.cost )
|
30
|
+
puts( "n:#{ new_edge.to_s } e:#{edge} n:#{end_node} s:#{path.end_node} #{edge.start_node == new_edge.end_node } #{edge.end_node}")
|
31
|
+
new_path = Path.new(edges:path.edges).add_edge( new_edge )
|
32
|
+
if @explored[new_path.end_node].nil? || measure( path ) < @explored[new_path.end_node]
|
33
|
+
@frontier << new_path
|
34
|
+
@explored[ new_path.end_node ] = measure( new_path )
|
35
|
+
end
|
36
|
+
rescue Errors::CircularPath
|
37
|
+
puts "circular #{ new_path.to_s }"
|
38
|
+
# no need to check this path
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
raise Errors::FrontierEmpty
|
43
|
+
end
|
44
|
+
|
45
|
+
def remove_choice
|
46
|
+
@frontier.sort! do |path1,path2|
|
47
|
+
measure( path2 ) <=> measure( path1 ) # reverse
|
48
|
+
end
|
49
|
+
puts "frontier: #{@frontier.length}"
|
50
|
+
@frontier.pop # pops shortest
|
51
|
+
end
|
52
|
+
|
53
|
+
# the standard approach is breath first
|
54
|
+
def measure( path )
|
55
|
+
path.length
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Newral
|
2
|
+
module Networks
|
3
|
+
module Errors
|
4
|
+
class Errors::OnlyPossibleForHidden < StandardError ; end
|
5
|
+
end
|
6
|
+
|
7
|
+
class BackpropagationNetwork < Network
|
8
|
+
def initialize( number_of_inputs:2, number_of_hidden:2, number_of_outputs:2 )
|
9
|
+
super()
|
10
|
+
add_layer "hidden" do
|
11
|
+
number_of_hidden.times do |idx|
|
12
|
+
add_neuron "hidden_#{idx}", weight_length:number_of_inputs
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
add_layer "output" do
|
18
|
+
number_of_outputs.times do |idx|
|
19
|
+
add_neuron "output_#{idx}", weight_length:number_of_hidden
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# in this network all hidden neurons link to all output neurons
|
24
|
+
@layers["hidden"].neurons.each do |hidden_neuron|
|
25
|
+
@layers["output"].neurons.each do |output_neuron|
|
26
|
+
output_neuron.add_input hidden_neuron
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
|
33
|
+
# gets an array of inputs and the corresponding expected outputs
|
34
|
+
# first we update our output layer then our hidden layer
|
35
|
+
def train( input: [], output: [] )
|
36
|
+
before_error = calculate_error( input: input,output: output )
|
37
|
+
input.each_with_index do |input,idx|
|
38
|
+
calculated_output = update_with_vector( input )
|
39
|
+
@layers["output"].neurons.each_with_index do |neuron,neuron_idx|
|
40
|
+
neuron.adjust_weights( expected: output[ idx ][ neuron_idx ])
|
41
|
+
end
|
42
|
+
|
43
|
+
@layers["hidden"].neurons.each do |neuron|
|
44
|
+
neuron.adjust_weights( expected: output[ idx ], layer: :hidden, output: calculated_output, weights_at_output_nodes: output_weights( neuron ))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
new_error = calculate_error( input: input,output: output )
|
48
|
+
before_error-new_error
|
49
|
+
end
|
50
|
+
|
51
|
+
# gets the weights of the output neurons this input feeds to
|
52
|
+
# this of course can be done much simpler (as its always the nth weight of the output neuron)
|
53
|
+
# however we want to stay explicit
|
54
|
+
def output_weights( neuron )
|
55
|
+
raise Errors::OnlyPossibleForHidden unless @layers["hidden"].neurons.member?( neuron )
|
56
|
+
weights = []
|
57
|
+
@layers["output"].neurons.each do |output_neuron|
|
58
|
+
output_neuron.inputs.each_with_index do |input,idx|
|
59
|
+
weights << output_neuron.weights[ idx ] if input == neuron
|
60
|
+
end
|
61
|
+
end
|
62
|
+
weights
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Newral
|
2
|
+
module Networks
|
3
|
+
class Layer
|
4
|
+
attr_reader :neurons, :identifier
|
5
|
+
|
6
|
+
def initialize( identifier: nil )
|
7
|
+
@identifier = identifier
|
8
|
+
@neurons = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def add_neuron( neuron )
|
12
|
+
@neurons << neuron
|
13
|
+
end
|
14
|
+
|
15
|
+
def weights
|
16
|
+
@neurons.collect(&:weights).flatten
|
17
|
+
end
|
18
|
+
|
19
|
+
def biases
|
20
|
+
neurons.collect(&:bias)
|
21
|
+
end
|
22
|
+
|
23
|
+
def outputs
|
24
|
+
neurons.collect(&:output)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
module Newral
|
2
|
+
module Networks
|
3
|
+
module Errors
|
4
|
+
class InvalidType < StandardError; end
|
5
|
+
class IdentifierExists < StandardError; end
|
6
|
+
class NotImplemented < StandardError; end
|
7
|
+
end
|
8
|
+
|
9
|
+
class Network
|
10
|
+
attr_reader :output, :neurons, :layers
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@layers = {}
|
14
|
+
@neurons = {}
|
15
|
+
@layer_identifier = "input"
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.define( &block )
|
19
|
+
layout = self.new
|
20
|
+
layout.instance_eval( &block )
|
21
|
+
layout
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_layer( identifier, &block )
|
25
|
+
@layer_identifier = identifier
|
26
|
+
@layers[ identifier ] = Layer.new( identifier: identifier )
|
27
|
+
self.instance_eval &block if block_given?
|
28
|
+
end
|
29
|
+
|
30
|
+
def add_neuron( identifier, neuron: nil, weights: nil, bias: nil, weight_length: nil, type: 'sigmoid' )
|
31
|
+
raise Errors::IdentifierExists if @neurons[ identifier ] && ( neuron || weights || bias )
|
32
|
+
unless neuron
|
33
|
+
neuron = case type.to_s
|
34
|
+
when 'perceptron' then Perceptron.new( weights: weights, bias: bias, weight_length: weight_length )
|
35
|
+
when 'sigmoid' then Sigmoid.new( weights: weights, bias: bias , weight_length: weight_length )
|
36
|
+
else
|
37
|
+
raise Errors::InvalidType
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
@neurons[ identifier ] = neuron
|
42
|
+
@layers[ @layer_identifier ].add_neuron( neuron )
|
43
|
+
end
|
44
|
+
|
45
|
+
# specify the identifiers of the two neurons to connect
|
46
|
+
def connect( from: nil, to: nil )
|
47
|
+
input_neuron = @neurons[ to ]
|
48
|
+
output_neuron = @neurons[ from ]
|
49
|
+
input_neuron.add_input( output_neuron )
|
50
|
+
end
|
51
|
+
|
52
|
+
def update_first_layer_with_vector( input )
|
53
|
+
layer = @layers.first
|
54
|
+
@output = layer[1].neurons.collect do |n|
|
55
|
+
n.update_with_vector input
|
56
|
+
end
|
57
|
+
@output
|
58
|
+
end
|
59
|
+
|
60
|
+
def update_neuron( identifier, input )
|
61
|
+
@neurons[ identifier ].update_with_vector( input )
|
62
|
+
end
|
63
|
+
|
64
|
+
def update_layers( start:0 )
|
65
|
+
@layers.to_a[start..@layers.size].each do |layer |
|
66
|
+
@output = layer[1].neurons.collect do |n|
|
67
|
+
n.output
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def update_with_vector( input )
|
73
|
+
update_first_layer_with_vector( input )
|
74
|
+
update_layers( start: 1)
|
75
|
+
@output
|
76
|
+
end
|
77
|
+
|
78
|
+
# use this for simple networks were neurons are set by hand
|
79
|
+
def update( &block )
|
80
|
+
self.instance_eval( &block ) if block_given?
|
81
|
+
update_layers
|
82
|
+
@output
|
83
|
+
end
|
84
|
+
|
85
|
+
def output_of_neuron( identifier )
|
86
|
+
@neurons[ identifier ].output
|
87
|
+
end
|
88
|
+
|
89
|
+
def train( inputs: [], output: [] )
|
90
|
+
raise Errors::NotImplemented, "Use Subclass Backpropagation Training"
|
91
|
+
end
|
92
|
+
|
93
|
+
def set_weights_and_bias( layer: 'hidden', weights: [], bias: [])
|
94
|
+
@layers[layer].neurons.each_with_index do |neuron,idx|
|
95
|
+
neuron.set_weights_and_bias( weights: weights[ idx ], bias: bias[idx])
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# by implementing these functions we can use a network for all
|
100
|
+
# training algorithms (although this is really just a proove of concept as using Greedy for Neural Networks does not lead to great results)
|
101
|
+
|
102
|
+
def calculate( input )
|
103
|
+
update_with_vector( input )
|
104
|
+
end
|
105
|
+
|
106
|
+
def calculate_error( input: [],output: [] )
|
107
|
+
expected_values = [] # output can be longer than input
|
108
|
+
calculated_values = []
|
109
|
+
input.each_with_index do |x,idx|
|
110
|
+
calculated_values << calculate( x )
|
111
|
+
expected_values << output[idx]
|
112
|
+
end
|
113
|
+
Newral::ErrorCalculation.mean_square( calculated_values, expected_values )/2
|
114
|
+
end
|
115
|
+
|
116
|
+
def number_of_directions
|
117
|
+
@neurons.sum{ |n| n[1].number_of_directions }
|
118
|
+
end
|
119
|
+
|
120
|
+
def move( direction: 0, step:0.01, step_percentage: nil )
|
121
|
+
raise Errors::InvalidDirection if direction >= number_of_directions
|
122
|
+
new_network = Marshal.load(Marshal.dump(self))
|
123
|
+
idx = 0
|
124
|
+
new_network.neurons.each do |key,neuron|
|
125
|
+
if idx+neuron.number_of_directions-1 >= direction #
|
126
|
+
meuron = neuron.dup.move( direction: direction-idx, step: step, step_percentage: step_percentage)
|
127
|
+
return new_network
|
128
|
+
end
|
129
|
+
idx = idx+neuron.number_of_directions
|
130
|
+
end
|
131
|
+
new_network
|
132
|
+
end
|
133
|
+
|
134
|
+
def move_random( low_range: -0.9, high_range: 0.9 )
|
135
|
+
number_of_directions.times do |direction|
|
136
|
+
step = low_range+rand()*(high_range.to_f-low_range.to_f)
|
137
|
+
move( direction: direction, step: step )
|
138
|
+
end
|
139
|
+
self
|
140
|
+
end
|
141
|
+
|
142
|
+
|
143
|
+
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|