ai4r 1.13 → 2.0

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 (129) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +174 -0
  3. data/examples/classifiers/hyperpipes_data.csv +14 -0
  4. data/examples/classifiers/hyperpipes_example.rb +22 -0
  5. data/examples/classifiers/ib1_example.rb +12 -0
  6. data/examples/classifiers/id3_example.rb +15 -10
  7. data/examples/classifiers/id3_graphviz_example.rb +17 -0
  8. data/examples/classifiers/logistic_regression_example.rb +11 -0
  9. data/examples/classifiers/naive_bayes_attributes_example.rb +13 -0
  10. data/examples/classifiers/naive_bayes_example.rb +12 -13
  11. data/examples/classifiers/one_r_example.rb +27 -0
  12. data/examples/classifiers/parameter_tutorial.rb +29 -0
  13. data/examples/classifiers/prism_nominal_example.rb +15 -0
  14. data/examples/classifiers/prism_numeric_example.rb +21 -0
  15. data/examples/classifiers/simple_linear_regression_example.rb +14 -11
  16. data/examples/classifiers/zero_and_one_r_example.rb +34 -0
  17. data/examples/classifiers/zero_one_r_data.csv +8 -0
  18. data/examples/clusterers/clusterer_example.rb +40 -34
  19. data/examples/clusterers/dbscan_example.rb +17 -0
  20. data/examples/clusterers/dendrogram_example.rb +17 -0
  21. data/examples/clusterers/hierarchical_dendrogram_example.rb +20 -0
  22. data/examples/clusterers/kmeans_custom_example.rb +26 -0
  23. data/examples/genetic_algorithm/bitstring_example.rb +41 -0
  24. data/examples/genetic_algorithm/genetic_algorithm_example.rb +26 -18
  25. data/examples/genetic_algorithm/kmeans_seed_tuning.rb +45 -0
  26. data/examples/neural_network/backpropagation_example.rb +48 -48
  27. data/examples/neural_network/hopfield_example.rb +45 -0
  28. data/examples/neural_network/patterns_with_base_noise.rb +39 -39
  29. data/examples/neural_network/patterns_with_noise.rb +41 -39
  30. data/examples/neural_network/train_epochs_callback.rb +25 -0
  31. data/examples/neural_network/training_patterns.rb +39 -39
  32. data/examples/neural_network/transformer_text_classification.rb +78 -0
  33. data/examples/neural_network/xor_example.rb +23 -22
  34. data/examples/reinforcement/q_learning_example.rb +10 -0
  35. data/examples/som/som_data.rb +155 -152
  36. data/examples/som/som_multi_node_example.rb +12 -13
  37. data/examples/som/som_single_example.rb +12 -15
  38. data/examples/transformer/decode_classifier_example.rb +68 -0
  39. data/examples/transformer/deterministic_example.rb +10 -0
  40. data/examples/transformer/seq2seq_example.rb +16 -0
  41. data/lib/ai4r/classifiers/classifier.rb +24 -16
  42. data/lib/ai4r/classifiers/gradient_boosting.rb +64 -0
  43. data/lib/ai4r/classifiers/hyperpipes.rb +119 -43
  44. data/lib/ai4r/classifiers/ib1.rb +122 -32
  45. data/lib/ai4r/classifiers/id3.rb +524 -145
  46. data/lib/ai4r/classifiers/logistic_regression.rb +96 -0
  47. data/lib/ai4r/classifiers/multilayer_perceptron.rb +75 -59
  48. data/lib/ai4r/classifiers/naive_bayes.rb +95 -34
  49. data/lib/ai4r/classifiers/one_r.rb +112 -44
  50. data/lib/ai4r/classifiers/prism.rb +167 -76
  51. data/lib/ai4r/classifiers/random_forest.rb +72 -0
  52. data/lib/ai4r/classifiers/simple_linear_regression.rb +83 -58
  53. data/lib/ai4r/classifiers/support_vector_machine.rb +91 -0
  54. data/lib/ai4r/classifiers/votes.rb +57 -0
  55. data/lib/ai4r/classifiers/zero_r.rb +71 -30
  56. data/lib/ai4r/clusterers/average_linkage.rb +46 -27
  57. data/lib/ai4r/clusterers/bisecting_k_means.rb +50 -44
  58. data/lib/ai4r/clusterers/centroid_linkage.rb +52 -36
  59. data/lib/ai4r/clusterers/cluster_tree.rb +50 -0
  60. data/lib/ai4r/clusterers/clusterer.rb +29 -14
  61. data/lib/ai4r/clusterers/complete_linkage.rb +42 -31
  62. data/lib/ai4r/clusterers/dbscan.rb +134 -0
  63. data/lib/ai4r/clusterers/diana.rb +75 -49
  64. data/lib/ai4r/clusterers/k_means.rb +270 -135
  65. data/lib/ai4r/clusterers/median_linkage.rb +49 -33
  66. data/lib/ai4r/clusterers/single_linkage.rb +196 -88
  67. data/lib/ai4r/clusterers/ward_linkage.rb +51 -35
  68. data/lib/ai4r/clusterers/ward_linkage_hierarchical.rb +25 -10
  69. data/lib/ai4r/clusterers/weighted_average_linkage.rb +48 -32
  70. data/lib/ai4r/data/data_set.rb +223 -103
  71. data/lib/ai4r/data/parameterizable.rb +31 -25
  72. data/lib/ai4r/data/proximity.rb +62 -62
  73. data/lib/ai4r/data/statistics.rb +46 -35
  74. data/lib/ai4r/experiment/classifier_evaluator.rb +84 -32
  75. data/lib/ai4r/experiment/split.rb +39 -0
  76. data/lib/ai4r/genetic_algorithm/chromosome_base.rb +43 -0
  77. data/lib/ai4r/genetic_algorithm/genetic_algorithm.rb +92 -170
  78. data/lib/ai4r/genetic_algorithm/tsp_chromosome.rb +83 -0
  79. data/lib/ai4r/hmm/hidden_markov_model.rb +134 -0
  80. data/lib/ai4r/neural_network/activation_functions.rb +37 -0
  81. data/lib/ai4r/neural_network/backpropagation.rb +399 -134
  82. data/lib/ai4r/neural_network/hopfield.rb +175 -58
  83. data/lib/ai4r/neural_network/transformer.rb +194 -0
  84. data/lib/ai4r/neural_network/weight_initializations.rb +40 -0
  85. data/lib/ai4r/reinforcement/policy_iteration.rb +66 -0
  86. data/lib/ai4r/reinforcement/q_learning.rb +51 -0
  87. data/lib/ai4r/search/a_star.rb +76 -0
  88. data/lib/ai4r/search/bfs.rb +50 -0
  89. data/lib/ai4r/search/dfs.rb +50 -0
  90. data/lib/ai4r/search/mcts.rb +118 -0
  91. data/lib/ai4r/search.rb +12 -0
  92. data/lib/ai4r/som/distance_metrics.rb +29 -0
  93. data/lib/ai4r/som/layer.rb +28 -17
  94. data/lib/ai4r/som/node.rb +61 -32
  95. data/lib/ai4r/som/som.rb +158 -41
  96. data/lib/ai4r/som/two_phase_layer.rb +21 -25
  97. data/lib/ai4r/version.rb +3 -0
  98. data/lib/ai4r.rb +57 -28
  99. metadata +79 -109
  100. data/README.rdoc +0 -39
  101. data/test/classifiers/hyperpipes_test.rb +0 -84
  102. data/test/classifiers/ib1_test.rb +0 -78
  103. data/test/classifiers/id3_test.rb +0 -220
  104. data/test/classifiers/multilayer_perceptron_test.rb +0 -79
  105. data/test/classifiers/naive_bayes_test.rb +0 -43
  106. data/test/classifiers/one_r_test.rb +0 -62
  107. data/test/classifiers/prism_test.rb +0 -85
  108. data/test/classifiers/simple_linear_regression_test.rb +0 -37
  109. data/test/classifiers/zero_r_test.rb +0 -50
  110. data/test/clusterers/average_linkage_test.rb +0 -51
  111. data/test/clusterers/bisecting_k_means_test.rb +0 -66
  112. data/test/clusterers/centroid_linkage_test.rb +0 -53
  113. data/test/clusterers/complete_linkage_test.rb +0 -57
  114. data/test/clusterers/diana_test.rb +0 -69
  115. data/test/clusterers/k_means_test.rb +0 -167
  116. data/test/clusterers/median_linkage_test.rb +0 -53
  117. data/test/clusterers/single_linkage_test.rb +0 -122
  118. data/test/clusterers/ward_linkage_hierarchical_test.rb +0 -81
  119. data/test/clusterers/ward_linkage_test.rb +0 -53
  120. data/test/clusterers/weighted_average_linkage_test.rb +0 -53
  121. data/test/data/data_set_test.rb +0 -104
  122. data/test/data/proximity_test.rb +0 -87
  123. data/test/data/statistics_test.rb +0 -65
  124. data/test/experiment/classifier_evaluator_test.rb +0 -76
  125. data/test/genetic_algorithm/chromosome_test.rb +0 -57
  126. data/test/genetic_algorithm/genetic_algorithm_test.rb +0 -81
  127. data/test/neural_network/backpropagation_test.rb +0 -82
  128. data/test/neural_network/hopfield_test.rb +0 -72
  129. data/test/som/som_test.rb +0 -97
