newral 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE +21 -0
  4. data/README.md +278 -0
  5. data/Rakefile +10 -0
  6. data/lib/newral.rb +53 -0
  7. data/lib/newral/bayes.rb +39 -0
  8. data/lib/newral/classifier/dendogram.rb +68 -0
  9. data/lib/newral/classifier/k_means_cluster.rb +45 -0
  10. data/lib/newral/classifier/node.rb +58 -0
  11. data/lib/newral/classifier/node_distance.rb +19 -0
  12. data/lib/newral/data/base.rb +153 -0
  13. data/lib/newral/data/cluster.rb +37 -0
  14. data/lib/newral/data/cluster_set.rb +38 -0
  15. data/lib/newral/data/csv.rb +23 -0
  16. data/lib/newral/data/idx.rb +48 -0
  17. data/lib/newral/error_calculation.rb +28 -0
  18. data/lib/newral/functions/base.rb +102 -0
  19. data/lib/newral/functions/block.rb +34 -0
  20. data/lib/newral/functions/gaussian.rb +41 -0
  21. data/lib/newral/functions/line.rb +52 -0
  22. data/lib/newral/functions/polynomial.rb +48 -0
  23. data/lib/newral/functions/radial_basis_function_network.rb +54 -0
  24. data/lib/newral/functions/ricker_wavelet.rb +13 -0
  25. data/lib/newral/functions/vector.rb +59 -0
  26. data/lib/newral/genetic/tree.rb +70 -0
  27. data/lib/newral/graphs/a_star.rb +12 -0
  28. data/lib/newral/graphs/cheapest_first.rb +11 -0
  29. data/lib/newral/graphs/edge.rb +24 -0
  30. data/lib/newral/graphs/graph.rb +63 -0
  31. data/lib/newral/graphs/node.rb +11 -0
  32. data/lib/newral/graphs/path.rb +50 -0
  33. data/lib/newral/graphs/tree_search.rb +60 -0
  34. data/lib/newral/networks/backpropagation_network.rb +68 -0
  35. data/lib/newral/networks/layer.rb +28 -0
  36. data/lib/newral/networks/network.rb +146 -0
  37. data/lib/newral/networks/perceptron.rb +84 -0
  38. data/lib/newral/networks/sigmoid.rb +55 -0
  39. data/lib/newral/probability.rb +42 -0
  40. data/lib/newral/probability_set.rb +108 -0
  41. data/lib/newral/q_learning/base.rb +90 -0
  42. data/lib/newral/tools.rb +135 -0
  43. data/lib/newral/training/gradient_descent.rb +36 -0
  44. data/lib/newral/training/greedy.rb +36 -0
  45. data/lib/newral/training/hill_climbing.rb +77 -0
  46. data/lib/newral/training/linear_regression.rb +30 -0
  47. data/lib/newral/training/linear_regression_matrix.rb +32 -0
  48. metadata +147 -0
