machine_learning_workbench 0.7.0 → 0.8.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: 8897ba173dbfa944cf55b3ca7b57eb3af87bbff7
4
- data.tar.gz: 44883310f216b187d5d3ccce669e85f946e6ee5f
3
+ metadata.gz: 1f580e6defa79dfb0c801b9e57c336b23fdea7e7
4
+ data.tar.gz: 0e4f17edef5773bdc78f38184028a916e14bb52b
5
5
  SHA512:
6
- metadata.gz: 75d8a1f4d2087746dae316ca47c07925858826bdf393eddde7bb2f82e22b47e2d9c2c6bbaa6e0ce10fea1ccb0b9df1882e80b58519a2107f59732d6f99ea1a76
7
- data.tar.gz: d4945335adc99edaabd26b56ac7bb49936d0642d58a09516ac63fae27e11d871db879e12db9cc3d0ec78788363c85c29558d382deb145713fa4051985e330f28
6
+ metadata.gz: c4810984620a1e19717465bafde01c908b3803cf872e49a326b6ee63788692d40b87623e053bd7e4c0563067dfd46f1117c4aebb6aca723f75dd6fef76857642
7
+ data.tar.gz: 7b93ca2e63b4d9f69aeb77efc552b8b94aeb2c52576041a41e30a1dc0cb924e78019cd00b6b5edffa49bac7c14a1d9ade9c1c0f1c6bfab0bc0ae1bf014df4ee5
@@ -41,7 +41,9 @@ module MachineLearningWorkbench::Compressor
41
41
  @next_train += 1
42
42
  # require 'pry'; binding.pry if next_train == ncentrs
43
43
  puts "Overwriting centr #{next_train}"
44
- centrs[trg_idx] = vec
44
+ # norm_vec = vec / NLinalg.norm(vec)
45
+ # centrs[trg_idx, true] = norm_vec
46
+ centrs[trg_idx, true] = vec
45
47
  trg_idx
46
48
  end
47
49
 
@@ -29,9 +29,12 @@ module MachineLearningWorkbench::Compressor
29
29
 
30
30
  # Train on one vector
31
31
  # @return [Integer] index of trained centroid
32
- def train_one vec
32
+ def train_one vec, eps: nil
33
+ # NOTE: ignores epsilon if passed
33
34
  trg_idx, _simil = most_similar_centr(vec)
34
- centrs[trg_idx] = centrs[trg_idx] * (1-lrate(trg_idx)) + vec * lrate(trg_idx)
35
+ # norm_vec = vec / NLinalg.norm(vec)
36
+ # centrs[trg_idx, true] = centrs[trg_idx, true] * (1-lrate(trg_idx)) + norm_vec * lrate(trg_idx)
37
+ centrs[trg_idx, true] = centrs[trg_idx, true] * (1-lrate(trg_idx)) + vec * lrate(trg_idx)
35
38
  trg_idx
36
39
  end
37
40
 
@@ -11,12 +11,13 @@ module MachineLearningWorkbench::Compressor
11
11
 
12
12
  def initialize **opts
13
13
  puts "Ignoring learning rate: `lrate: #{opts[:lrate]}`" if opts[:lrate]
14
- puts "Ignoring similarity: `simil_type: #{opts[:simil_type]}`" if opts[:simil_type]
14
+ puts "Ignoring similarity: `simil_type: #{opts[:simil_type]}`" unless opts[:simil_type] == :dot
15
15
  puts "Ignoring ncentrs: `ncentrs: #{opts[:ncentrs]}`" if opts[:ncentrs]
16
16
  # TODO: try different epsilons to reduce the number of states
17
17
  # for example, in qbert we care what is lit and what is not, not the colors
18
18
  @equal_simil = opts.delete(:equal_simil) || 0.0
19
- super **opts.merge({ncentrs: 1, lrate: nil, simil_type: nil})
19
+ super **opts.merge({ncentrs: 1, lrate: nil, simil_type: :dot})
20
+
20
21
  @ntrains = nil # will disable the counting