@@ -1,26 +1,24 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Author:: Sergio Fierens
2
4
  # License:: MPL 1.1
3
5
  # Project:: ai4r
4
- # Url:: http://ai4r.org/
6
+ # Url:: https://github.com/SergioFierens/ai4r
5
7
  #
6
- # You can redistribute it and/or modify it under the terms of
7
- # the Mozilla Public License version 1.1 as published by the
8
+ # You can redistribute it and/or modify it under the terms of
9
+ # the Mozilla Public License version 1.1 as published by the
8
10
  # Mozilla Foundation at http://www.mozilla.org/MPL/MPL-1.1.txt
11
+ require_relative 'chromosome_base'
12
+ require_relative 'tsp_chromosome'
13
+
9
14
  module Ai4r
10
-
11
- # The GeneticAlgorithm module implements the GeneticSearch and Chromosome
12
- # classes. The GeneticSearch is a generic class, and can be used to solved
13
- # any kind of problems. The GeneticSearch class performs a stochastic search
14
- # of the solution of a given problem.
15
- #
16
- # The Chromosome is "problem specific". Ai4r built-in Chromosome class was
17
- # designed to model the Travelling salesman problem. If you want to solve other
18
- # type of problem, you will have to modify the Chromosome class, by overwriting
19
- # its fitness, reproduce, and mutate functions, to model your specific problem.
15
+ # The GeneticAlgorithm module implements the GeneticSearch class. The
16
+ # GeneticSearch is a generic class, and can be used to solve any kind of
17
+ # problem. The Chromosome implementation is problem specific and must conform
18
+ # to +ChromosomeBase+.
20
19
  module GeneticAlgorithm
