machine_learning_workbench 0.2.1 → 0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f393f2183c3371081f694e47e35a14cf93997098
4
- data.tar.gz: 754c861e440af0a40a5e328dfdde143a5e1bff59
3
+ metadata.gz: 1a0550319ef523cd49f7c09b635a4e21508cf730
4
+ data.tar.gz: b3fb9a716bfac1850bc5af0c8abf96f17f0292b6
5
5
  SHA512:
6
- metadata.gz: 9ed7f6be2d1ed63dd00f26dc8d4b4e47c3ece23c80192bc877c4acaa1c03e1f37a34a64e32b7c6f4af2993439492b39e20f17110f50a14add762345685485fff
7
- data.tar.gz: e6d116d1a8011da42a24ad3b10209e3764cb3195e6f3149659b9d91a637a029b0ffe9b50f47f7cbed4fd9970ceb7a112884b54f0c566561b9121c434e1455d4c
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/[USERNAME]/machine_learning_workbench.
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 Strategies, from the homonymous paper _"Block Diagonal Natural Evolution Strategies" (PPSN2012)_. Check [here](https://exascale.info/members/giuseppe-cuccu/) for citation reference and pdf.
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
-
@@ -1 +1,2 @@
1
1
  require_relative 'compressor/vector_quantization'
2
+ require_relative 'compressor/online_vector_quantization'
@@ -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, :ntrains
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
- # Train on one image
22
- # @return [Integer] index of trained centroid
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 then [vrange.first, vrange.last].map &method(:Float)
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, simils: nil
85
- trg_idx, _simil = simils || most_similar_centr(vec)
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.each { |vec| train_one vec; print '.' if debug }
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 = layer_shapes.collect do |shape|
130
- NMatrix.new(shape, dtype: dtype) { weights_iter.next }
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
- # Execute block in a fork. Be sure to check also `#kill_forks`
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
- # Call this in an `ensure` block after using `in_fork`
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 'KILL', pid }
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: [300, 300], shape: nil, in_fork: true
25
- img = nmat_to_img(nmat, shape: shape).resize(*disp_size)
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.2.1
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-14 00:00:00.000000000 Z
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