21
22
  end
22
23
 
@@ -28,17 +29,23 @@ module MachineLearningWorkbench::Compressor
28
29
  # - create new centroid from the image
29
30
  # @return [Integer] index of new centroid
30
31
  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}
32
+ # NOTE: novelty needs to be re-computed for each image, as after each
33
+ # training the novelty signal changes!
34
+
35
+ # NOTE the reconstruction error here depends once more on the _color_
36
+ # this is wrong and should be taken out of the equation
37
+ # NOTE: this is fixed if I use the differences sparse coding method
38
+ residual_img = reconstr_error(vec)
39
+ rec_err = residual_img.mean
40
+ return -1 if rec_err < eps
41
+ puts "Creating centr #{ncentrs} (rec_err: #{rec_err})"
42
+ # norm_vec = vec / NLinalg.norm(vec)
43
+ # @centrs = centrs.concatenate norm_vec
44
+ # @centrs = centrs.concatenate vec
45
+ @centrs = centrs.concatenate residual_img
46
+ # HACK: make it more general by using `code_size`
47
+ @utility = @utility.concatenate [0] * (encoding_type == :sparse_coding_v1 ? 2 : 1)
48
+ ncentrs
42
49
  end
43
50
 
44
51
  end
@@ -4,7 +4,7 @@ module MachineLearningWorkbench::Compressor
4
4
 
5
5
  # Standard Vector Quantization
6
6
  class VectorQuantization
7
- attr_reader :ncentrs, :centrs, :dims, :vrange, :init_centr_vrange, :lrate,
7
+ attr_reader :centrs, :dims, :vrange, :init_centr_vrange, :lrate,
8
8
  :simil_type, :encoding_type, :rng, :ntrains, :utility, :ncodes
9
9
  attr_writer :utility, :ncodes # allows access from outside
10
10
 
@@ -12,12 +12,11 @@ module MachineLearningWorkbench::Compressor
12
12
 
13
13
  @rng = Random.new rseed # TODO: RNG CURRENTLY NOT USED!!
14
14
 
15
- @ncentrs = ncentrs
16
15
  @dims = Array(dims)
17
16
  check_lrate lrate # hack: so that we can overload it in dlr_vq
18
17
  @lrate = lrate
19
- @simil_type = simil_type || :dot
20
- @encoding_type = encoding_type || :norm_ensemble
18
+ @simil_type = simil_type || raise("missing simil_type")
19
+ @encoding_type = encoding_type || raise("missing encoding_type")
21
20
  @init_centr_vrange ||= vrange
22
21
  @vrange = case vrange
23
22
  when Array
@@ -27,12 +26,21 @@ module MachineLearningWorkbench::Compressor
27
26
  [vrange.first, vrange.last].map &method(:Float)
28
27
  else raise ArgumentError, "vrange: unrecognized type: #{vrange.class}"
29
28
  end
30
- init_centrs
31
- @ntrains = [0]*ncentrs # useful to understand what happens
32
- @utility = NArray.zeros [ncentrs] # trace how 'useful' are centroids to encodings
29
+ init_centrs nc: ncentrs
30
+ @ntrains = [0]*ncentrs # per-centroid number of trainings
31
+ @utility = NArray.zeros [code_size] # trace how 'useful' are centroids to encodings
33
32
  @ncodes = 0
34
33
  end
35
34
 
35
+ def ncentrs
36
+ @centrs.shape.first
37
+ end
38
+
39
+ # HACKKETY HACKKETY HACK (can't wait to refactor after the deadline)
40
+ def code_size
41
+ encoding_type == :sparse_coding_v1 ? 2*ncentrs : ncentrs
42
+ end
43
+
36
44
  # Verify lrate to be present and withing unit bounds
37
45
  # As a separate method only so it can be overloaded in `DecayingLearningRateVQ`
38
46
  def check_lrate lrate