21
-
22
20
  # This class is used to automatically:
23
- #
21
+ #
24
22
  # 1. Choose initial population
25
23
  # 2. Evaluate the fitness of each individual in the population
26
24
  # 3. Repeat
@@ -34,14 +32,23 @@ module Ai4r
34
32
  # - Chromosome
35
33
  # - Population
36
34
  class GeneticSearch
37
-
38
- attr_accessor :population
39
-
40
-
41
- def initialize(initial_population_size, generations)
35
+ attr_accessor :population, :mutation_rate, :crossover_rate, :fitness_threshold,
36
+ :max_stagnation, :on_generation
37
+ attr_reader :chromosome_class
38
+
39
+ # @return [Object]
40
+ def initialize(initial_population_size, generations, chromosome_class = TspChromosome,
41
+ mutation_rate = 0.3, crossover_rate = 0.4,
42
+ fitness_threshold = nil, max_stagnation = nil, on_generation = nil)
42
43
  @population_size = initial_population_size
43
44
  @max_generation = generations
44
45
  @generation = 0
46
+ @chromosome_class = chromosome_class
47
+ @mutation_rate = mutation_rate
48
+ @crossover_rate = crossover_rate
49
+ @fitness_threshold = fitness_threshold
50
+ @max_stagnation = max_stagnation
51
+ @on_generation = on_generation
45
52
  end
46
53
 
47
54
  # 1. Choose initial population
