machine_learning_workbench 0.1.2 → 0.2.0

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: 07202eab431a1fcddeb2f8c9cc669a3f63fa762d
4
- data.tar.gz: 64c17b81b9a8476e664a40fb64c1faec3e8fb807
3
+ metadata.gz: 8107a7a0b51fc50d183e08c6ef60e43bd96d4ea1
4
+ data.tar.gz: 82f7af0fd0938433d749696122e9ddc588f49aa0
5
5
  SHA512:
6
- metadata.gz: 24acec6a1948299718e10b93a8d22e983212af0bb255deb78e3e60ab7d118316b2c6561d59c23508098fe67b14d1edb6e539ab4b1e28d5e0fd44e85618ecc43d
7
- data.tar.gz: 4123e04bb2eb291fb71accfe9041cab5dd474b9a302d3e26a589dd34f96491d3cddeedbde50024bb7deca62cedbd9b5161d98b43dc2e016ab8bcf8ea03eb9316
6
+ metadata.gz: 07a342404b87066ab60217ad7e2bde8cbf89d23bb7d49b504a280ce1142456a70eb24b44b6f6c76b4a4f10dfddf219acd8c7f37e85e7f91548b24771752a798c
7
+ data.tar.gz: bc99aec41e7f9365d47068e69398462e04bcc8dd38dda090ba46e00d27a1e5485bf96a6d55f270c1ffcd6fe8d93223c52014d479634f8e0d48673ebe2eb00bb6
data/README.md CHANGED
@@ -48,3 +48,14 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERN
48
48
  ## License
49
49
 
50
50
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
51
+
52
+ ## References
53
+
54
+ Please feel free to contribute to this list (see `Contributing` above).
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.
60
+ - **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
+
@@ -0,0 +1,35 @@
1
+ require 'rmagick'
2
+ require 'machine_learning_workbench'
3
+ VectorQuantization = MachineLearningWorkbench::Compressor::VectorQuantization
4
+ Img = MachineLearningWorkbench::Tools::Imaging
5
+ Norm = MachineLearningWorkbench::Tools::Normalization
6
+
7
+ ncentrs = 1
8
+ image_files = Dir[ENV['HOME']+'/jaffe/KA.HA*.png']
9
+ raise "Download the JAFFE dataset in your home dir" if image_files&.empty?
10
+ # ... and convert the `.tiff` in `.png`: `mogrify -format png jaffe/*.tiff`
11
+ centr_range = [-1, 1]
12
+ orig_shape = [256, 256]
13
+ img_range = [0, 2**16-1]
14
+
15
+ puts "Loading images"
16
+ images = image_files.map do |fname|
17
+ nmat = Img.nmat_from_png fname, flat: true, dtype: :float64
18
+ ret = Norm.feature_scaling nmat, from: img_range, to: centr_range
19
+ end
20
+
21
+ puts "Initializing VQ"
22
+ vq = VectorQuantization.new ncentrs: ncentrs,
23
+ dims: images.first.shape, lrate: 0.3,
24
+ dtype: images.first.dtype, vrange: centr_range
25
+
26
+ puts "Training"
27
+ vq.train images, debug: true
28
+
29
+ puts "Done!"
30
+ begin
31
+ vq.centrs.map { |c| Img.display c, shape: orig_shape }
32
+ require 'pry'; binding.pry
33
+ ensure
34
+ MachineLearningWorkbench::Tools::Execution.kill_forks
35
+ end
@@ -0,0 +1,27 @@
1
+ module MachineLearningWorkbench::Compressor
2
+ # Online Vector Quantization: VQ with per-centroid decaying learning rates.
3
+ # Optimized for online training.
4
+ class OnlineVectorQuantization < VectorQuantization
5
+
6
+ attr_reader :min_lrate, :ntrains
7
+
8
+ def initialize min_lrate: 0.01, **opts
9
+ super **opts.merge({lrate: nil})
10
+ @min_lrate = min_lrate
11
+ @ntrains = [0]*ncentrs
12
+ end
13
+
14
+ # Decaying per-centroid learning rate.
15
+ # @param centr_idx [Integer] index of the centroid
16
+ # @param lower_bound [Float] minimum learning rate
17
+ def lrate centr_idx, lower_bound: min_lrate
18
+ [1/ntrains[centr_idx], lower_bound].max
19
+ end
20
+
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 }
25
+ end
26
+ end
27
+ end
@@ -1,4 +1,6 @@
1
1
  module MachineLearningWorkbench::Compressor