@@ -41,7 +49,7 @@ module MachineLearningWorkbench::Compressor
41
49
 
42
50
  # Initializes a list of centroids
43
51
  def init_centrs nc: ncentrs, base: nil, proport: nil
44
- @centrs = nc.times.map { new_centr base, proport }
52
+ @centrs = nc.times.map { new_centr base, proport }.to_na
45
53
  end
46
54
 
47
55
  # Creates a new (random) centroid
@@ -56,39 +64,42 @@ module MachineLearningWorkbench::Compressor
56
64
  ret
57
65
  end
58
66
 
59
- SIMIL = {
60
- dot: -> (centr, vec) { centr.dot(vec) },
61
- mse: -> (centr, vec) { -((centr-vec)**2).sum / centr.size }
62
- }
67
+ # SIMIL = {
68
+ # dot: -> (centr, vec) { centr.dot(vec) },
69
+ # mse: -> (centr, vec) { -((centr-vec)**2).sum / centr.size }
70
+ # }
63
71
 
64
72
  # Computes similarities between vector and all centroids
65
73
  def similarities vec, type: simil_type
66
74
  raise NotImplementedError if vec.shape.size > 1
67
- simil_fn = SIMIL[type] || raise(ArgumentError, "Unrecognized simil #{type}")
68
- NArray[*centrs.map { |centr| simil_fn.call centr, vec }]
69
- # require 'parallel'
70
- # NArray[*Parallel.map(centrs) { |c| c.dot(vec).first }]
75
+ raise "need to check since centrs is a NArray now" if type == :mse
76
+ # simil_fn = SIMIL[type] || raise(ArgumentError, "Unrecognized simil #{type}")
77
+ # centrs.map { |centr| simil_fn.call centr, vec }
78
+ centrs.dot vec
71
79
  end
72
80
 
73
81
  # Encode a vector
74
82
  # tracks utility of centroids based on how much they contribute to encoding
75
83
  # TODO: `encode = Encodings.const_get(type)` in initialize`
76
84
  # NOTE: hashes of lambdas or modules cannot access ncodes and utility
85
+ # TODO: refactor anyway through `stats` object, this thing is getting out of hand
77
86
  def encode vec, type: encoding_type
78
- simils = similarities vec
79
87
  case type
80
88
  when :most_similar
89
+ simils = similarities vec
81
90
  code = simils.max_index
82
91
  @ncodes += 1
83
92
  @utility[code] += 1
84
93
  code
85
94
  when :most_similar_ary
95
+ simils = similarities vec
86
96
  code = simils.new_zeros
87
97
  code[simils.max_index] = 1
88
98
  @ncodes += 1
89
99
  @utility += code
90
100
  code
91
101
  when :ensemble
102
+ simils = similarities vec
92
103
  code = simils
93
104
  tot = simils.sum
94
105
  tot = 1 if tot < 1e-5 # HACK: avoid division by zero
@@ -97,16 +108,140 @@ module MachineLearningWorkbench::Compressor
97
108
  @utility += (contrib - utility) / ncodes # cumulative moving average
98
109
  code
99
110
  when :norm_ensemble
111
+ simils = similarities vec
100
112
  tot = simils.sum
113
+ # NOTE this actually makes a big discontinuity if the total is equal to zero.
114
+ # Does that even ever happen? I guess only w/ reset img (zeros) as lone centroid.
115
+ # Which after first gen is really useless and should just be dropped anyway...
101
116
  tot = 1 if tot < 1e-5 # HACK: avoid division by zero
102
117
  code = simils / tot