@@ -51,97 +58,127 @@ module Ai4r
51
58
  # 2. Breed new generation through crossover and mutation (genetic operations) and give birth to offspring
52
59
  # 3. Evaluate the individual fitnesses of the offspring
53
60
  # 4. Replace worst ranked part of population with offspring
54
- # 4. Until termination
61
+ # 4. Until termination
55
62
  # 5. Return the best chromosome
63
+ # @return [Object]
56
64
  def run
57
- generate_initial_population #Generate initial population
65
+ generate_initial_population # Generate initial population
66
+ best = best_chromosome
67
+ best_fitness = best.fitness
68
+ stagnation = 0
69
+ @on_generation&.call(@generation, best_fitness)
58
70
  @max_generation.times do
59
- selected_to_breed = selection #Evaluates current population
60
- offsprings = reproduction selected_to_breed #Generate the population for this new generation
71
+ @generation += 1
72
+ selected_to_breed = selection # Evaluates current population
73
+ offsprings = reproduction selected_to_breed # Generate the population for this new generation
61
74
  replace_worst_ranked offsprings
75
+ current_best = best_chromosome
76
+ if current_best.fitness > best_fitness
77
+ best_fitness = current_best.fitness
78
+ best = current_best
79
+ stagnation = 0
80
+ else
81
+ stagnation += 1
82
+ end
83
+ @on_generation&.call(@generation, best_fitness)
84
+ break if (@fitness_threshold && best_fitness >= @fitness_threshold) ||
85
+ (@max_stagnation && stagnation >= @max_stagnation)
62
86
  end
63
- return best_chromosome
87
+ best
64
88
  end
65
89
 
66
-
90
+ # @return [Object]
67
91
  def generate_initial_population
68
- @population = []
69
- @population_size.times do
70
- population << Chromosome.seed
71
- end
92
+ @population = []
93
+ @population_size.times do
94
+ population << @chromosome_class.seed
95
+ end
72
96
  end
73
97
 
74
98
  # Select best-ranking individuals to reproduce
75
- #
76
- # Selection is the stage of a genetic algorithm in which individual
77
- # genomes are chosen from a population for later breeding.
78
- # There are several generic selection algorithms, such as
99
+ #
100
+ # Selection is the stage of a genetic algorithm in which individual
101
+ # genomes are chosen from a population for later breeding.
102
+ # There are several generic selection algorithms, such as
79
103
  # tournament selection and roulette wheel selection. We implemented the
80
104
  # latest.
81
- #
105
+ #
82
106
  # Steps:
83
- #
107
+ #
84
108
  # 1. The fitness function is evaluated for each individual, providing fitness values
85
109
  # 2. The population is sorted by descending fitness values.
86
110
  # 3. The fitness values ar then normalized. (Highest fitness gets 1, lowest fitness gets 0). The normalized value is stored in the "normalized_fitness" attribute of the chromosomes.
87
111
  # 4. A random number R is chosen. R is between 0 and the accumulated normalized value (all the normalized fitness values added togheter).
88
112
  # 5. The selected individual is the first one whose accumulated normalized value (its is normalized value plus the normalized values of the chromosomes prior it) greater than R.
89
- # 6. We repeat steps 4 and 5, 2/3 times the population size.
113
+ # 6. We repeat steps 4 and 5, 2/3 times the population size.
114
+ # @return [Object]
90
115
  def selection
91
- @population.sort! { |a, b| b.fitness <=> a.fitness}
116
+ @population.sort! { |a, b| b.fitness <=> a.fitness }
92
117
  best_fitness = @population[0].fitness
93
118
  worst_fitness = @population.last.fitness
94
119
  acum_fitness = 0
95
- if best_fitness-worst_fitness > 0
96
- @population.each do |chromosome|
97
- chromosome.normalized_fitness = (chromosome.fitness - worst_fitness)/(best_fitness-worst_fitness)
98
- acum_fitness += chromosome.normalized_fitness
99
- end
120
+ if (best_fitness - worst_fitness).positive?
121
+ @population.each do |chromosome|
122
+ chromosome.normalized_fitness = (chromosome.fitness - worst_fitness) / (best_fitness - worst_fitness)
123
+ acum_fitness += chromosome.normalized_fitness
124
+ end
100
125
  else