2
+
3
+ # Standard Vector Quantization
2
4
  class VectorQuantization
3
5
  attr_reader :ncentrs, :centrs, :dims, :vrange, :dtype, :lrate, :rng
4
6
  Verification = MachineLearningWorkbench::Tools::Verification
@@ -7,68 +9,92 @@ module MachineLearningWorkbench::Compressor
7
9
  @rng = Random.new rseed
8
10
  @ncentrs = ncentrs
9
11
  @dtype = dtype
10
- @dims = dims
12
+ @dims = Array(dims)
11
13
  @lrate = lrate
12
14
  @vrange = case vrange
13
- when Array
14
- raise ArgumentError, "vrange size not 2: #{vrange}" unless vrange.size == 2
15
- vrange.map &method(:Float)
16
- when Range
17
- [vrange.first, vrange.last].map &method(:Float)
18
- else
19
- raise ArgumentError, "vrange: unrecognized type: #{vrange.class}"
15
+ when Array
16
+ raise ArgumentError, "vrange size not 2: #{vrange}" unless vrange.size == 2
17
+ vrange.map &method(:Float)
18
+ when Range then [vrange.first, vrange.last].map &method(:Float)
19
+ else raise ArgumentError, "vrange: unrecognized type: #{vrange.class}"
20
20
  end
21
21
  @centrs = ncentrs.times.map { new_centr }
22
22
  end
23
23
 
24
24
  # Creates a new (random) centroid
25
25
  def new_centr
26
- NMatrix.new(dims, dtype: dtype) { rng.rand Range.new *vrange }
26
+ # TODO: this is too slow, find another way to use the rng
27
+ # NMatrix.new(dims, dtype: dtype) { rng.rand Range.new *vrange }
28
+ NMatrix.random dims, dtype: dtype
27
29
  end
28
30
 
29
- # Computes similarities between image and all centroids
30
- def similarities img
31
- raise NotImplementedError if img.shape.size > 1
32
- # centrs.map { |c| c.dot(img).first }
33
- require 'parallel'
34
- Parallel.map(centrs) { |c| c.dot(img).first }
31
+ # Computes similarities between vector and all centroids
32
+ def similarities vec
33
+ raise NotImplementedError if vec.shape.size > 1
34
+ centrs.map { |c| c.dot(vec).first }
35
+ # require 'parallel'
36
+ # Parallel.map(centrs) { |c| c.dot(vec).first }
35
37
  end
36
- # The list of similarities also constitutes the encoding of the image
37
- alias encode similarities
38
38
 
39
- # Returns index and similitude of most similar centroid to image
40
- def most_similar_centr img
41
- simils = similarities img
39
+ # Encode a vector
40
+ def encode vec, type: :most_similar
41
+ simils = similarities vec
42
+ case type
43
+ when :most_similar
44
+ simils.index simils.max
45
+ when :ensemble
46
+ simils
47
+ when :ensemble_norm
48
+ tot = simils.reduce(:+)
49
+ simils.map { |s| s/tot }
50
+ else raise ArgumentError, "unrecognized encode type: #{type}"
51
+ end
52
+ end
53
+
54
+ # Reconstruct vector from its code (encoding)
55
+ def reconstruction code, type: :most_similar
56
+ case type
57
+ when :most_similar
58
+ centrs[code]
59
+ when :ensemble
60
+ tot = code.reduce :+
61
+ centrs.zip(code).map { |centr, contr| centr*contr/tot }.reduce :+
62
+ when :ensemble_norm
63
+ centrs.zip(code).map { |centr, contr| centr*contr }.reduce :+
64
+ else raise ArgumentError, "unrecognized reconstruction type: #{type}"
65
+ end
66
+ end
67
+
68
+ # Returns index and similitude of most similar centroid to vector
69
+ def most_similar_centr vec
70
+ simils = similarities vec
42
71
  max_simil = simils.max
43
72
  max_idx = simils.index max_simil
44
73
  [max_idx, max_simil]
45
74
  end
46
75
 