118
+ @ncodes += 1
119
+ @utility += (code - utility) / ncodes # cumulative moving average
120
+ code
121
+ when :sparse_coding_v1
122
+ raise "requires centroids normalized to unit length!"
123
+ @encoder = nil if @encoder&.shape&.first != centrs.shape.first
124
+ # Danafar & Cuccu: compact form linear regression encoder
125
+ @encoder ||= (centrs.dot centrs.transpose).invert.dot centrs
126
+
127
+ raw_code = @encoder.dot(vec)
128
+ # separate positive and negative features (NOTE: all features will be positive)
129
+ # i.e. split[0...n] = max {0, raw[i]}; split[n...2*n] = max {0, -raw[i]}
130
+ # TODO: cite Coates & Ng
131
+ # TODO: optimize and remove redundant variables
132
+ split_code = raw_code.concatenate(-raw_code)
133
+ split_code[split_code<0] = 0
134
+ # normalize such that the code sums to 1
135
+ norm_code = split_code / split_code.sum
136
+ # Danafar: drop to say 80% of info (à la pca)
137
+ thold = 0.2
138
+ sparse_code = norm_code.dup
139
+ sum = 0
140
+ # NOTE: the last element in the sort below has the highest contribution and
141
+ # should NEVER be put to 0, even if it could contribute alone to 100% of the
142
+ # total
143
+ # NOTE: upon further study I disagree this represent information content unless
144
+ # the centroids are unit vectors. So I'm commenting this implementation now,
145
+ # together with the following, until I implement a switch to normalize the
146
+ # centroids based on configuration.
147
+
148
+
149
+
150
+ # BUG IN NARRAY SORT!! ruby-numo/numo-narray#97
151
+ # norm_code.sort_index[0...-1].each do |idx|
152
+ norm_code.size.times.sort_by { |i| norm_code[i] }[0...-1].each do |idx|
153
+
154
+
155
+
156
+ sparse_code[idx] = 0
157
+ sum += norm_code[idx]
158
+ break if sum >= thold # we know the code's total is normalized to 1 and has no negatives
159
+ end
160
+ code = sparse_code / sparse_code.sum # re-normalize sum to 1
161
+
162
+ @ncodes += 1
163
+ @utility += (code - utility) / ncodes # cumulative moving average
164
+ code
165
+ when :sparse_coding_v2
166
+ # Cuccu & Danafar: incremental reconstruction encoding
167
+ # turns out to be closely related to (Orthogonal) Matching Pursuit
168
+ raise "requires centroids normalized to unit length!"
169
+ # return centrs.dot vec # speed test for the rest of the system
170
+ sparse_code = NArray.zeros code_size
171
+ resid = vec
172
+ # cap the number of non-zero elements in the code
173
+ max_nonzero = [1,ncentrs/3].max
174
+ max_nonzero.times do |i|
175
+ # OPT: remove msc from centrs at each loop
176
+ # the algorithm should work even without this opt because
177
+ # we are working on the residuals each time
178
+ simils = centrs.dot resid
179
+
180
+
181
+
182
+ # BUG IN NARRAY SORT!! ruby-numo/numo-narray#97
183
+ # msc = simils.max_index
184
+ simils = simils.to_a
185
+ simils_abs = simils.map &:abs
186
+ msc = simils_abs.index simils_abs.max # most similar centroid
187
+
188
+
189
+
190
+ max_simil = simils[msc]
191
+ # remember to distinguish here to use the pos/neg features trick
192
+ sparse_code[msc] = max_simil
193
+ reconstr = max_simil * centrs[msc, true]
194
+ resid -= reconstr
195
+ # puts "resid#{i} #{resid.abs.mean}" # if debug
196
+ epsilon = 0.005
197
+ # print resid.abs.mean, ' '
198
+ # print sparse_code.to_a, ' '
199
+ break if resid.abs.mean <= epsilon
200
+ end
201
+
202
+ # should normalize sum to 1?
203
+ code = sparse_code #/ sparse_code.sum # normalize sum to 1
204
+
103
205
  @ncodes += 1
104
206
  @utility += (code - utility) / ncodes # cumulative moving average
105
207
  code
106
208
  when :sparse_coding