101
- @population.each { |chromosome| chromosome.normalized_fitness = 1}
126
+ @population.each { |chromosome| chromosome.normalized_fitness = 1 }
102
127
  end
103
128
  selected_to_breed = []
104
- ((2*@population_size)/3).times do
129
+ ((2 * @population_size) / 3).times do
105
130
  selected_to_breed << select_random_individual(acum_fitness)
106
131
  end
107
132
  selected_to_breed
108
133
  end
109
134
 
110
- # We combine each pair of selected chromosome using the method
135
+ # We combine each pair of selected chromosome using the method
111
136
  # Chromosome.reproduce
112
137
  #
113
- # The reproduction will also call the Chromosome.mutate method with
138
+ # The reproduction will also call the Chromosome.mutate method with
114
139
  # each member of the population. You should implement Chromosome.mutate
115
140
  # to only change (mutate) randomly. E.g. You could effectivly change the
116
- # chromosome only if
141
+ # chromosome only if
117
142
  # rand < ((1 - chromosome.normalized_fitness) * 0.4)
143
+ # @param selected_to_breed [Object]
144
+ # @return [Object]
118
145
  def reproduction(selected_to_breed)
119
146
  offsprings = []
120
- 0.upto(selected_to_breed.length/2-1) do |i|
121
- offsprings << Chromosome.reproduce(selected_to_breed[2*i], selected_to_breed[2*i+1])
147
+ 0.upto((selected_to_breed.length / 2) - 1) do |i|
148
+ offsprings << @chromosome_class.reproduce(
149
+ selected_to_breed[2 * i],
150
+ selected_to_breed[(2 * i) + 1],
151
+ @crossover_rate
152
+ )
122
153
  end
123
154
  @population.each do |individual|
124
- Chromosome.mutate(individual)
155
+ @chromosome_class.mutate(individual, @mutation_rate)
125
156
  end
126
- return offsprings
157
+ offsprings
127
158
  end
128
159
 
129
160
  # Replace worst ranked part of population with offspring
161
+ # @param offsprings [Object]
162
+ # @return [Object]
130
163
  def replace_worst_ranked(offsprings)
131
164
  size = offsprings.length
132
- @population = @population [0..((-1*size)-1)] + offsprings
165
+ @population = @population[0..(-size - 1)] + offsprings
133
166
  end
134
167
 
135
168
  # Select the best chromosome in the population
169
+ # @return [Object]
136
170
  def best_chromosome
137
171
  the_best = @population[0]
138
172
  @population.each do |chromosome|
139
173
  the_best = chromosome if chromosome.fitness > the_best.fitness
140
174
  end
141
- return the_best
175
+ the_best
142
176
  end
143
177
 
144
- private
178
+ private
179
+
180
+ # @param acum_fitness [Object]
181
+ # @return [Object]
145
182
  def select_random_individual(acum_fitness)
146
183
  select_random_target = acum_fitness * rand
147
184
  local_acum = 0
@@ -150,121 +187,6 @@ module Ai4r
150
187
  return chromosome if local_acum >= select_random_target
151
188
  end
152
189
  end
