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.
- 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,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
|
data/lib/newral/tools.rb
ADDED
@@ -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
|