machine_learning_workbench 0.7.0 → 0.8.0

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