machine_learning_workbench 0.6.1 → 0.7.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 +4 -4
- data/.gitignore +1 -1
- data/Rakefile +2 -0
- data/examples/image_compression.rb +4 -0
- data/examples/neuroevolution.rb +4 -0
- data/lib/machine_learning_workbench.rb +1 -0
- data/lib/machine_learning_workbench/compressor.rb +3 -0
- data/lib/machine_learning_workbench/compressor/copy_vq.rb +2 -0
- data/lib/machine_learning_workbench/compressor/decaying_learning_rate_vq.rb +2 -0
- data/lib/machine_learning_workbench/compressor/incr_dict_vq.rb +45 -0
- data/lib/machine_learning_workbench/compressor/vector_quantization.rb +23 -4
- data/lib/machine_learning_workbench/monkey.rb +1 -0
- data/lib/machine_learning_workbench/neural_network.rb +2 -0
- data/lib/machine_learning_workbench/neural_network/base.rb +6 -5
- data/lib/machine_learning_workbench/neural_network/feed_forward.rb +1 -0
- data/lib/machine_learning_workbench/neural_network/recurrent.rb +1 -0
- data/lib/machine_learning_workbench/optimizer.rb +2 -0
- data/lib/machine_learning_workbench/optimizer/natural_evolution_strategies/base.rb +13 -6
- data/lib/machine_learning_workbench/optimizer/natural_evolution_strategies/bdnes.rb +28 -7
- data/lib/machine_learning_workbench/optimizer/natural_evolution_strategies/fnes.rb +1 -0
- data/lib/machine_learning_workbench/optimizer/natural_evolution_strategies/rnes.rb +1 -0
- data/lib/machine_learning_workbench/optimizer/natural_evolution_strategies/snes.rb +1 -0
- data/lib/machine_learning_workbench/optimizer/natural_evolution_strategies/xnes.rb +14 -7
- data/lib/machine_learning_workbench/systems.rb +2 -0
- data/lib/machine_learning_workbench/systems/neuroevolution.rb +1 -0
- data/lib/machine_learning_workbench/tools.rb +2 -0
- data/lib/machine_learning_workbench/tools/execution.rb +2 -0
- data/lib/machine_learning_workbench/tools/imaging.rb +2 -0
- data/lib/machine_learning_workbench/tools/normalization.rb +2 -0
- data/lib/machine_learning_workbench/tools/verification.rb +2 -0
- data/machine_learning_workbench.gemspec +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8897ba173dbfa944cf55b3ca7b57eb3af87bbff7
|
4
|
+
data.tar.gz: 44883310f216b187d5d3ccce669e85f946e6ee5f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 75d8a1f4d2087746dae316ca47c07925858826bdf393eddde7bb2f82e22b47e2d9c2c6bbaa6e0ce10fea1ccb0b9df1882e80b58519a2107f59732d6f99ea1a76
|
7
|
+
data.tar.gz: d4945335adc99edaabd26b56ac7bb49936d0642d58a09516ac63fae27e11d871db879e12db9cc3d0ec78788363c85c29558d382deb145713fa4051985e330f28
|
data/.gitignore
CHANGED
data/Rakefile
CHANGED
data/examples/neuroevolution.rb
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Run as: `bundle exec ruby examples/neuroevolution.rb`
|
4
|
+
|
1
5
|
# Make sure the gem is installed first with `gem install machine_learning_workbench`
|
2
6
|
# Alternatively, add `gem 'machine_learning_workbench'` to your Gemfile if using Bundle,
|
3
7
|
# followed by a `bundle install`
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MachineLearningWorkbench::Compressor
|
4
|
+
# Incremental Dictionary Train-less VQ, creating new centroids rather than training
|
5
|
+
# Optimized for online training.
|
6
|
+
# TODO: as the deadline grows nigh, the hacks grow foul. Refactor all VQs together.
|
7
|
+
class IncrDictVQ < VectorQuantization
|
8
|
+
|
9
|
+
attr_reader :equal_simil
|
10
|
+
undef :ntrains # centroids are not trained
|
11
|
+
|
12
|
+
def initialize **opts
|
13
|
+
puts "Ignoring learning rate: `lrate: #{opts[:lrate]}`" if opts[:lrate]
|
14
|
+
puts "Ignoring similarity: `simil_type: #{opts[:simil_type]}`" if opts[:simil_type]
|
15
|
+
puts "Ignoring ncentrs: `ncentrs: #{opts[:ncentrs]}`" if opts[:ncentrs]
|
16
|
+
# TODO: try different epsilons to reduce the number of states
|
17
|
+
# for example, in qbert we care what is lit and what is not, not the colors
|
18
|
+
@equal_simil = opts.delete(:equal_simil) || 0.0
|
19
|
+
super **opts.merge({ncentrs: 1, lrate: nil, simil_type: nil})
|
20
|
+
@ntrains = nil # will disable the counting
|
21
|
+
end
|
22
|
+
|
23
|
+
# Overloading lrate check from original VQ
|
24
|
+
def check_lrate lrate; nil; end
|
25
|
+
|
26
|
+
# Train on one vector:
|
27
|
+
# - train only if the image is not already in dictionary
|
28
|
+
# - create new centroid from the image
|
29
|
+
# @return [Integer] index of new centroid
|
30
|
+
def train_one vec, eps: equal_simil
|
31
|
+
mses = centrs.map do |centr|
|
32
|
+
((centr-vec)**2).sum / centr.size # uhm get rid of division maybe? squares?
|
33
|
+
end
|
34
|
+
min_mse = mses.min
|
35
|
+
# skip training if the centr with smallest mse (most similar) has less than eps error (equal)
|
36
|
+
# TODO: maintain an average somewhere, make eps dynamic
|
37
|
+
return if min_mse < eps
|
38
|
+
puts "Creating centr #{ncentrs} (min_mse: #{min_mse})"
|
39
|
+
centrs << vec
|
40
|
+
@utility = @utility.concatenate 0
|
41
|
+
@ncentrs.tap{ @ncentrs += 1}
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module MachineLearningWorkbench::Compressor
|
2
4
|
|
3
5
|
# Standard Vector Quantization
|
@@ -15,7 +17,7 @@ module MachineLearningWorkbench::Compressor
|
|
15
17
|
check_lrate lrate # hack: so that we can overload it in dlr_vq
|
16
18
|
@lrate = lrate
|
17
19
|
@simil_type = simil_type || :dot
|
18
|
-
@encoding_type = encoding_type || :
|
20
|
+
@encoding_type = encoding_type || :norm_ensemble
|
19
21
|
@init_centr_vrange ||= vrange
|
20
22
|
@vrange = case vrange
|
21
23
|
when Array
|
@@ -80,6 +82,12 @@ module MachineLearningWorkbench::Compressor
|
|
80
82
|
@ncodes += 1
|
81
83
|
@utility[code] += 1
|
82
84
|
code
|
85
|
+
when :most_similar_ary
|
86
|
+
code = simils.new_zeros
|
87
|
+
code[simils.max_index] = 1
|
88
|
+
@ncodes += 1
|
89
|
+
@utility += code
|
90
|
+
code
|
83
91
|
when :ensemble
|
84
92
|
code = simils
|
85
93
|
tot = simils.sum
|
@@ -88,10 +96,17 @@ module MachineLearningWorkbench::Compressor
|
|
88
96
|
@ncodes += 1
|
89
97
|
@utility += (contrib - utility) / ncodes # cumulative moving average
|
90
98
|
code
|
91
|
-
when :
|
99
|
+
when :norm_ensemble
|
92
100
|
tot = simils.sum
|
93
101
|
tot = 1 if tot < 1e-5 # HACK: avoid division by zero
|
94
102
|
code = simils / tot
|
103
|
+
@ncodes += 1
|
104
|
+
@utility += (code - utility) / ncodes # cumulative moving average
|
105
|
+
code
|
106
|
+
when :sparse_coding
|
107
|
+
raise NotImplementedError, "do this next"
|
108
|
+
|
109
|
+
|
95
110
|
@ncodes += 1
|
96
111
|
@utility += (code - utility) / ncodes # cumulative moving average
|
97
112
|
code
|
@@ -104,11 +119,15 @@ module MachineLearningWorkbench::Compressor
|
|
104
119
|
case type
|
105
120
|
when :most_similar
|
106
121
|
centrs[code]
|
122
|
+
when :most_similar_ary
|
123
|
+
centrs[code.eq(1).where[0]]
|
107
124
|
when :ensemble
|
108
125
|
tot = code.reduce :+
|
109
126
|
centrs.zip(code).map { |centr, contr| centr*contr/tot }.reduce :+
|
110
|
-
when :
|
127
|
+
when :norm_ensemble
|
111
128
|
centrs.zip(code).map { |centr, contr| centr*contr }.reduce :+
|
129
|
+
when :sparse_coding
|
130
|
+
raise NotImplementedError, "do this next"
|
112
131
|
else raise ArgumentError, "unrecognized reconstruction type: #{type}"
|
113
132
|
end
|
114
133
|
end
|
@@ -148,7 +167,7 @@ module MachineLearningWorkbench::Compressor
|
|
148
167
|
vec_lst.each_with_index do |vec, i|
|
149
168
|
trained_idx = train_one vec
|
150
169
|
print '.' if debug
|
151
|
-
@ntrains[trained_idx] += 1
|
170
|
+
@ntrains[trained_idx] += 1 if @ntrains
|
152
171
|
end
|
153
172
|
end
|
154
173
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
3
|
module MachineLearningWorkbench::NeuralNetwork
|
3
4
|
# Neural Network base class
|
@@ -26,10 +27,10 @@ module MachineLearningWorkbench::NeuralNetwork
|
|
26
27
|
|
27
28
|
# @param struct [Array<Integer>] list of layer sizes
|
28
29
|
# @param act_fn [Symbol] choice of activation function for the neurons
|
29
|
-
def initialize struct, act_fn: nil
|
30
|
+
def initialize struct, act_fn: nil, **act_fn_args
|
30
31
|
@struct = struct
|
31
32
|
@act_fn_name = act_fn || :sigmoid
|
32
|
-
@act_fn = send
|
33
|
+
@act_fn = send act_fn_name, **act_fn_args
|
33
34
|
# @state holds both inputs, possibly recurrency, and bias
|
34
35
|
# it is a complete input for the next layer, hence size from layer sizes
|
35
36
|
@state = layer_row_sizes.collect do |size|
|
@@ -163,10 +164,10 @@ module MachineLearningWorkbench::NeuralNetwork
|
|
163
164
|
## Activation functions
|
164
165
|
|
165
166
|
# Traditional sigmoid (logistic) with variable steepness
|
166
|
-
def sigmoid
|
167
|
-
#
|
167
|
+
def sigmoid steepness: 1
|
168
|
+
# steepness: 0<s<1 is flatter, 1<s is flatter
|
168
169
|
# flatter makes activation less sensitive, better with large number of inputs
|
169
|
-
-> (vec) { 1.0 / (NMath.exp(-
|
170
|
+
-> (vec) { 1.0 / (NMath.exp(-steepness * vec) + 1.0) }
|
170
171
|
end
|
171
172
|
alias logistic sigmoid
|
172
173
|
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
3
|
module MachineLearningWorkbench::Optimizer::NaturalEvolutionStrategies
|
3
4
|
# Natural Evolution Strategies base class
|
@@ -19,11 +20,15 @@ module MachineLearningWorkbench::Optimizer::NaturalEvolutionStrategies
|
|
19
20
|
# a single instance.
|
20
21
|
# @param rescale_popsize [Float] scaling for the default population size
|
21
22
|
# @param rescale_lrate [Float] scaling for the default learning rate
|
22
|
-
def initialize ndims, obj_fn, opt_type, rseed: nil, mu_init: 0, sigma_init: 1, parallel_fit: false, rescale_popsize: 1, rescale_lrate: 1
|
23
|
-
raise ArgumentError unless [:min, :max].include? opt_type
|
24
|
-
raise ArgumentError unless obj_fn.respond_to? :call
|
23
|
+
def initialize ndims, obj_fn, opt_type, rseed: nil, mu_init: 0, sigma_init: 1, parallel_fit: false, rescale_popsize: 1, rescale_lrate: 1, utilities: nil, popsize: nil, lrate: nil
|
24
|
+
raise ArgumentError, "opt_type: #{opt_type}" unless [:min, :max].include? opt_type
|
25
|
+
raise ArgumentError, "obj_fn not callable: #{obj_fn}" unless obj_fn.respond_to? :call
|
26
|
+
raise ArgumentError, "utilities only if popsize" if utilities && popsize.nil?
|
27
|
+
raise ArgumentError, "wrong sizes" if utilities && utilities.size != popsize
|
28
|
+
raise ArgumentError, "minimum popsize 5 for default utilities" if popsize&.<(5) && utilities.nil?
|
25
29
|
@ndims, @opt_type, @obj_fn, @parallel_fit = ndims, opt_type, obj_fn, parallel_fit
|
26
|
-
@rescale_popsize, @rescale_lrate = rescale_popsize, rescale_lrate
|
30
|
+
@rescale_popsize, @rescale_lrate = rescale_popsize, rescale_lrate # rescale defaults
|
31
|
+
@utilities, @popsize, @lrate = utilities, popsize, lrate # if not set, defaults below
|
27
32
|
@eye = NArray.eye(ndims)
|
28
33
|
rseed ||= Random.new_seed
|
29
34
|
# puts "NES rseed: #{s}" # currently disabled
|
@@ -44,18 +49,20 @@ module MachineLearningWorkbench::Optimizer::NaturalEvolutionStrategies
|
|
44
49
|
end
|
45
50
|
|
46
51
|
# Memoized automatic magic numbers
|
52
|
+
# Initialization options allow to rescale or entirely override these.
|
47
53
|
# NOTE: Doubling popsize and halving lrate often helps
|
48
54
|
def utils; @utilities ||= cmaes_utilities end
|
49
55
|
# (see #utils)
|
50
|
-
def popsize; @popsize ||= cmaes_popsize * rescale_popsize end
|
56
|
+
def popsize; @popsize ||= Integer(cmaes_popsize * rescale_popsize) end
|
51
57
|
# (see #utils)
|
52
58
|
def lrate; @lrate ||= cmaes_lrate * rescale_lrate end
|
53
59
|
|
54
|
-
# Magic numbers from CMA-ES (
|
60
|
+
# Magic numbers from CMA-ES (see `README` for citation)
|
55
61
|
# @return [NArray] scale-invariant utilities
|
56
62
|
def cmaes_utilities
|
57
63
|
# Algorithm equations are meant for fitness maximization
|
58
64
|
# Match utilities with individuals sorted by INCREASING fitness
|
65
|
+
raise ArgumentError, "Minimum `popsize` should be 5 (is #{popsize})" if popsize < 5
|
59
66
|
log_range = (1..popsize).collect do |v|
|
60
67
|
[0, Math.log(popsize.to_f/2 - 1) - Math.log(v)].max
|
61
68
|
end
|
@@ -1,11 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
3
|
module MachineLearningWorkbench::Optimizer::NaturalEvolutionStrategies
|
3
4
|
# Block-Diagonal Natural Evolution Strategies
|
4
5
|
class BDNES < Base
|
5
6
|
|
6
|
-
MAX_RSEED = 10**Random.new_seed.size # same range as Random.new_seed
|
7
|
+
MAX_RSEED = 10**Random.new_seed.size # block random seeds to be on the same range as `Random.new_seed`
|
7
8
|
|
8
|
-
attr_reader :ndims_lst, :blocks, :popsize
|
9
|
+
attr_reader :ndims_lst, :blocks, :popsize, :parallel_update
|
10
|
+
undef :ndims # only `ndims_lst` here
|
9
11
|
|
10
12
|
# Initialize a list of XNES, one for each block
|
11
13
|
# see class `Base` for the description of the rest of the arguments.
|
@@ -13,7 +15,8 @@ module MachineLearningWorkbench::Optimizer::NaturalEvolutionStrategies
|
|
13
15
|
# matrix. Note: entire (reconstructed) individuals will be passed to the `obj_fn`
|
14
16
|
# regardless of the division here described.
|
15
17
|
# @param init_opts [Hash] the rest of the options will be passed directly to XNES
|
16
|
-
|
18
|
+
# @parellel_update [bool] whether to parallelize block updates
|
19
|
+
def initialize ndims_lst, obj_fn, opt_type, parallel_fit: false, rseed: nil, parallel_update: false, **init_opts
|
17
20
|
# mu_init: 0, sigma_init: 1
|
18
21
|
# init_opts = {rseed: rseed, mu_init: mu_init, sigma_init: sigma_init}
|
19
22
|
# TODO: accept list of `mu_init`s and `sigma_init`s
|
@@ -21,9 +24,8 @@ module MachineLearningWorkbench::Optimizer::NaturalEvolutionStrategies
|
|
21
24
|
block_fit = -> (*args) { raise "Should never be called" }
|
22
25
|
# the BD-NES seed should ensure deterministic reproducibility
|
23
26
|
# but each block should have a different seed
|
24
|
-
rseed ||= Random.new_seed
|
25
27
|
# puts "BD-NES rseed: #{s}" # currently disabled
|
26
|
-
@rng = Random.new rseed
|
28
|
+
@rng = Random.new rseed || Random.new_seed
|
27
29
|
@blocks = ndims_lst.map do |ndims|
|
28
30
|
b_rseed = rng.rand MAX_RSEED
|
29
31
|
XNES.new ndims, block_fit, opt_type, rseed: b_rseed, **init_opts
|
@@ -34,6 +36,8 @@ module MachineLearningWorkbench::Optimizer::NaturalEvolutionStrategies
|
|
34
36
|
|
35
37
|
@best = [(opt_type==:max ? -1 : 1) * Float::INFINITY, nil]
|
36
38
|
@last_fits = []
|
39
|
+
@parallel_update = parallel_update
|
40
|
+
require 'parallel' if parallel_update
|
37
41
|
end
|
38
42
|
|
39
43
|
def sorted_inds_lst
|
@@ -82,9 +86,22 @@ module MachineLearningWorkbench::Optimizer::NaturalEvolutionStrategies
|
|
82
86
|
|
83
87
|
# duck-type the interface: [:train, :mu, :convergence, :save, :load]
|
84
88
|
|
89
|
+
# TODO: refactor DRY
|
85
90
|
def train picks: sorted_inds_lst
|
86
|
-
|
87
|
-
|
91
|
+
if parallel_update
|
92
|
+
# Parallel.each(blocks.zip(picks)) do |xnes, s_inds|
|
93
|
+
# xnes.train picks: s_inds
|
94
|
+
# end
|
95
|
+
# Actually it's not this simple.
|
96
|
+
# Forks do not act on the parent, so I need to send back updated mu and sigma
|
97
|
+
# Luckily we have `NES#save` and `NES#load` at the ready
|
98
|
+
# Next: need to implement `#marshal_dump` and `#marshal_load` in `Base`
|
99
|
+
# Actually using `Cumo` rather than `Parallel` may avoid marshaling altogether
|
100
|
+
raise NotImplementedError, "Should dump and load each instance"
|
101
|
+
else
|
102
|
+
blocks.zip(picks).each do |xnes, s_inds|
|
103
|
+
xnes.train picks: s_inds
|
104
|
+
end
|
88
105
|
end
|
89
106
|
end
|
90
107
|
|
@@ -92,6 +109,10 @@ module MachineLearningWorkbench::Optimizer::NaturalEvolutionStrategies
|
|
92
109
|
blocks.map(&:mu).reduce { |mem, var| mem.concatenate var, axis: 1 }
|
93
110
|
end
|
94
111
|
|
112
|
+
def sigma
|
113
|
+
raise NotImplementedError, "need to write a concatenation like for mu here"
|
114
|
+
end
|
115
|
+
|
95
116
|
def convergence
|
96
117
|
blocks.map(&:convergence).reduce(:+)
|
97
118
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
3
|
module MachineLearningWorkbench::Optimizer::NaturalEvolutionStrategies
|
3
4
|
# Exponential Natural Evolution Strategies
|
@@ -11,17 +12,23 @@ module MachineLearningWorkbench::Optimizer::NaturalEvolutionStrategies
|
|
11
12
|
NArray[mu_init]
|
12
13
|
when Numeric
|
13
14
|
NArray.new([1,ndims]).fill mu_init
|
15
|
+
when NArray
|
16
|
+
raise ArgumentError unless mu_init.size == ndims
|
17
|
+
mu_init.ndim < 2 ? mu_init.reshape(1, ndims) : mu_init
|
14
18
|
else
|
15
19
|
raise ArgumentError, "Something is wrong with mu_init: #{mu_init}"
|
16
20
|
end
|
17
21
|
@sigma = case sigma_init
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
when Array
|
23
|
+
raise ArgumentError unless sigma_init.size == ndims
|
24
|
+
NArray[*sigma_init].diag
|
25
|
+
when Numeric
|
26
|
+
NArray.new([ndims]).fill(sigma_init).diag
|
27
|
+
when NArray
|
28
|
+
raise ArgumentError unless sigma_init.size == ndims**2
|
29
|
+
sigma_init.ndim < 2 ? sigma_init.reshape(ndims, ndims) : sigma_init
|
30
|
+
else
|
31
|
+
raise ArgumentError, "Something is wrong with sigma_init: #{sigma_init}"
|
25
32
|
end
|
26
33
|
# Works with the log of sigma to avoid continuous decompositions (thanks Sun Yi)
|
27
34
|
@log_sigma = NMath.log(sigma.diagonal).diag
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: machine_learning_workbench
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Giuseppe Cuccu
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-04-
|
11
|
+
date: 2018-04-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -204,6 +204,7 @@ files:
|
|
204
204
|
- lib/machine_learning_workbench/compressor.rb
|
205
205
|
- lib/machine_learning_workbench/compressor/copy_vq.rb
|
206
206
|
- lib/machine_learning_workbench/compressor/decaying_learning_rate_vq.rb
|
207
|
+
- lib/machine_learning_workbench/compressor/incr_dict_vq.rb
|
207
208
|
- lib/machine_learning_workbench/compressor/vector_quantization.rb
|
208
209
|
- lib/machine_learning_workbench/monkey.rb
|
209
210
|
- lib/machine_learning_workbench/neural_network.rb
|