@@ -0,0 +1,84 @@
1
+ # http://neuralnetworksanddeeplearning.com/chap1.html
2
+ module Newral
3
+ module Networks
4
+ module Errors
5
+ class InputAndWeightSizeDiffer < StandardError; end
6
+ class WeightsAndWeightLengthGiven < StandardError; end
7
+ end
8
+
9
+ class Perceptron
10
+ attr_reader :weights,:bias, :last_output, :inputs
11
+ # to create random weights just specify weight_length
12
+ def initialize(weights:[],bias:0, weight_length: nil, max_random_weight:1, min_random_weight:-1 )
13
+ raise Errors::WeightsAndWeightLengthGiven if weight_length && ( weights || ( weights && weights.size > 0 ))
14
+ @max_random_weight = max_random_weight
15
+ @min_random_weight= min_random_weight
16
+ @weights =weights || []
17
+ @weights = weight_length.times.collect{ |i| (max_random_weight-min_random_weight)*rand()+min_random_weight } if weight_length
18
+ @bias = bias || (weight_length && (max_random_weight-min_random_weight)*rand()+min_random_weight ) || 0
19
+ @inputs = []
20
+ @last_output = nil
21
+ end
22
+
23
+ def set_weights_and_bias( weights:[], bias: nil )
24
+ @weights = weights
25
+ @bias = bias || 0 # if none specified
26
+ end
27
+
28
+ def calculate_value
29
+ raise Errors::InputAndWeightSizeDiffer, "weights: #{@weights.size }, #{ @inputs.size }" unless @weights.size == @inputs.size
30
+ value = 0
31
+ @inputs.each_with_index do |input,idx|
32
+ val = calculate_input( input )
33
+ value = value+val*@weights[idx]
34
+ end
35
+ value = value+@bias
36
+ end
37
+
38
+ def calculate_input( input )
39
+ input.kind_of?( Perceptron ) ? input.last_output : input
40
+ end
41
+
42
+ def output
43
+ @last_output = calculate_value <= 0 ? 0 : 1
44
+ end
45
+
46
+ def update_with_vector( inputs )
47
+ @inputs = inputs
48
+ output
49
+ end
50
+
51
+ def add_input( object )
52
+ if object.kind_of?( Array )
53
+ @inputs=object
54
+ else
55
+ @inputs << object
56
+ end
57
+ # automatically add a weight if number of inputs exceeds weight size
58
+ weights << (@max_random_weight-@min_random_weight)*rand()+@min_random_weight if weights.length < @inputs.length
59
+ end
60
+
61
+ # move + number of directions are just needed for some training algorithms
62
+ # not typically used for neural networks (like greedy)
63
+ # mainly implemented here for a proove of concept
64
+ def number_of_directions
65
+ @weights.size+1
66
+ end
67
+
68
+
69
+ def move( direction: 0, step:0.01, step_percentage: nil )
70
+ raise Errors::InvalidDirection if direction >= number_of_directions
71
+ if direction < @weights.size
72
+ @weights[direction] = step_percentage ? @weights[direction]*(1+step_percentage.to_f/100) : @weights[direction]+step
73
+ else
74
+ @bias = step_percentage ? @bias*(1+step_percentage.to_f/100) : @bias+step
75
+ end
76
+ self
77
+ end
78
+
79
+
80
+ end
81
+
82
+ end
83
+ end
84
+
@@ -0,0 +1,55 @@
1
+ module Newral
2
+ module Networks
3
+ class Sigmoid < Perceptron
4
+
5
+ def output
6
+ value = calculate_value
7
+ @last_output = 1/(1+Math.exp(value*-1))
8
+ end
9
+
10
+ # if you want to know how this works
11
+ # visit https://mattmazur.com/2015/03/17/a-step-by-step-backpropagation-example/
12
+ def delta_rule( expected: nil )
13
+ error_delta = []
14
+ @weights.each_with_index do |weight,idx|
15
+ input = calculate_input( @inputs[ idx ] )
16
+ error_delta << -(expected-output)*(output)*(1-output )*input
17
+ end
18
+ error_delta
19
+ end
20
+
21
+
22
+ # this just works for 1 hidden layer
23
+ # https://stats.stackexchange.com/questions/70168/back-propagation-in-neural-nets-with-2-hidden-layers
24
+ # bias also not adjusted as biases are seen as constants
25
+ def delta_rule_hidden( output: nil, expected: nil, weights_at_output_nodes: nil )
26
+ error_delta = []
27
+ @inputs.each do |input|
28
+ d_e_total_d_out = 0
29
+ output.each_with_index do |result,idx|
30
+ d_e_total_d_out_idx = -( expected[idx]-result )
31
+ d_e_out_d_net = (1-result)*result
32
+ d_e_total_d_out_idx = d_e_total_d_out_idx*d_e_out_d_net*weights_at_output_nodes[idx]
33
+ d_e_total_d_out = d_e_total_d_out+d_e_total_d_out_idx
34
+ end
35
+ d_out_d_net = (1-@last_output)*@last_output
36
+ error_delta << d_e_total_d_out*d_out_d_net*calculate_input( input )
37
+ end
38
+ error_delta
39
+ end
40
+
41
+ # depending on where the neuron is placed we need other infos to adjust the weights
42
+ # on output we just need the expected results
43
+ # for hidden neurons we also need to know the weights at the output nodes
44
+ # and the actual output of the network
45
+ def adjust_weights( expected: nil, learning_rate:0.5, layer: :output, weights_at_output_nodes: nil, output: nil )
46
+ error_delta = layer.to_sym == :output ? delta_rule( expected: expected ) : delta_rule_hidden( output: output, expected: expected, weights_at_output_nodes: weights_at_output_nodes )
47
+ @weights.each_with_index do |weight,idx|
48
+ @weights[idx] = @weights[idx]-error_delta[ idx ]*learning_rate
49
+ end
50
+ end
51
+
52
+ end
53
+ end
54
+
55
+ end
@@ -0,0 +1,42 @@
1
+ module Newral
2
+
3
+ class Probability
4
+ attr_reader :key, :probability, :apriori
5
+ def initialize( key, probability, apriori:nil )
6
+ @key = key
7
+ @probability = probability
8
+ @apriori = apriori
9
+ @key = "#{key}|#{apriori}" if apriori
10
+ end
11
+
12
+ def *( other_probability )
13
+ Probability.new("#{self.key}*#{other_probability.key}",self.probability*other_probability.probability)
14
+ end
15
+
16
+ def /( other_probability )
17
+ Probability.new("#{self.key}/#{other_probability.key}",self.probability/other_probability.probability)
18
+ end
19
+
20
+ def !
21
+ Probability.new("!#{key}",1-probability)
22
+ end
23
+
24
+ def+(other_probability)
25
+ Probability.new("#{self.key}+#{other_probability.key}",self.probability+other_probability.probability)
26
+ end
27
+
28
+ def apriori=( apriori: other_probability, probability: nil )
29
+ Probability.new("#{self.key}|#{other_probability.key}",probability)
30
+ end
31
+
32
+ def and( other_probability )
33
+ Probability.new("#{self.key}^#{other_probability.key}",self.probability*other_probability.probability)
34
+ end
35
+
36
+ def or( other_probability )
37
+ Probability.new("#{self.key}*#{other_probability.key}",self.probability*other_probability.probability)
38
+ end
39
+
40
+ end
41
+
42
+ end
@@ -0,0 +1,108 @@
1
+ module Newral
2
+
3
+ class ProbabilitySet
4
+ attr_reader :history
5
+ def initialize( )
6
+ @probabilities={}
7
+ @history = []
8
+ end
9
+
10
+ def []( key )
11
+ @probabilities[ key ] || ( @probabilities[key] = compute(key))
12
+ end
13
+
14
+ def set( key, set_value )
15
+ @probabilities[ key ] = set_value
16
+ end
17
+
18
+ def multiply( key,other_key, set_value: nil )
19
+ computed_key_name = "#{key}*#{other_key}"
20
+ return @probabilities[computed_key_name] if @probabilities[ computed_key_name ] &&set_value.nil?
21
+ set_value ||= compute( key )*compute( other_key )
22
+ set( computed_key_name, set_value )
23
+ end
24
+
25
+ def divide( key,other_key, set_value: nil )
26
+ computed_key_name = "#{key}/#{other_key}"
27
+ return @probabilities[ computed_key_name ] if @probabilities[ computed_key_name] &&set_value.nil?
28
+ set_value ||= compute( key )*compute( other_key )
29
+ set( computed_key_name, set_value )
30
+ end
31
+
32
+ def not( key, set_value: nil )
33
+ computed_key_name = "!#{key}"
34
+ return @probabilities[ computed_key_name ] if @probabilities[computed_key_name ] &&set_value.nil?
35
+ set_value ||= 1-compute( key )
36
+ set( computed_key_name, set_value )
37
+ end
38
+
39
+ def add( key,other_key, set_value: nil )
40
+ computed_key_name = "#{key}+#{other_key}"
41
+ return @probabilities[ computed_key_name] if @probabilities[ computed_key_name ] && set_value.nil?
42
+ set_value ||= compute( key )+compute( other_key )
43
+ set( computed_key_name, set_value )
44
+ end
45
+ # aposteriori =( Likelihood * Prior )/ marginal likelihood
46
+ # P(A|B) = P(B|A)*P(A)/P(B)
47
+ def aposteriori( key,apriori:nil, set_value: nil )
48
+ computed_key_name = "#{key}|#{apriori}"
49
+ return @probabilities[ computed_key_name ] if @probabilities[ computed_key_name ] &&set_value.nil?
50
+ set_value ||= compute("#{apriori}|#{key}")*compute(key)/compute(apriori)
51
+ set( computed_key_name, set_value )
52
+ end
53
+
54
+
55
+ def total( key, apriori: nil )
56
+ aposteriori(key,apriori: apriori)*compute(apriori)+self.aposteriori(key,apriori: "!#{apriori}")*self.not(apriori)
57
+ end
58
+
59
+
60
+ def and( key,other_key, set_value: nil )
61
+ computed_key_name = "#{key}^#{other_key}"
62
+ return @probabilities[computed_key_name ] if @probabilities[ computed_key_name ] &&set_value.nil?
63
+ set_value ||= compute("#{key}|#{other_key}")*compute(other_key)
64
+ set( computed_key_name, set_value )
65
+ end
66
+
67
+ def or( key,other_key, set_value: nil )
68
+ computed_key_name = "#{key}.#{other_key}"
69
+ return @probabilities[ computed_key_name ] if @probabilities[computed_key_name] &&set_value.nil?
70
+ set_value ||= compute(key)+compute(other_key)-compute("#{key}^#{other_key}")
71
+ set( computed_key_name, set_value )
72
+ end
73
+
74
+ # P(R|H,S) = P(H|R,S)*P(R|S)/P(H|S)
75
+ # P(R|H,!S) = P(H|R,!S)*P(R|!S)/P(H|!S)
76
+ # 0.9*0.01/(0.9*0.01+0.1*0.99)
77
+ # P(R|H) = P(H|R)*P(R)/P(H) = 0.9*0.01/ =0.097*0.01
78
+ # P(H) = P 0.5244999999999999
79
+
80
+ def compute( key )
81
+ probability = if @probabilities[key]
82
+ @probabilities[key]
83
+ elsif key.start_with?("!") && @probabilities[key.sub("!","")]
84
+ self.not(key.sub("!",''))
85
+ elsif key.match('\|')
86
+ @history << key unless @history.member?( key )
87
+ key,apriori=key.split("|")
88
+ self.aposteriori( key, apriori: apriori)
89
+ elsif key.match('\^')
90
+ @history << key unless @history.member?( key )
91
+ key,other_key=key.split("^")
92
+ self.and(key,other_key)
93
+ else
94
+ apriori_key = @probabilities.keys.find{|p| p.split("|")[0]==key && !p.split("|")[1].match('!') }
95
+ if apriori_key
96
+ @history << key unless @history.member?( key )
97
+ apriori_key = apriori_key.split("|")[1]
98
+ value = self.total(key,apriori: apriori_key)
99
+ self.set( key, value)
100
+ else
101
+ raise "not found #{key}"
102
+ end
103
+ end
104
+ probability
105
+ end
106
+ end
107
+
108
+ end
@@ -0,0 +1,90 @@
1
+ module Newral
2
+ module QLearning
3
+ class Base
4
+ attr_accessor :game
5
+ # this q_learning algorithm was posted here
6
+ # https://www.practicalai.io/teaching-ai-play-simple-game-using-q-learning/
7
+ # however I extended it so it can play more games
8
+ # also the q_table is implemente as a hash so actions can differ at different positions
9
+ # this way the algorithm also needs to know less about the game
10
+ def initialize( id: nil, game: nil, learning_rate: 0.4, discount: 0.9, epsilon: 0.9, sleep_time: 0.001 )
11
+ game.set_player( self )
12
+ @id = id
13
+ @game = game
14
+ @learning_rate = learning_rate
15
+ @discount = discount
16
+ @epsilon = epsilon
17
+ @sleep = sleep_time
18
+ @random = Random.new
19
+ @q_hash = {}
20
+ end
21
+
22
+ def set_epsilon( epsilon )
23
+ @epsilon = epsilon
24
+ end
25
+
26
+ def inform_game_ended
27
+ get_input( move: false )
28
+ end
29
+
30
+
31
+ def get_input( move: true )
32
+ # Our new state is equal to the player position
33
+ @outcome_state = @game.get_position( player: self )
34
+
35
+ # which actions are available to the player at the moment?
36
+ @actions = @game.get_actions( player: self )
37
+
38
+ # is this the first run
39
+ initial_run = @q_hash.empty?
40
+
41
+ @q_hash[@outcome_state] = @q_hash[@outcome_state] || {}
42
+ @actions.each do |action|
43
+ @q_hash[@outcome_state][action] = @q_hash[@outcome_state][action] || 0.1 # @random.rand/10.0
44
+ end
45
+
46
+ if initial_run
47
+ @action_taken = @actions.first
48
+ elsif @old_state
49
+ # If this is not the first run
50
+ # Evaluate what happened on last action and update Q table
51
+
52
+ # Calculate reward
53
+ reward = 0 # default is 0
54
+ if @old_score < @game.get_score( player: self )
55
+ reward = [@game.get_score( player: self )-@old_score,1].max # reward is at least 1 if our score increased
56
+ elsif @old_score > @game.get_score( player: self )
57
+ reward = [@old_score-@game.get_score( player: self ),-1].min # reward is smaller or equal -1 if our score decreased
58
+ else
59
+ reward = -0.1 # time is money, we punish moves
60
+ end
61
+ @q_hash[@old_state][@action_taken] = @q_hash[@old_state][@action_taken] + @learning_rate * (reward + @discount * (@q_hash[@outcome_state]).values.max.to_f - @q_hash[@old_state][@action_taken])
62
+ end
63
+
64
+ # Capture current state and score
65
+ @old_score = @game.get_score( player: self )
66
+ @old_state = @game.get_position( player: self ) # we remember this for next run, its current state
67
+ @old_actions = @actions
68
+ if move # in the goal state we just update the q_hash
69
+
70
+ # Chose action based on Q value estimates for state
71
+ if @random.rand > @epsilon || @q_hash[@old_state].nil?
72
+ # Select random action
73
+ @action_taken_index = @random.rand(@actions.length).round
74
+ @action_taken = @actions[@action_taken_index]
75
+ else
76
+ # Select based on Q table, remember @old_state is equal to current state at this point
77
+ @action_taken = @q_hash[@old_state].to_a.sort{ |v1,v2| v2[1]<=>v1[1]}[0][0]
78
+ raise "impossible action #{ @action_taken } #{@old_state} #{@q_hash[ @old_state ] } #{ @actions } #{@old_actions } " unless @actions.member?( @action_taken)
79
+ end
80
+
81
+ # Take action
82
+ return @action_taken
83
+ else
84
+ @old_state = nil # we do not have a old state any more as we have reached an end state
85
+ end
86
+ end
87
+
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,135 @@
1
+ module Newral
2
+
3
+ module Tools
4
+
5
+ module Errors
6
+ class NoElements < StandardError; end
7
+ end
8
+
9
+ def self.mean( array )
10
+ array.sum.to_f/array.length
11
+ end
12
+
13
+ def self.sigma( array )
14
+ mean = self.mean( array )
15
+ sigma_square = array.inject(0){ |value,el| value+(el-mean)**2 }
16
+ (sigma_square/array.length)**0.5
17
+ end
18
+
19
+ def self.normalize( data, high: nil, low:nil,normalized_high:1,normalized_low:-1)
20
+ unless high && low
21
+ data.sort!
22
+ high ||= [data[data.length-1],data[0].abs].max
23
+ low ||= data[0] >= 0 ? data[0] : [high*-1,data[0]].min
24
+ end
25
+ data.collect do |data_point|
26
+ (data_point-low)/(high-low).to_f*(normalized_high-normalized_low)+normalized_low
27
+ end
28
+ end
29
+
30
+ def self.denormalize( data, high: nil, low:nil,normalized_high:1,normalized_low:-1)
31
+ normalize( data, normalized_high: high, normalized_low: low, high: normalized_high, low: normalized_low)
32
+ end
33
+
34
+ def self.euclidian_distance( v1, v2 )
35
+ total = 0.0
36
+ v1.each_with_index do |value,idx|
37
+ total=total+(value-v2[idx])**2
38
+ end
39
+ total**0.5
40
+ end
41
+
42
+ def self.max_distance( v1, v2 )
43
+ max = 0.0
44
+ v1.each_with_index do |value,idx|
45
+ dist=(value-v2[idx]).abs
46
+ max = dist unless max > dist
47
+ end
48
+ max
49
+ end
50
+
51
+ def self.taxi_cab_distance( v1, v2 )
52
+ total = 0.0
53
+ v1.each_with_index do |value,idx|
54
+ total=total+(value-v2[idx]).abs
55
+ end
56
+ total
57
+ end
58
+
59
+ def self.sort_and_slice( distances, number_of_entries:5,element_at_index:1 )
60
+ distances.sort do |v1,v2|
61
+ v1[ element_at_index ]<=>v2[ element_at_index ]
62
+ end[0..number_of_entries-1]
63
+ end
64
+
65
+ # point is a vector
66
+ # clusters have a key , values are an array of point
67
+ # we iterate over all points and return the closest ones
68
+ # as an Array of [key,distance,point]
69
+ # to classify you can then count the nearest neighbours by key or use the weighted k_nearest_neighbour approach
70
+ def self.k_nearest_neighbour( point, clusters, number_of_neighbours: 5 )
71
+ distances = []
72
+
73
+ clusters.each do |key,values|
74
+ values.each do |value|
75
+ # we optimize here by sorting on insert, or sort and slice after distances
76
+ # array exceeds number_of_neighbours*5
77
+ distances = sort_and_slice( distances, number_of_entries: number_of_neighbours ) if distances.length > number_of_neighbours*5
78
+ distances << [key,euclidian_distance( point, value ),value]
79
+ end
80
+ end
81
+ sort_and_slice( distances, number_of_entries: number_of_neighbours )
82
+ end
83
+
84
+ # input: array of samples which lead to positive result
85
+ # example movies user likes with 3 properties action, comedy, romance
86
+ # [[1,0,1],[0,0,1]] => user likes action+romance, romance
87
+ # output be [0,-1,1] => action y/n does not matter, comedy no, romance yes
88
+ def self.more_general_than_or_equal( samples )
89
+ hypotheses=[-1]*samples.first.length
90
+ samples.each do |sample|
91
+ sample.each_with_index do |v,idx|
92
+ if v == 1
93
+ hypotheses[idx] = 1 unless hypotheses[idx] == 0
94
+ else
95
+ hypotheses[idx] = 0 if hypotheses[idx] == 1
96
+ end
97
+ end
98
+ end
99
+ hypotheses
100
+ end
101
+
102
+ # input: array of samples which lead to negative result
103
+ # example movies user does not likes with 3 properties action, comedy, romance
104
+ # [[1,0,1],[0,0,1]] => user does not like action+romance, romance
105
+ # output be [-1,1,-1] => action no, romance no
106
+ def self.general_to_specific( samples )
107
+ hypotheses=[0]*samples.first.length
108
+ samples.each do |sample|
109
+ sample.each_with_index do |v,idx|
110
+ if v == 1
111
+ hypotheses[idx] = -1 unless hypotheses[idx] == 1
112
+ else
113
+ hypotheses[idx] = 1 if hypotheses[idx] == 0
114
+ end
115
+ end
116
+ end
117
+ hypotheses
118
+ end
119
+
120
+
121
+
122
+ # https://youtu.be/_DhelJs0BFc?t=4m10s
123
+ # http://keisan.casio.com/exec/system/1180573188
124
+ def self.gaussian_density( x, mu: nil , sigma: nil, elements:nil )
125
+ raise Errors::NoElements if ( mu.nil? || sigma.nil? ) && elements.nil?
126
+ mu = mean( elements ) unless mu
127
+ sigma = sigma( elements ) unless sigma
128
+ (1.0/((2*Math::PI)**0.5*sigma.to_f))*Math.exp((-1.0/2)*((x-mu)**2/sigma.to_f**2 ))
129
+ end
130
+
131
+
132
+ end
133
+
134
+
135
+ end