newral 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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