153
-
154
- end
155
-
156
- # A Chromosome is a representation of an individual solution for a specific
157
- # problem. You will have to redifine the Chromosome representation for each
158
- # particular problem, along with its fitness, mutate, reproduce, and seed
159
- # methods.
160
- class Chromosome
161
-
162
- attr_accessor :data
163
- attr_accessor :normalized_fitness
164
-
165
- def initialize(data)
166
- @data = data
167
- end
168
-
169
- # The fitness method quantifies the optimality of a solution
170
- # (that is, a chromosome) in a genetic algorithm so that that particular
171
- # chromosome may be ranked against all the other chromosomes.
172
- #
173
- # Optimal chromosomes, or at least chromosomes which are more optimal,
174
- # are allowed to breed and mix their datasets by any of several techniques,
175
- # producing a new generation that will (hopefully) be even better.
176
- def fitness
177
- return @fitness if @fitness
178
- last_token = @data[0]
179
- cost = 0
180
- @data[1..-1].each do |token|
181
- cost += @@costs[last_token][token]
182
- last_token = token
183
- end
184
- @fitness = -1 * cost
185
- return @fitness
186
- end
187
-
188
- # mutation method is used to maintain genetic diversity from one
189
- # generation of a population of chromosomes to the next. It is analogous
190
- # to biological mutation.
191
- #
192
- # The purpose of mutation in GAs is to allow the
193
- # algorithm to avoid local minima by preventing the population of
194
- # chromosomes from becoming too similar to each other, thus slowing or even
195
- # stopping evolution.
196
- #
197
- # Calling the mutate function will "probably" slightly change a chromosome
198
- # randomly.
199
- #
200
- # This implementation of "mutation" will (probably) reverse the
201
- # order of 2 consecutive randome nodes
202
- # (e.g. from [ 0, 1, 2, 4] to [0, 2, 1, 4]) if:
203
- # ((1 - chromosome.normalized_fitness) * 0.4)
204
- def self.mutate(chromosome)
205
- if chromosome.normalized_fitness && rand < ((1 - chromosome.normalized_fitness) * 0.3)
206
- data = chromosome.data
207
- index = rand(data.length-1)
208
- data[index], data[index+1] = data[index+1], data[index]
209
- chromosome.data = data
210
- @fitness = nil
211
- end
212
- end
213
-
214
- # Reproduction method is used to combine two chromosomes (solutions) into
215
- # a single new chromosome. There are several ways to
216
- # combine two chromosomes: One-point crossover, Two-point crossover,
217
- # "Cut and splice", edge recombination, and more.
218
- #
219
- # The method is usually dependant of the problem domain.
220
- # In this case, we have implemented edge recombination, wich is the
221
- # most used reproduction algorithm for the Travelling salesman problem.
222
- def self.reproduce(a, b)
223
- data_size = @@costs[0].length
224
- available = []
225
- 0.upto(data_size-1) { |n| available << n }
226
- token = a.data[0]
227
- spawn = [token]
228
- available.delete(token)
229
- while available.length > 0 do
230
- #Select next
231
- if token != b.data.last && available.include?(b.data[b.data.index(token)+1])
232
- next_token = b.data[b.data.index(token)+1]
233
- elsif token != a.data.last && available.include?(a.data[a.data.index(token)+1])
234
- next_token = a.data[a.data.index(token)+1]
235
- else
236
- next_token = available[rand(available.length)]
237
- end
238
- #Add to spawn
239
- token = next_token
240
- available.delete(token)
241
- spawn << next_token
242
- a, b = b, a if rand < 0.4
243
- end
244
- return Chromosome.new(spawn)
245
- end
246
-
247
- # Initializes an individual solution (chromosome) for the initial
248
- # population. Usually the chromosome is generated randomly, but you can
249
- # use some problem domain knowledge, to generate a
250
- # (probably) better initial solution.
251
- def self.seed
252
- data_size = @@costs[0].length
253
- available = []
254
- 0.upto(data_size-1) { |n| available << n }
255
- seed = []
256
- while available.length > 0 do
257
- index = rand(available.length)
258
- seed << available.delete_at(index)
259
- end
260
- return Chromosome.new(seed)
261
- end
262
-
263
- def self.set_cost_matrix(costs)
264
- @@costs = costs
265
- end
266
190
  end
267
-
268
191
  end
269
-
270
192
  end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'chromosome_base'
