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 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