47
- # Reconstruct image as its most similar centroid
48
- def reconstruction img
49
- centrs[most_similar_centr(img).first]
50
- end
51
-
52
- # Per-pixel errors in reconstructing image
53
- def reconstr_error img
54
- reconstruction(img) - img
76
+ # Per-pixel errors in reconstructing vector
77
+ def reconstr_error vec
78
+ reconstruction(vec) - vec
55
79
  end
56
80
 
57
- # Train on one image
58
- def train_one img, simils: nil
59
- trg_idx, _simil = simils || most_similar_centr(img)
60
- centrs[trg_idx] = centrs[trg_idx] * (1-lrate) + img * lrate
81
+ # Train on one vector
82
+ # @param vec [NMatrix]
83
+ # @return [Integer] index of trained centroid
84
+ def train_one vec, simils: nil
85
+ trg_idx, _simil = simils || most_similar_centr(vec)
86
+ centrs[trg_idx] = centrs[trg_idx] * (1-lrate) + vec * lrate
61
87
  Verification.in_range! centrs[trg_idx], vrange
62
- centrs[trg_idx]
88
+ trg_idx
63
89
  end
64
90
 
65
- # Train on image list
66
- def train img_lst, debug: false
91
+ # Train on vector list
92
+ def train vec_lst, debug: false
67
93
  # Two ways here:
68
- # - Batch: canonical, centrs updated with each img
94
+ # - Batch: canonical, centrs updated with each vec
69
95
  # - Parallel: could be parallel either on simils or on training (?)
70
96
  # Unsure on the correctness of either Parallel, let's stick with Batch
71
- img_lst.each { |img| train_one img; print '.' if debug }
97
+ vec_lst.each { |vec| train_one vec; print '.' if debug }
72
98
  end
73
99
  end
74
100
  end
@@ -2,7 +2,7 @@
2
2
  module MachineLearningWorkbench::Optimizer::NaturalEvolutionStrategies
3
3
  # Natural Evolution Strategies base class
4
4
  class Base
5
- attr_reader :ndims, :mu, :sigma, :opt_type, :obj_fn, :parallel_fit, :id, :rng, :last_fits, :best
5
+ attr_reader :ndims, :mu, :sigma, :opt_type, :obj_fn, :parallel_fit, :id, :rng, :last_fits, :best, :rescale_popsize, :rescale_lrate
6
6
 
7
7
  # NES object initialization
8
8
  # @param ndims [Integer] number of parameters to optimize
@@ -11,15 +11,16 @@ module MachineLearningWorkbench::Optimizer::NaturalEvolutionStrategies
11
11
  # @param rseed [Integer] allow for deterministic execution on rseed provided
12
12
  # @param mu_init [Numeric] values to initalize the distribution's mean
13
13
  # @param sigma_init [Numeric] values to initialize the distribution's covariance
14
- # @param parallel_fit [boolean] whether the `obj_fn` should be passed all the individuals
14
+ # @param parallel_fit [boolean] whether the `obj_fn` should be passed all the individuals
15
15
  # together. In the canonical case the fitness function always scores a single individual;
16
16
  # in practical cases though it is easier to delegate the scoring parallelization to the
17
17
  # external fitness function. Turning this to `true` will make the algorithm pass _an
18
18
  # Array_ of individuals to the fitness function, rather than a single instance.
19
- def initialize ndims, obj_fn, opt_type, rseed: nil, mu_init: 0, sigma_init: 1, parallel_fit: false
19
+ def initialize ndims, obj_fn, opt_type, rseed: nil, mu_init: 0, sigma_init: 1, parallel_fit: false, rescale_popsize: 1, rescale_lrate: 1
20
20
  raise ArgumentError unless [:min, :max].include? opt_type
21
21
  raise ArgumentError unless obj_fn.respond_to? :call
22
22
  @ndims, @opt_type, @obj_fn, @parallel_fit = ndims, opt_type, obj_fn, parallel_fit
23
+ @rescale_popsize, @rescale_lrate = rescale_popsize, rescale_lrate
23
24
  @id = NMatrix.identity(ndims, dtype: :float64)
24
25
  rseed ||= Random.new_seed
25
26
  # puts "NES rseed: #{s}" # currently disabled
@@ -40,11 +41,11 @@ module MachineLearningWorkbench::Optimizer::NaturalEvolutionStrategies
40
41
 
41
42
  # Memoized automatic magic numbers
42
43
  # NOTE: Doubling popsize and halving lrate often helps