107
- raise NotImplementedError, "do this next"
209
+ # Cuccu: Direct residual encoding
210
+ # return centrs.dot vec # speed test for the rest of the system
211
+ sparse_code = NArray.zeros code_size
212
+ resid = vec
213
+ # cap the number of non-zero elements in the code
214
+ max_nonzero = [1,ncentrs/3].max
215
+ max_nonzero.times do |i|
216
+ # OPT: remove msc from centrs at each loop
217
+ # the algorithm should work even without this opt because
218
+ # we are working on the residuals each time
219
+ diff = (centrs - resid).abs.sum(1)
220
+
108
221
 
109
222
 
223
+ # BUG IN NARRAY SORT!! ruby-numo/numo-narray#97
224
+ # msc = diff.max_index
225
+ diff = diff.to_a
226
+ msc = diff.index diff.min # most similar centroid
227
+
228
+
229
+
230
+ min_diff = diff[msc]
231
+ # remember to distinguish here to use the pos/neg features trick
232
+ sparse_code[msc] = 1
233
+ reconstr = centrs[msc, true]
234
+ resid -= reconstr
235
+ resid[(resid<0).where] = 0 # ignore artifacts introduced by the centroids in reconstruction
236
+
237
+ # puts "resid#{i} #{resid.abs.mean}" # if debug
238
+ epsilon = 0.005
239
+ # print resid.abs.mean, ' ' if $ngen == 2; exit if $ngen==3
240
+ # print sparse_code.to_a, ' ' if $ngen == 3; exit if $ngen==4
241
+ break if resid.abs.mean <= epsilon
242
+ end
243
+
244
+ code = sparse_code
110
245
  @ncodes += 1
111
246
  @utility += (code - utility) / ncodes # cumulative moving average
112
247
  code
@@ -118,16 +253,33 @@ module MachineLearningWorkbench::Compressor
118
253
  def reconstruction code, type: encoding_type
119
254
  case type
120
255
  when :most_similar
121
- centrs[code]
256
+ centrs[code, true]
122
257
  when :most_similar_ary
123
- centrs[code.eq(1).where[0]]
258
+ centrs[code.eq(1), true]
124
259
  when :ensemble
125
- tot = code.reduce :+
126
- centrs.zip(code).map { |centr, contr| centr*contr/tot }.reduce :+
260
+ # tot = code.reduce :+
261
+ # centrs.zip(code).map { |centr, contr| centr*contr/tot }.reduce :+
262
+ centrs.dot(code) / code.sum
127
263
  when :norm_ensemble
128
- centrs.zip(code).map { |centr, contr| centr*contr }.reduce :+
264
+ centrs.dot code
265
+ # centrs.zip(code).map { |centr, contr| centr*contr }.reduce :+
266
+ when :sparse_coding_v1
267
+ raise "requires normalized centroids!"
268
+ reconstr_code = code[0...(code.size/2)] - code[(code.size/2)..-1]
269
+ reconstr = centrs.transpose.dot reconstr_code
270
+ when :sparse_coding_v2
271
+ raise "requires normalized centroids!"
272
+
273
+
274
+ # BUG IN NARRAY DOT!! ruby-numo/numo-narray#99
275
+ # reconstr = code.dot centrs
276
+ reconstr = code.expand_dims(0).dot centrs
277
+
278
+
129
279
  when :sparse_coding
130
- raise NotImplementedError, "do this next"
280
+ # the code is binary, so just sum over the corresponding centroids
281
+ # note: sum, not mean, because of how it's used in reconstr_error
282
+ reconstr = centrs[code.cast_to(Numo::Bit).where, true].sum(0)
131
283
  else raise ArgumentError, "unrecognized reconstruction type: #{type}"
132
284
  end
133
285
  end
@@ -145,16 +297,23 @@ module MachineLearningWorkbench::Compressor
145
297
  # @return [NArray] residuals
146
298
  def reconstr_error vec, code: nil, type: encoding_type
147
299
  code ||= encode vec, type: type