4
+
5
+ module Ai4r
6
+ module GeneticAlgorithm
7
+ # Chromosome implementation for the Travelling Salesman Problem.
8
+ class TspChromosome < ChromosomeBase
9
+ # @return [Object]
10
+ def fitness
11
+ return @fitness if @fitness
12
+
13
+ last_token = @data[0]
14
+ cost = 0
15
+ @data[1..].each do |token|
16
+ cost += @@costs[last_token][token]
17
+ last_token = token
18
+ end
19
+ @fitness = -1 * cost
20
+ @fitness
21
+ end
22
+
23
+ # @param chromosome [Object]
24
+ # @param mutation_rate [Object]
25
+ # @return [Object]
26
+ def self.mutate(chromosome, mutation_rate = 0.3)
27
+ return unless chromosome.normalized_fitness && rand < ((1 - chromosome.normalized_fitness) * mutation_rate)
28
+
29
+ data = chromosome.data
30
+ # Swapping the first two cities can sometimes keep the fitness
31
+ # unchanged depending on the cost matrix. Pick an inner segment
32
+ # instead to ensure the route actually changes.
33
+ index = (1...(data.length - 1)).to_a.sample
34
+ data[index], data[index + 1] = data[index + 1], data[index]
35
+ chromosome.data = data
36
+ chromosome.instance_variable_set(:@fitness, nil)
37
+ end
38
+
39
+ # @param a [Object]
40
+ # @param b [Object]
41
+ # @param crossover_rate [Object]
42
+ # @return [Object]
43
+ def self.reproduce(a, b, crossover_rate = 0.4)
44
+ data_size = @@costs[0].length
45
+ available = []
46
+ 0.upto(data_size - 1) { |n| available << n }
47
+ token = a.data[0]
48
+ spawn = [token]
49
+ available.delete(token)
50
+ while available.length.positive?
51
+ next_token = if token != b.data.last && available.include?(b.data[b.data.index(token) + 1])
52
+ b.data[b.data.index(token) + 1]
53
+ elsif token != a.data.last && available.include?(a.data[a.data.index(token) + 1])
54
+ a.data[a.data.index(token) + 1]
55
+ else
56
+ available.sample
57
+ end
58
+ token = next_token
59
+ available.delete(token)
60
+ spawn << next_token
61
+ a, b = b, a if rand < crossover_rate
62
+ end
63
+ new(spawn)
64
+ end
65
+
66
+ # @return [Object]
67
+ def self.seed
68
+ data_size = @@costs[0].length
69
+ available = []
70
+ 0.upto(data_size - 1) { |n| available << n }
71
+ seed = []
72
+ seed << available.delete(available.sample) while available.length.positive?
73
+ new(seed)
74
+ end
75
+
76
+ # @param costs [Object]
77
+ # @return [Object]
78
+ def self.set_cost_matrix(costs)
79
+ @@costs = costs
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Author:: OpenAI Codex
4
+ # License:: MPL 1.1
5
+ # Project:: ai4r
6
+ # Url:: https://github.com/SergioFierens/ai4r
7
+ #
8
+ # You can redistribute it and/or modify it under the terms of
9
+ # the Mozilla Public License version 1.1 as published by the
10
+ # Mozilla Foundation at http://www.mozilla.org/MPL/MPL-1.1.txt
11
+
12
+ require_relative '../data/parameterizable'
13
+
14
+ module Ai4r
15
+ module Hmm
16
+ # = Introduction
17
+ #
18
+ # A simple implementation of a discrete Hidden Markov Model (HMM).
19
+ # You must provide the states and observations as well as the
20
+ # probability matrices. This class exposes two main operations:
21
+ #
22
+ # * +eval(sequence)+: probability of the observation sequence.
23
+ # * +decode(sequence)+: most likely hidden state sequence (Viterbi).
24
+ #
25
+ # Probabilities are provided as arrays. Example:
26
+ #
27
+ # states = [:Rainy, :Sunny]
28
+ # observations = [:walk, :shop, :clean]
29
+ # start_prob = [0.6, 0.4]
30
+ # transition = [[0.7, 0.3], [0.4, 0.6]]
31
+ # emission = [[0.1, 0.4, 0.5], [0.6, 0.3, 0.1]]
32
+ # hmm = Ai4r::Hmm::HiddenMarkovModel.new(
33
+ # states: states,
34
+ # observations: observations,
35
+ # start_prob: start_prob,
36
+ # transition_prob: transition,
37
+ # emission_prob: emission
38
+ # )
39
+ # hmm.eval([:walk, :shop, :clean])
40
+ # hmm.decode([:walk, :shop, :clean])
41
+ class HiddenMarkovModel
42
+ include Ai4r::Data::Parameterizable
43
+
44
+ parameters_info states: 'Array of hidden states',
45
+ observations: 'Array of observation symbols',
46
+ start_prob: 'Initial state probabilities',
47
+ transition_prob: 'State transition probability matrix',
48
+ emission_prob: 'Observation probability matrix'
49
+
50
+ def initialize(params = {})
51
+ @states = []
52
+ @observations = []
53
+ @start_prob = []
54
+ @transition_prob = []
55
+ @emission_prob = []
56
+ set_parameters(params) if params && !params.empty?
57
+ end
58
+
59
+ # Probability of the given observation sequence using the
60
+ # forward algorithm.
61
+ def eval(sequence)
62
+ forward(sequence).last.sum
63
+ end
64
+
65
+ # Return the most likely hidden state sequence for the given
66
+ # observations using the Viterbi algorithm.
67
+ def decode(sequence)
68
+ viterbi(sequence)
69
+ end
70
+
71
+ protected
72
+
73
+ def forward(sequence)
74
+ probs = []
75
+ sequence.each_with_index do |obs, t|
76
+ probs[t] = []
77
+ obs_index = @observations.index(obs)
78
+ if t.zero?
79
+ @states.each_index do |i|
80
+ probs[t][i] = @start_prob[i] * @emission_prob[i][obs_index]
81
+ end
82
+ else
83
+ @states.each_index do |j|
84
+ sum = 0.0
85
+ @states.each_index do |i|
86
+ sum += probs[t - 1][i] * @transition_prob[i][j]
87
+ end
88
+ probs[t][j] = sum * @emission_prob[j][obs_index]
89
+ end
90
+ end
91
+ end
92
+ probs
93
+ end
94
+
95
+ def viterbi(sequence)
96
+ v = []
97
+ bptr = []
98
+ sequence.each_with_index do |obs, t|
99
+ obs_index = @observations.index(obs)
100
+ v[t] = []
101
+ bptr[t] = []
102
+ if t.zero?
103
+ @states.each_index do |i|
104
+ v[t][i] = @start_prob[i] * @emission_prob[i][obs_index]
105
+ bptr[t][i] = 0
106
+ end
107
+ else
108
+ @states.each_index do |j|
109
+ max_prob = -Float::INFINITY
110
+ max_state = 0
111
+ @states.each_index do |i|
112
+ prob = v[t - 1][i] * @transition_prob[i][j]
113
+ if prob > max_prob
114
+ max_prob = prob
115
+ max_state = i
116
+ end
117
+ end
118
+ v[t][j] = max_prob * @emission_prob[j][obs_index]
119
+ bptr[t][j] = max_state
120
+ end
121
+ end
122
+ end
123
+ path = Array.new(sequence.length)
124
+ last_state = v.last.each_with_index.max[1]
125
+ path[-1] = @states[last_state]
126
+ (sequence.length - 1).downto(1) do |t|
127
+ last_state = bptr[t][last_state]
128
+ path[t - 1] = @states[last_state]
129
+ end
130
+ path
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Author:: Sergio Fierens
4
+ # License:: MPL 1.1
5
+ # Project:: ai4r
6
+ # Url:: https://github.com/SergioFierens/ai4r
7
+ #
8
+ # You can redistribute it and/or modify it under the terms of
9
+ # the Mozilla Public License version 1.1 as published by the
10
+ # Mozilla Foundation at http://www.mozilla.org/MPL/MPL-1.1.txt
11
+
12
+ module Ai4r
13
+ module NeuralNetwork
14
+ # Collection of common activation functions and their derivatives.
15
+ module ActivationFunctions
16
+ FUNCTIONS = {
17
+ sigmoid: ->(x) { 1.0 / (1.0 + Math.exp(-x)) },
18
+ tanh: ->(x) { Math.tanh(x) },
19
+ relu: ->(x) { [x, 0].max },
20
+
21
+ softmax: lambda do |arr|
22
+ max = arr.max
23
+ exps = arr.map { |v| Math.exp(v - max) }
24
+ sum = exps.inject(:+)
25
+ exps.map { |e| e / sum }
26
+ end
27
+ }.freeze
28
+
29
+ DERIVATIVES = {
30
+ sigmoid: ->(y) { y * (1 - y) },
31
+ tanh: ->(y) { 1.0 - (y**2) },
32
+ relu: ->(y) { y.positive? ? 1.0 : 0.0 },
33
+ softmax: ->(y) { y * (1 - y) }
34
+ }.freeze
35
+ end
36
+ end
37
+ end