43
- def utils; @utilities ||= cmaes_utilities end
44
+ def utils; @utilities ||= cmaes_utilities end
44
45
  # (see #utils)
45
- def popsize; @popsize ||= cmaes_popsize * 2 end
46
+ def popsize; @popsize ||= cmaes_popsize * rescale_popsize end
46
47
  # (see #utils)
47
- def lrate; @lrate ||= cmaes_lrate end
48
+ def lrate; @lrate ||= cmaes_lrate * rescale_lrate end
48
49
 
49
50
  # Magic numbers from CMA-ES (TODO: add proper citation)
50
51
  # @return [NMatrix] scale-invariant utilities
@@ -116,4 +117,4 @@ module MachineLearningWorkbench::Optimizer::NaturalEvolutionStrategies
116
117
  end
117
118
  end
118
119
  end
119
- end
120
+ end
@@ -4,7 +4,7 @@ module MachineLearningWorkbench::Tools
4
4
  from ||= nmat.minmax
5
5
  old_min, old_max = from
6
6
  new_min, new_max = to
7
- res = (nmat-old_min)*(new_max-new_min)/(old_max-old_min)+new_min
7
+ (nmat-old_min)*(new_max-new_min)/(old_max-old_min)+new_min
8
8
  end
9
9
 
10
10
  # @param per_column [bool] wheather to compute stats per-column or matrix-wise
@@ -16,7 +16,7 @@ module MachineLearningWorkbench::Tools
16
16
  stddevs.map! { |v| v.zero? ? 1 : v }
17
17
  mean_mat = means.repeat nmat.rows, 0
18
18
  stddev_mat = stddevs.repeat nmat.rows, 0
19
- ret = (nmat - mean_mat) / stddev_mat
19
+ (nmat - mean_mat) / stddev_mat
20
20
  end
21
21
  end
22
22
  end
@@ -5,32 +5,46 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "machine_learning_workbench"
7
7
  spec.version = `git describe`
8
- spec.authors = ["Giuseppe Cuccu"]
9
- spec.email = ["giuseppe.cuccu@gmail.com"]
8
+ spec.author = "Giuseppe Cuccu"
9
+ spec.email = "giuseppe.cuccu@gmail.com"
10
10
 
11
- spec.summary = %q{Workbench for practical machine learning in Ruby.}
12
- spec.description = %q{This workbench holds a collection of machine learning methods in Ruby. Rather than specializing on a single task or method, this gem aims at providing an encompassing framework for any machine learning application.}
11
+ spec.summary = %q[Workbench for practical machine learning in Ruby.]
12
+ spec.description = %q[\
13
+ This workbench holds a collection of machine learning
14
+ methods in Ruby. Rather than specializing on a single task or method, this
15
+ gem aims at providing an encompassing framework for any machine learning
16
+ application.].gsub(' ', '')
13
17
  spec.homepage = "https://github.com/giuse/machine_learning_workbench"
14
18
  spec.license = "MIT"
19
+ spec.post_install_message = %Q[\
20
+ Thanks for installing the machine learning workbench!
21
+ It is still a work in progress, feel free to open an issue or drop me an email
22
+ and start a discussion if you are using this gem. Cheers!
23
+ ].gsub(' ', '')
15
24
 
16
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
- f.match(%r{^(test|spec|features)/})
18
- end
19
- spec.bindir = "exe"
20
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
- spec.require_paths = ["lib"]
25
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.start_with? "spec" }
22
26
 
23
- spec.add_dependency "nmatrix-atlas", "~> 0.2"
27
+ # spec.bindir = "exe"
28
+ # spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
29
+ spec.require_paths = ["lib"]
30
+ spec.required_ruby_version = '>= 2.4.0'
24
31
 
32
+ # Install
25
33
  spec.add_development_dependency "bundler", "~> 1.16"
26
34
  spec.add_development_dependency "rake", "~> 10.0"
35
+
36
+ # Test
27
37
  spec.add_development_dependency "rspec", "~> 3.0"
38
+ spec.add_development_dependency "rmagick" # only used for one example
39
+
40
+ # Debug
28
41
  spec.add_development_dependency "pry", "~> 0.10"
29
42
  spec.add_development_dependency "pry-nav", "~> 0.2"
30
43
  spec.add_development_dependency "pry-rescue", "~> 1.4"
31
44
  spec.add_development_dependency "pry-stack_explorer", "~> 0.4"
