machine_learning_workbench 0.2.1 → 0.3
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/README.md +6 -6
- data/lib/machine_learning_workbench/compressor.rb +1 -0
- data/lib/machine_learning_workbench/compressor/online_vector_quantization.rb +8 -6
- data/lib/machine_learning_workbench/compressor/vector_quantization.rb +25 -7
- data/lib/machine_learning_workbench/monkey.rb +32 -1
- data/lib/machine_learning_workbench/neural_network/base.rb +11 -3
- data/lib/machine_learning_workbench/optimizer.rb +3 -0
- data/lib/machine_learning_workbench/optimizer/natural_evolution_strategies/fnes.rb +11 -0
- data/lib/machine_learning_workbench/optimizer/natural_evolution_strategies/rnes.rb +38 -0
- data/lib/machine_learning_workbench/tools/execution.rb +8 -3
- data/lib/machine_learning_workbench/tools/imaging.rb +5 -4
- data/lib/machine_learning_workbench/tools/verification.rb +9 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1a0550319ef523cd49f7c09b635a4e21508cf730
|
4
|
+
data.tar.gz: b3fb9a716bfac1850bc5af0c8abf96f17f0292b6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 51b05034a2fffcc135388c5760b14067728d4fcf7210ff47d4a7f58f66fb174d3940fd538e3551a78d9df24a0c14b01e0f277aba2ceb898d7206302f8c30721b
|
7
|
+
data.tar.gz: 0dd1cf85fdb8577278882fe197e90032b1061a11fec9176b8d64ef38b97140876059ec964199007f52840eaab13ca2a25b2b01b8d0935c5f55c2ffc518f59124
|
data/README.md
CHANGED
@@ -43,7 +43,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
43
43
|
|
44
44
|
## Contributing
|
45
45
|
|
46
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
46
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/giuse/machine_learning_workbench.
|
47
47
|
|
48
48
|
## License
|
49
49
|
|
@@ -53,9 +53,9 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
53
53
|
|
54
54
|
Please feel free to contribute to this list (see `Contributing` above).
|
55
55
|
|
56
|
-
- NES stands for Natural Evolution Strategies. Check its [Wikipedia page](https://en.wikipedia.org/wiki/Natural_evolution_strategy) for more info.
|
57
|
-
- CMA-ES stands for Covariance Matrix Adaptation Evolution Strategy. Check its [Wikipedia page](https://en.wikipedia.org/wiki/CMA-ES) for more info.
|
58
|
-
- UL-ELR stands for Unsupervised Learning plus Evolutionary Reinforcement Learning, from the paper _"Intrinsically Motivated Neuroevolution for Vision-Based Reinforcement Learning" (ICDL2011)_. Check [here](https://exascale.info/members/giuseppe-cuccu/) for citation reference and pdf.
|
59
|
-
- BD-NES stands for Block Diagonal Natural Evolution
|
56
|
+
- **NES** stands for Natural Evolution Strategies. Check its [Wikipedia page](https://en.wikipedia.org/wiki/Natural_evolution_strategy) for more info.
|
57
|
+
- **CMA-ES** stands for Covariance Matrix Adaptation Evolution Strategy. Check its [Wikipedia page](https://en.wikipedia.org/wiki/CMA-ES) for more info.
|
58
|
+
- **UL-ELR** stands for Unsupervised Learning plus Evolutionary Reinforcement Learning, from the paper _"Intrinsically Motivated Neuroevolution for Vision-Based Reinforcement Learning" (ICDL2011)_. Check [here](https://exascale.info/members/giuseppe-cuccu/) for citation reference and pdf.
|
59
|
+
- **BD-NES** stands for Block Diagonal Natural Evolution Strategy, from the homonymous paper _"Block Diagonal Natural Evolution Strategies" (PPSN2012)_. Check [here](https://exascale.info/members/giuseppe-cuccu/) for citation reference and pdf.
|
60
|
+
- **RNES** stands for Radial Natural Evolution Strategy, from the paper _"Novelty-Based Restarts for Evolution Strategies" (CEC2011)_. Check [here](https://exascale.info/members/giuseppe-cuccu/) for citation reference and pdf.
|
60
61
|
- **Online VQ** stands for Online Vector Quantization, from the paper _"Intrinsically Motivated Neuroevolution for Vision-Based Reinforcement Learning" (ICDL2011)_. Check [here](https://exascale.info/members/giuseppe-cuccu/) for citation reference and pdf.
|
61
|
-
|
@@ -3,25 +3,27 @@ module MachineLearningWorkbench::Compressor
|
|
3
3
|
# Optimized for online training.
|
4
4
|
class OnlineVectorQuantization < VectorQuantization
|
5
5
|
|
6
|
-
attr_reader :min_lrate
|
6
|
+
attr_reader :min_lrate
|
7
7
|
|
8
8
|
def initialize min_lrate: 0.01, **opts
|
9
9
|
super **opts.merge({lrate: nil})
|
10
10
|
@min_lrate = min_lrate
|
11
|
-
@ntrains = [0]*ncentrs
|
12
11
|
end
|
13
12
|
|
13
|
+
# Overloading lrate check from original VQ
|
14
|
+
def check_lrate lrate; nil; end
|
15
|
+
|
14
16
|
# Decaying per-centroid learning rate.
|
15
17
|
# @param centr_idx [Integer] index of the centroid
|
16
18
|
# @param lower_bound [Float] minimum learning rate
|
19
|
+
# @note nicely overloads the `attr_reader` of parent class
|
17
20
|
def lrate centr_idx, lower_bound: min_lrate
|
18
21
|
[1/ntrains[centr_idx], lower_bound].max
|
19
22
|
end
|
20
23
|
|
21
|
-
|
22
|
-
|
23
|
-
def train_one *args, **opts
|
24
|
-
super.tap { |trg_idx| ntrains[trg_idx] += 1 }
|
24
|
+
def train_one *args, **kwargs
|
25
|
+
raise NotImplementedError, "Remember to overload this using the new lrate(idx)"
|
25
26
|
end
|
27
|
+
|
26
28
|
end
|
27
29
|
end
|
@@ -2,7 +2,7 @@ module MachineLearningWorkbench::Compressor
|
|
2
2
|
|
3
3
|
# Standard Vector Quantization
|
4
4
|
class VectorQuantization
|
5
|
-
attr_reader :ncentrs, :centrs, :dims, :vrange, :dtype, :lrate, :rng
|
5
|
+
attr_reader :ncentrs, :centrs, :dims, :vrange, :dtype, :lrate, :rng, :ntrains
|
6
6
|
Verification = MachineLearningWorkbench::Tools::Verification
|
7
7
|
|
8
8
|
def initialize ncentrs:, dims:, vrange:, dtype:, lrate:, rseed: Random.new_seed
|
@@ -10,15 +10,24 @@ module MachineLearningWorkbench::Compressor
|
|
10
10
|
@ncentrs = ncentrs
|
11
11
|
@dtype = dtype
|
12
12
|
@dims = Array(dims)
|
13
|
+
check_lrate lrate # hack: so that we can overload it in online_vq
|
13
14
|
@lrate = lrate
|
14
15
|
@vrange = case vrange
|
15
16
|
when Array
|
16
17
|
raise ArgumentError, "vrange size not 2: #{vrange}" unless vrange.size == 2
|
17
18
|
vrange.map &method(:Float)
|
18
|
-
when Range
|
19
|
+
when Range
|
20
|
+
[vrange.first, vrange.last].map &method(:Float)
|
19
21
|
else raise ArgumentError, "vrange: unrecognized type: #{vrange.class}"
|
20
22
|
end
|
21
23
|
@centrs = ncentrs.times.map { new_centr }
|
24
|
+
@ntrains = [0]*ncentrs # useful to understand what happens
|
25
|
+
end
|
26
|
+
|
27
|
+
# Verify lrate to be present and withing unit bounds
|
28
|
+
# As a separate method only so it can be overloaded in online_vq
|
29
|
+
def check_lrate lrate
|
30
|
+
raise ArgumentError, "Pass a `lrate` between 0 and 1" unless lrate&.between?(0,1)
|
22
31
|
end
|
23
32
|
|
24
33
|
# Creates a new (random) centroid
|
@@ -66,6 +75,8 @@ module MachineLearningWorkbench::Compressor
|
|
66
75
|
end
|
67
76
|
|
68
77
|
# Returns index and similitude of most similar centroid to vector
|
78
|
+
# @return [Array<Integer, Float>] the index of the most similar centroid,
|
79
|
+
# followed by the corresponding similarity
|
69
80
|
def most_similar_centr vec
|
70
81
|
simils = similarities vec
|
71
82
|
max_simil = simils.max
|
@@ -74,17 +85,20 @@ module MachineLearningWorkbench::Compressor
|
|
74
85
|
end
|
75
86
|
|
76
87
|
# Per-pixel errors in reconstructing vector
|
88
|
+
# @return [NMatrix] residuals
|
77
89
|
def reconstr_error vec
|
78
90
|
reconstruction(vec) - vec
|
79
91
|
end
|
80
92
|
|
81
93
|
# Train on one vector
|
82
|
-
# @param vec [NMatrix]
|
83
94
|
# @return [Integer] index of trained centroid
|
84
|
-
def train_one vec
|
85
|
-
|
95
|
+
def train_one vec
|
96
|
+
|
97
|
+
trg_idx, _simil = most_similar_centr(vec)
|
98
|
+
# note: uhm that actually looks like a dot product... optimizable?
|
99
|
+
# `[c[i], vec].dot([1-lrate, lrate])`
|
86
100
|
centrs[trg_idx] = centrs[trg_idx] * (1-lrate) + vec * lrate
|
87
|
-
Verification.in_range! centrs[trg_idx], vrange
|
101
|
+
# Verification.in_range! centrs[trg_idx], vrange # I verified it's not needed
|
88
102
|
trg_idx
|
89
103
|
end
|
90
104
|
|
@@ -94,7 +108,11 @@ module MachineLearningWorkbench::Compressor
|
|
94
108
|
# - Batch: canonical, centrs updated with each vec
|
95
109
|
# - Parallel: could be parallel either on simils or on training (?)
|
96
110
|
# Unsure on the correctness of either Parallel, let's stick with Batch
|
97
|
-
vec_lst.
|
111
|
+
vec_lst.each_with_index do |vec, i|
|
112
|
+
trained_idx = train_one vec
|
113
|
+
print '.' if debug
|
114
|
+
ntrains[trained_idx] += 1
|
115
|
+
end
|
98
116
|
end
|
99
117
|
end
|
100
118
|
end
|
@@ -145,9 +145,29 @@ module MachineLearningWorkbench::Monkey
|
|
145
145
|
end
|
146
146
|
|
147
147
|
|
148
|
+
# The NMatrix documentation refers to a function `#nrm2` (aliased to `#norm2`)
|
149
|
+
# to compute the norm of a matrix. Fun fact: that is the implementation for vectors,
|
150
|
+
# and calling it on a matrix returns NotImplementedError :) you have to toggle the
|
151
|
+
# source to understand why:
|
152
|
+
# http://sciruby.com/nmatrix/docs/NMatrix.html#method-i-norm2 .
|
153
|
+
# A search for the actual source on GitHub reveals a (I guess new?) method
|
154
|
+
# `#matrix_norm`, with a decent choice of norms to choose from. Unfortunately, as the
|
155
|
+
# name says, it is stuck to compute full-matrix norms.
|
156
|
+
# So I resigned to dance to `Array`s and back, and implemented it with `#each_rank`.
|
157
|
+
# Unexplicably, I get a list of constant values as the return value; same with
|
158
|
+
# `#each_row`.
|
159
|
+
# What can I say, we're back to referencing rows by index. I am just wasting too much
|
160
|
+
# time figuring out these details to write a generalized version with an optional
|
161
|
+
# `dimension` to go along.
|
162
|
+
# @return [NMatrix] the vector norm along the rows
|
163
|
+
def row_norms
|
164
|
+
norms = rows.times.map { |i| row(i).norm2 }
|
165
|
+
NMatrix.new [rows, 1], norms, dtype: dtype
|
166
|
+
end
|
167
|
+
|
148
168
|
# `NMatrix#to_a` has inconsistent behavior: single-row matrices are
|
149
169
|
# converted to one-dimensional Arrays rather than a 2D Array with
|
150
|
-
# only one row. Patching `#to_a` directly is not feasible as the
|
170
|
+
# only one row. Patching `#to_a` directly is not feasible as the
|
151
171
|
# constructor seems to depend on it, and I have little interest in
|
152
172
|
# investigating further.
|
153
173
|
# @return [Array<Array>] a consistent array representation, such that
|
@@ -187,6 +207,16 @@ module MachineLearningWorkbench::Monkey
|
|
187
207
|
end
|
188
208
|
end
|
189
209
|
end
|
210
|
+
|
211
|
+
module CPtrDumpable
|
212
|
+
def marshall_dump
|
213
|
+
[shape, dtype, data_pointer]
|
214
|
+
end
|
215
|
+
|
216
|
+
def marshall_load
|
217
|
+
raise NotImplementedError, "There's no setter for the data pointer!"
|
218
|
+
end
|
219
|
+
end
|
190
220
|
end
|
191
221
|
|
192
222
|
Array.include MachineLearningWorkbench::Monkey::Dimensionable
|
@@ -195,3 +225,4 @@ require 'nmatrix/lapack_plugin' # loads whichever is installed between atlas and
|
|
195
225
|
NMatrix.include MachineLearningWorkbench::Monkey::AdvancelyOperationable
|
196
226
|
Numeric.include MachineLearningWorkbench::Monkey::NumericallyApproximatable
|
197
227
|
NMatrix.include MachineLearningWorkbench::Monkey::MatrixApproximatable
|
228
|
+
NMatrix.include MachineLearningWorkbench::Monkey::CPtrDumpable
|
@@ -119,15 +119,18 @@ module MachineLearningWorkbench::NeuralNetwork
|
|
119
119
|
end
|
120
120
|
|
121
121
|
# Loads a plain list of weights into the weight matrices (one per layer).
|
122
|
-
# Preserves order.
|
122
|
+
# Preserves order. Reuses allocated memory if available.
|
123
123
|
# @input weights [Array<Float>] weights to load
|
124
124
|
# @return [true] always true. If something's wrong it simply fails, and if
|
125
125
|
# all goes well there's nothing to return but a confirmation to the caller.
|
126
126
|
def load_weights weights
|
127
127
|
raise ArgumentError unless weights.size == nweights
|
128
128
|
weights_iter = weights.each
|
129
|
-
@layers
|
130
|
-
|
129
|
+
@layers ||= layer_shapes.collect { |shape| NMatrix.new shape, dtype: dtype }
|
130
|
+
layers.each do |nmat|
|
131
|
+
nmat.each_with_indices do |_val, *idxs|
|
132
|
+
nmat[*idxs] = weights_iter.next
|
133
|
+
end
|
131
134
|
end
|
132
135
|
reset_state
|
133
136
|
return true
|
@@ -200,6 +203,11 @@ module MachineLearningWorkbench::NeuralNetwork
|
|
200
203
|
lambda { |x| 1.7159 * Math.tanh(2.0*x/3.0) + 1e-3*x }
|
201
204
|
end
|
202
205
|
|
206
|
+
# Rectified Linear Unit (ReLU)
|
207
|
+
def relu
|
208
|
+
lambda { |x| x>0 && x || 0 }
|
209
|
+
end
|
210
|
+
|
203
211
|
|
204
212
|
# @!method interface_methods
|
205
213
|
# Declaring interface methods - implement in child class!
|
@@ -4,4 +4,7 @@ end
|
|
4
4
|
require_relative 'optimizer/natural_evolution_strategies/base'
|
5
5
|
require_relative 'optimizer/natural_evolution_strategies/xnes'
|
6
6
|
require_relative 'optimizer/natural_evolution_strategies/snes'
|
7
|
+
require_relative 'optimizer/natural_evolution_strategies/rnes'
|
8
|
+
# FIX SPECS FIRST
|
9
|
+
# require_relative 'optimizer/natural_evolution_strategies/fnes'
|
7
10
|
require_relative 'optimizer/natural_evolution_strategies/bdnes'
|
@@ -0,0 +1,11 @@
|
|
1
|
+
|
2
|
+
module MachineLearningWorkbench::Optimizer::NaturalEvolutionStrategies
|
3
|
+
# Fixed Variance Natural Evolution Strategies
|
4
|
+
class FNES < RNES
|
5
|
+
|
6
|
+
def train picks: sorted_inds
|
7
|
+
g_mu = utils.dot(picks)
|
8
|
+
@mu += sigma.dot(g_mu.transpose).transpose * lrate
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
|
2
|
+
module MachineLearningWorkbench::Optimizer::NaturalEvolutionStrategies
|
3
|
+
# Radial Natural Evolution Strategies
|
4
|
+
class RNES < Base
|
5
|
+
attr_reader :variance
|
6
|
+
|
7
|
+
def initialize_distribution mu_init: 0, sigma_init: 1
|
8
|
+
@mu = NMatrix.new([1, ndims], mu_init, dtype: dtype)
|
9
|
+
raise ArgumentError unless sigma_init.kind_of? Numeric
|
10
|
+
@variance = sigma_init
|
11
|
+
@sigma = id * variance
|
12
|
+
end
|
13
|
+
|
14
|
+
def train picks: sorted_inds
|
15
|
+
g_mu = utils.dot(picks)
|
16
|
+
g_sigma = utils.dot(picks.row_norms**2 - ndims).first # back to scalar
|
17
|
+
@mu += sigma.dot(g_mu.transpose).transpose * lrate
|
18
|
+
@variance *= Math.exp(g_sigma * lrate / 2)
|
19
|
+
@sigma = id * variance
|
20
|
+
end
|
21
|
+
|
22
|
+
# Estimate algorithm convergence based on variance
|
23
|
+
def convergence
|
24
|
+
variance
|
25
|
+
end
|
26
|
+
|
27
|
+
def save
|
28
|
+
[mu.to_consistent_a, variance]
|
29
|
+
end
|
30
|
+
|
31
|
+
def load data
|
32
|
+
raise ArgumentError unless data.size == 2
|
33
|
+
mu_ary, @variance = data
|
34
|
+
@mu = NMatrix[*mu_ary, dtype: dtype]
|
35
|
+
@sigma = id * variance
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -2,7 +2,9 @@ module MachineLearningWorkbench::Tools
|
|
2
2
|
module Execution
|
3
3
|
$fork_pids ||= []
|
4
4
|
|
5
|
-
#
|
5
|
+
# Executes block in a (detached) fork, saving the `pid` for later termination.
|
6
|
+
# @note add `ensure MachineLearningWorkbench::Tools.kill_forks` to the block
|
7
|
+
# where `in_fork` is called (see `#kill_forks`).
|
6
8
|
def self.in_fork &block
|
7
9
|
raise ArgumentError "Need block to be executed in fork" unless block
|
8
10
|
pid = fork(&block)
|
@@ -10,9 +12,12 @@ module MachineLearningWorkbench::Tools
|
|
10
12
|
$fork_pids << pid
|
11
13
|
end
|
12
14
|
|
13
|
-
#
|
15
|
+
# Kills processes spawned by `#in_fork`.
|
16
|
+
# Call this in an `ensure` block after using `in_fork`.
|
17
|
+
# => `ensure MachineLearningWorkbench::Tools.kill_forks`
|
14
18
|
def self.kill_forks
|
15
|
-
$fork_pids&.each { |pid| Process.kill
|
19
|
+
$fork_pids&.each { |pid| Process.kill('KILL', pid) rescue Errno::ESRCH }
|
20
|
+
$fork_pids = []
|
16
21
|
end
|
17
22
|
end
|
18
23
|
end
|
@@ -18,11 +18,12 @@ module MachineLearningWorkbench::Tools
|
|
18
18
|
end
|
19
19
|
|
20
20
|
# Show a NMatrix as image in a RMagick window
|
21
|
-
# @param disp_size the size of the image to display
|
22
|
-
# @param shape the true shape of the image (NMatrix could be flattened)
|
21
|
+
# @param disp_size [Array] the size of the image to display
|
22
|
+
# @param shape [Array] the true shape of the image (NMatrix could be flattened)
|
23
23
|
# @param in_fork [bool] whether to execute the display in fork (and continue running)
|
24
|
-
def self.display nmat, disp_size:
|
25
|
-
img = nmat_to_img
|
24
|
+
def self.display nmat, disp_size: nil, shape: nil, in_fork: true
|
25
|
+
img = nmat_to_img nmat, shape: shape
|
26
|
+
img.resize!(*disp_size, Magick::TriangleFilter,0.51) if disp_size
|
26
27
|
if in_fork
|
27
28
|
MachineLearningWorkbench::Tools::Execution.in_fork { img.display }
|
28
29
|
else
|
@@ -1,6 +1,15 @@
|
|
1
1
|
module MachineLearningWorkbench::Tools
|
2
2
|
module Verification
|
3
3
|
def self.in_range! nmat, vrange
|
4
|
+
# Raise if values not in range
|
5
|
+
vmin, vmax = vrange.to_a
|
6
|
+
nmat.each_with_indices do |v, *idxs|
|
7
|
+
raise "Value not in range" unless v&.between? vmin, vmax
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# Fix if values not in range
|
12
|
+
def self.in_range nmat, vrange
|
4
13
|
vmin, vmax = vrange.to_a
|
5
14
|
nmat.each_with_indices do |v, *idxs|
|
6
15
|
nmat[*idxs] = vmin if v < vmin
|
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.3'
|
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-03-
|
11
|
+
date: 2018-03-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -185,6 +185,8 @@ files:
|
|
185
185
|
- lib/machine_learning_workbench/optimizer.rb
|
186
186
|
- lib/machine_learning_workbench/optimizer/natural_evolution_strategies/base.rb
|
187
187
|
- lib/machine_learning_workbench/optimizer/natural_evolution_strategies/bdnes.rb
|
188
|
+
- lib/machine_learning_workbench/optimizer/natural_evolution_strategies/fnes.rb
|
189
|
+
- lib/machine_learning_workbench/optimizer/natural_evolution_strategies/rnes.rb
|
188
190
|
- lib/machine_learning_workbench/optimizer/natural_evolution_strategies/snes.rb
|
189
191
|
- lib/machine_learning_workbench/optimizer/natural_evolution_strategies/xnes.rb
|
190
192
|
- lib/machine_learning_workbench/systems.rb
|