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,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
|