32
45
 
33
- # currently under trial
34
- spec.add_development_dependency "rmagick"
35
- spec.add_development_dependency "parallel"
46
+ # Run
47
+ spec.requirements << "libatlas-base-dev" # library for following dependency
48
+ spec.add_dependency "nmatrix-atlas", "~> 0.2"
49
+ spec.add_dependency "parallel", "~> 1.12"
36
50
  end
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: machine_learning_workbench
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Giuseppe Cuccu
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2018-03-06 00:00:00.000000000 Z
11
+ date: 2018-03-14 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: nmatrix-atlas
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '0.2'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '0.2'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: bundler
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -66,6 +52,20 @@ dependencies:
66
52
  - - "~>"
67
53
  - !ruby/object:Gem::Version
68
54
  version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rmagick
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: pry
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -123,38 +123,40 @@ dependencies:
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0.4'
125
125
  - !ruby/object:Gem::Dependency
126
- name: rmagick
126
+ name: nmatrix-atlas
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
- - - ">="
129
+ - - "~>"
130
130
  - !ruby/object:Gem::Version
131
- version: '0'
132
- type: :development
131
+ version: '0.2'
132
+ type: :runtime
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
- - - ">="
136
+ - - "~>"
137
137
  - !ruby/object:Gem::Version
138
- version: '0'
138
+ version: '0.2'
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: parallel
141
141
  requirement: !ruby/object:Gem::Requirement
142
142
  requirements:
143
- - - ">="
143
+ - - "~>"
144
144
  - !ruby/object:Gem::Version
145
- version: '0'
146
- type: :development
145
+ version: '1.12'
146
+ type: :runtime
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
- - - ">="
150
+ - - "~>"
151
151
  - !ruby/object:Gem::Version
152
- version: '0'
153
- description: This workbench holds a collection of machine learning methods in Ruby.
154
- Rather than specializing on a single task or method, this gem aims at providing
155
- an encompassing framework for any machine learning application.
156
- email:
157
- - giuseppe.cuccu@gmail.com
152
+ version: '1.12'
153
+ description: |-
154
+ \
155
+ This workbench holds a collection of machine learning
156
+ methods in Ruby. Rather than specializing on a single task or method, this
157
+ gem aims at providing an encompassing framework for any machine learning
158
+ application.
159
+ email: giuseppe.cuccu@gmail.com
158
160
  executables: []
159
161
  extensions: []
160
162
  extra_rdoc_files: []
@@ -169,9 +171,11 @@ files:
169
171
  - Rakefile
170
172
  - bin/console
171
173
  - bin/setup
174
+ - examples/image_compression.rb
172
175
  - examples/neuroevolution.rb
173
176
  - lib/machine_learning_workbench.rb
174
177
  - lib/machine_learning_workbench/compressor.rb
178
+ - lib/machine_learning_workbench/compressor/online_vector_quantization.rb
175
179
  - lib/machine_learning_workbench/compressor/vector_quantization.rb
176
180
  - lib/machine_learning_workbench/monkey.rb
177
181
  - lib/machine_learning_workbench/neural_network.rb
@@ -195,7 +199,10 @@ homepage: https://github.com/giuse/machine_learning_workbench
195
199
  licenses:
196
200
  - MIT
197
201
  metadata: {}
198
- post_install_message:
202
+ post_install_message: |
203
+ Thanks for installing the machine learning workbench!
204
+ It is still a work in progress, feel free to open an issue or drop me an email
205
+ and start a discussion if you are using this gem. Cheers!
199
206
  rdoc_options: []
200
207
  require_paths:
201
208
  - lib
@@ -203,15 +210,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
203
210
  requirements:
204
211
  - - ">="
205
212
  - !ruby/object:Gem::Version
206
- version: '0'
213
+ version: 2.4.0
207
214
  required_rubygems_version: !ruby/object:Gem::Requirement
208
215
  requirements:
209
216
  - - ">="
210
217
  - !ruby/object:Gem::Version
211
218
  version: '0'
212
- requirements: []
219
+ requirements:
220
+ - libatlas-base-dev
213
221
  rubyforge_project:
214
- rubygems_version: 2.6.11
222
+ rubygems_version: 2.6.13
215
223
  signing_key:
216
224
  specification_version: 4
217
225
  summary: Workbench for practical machine learning in Ruby.