148
- (vec - reconstruction(code, type: type)).abs.sum
300
+ resid = vec - reconstruction(code, type: type)
301
+ # we ignore the extra stuff coming from the centroids,
302
+ # only care that everything in the obs is represented in centrs
303
+ resid[resid<0] = 0 if encoding_type == :sparse_coding
304
+ resid
149
305
  end
150
306
 
151
307
  # Train on one vector
152
308
  # @return [Integer] index of trained centroid
153
- def train_one vec
309
+ def train_one vec, eps: nil
310
+ # NOTE: ignores epsilon if passed
154
311
  trg_idx, _simil = most_similar_centr(vec)
155
312
  # note: uhm that actually looks like a dot product... maybe faster?
156
313
  # `[c[i], vec].dot([1-lrate, lrate])`
157
- centrs[trg_idx] = centrs[trg_idx] * (1-lrate) + vec * lrate
314
+ # norm_vec = vec / NLinalg.norm(vec)
315
+ # centrs[trg_idx, true] = centrs[trg_idx, true] * (1-lrate) + norm_vec * lrate
316
+ centrs[trg_idx, true] = centrs[trg_idx, true] * (1-lrate) + vec * lrate
158
317
  trg_idx
159
318
  end
160
319
 
@@ -53,6 +53,7 @@ module MachineLearningWorkbench::NeuralNetwork
53
53
  # Initialize the network with random weights
54
54
  def init_random
55
55
  # Reusing `#load_weights` instead helps catching bugs
56
+ deep_reset
56
57
  load_weights NArray.new(nweights).rand(-1,1)
57
58
  end
58
59
 
@@ -125,14 +125,20 @@ module MachineLearningWorkbench::Optimizer::NaturalEvolutionStrategies
125
125
  # this_best = sorted.last.take(2)
126
126
  # NArray[*sorted.map(&:last)]
127
127
 
128
- sort_idxs = fits.sort_index
128
+
129
+
130
+ # BUG IN NARRAY SORT!! ruby-numo/numo-narray#97
131
+ # sort_idxs = fits.sort_index
132
+ sort_idxs = fits.size.times.sort_by { |i| fits[i] }.to_na
133
+
134
+
135
+
129
136
  sort_idxs = sort_idxs.reverse if opt_type == :min
130
137
  this_best = [fits[sort_idxs[-1]], inds[sort_idxs[-1], true]]
131
-
132
138
  opt_cmp_fn = opt_type==:min ? :< : :>
133
139
  @best = this_best if this_best.first.send(opt_cmp_fn, best.first)
134
140
 
135
- samples[sort_idxs,true]
141
+ samples[sort_idxs, true]
136
142
  end
137
143
 
138
144
  # @!method interface_methods
@@ -68,7 +68,15 @@ module MachineLearningWorkbench::Optimizer::NaturalEvolutionStrategies
68
68
  # opt_cmp_fn = opt_type==:min ? :< : :>
69
69
  # @best = this_best if this_best.first.send(opt_cmp_fn, best.first)
70
70
  # sorted_samples = sorted.map(&:last)
71
- sort_idxs = fits.sort_index
71
+
72
+
73
+
74
+ # BUG IN NARRAY SORT!! ruby-numo/numo-narray#97
75
+ # sort_idxs = fits.sort_index
76
+ sort_idxs = fits.size.times.sort_by { |i| fits[i] }.to_na
77
+
78
+
79
+
72
80
  sort_idxs = sort_idxs.reverse if opt_type == :min
73
81
  this_best = [fits[sort_idxs[-1]], full_inds[sort_idxs[-1], true]]
74
82
  opt_cmp_fn = opt_type==:min ? :< : :>
@@ -23,7 +23,8 @@ module MachineLearningWorkbench::Optimizer::NaturalEvolutionStrategies
23
23
  when Numeric
24
24
  NArray.new([ndims]).fill(sigma_init)
25
25
  else
26
- raise ArgumentError, "Something is wrong with sigma_init: #{sigma_init}"
26
+ raise ArgumentError, "Something is wrong with sigma_init: #{sigma_init}" \
27
+ "(did you remember to copy the other cases from XNES?)"
27
28
  end
