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.
- checksums.yaml +7 -0
- data/README.md +174 -0
- data/examples/classifiers/hyperpipes_data.csv +14 -0
- data/examples/classifiers/hyperpipes_example.rb +22 -0
- data/examples/classifiers/ib1_example.rb +12 -0
- data/examples/classifiers/id3_example.rb +15 -10
- data/examples/classifiers/id3_graphviz_example.rb +17 -0
- data/examples/classifiers/logistic_regression_example.rb +11 -0
- data/examples/classifiers/naive_bayes_attributes_example.rb +13 -0
- data/examples/classifiers/naive_bayes_example.rb +12 -13
- data/examples/classifiers/one_r_example.rb +27 -0
- data/examples/classifiers/parameter_tutorial.rb +29 -0
- data/examples/classifiers/prism_nominal_example.rb +15 -0
- data/examples/classifiers/prism_numeric_example.rb +21 -0
- data/examples/classifiers/simple_linear_regression_example.rb +14 -11
- data/examples/classifiers/zero_and_one_r_example.rb +34 -0
- data/examples/classifiers/zero_one_r_data.csv +8 -0
- data/examples/clusterers/clusterer_example.rb +40 -34
- data/examples/clusterers/dbscan_example.rb +17 -0
- data/examples/clusterers/dendrogram_example.rb +17 -0
- data/examples/clusterers/hierarchical_dendrogram_example.rb +20 -0
- data/examples/clusterers/kmeans_custom_example.rb +26 -0
- data/examples/genetic_algorithm/bitstring_example.rb +41 -0
- data/examples/genetic_algorithm/genetic_algorithm_example.rb +26 -18
- data/examples/genetic_algorithm/kmeans_seed_tuning.rb +45 -0
- data/examples/neural_network/backpropagation_example.rb +48 -48
- data/examples/neural_network/hopfield_example.rb +45 -0
- data/examples/neural_network/patterns_with_base_noise.rb +39 -39
- data/examples/neural_network/patterns_with_noise.rb +41 -39
- data/examples/neural_network/train_epochs_callback.rb +25 -0
- data/examples/neural_network/training_patterns.rb +39 -39
- data/examples/neural_network/transformer_text_classification.rb +78 -0
- data/examples/neural_network/xor_example.rb +23 -22
- data/examples/reinforcement/q_learning_example.rb +10 -0
- data/examples/som/som_data.rb +155 -152
- data/examples/som/som_multi_node_example.rb +12 -13
- data/examples/som/som_single_example.rb +12 -15
- data/examples/transformer/decode_classifier_example.rb +68 -0
- data/examples/transformer/deterministic_example.rb +10 -0
- data/examples/transformer/seq2seq_example.rb +16 -0
- data/lib/ai4r/classifiers/classifier.rb +24 -16
- data/lib/ai4r/classifiers/gradient_boosting.rb +64 -0
- data/lib/ai4r/classifiers/hyperpipes.rb +119 -43
- data/lib/ai4r/classifiers/ib1.rb +122 -32
- data/lib/ai4r/classifiers/id3.rb +524 -145
- data/lib/ai4r/classifiers/logistic_regression.rb +96 -0
- data/lib/ai4r/classifiers/multilayer_perceptron.rb +75 -59
- data/lib/ai4r/classifiers/naive_bayes.rb +95 -34
- data/lib/ai4r/classifiers/one_r.rb +112 -44
- data/lib/ai4r/classifiers/prism.rb +167 -76
- data/lib/ai4r/classifiers/random_forest.rb +72 -0
- data/lib/ai4r/classifiers/simple_linear_regression.rb +83 -58
- data/lib/ai4r/classifiers/support_vector_machine.rb +91 -0
- data/lib/ai4r/classifiers/votes.rb +57 -0
- data/lib/ai4r/classifiers/zero_r.rb +71 -30
- data/lib/ai4r/clusterers/average_linkage.rb +46 -27
- data/lib/ai4r/clusterers/bisecting_k_means.rb +50 -44
- data/lib/ai4r/clusterers/centroid_linkage.rb +52 -36
- data/lib/ai4r/clusterers/cluster_tree.rb +50 -0
- data/lib/ai4r/clusterers/clusterer.rb +29 -14
- data/lib/ai4r/clusterers/complete_linkage.rb +42 -31
- data/lib/ai4r/clusterers/dbscan.rb +134 -0
- data/lib/ai4r/clusterers/diana.rb +75 -49
- data/lib/ai4r/clusterers/k_means.rb +270 -135
- data/lib/ai4r/clusterers/median_linkage.rb +49 -33
- data/lib/ai4r/clusterers/single_linkage.rb +196 -88
- data/lib/ai4r/clusterers/ward_linkage.rb +51 -35
- data/lib/ai4r/clusterers/ward_linkage_hierarchical.rb +25 -10
- data/lib/ai4r/clusterers/weighted_average_linkage.rb +48 -32
- data/lib/ai4r/data/data_set.rb +223 -103
- data/lib/ai4r/data/parameterizable.rb +31 -25
- data/lib/ai4r/data/proximity.rb +62 -62
- data/lib/ai4r/data/statistics.rb +46 -35
- data/lib/ai4r/experiment/classifier_evaluator.rb +84 -32
- data/lib/ai4r/experiment/split.rb +39 -0
- data/lib/ai4r/genetic_algorithm/chromosome_base.rb +43 -0
- data/lib/ai4r/genetic_algorithm/genetic_algorithm.rb +92 -170
- data/lib/ai4r/genetic_algorithm/tsp_chromosome.rb +83 -0
- data/lib/ai4r/hmm/hidden_markov_model.rb +134 -0
- data/lib/ai4r/neural_network/activation_functions.rb +37 -0
- data/lib/ai4r/neural_network/backpropagation.rb +399 -134
- data/lib/ai4r/neural_network/hopfield.rb +175 -58
- data/lib/ai4r/neural_network/transformer.rb +194 -0
- data/lib/ai4r/neural_network/weight_initializations.rb +40 -0
- data/lib/ai4r/reinforcement/policy_iteration.rb +66 -0
- data/lib/ai4r/reinforcement/q_learning.rb +51 -0
- data/lib/ai4r/search/a_star.rb +76 -0
- data/lib/ai4r/search/bfs.rb +50 -0
- data/lib/ai4r/search/dfs.rb +50 -0
- data/lib/ai4r/search/mcts.rb +118 -0
- data/lib/ai4r/search.rb +12 -0
- data/lib/ai4r/som/distance_metrics.rb +29 -0
- data/lib/ai4r/som/layer.rb +28 -17
- data/lib/ai4r/som/node.rb +61 -32
- data/lib/ai4r/som/som.rb +158 -41
- data/lib/ai4r/som/two_phase_layer.rb +21 -25
- data/lib/ai4r/version.rb +3 -0
- data/lib/ai4r.rb +57 -28
- metadata +79 -109
- data/README.rdoc +0 -39
- data/test/classifiers/hyperpipes_test.rb +0 -84
- data/test/classifiers/ib1_test.rb +0 -78
- data/test/classifiers/id3_test.rb +0 -220
- data/test/classifiers/multilayer_perceptron_test.rb +0 -79
- data/test/classifiers/naive_bayes_test.rb +0 -43
- data/test/classifiers/one_r_test.rb +0 -62
- data/test/classifiers/prism_test.rb +0 -85
- data/test/classifiers/simple_linear_regression_test.rb +0 -37
- data/test/classifiers/zero_r_test.rb +0 -50
- data/test/clusterers/average_linkage_test.rb +0 -51
- data/test/clusterers/bisecting_k_means_test.rb +0 -66
- data/test/clusterers/centroid_linkage_test.rb +0 -53
- data/test/clusterers/complete_linkage_test.rb +0 -57
- data/test/clusterers/diana_test.rb +0 -69
- data/test/clusterers/k_means_test.rb +0 -167
- data/test/clusterers/median_linkage_test.rb +0 -53
- data/test/clusterers/single_linkage_test.rb +0 -122
- data/test/clusterers/ward_linkage_hierarchical_test.rb +0 -81
- data/test/clusterers/ward_linkage_test.rb +0 -53
- data/test/clusterers/weighted_average_linkage_test.rb +0 -53
- data/test/data/data_set_test.rb +0 -104
- data/test/data/proximity_test.rb +0 -87
- data/test/data/statistics_test.rb +0 -65
- data/test/experiment/classifier_evaluator_test.rb +0 -76
- data/test/genetic_algorithm/chromosome_test.rb +0 -57
- data/test/genetic_algorithm/genetic_algorithm_test.rb +0 -81
- data/test/neural_network/backpropagation_test.rb +0 -82
- data/test/neural_network/hopfield_test.rb +0 -72
- 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::
|
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
|
-
#
|
12
|
-
#
|
13
|
-
#
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
60
|
-
|
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
|
-
|
87
|
+
best
|
64
88
|
end
|
65
89
|
|
66
|
-
|
90
|
+
# @return [Object]
|
67
91
|
def generate_initial_population
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
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
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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
|
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 <<
|
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
|
-
|
155
|
+
@chromosome_class.mutate(individual, @mutation_rate)
|
125
156
|
end
|
126
|
-
|
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
|
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
|
-
|
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
|