28
29
  @sigma = @variances.diag
29
30
  end
@@ -7,6 +7,11 @@ module MachineLearningWorkbench::Optimizer::NaturalEvolutionStrategies
7
7
 
8
8
  def initialize_distribution mu_init: 0, sigma_init: 1
9
9
  @mu = case mu_init
10
+ when Range # initialize with random in range
11
+ raise ArgumentError, "mu_init: `Range` start/end in `Float`s" \
12
+ unless mu_init.first.kind_of?(Float) && mu_init.last.kind_of?(Float)
13
+ mu_rng = Random.new rng.rand 10**Random.new_seed.size
14
+ NArray[*ndims.times.map { mu_rng.rand mu_init }]
10
15
  when Array
11
16
  raise ArgumentError unless mu_init.size == ndims
12
17
  NArray[mu_init]
@@ -4,3 +4,4 @@ require_relative 'tools/execution'
4
4
  require_relative 'tools/normalization'
5
5
  require_relative 'tools/imaging'
6
6
  require_relative 'tools/verification'
7
+ require_relative 'tools/logging'
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MachineLearningWorkbench::Tools
4
+ module Logging
5
+ # Splits calls to standard streams to be both displayed on terminal and saved to file
6
+ class LogSplitter < File
7
+ def initialize dest
8
+ fname = if File.directory?(dest)
9
+ "#{dest}/#{Time.now.strftime "%y%m%d_%H%M"}.log"
10
+ else dest
11
+ end
12
+ super fname, 'w'
13
+ end
14
+
15
+ def write *args
16
+ STDOUT.write *args
17
+ super
18
+ end
19
+ end
20
+
21
+ def self.split_to dest, also_stderr: false
22
+ $stdout = LogSplitter.new dest
23
+ $stderr = $stdout if also_stderr
24
+ end
25
+
26
+ def self.restore_streams
27
+ logger = $stdout
28
+ $stdout = STDOUT
29
+ $stderr = STDERR
30
+ logger.close
31
+ end
32
+ end
33
+ end
@@ -48,6 +48,6 @@ Gem::Specification.new do |spec|
48
48
  spec.requirements << "libopenblas-base" # requirement for `numo-linalg`
49
49
  spec.requirements << "liblapacke" # requirement for `numo-linalg`
50
50
  spec.add_dependency "numo-narray", "~> 0.9"
51
- spec.add_dependency "numo-linalg", "~> 0.1.2"
51
+ spec.add_dependency "numo-linalg", "~> 0.1"
52
52
  spec.add_dependency "parallel", "~> 1.12"
53
53
  end
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.7.0
4
+ version: 0.8.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-20 00:00:00.000000000 Z
11
+ date: 2018-05-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -156,14 +156,14 @@ dependencies:
156
156
  requirements:
157
157
  - - "~>"
158
158
  - !ruby/object:Gem::Version
159
- version: 0.1.2
159
+ version: '0.1'
160
160
  type: :runtime
161
161
  prerelease: false
162
162
  version_requirements: !ruby/object:Gem::Requirement
163
163
  requirements:
164
164
  - - "~>"
165
165
  - !ruby/object:Gem::Version
166
- version: 0.1.2
166
+ version: '0.1'
167
167
  - !ruby/object:Gem::Dependency
168
168
  name: parallel
169
169
  requirement: !ruby/object:Gem::Requirement
@@ -223,6 +223,7 @@ files:
223
223
  - lib/machine_learning_workbench/tools.rb
224
224
  - lib/machine_learning_workbench/tools/execution.rb
225
225
  - lib/machine_learning_workbench/tools/imaging.rb
226
+ - lib/machine_learning_workbench/tools/logging.rb
226
227
  - lib/machine_learning_workbench/tools/normalization.rb
227
228
  - lib/machine_learning_workbench/tools/verification.rb
228
229
  - machine_learning_workbench.gemspec