ruby-dnn 0.12.4 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,12 +7,12 @@ module DNN
7
7
  end
8
8
 
9
9
  def call(input1, input2)
10
- x1, prev_link1, learning_phase = *input1
11
- x2, prev_link2, * = *input2
10
+ x1, prev_link1 = *input1
11
+ x2, prev_link2 = *input2
12
12
  build(x1.shape[1..-1]) unless built?
13
13
  y = forward(x1, x2)
14
14
  link = TwoInputLink.new(prev_link1, prev_link2, self)
15
- [y, link, learning_phase]
15
+ [y, link]
16
16
  end
17
17
  end
18
18
 
@@ -1,25 +1,35 @@
1
- require "zlib"
2
- require "json"
3
- require "base64"
4
-
5
1
  module DNN
6
2
  module Models
7
3
 
8
4
  # This class deals with the model of the network.
9
5
  class Model
6
+ attr_accessor :optimizer
7
+ attr_accessor :loss_func
8
+
10
9
  # Load marshal model.
11
10
  # @param [String] file_name File name of marshal model to load.
12
11
  def self.load(file_name)
13
- Marshal.load(Zlib::Inflate.inflate(File.binread(file_name)))
12
+ loader = Loaders::MarshalLoader.new(self.new)
13
+ loader.load(file_name)
14
14
  end
15
15
 
16
16
  def initialize
17
17
  @optimizer = nil
18
+ @loss_func = nil
18
19
  @last_link = nil
19
- @setup_completed = false
20
20
  @built = false
21
+ @callbacks = {
22
+ before_epoch: [],
23
+ after_epoch: [],
24
+ before_train_on_batch: [],
25
+ after_train_on_batch: [],
26
+ before_test_on_batch: [],
27
+ after_test_on_batch: [],
28
+ }
29
+ @layers_cache = nil
21
30
  end
22
31
 
32
+ # This method is provided for compatibility with v0.12.4.
23
33
  # Load hash model parameters.
24
34
  # @param [Hash] hash Hash to load model parameters.
25
35
  def load_hash_params(hash)
@@ -35,6 +45,7 @@ module DNN
35
45
  end
36
46
  end
37
47
 
48
+ # This method is provided for compatibility with v0.12.4.
38
49
  # Load json model parameters.
39
50
  # @param [String] json_str JSON string to load model parameters.
40
51
  def load_json_params(json_str)
@@ -52,30 +63,6 @@ module DNN
52
63
  end
53
64
  end
54
65
 
55
- # Convert model parameters to hash.
56
- # @return [Hash] Return the hash of model parameters.
57
- def params_to_hash
58
- has_param_layers_params = has_param_layers.uniq.map do |layer|
59
- layer.get_params.map { |key, param|
60
- [key, [param.data.shape, param.data.to_binary]]
61
- }.to_h
62
- end
63
- { version: VERSION, params: has_param_layers_params }
64
- end
65
-
66
- # Convert model parameters to JSON string.
67
- # @return [String] Return the JSON string.
68
- def params_to_json
69
- has_param_layers_params = has_param_layers.uniq.map do |layer|
70
- layer.get_params.map { |key, param|
71
- base64_data = Base64.encode64(param.data.to_binary)
72
- [key, [param.data.shape, base64_data]]
73
- }.to_h
74
- end
75
- hash = { version: VERSION, params: has_param_layers_params }
76
- JSON.dump(hash)
77
- end
78
-
79
66
  # Set optimizer and loss_func to model.
80
67
  # @param [DNN::Optimizers::Optimizer] optimizer Optimizer to use for learning.
81
68
  # @param [DNN::Losses::Loss] loss_func Loss function to use for learning.
@@ -86,7 +73,6 @@ module DNN
86
73
  unless loss_func.is_a?(Losses::Loss)
87
74
  raise TypeError.new("loss_func:#{loss_func.class} is not an instance of DNN::Losses::Loss class.")
88
75
  end
89
- @setup_completed = true
90
76
  @optimizer = optimizer
91
77
  @loss_func = loss_func
92
78
  end
@@ -100,32 +86,20 @@ module DNN
100
86
  # @param [Array | NilClass] test If you to test the model for every 1 epoch,
101
87
  # specify [x_test, y_test]. Don't test to the model, specify nil.
102
88
  # @param [Boolean] verbose Set true to display the log. If false is set, the log is not displayed.
103
- # @param [Lambda] before_epoch_cbk Process performed before one training.
104
- # @param [Lambda] after_epoch_cbk Process performed after one training.
105
- # @param [Lambda] before_train_on_batch_cbk Set the proc to be performed before train on batch processing.
106
- # @param [Lambda] after_train_on_batch_cbk Set the proc to be performed after train on batch processing.
107
- # @param [Lambda] before_test_on_batch_cbk Set the proc to be performed before test on batch processing.
108
- # @param [Lambda] after_test_on_batch_cbk Set the proc to be performed after test on batch processing.
109
89
  def train(x, y, epochs,
110
90
  batch_size: 1,
111
91
  test: nil,
112
- verbose: true,
113
- before_epoch_cbk: nil,
114
- after_epoch_cbk: nil,
115
- before_train_on_batch_cbk: nil,
116
- after_train_on_batch_cbk: nil,
117
- before_test_on_batch_cbk: nil,
118
- after_test_on_batch_cbk: nil)
119
- raise DNN_Error.new("The model is not setup complete.") unless setup_completed?
92
+ verbose: true)
93
+ raise DNN_Error.new("The model is not optimizer setup complete.") unless @optimizer
94
+ raise DNN_Error.new("The model is not loss_func setup complete.") unless @loss_func
120
95
  check_xy_type(x, y)
121
96
  iter = Iterator.new(x, y)
122
- num_train_datas = x.shape[0]
97
+ num_train_datas = x.is_a?(Array) ? x[0].shape[0] : x.shape[0]
123
98
  (1..epochs).each do |epoch|
124
- before_epoch_cbk&.call(epoch)
99
+ call_callbacks(:before_epoch, epoch)
125
100
  puts "【 epoch #{epoch}/#{epochs} 】" if verbose
126
101
  iter.foreach(batch_size) do |x_batch, y_batch, index|
127
- loss_value = train_on_batch(x_batch, y_batch, before_train_on_batch_cbk: before_train_on_batch_cbk,
128
- after_train_on_batch_cbk: after_train_on_batch_cbk)
102
+ loss_value = train_on_batch(x_batch, y_batch)
129
103
  if loss_value.is_a?(Xumo::SFloat)
130
104
  loss_value = loss_value.mean
131
105
  elsif loss_value.nan?
@@ -148,85 +122,82 @@ module DNN
148
122
  print log if verbose
149
123
  end
150
124
  if test
151
- acc, test_loss = accurate(test[0], test[1], batch_size: batch_size, before_test_on_batch_cbk: before_test_on_batch_cbk,
152
- after_test_on_batch_cbk: after_test_on_batch_cbk)
153
- print " accurate: #{acc}, test loss: #{sprintf('%.8f', test_loss)}" if verbose
125
+ acc, test_loss = accuracy(test[0], test[1], batch_size: batch_size)
126
+ print " accuracy: #{acc}, test loss: #{sprintf('%.8f', test_loss)}" if verbose
154
127
  end
155
128
  puts "" if verbose
156
- after_epoch_cbk&.call(epoch)
129
+ call_callbacks(:after_epoch, epoch)
157
130
  end
158
131
  end
159
132
 
133
+ alias fit train
134
+
160
135
  # Training once.
161
136
  # Setup the model before use this method.
162
137
  # @param [Numo::SFloat] x Input training data.
163
138
  # @param [Numo::SFloat] y Output training data.
164
- # @param [Lambda] before_train_on_batch_cbk Set the proc to be performed before train on batch processing.
165
- # @param [Lambda] after_train_on_batch_cbk Set the proc to be performed after train on batch processing.
166
139
  # @return [Float | Numo::SFloat] Return loss value in the form of Float or Numo::SFloat.
167
- def train_on_batch(x, y, before_train_on_batch_cbk: nil, after_train_on_batch_cbk: nil)
168
- raise DNN_Error.new("The model is not setup complete.") unless setup_completed?
140
+ def train_on_batch(x, y)
141
+ raise DNN_Error.new("The model is not optimizer setup complete.") unless @optimizer
142
+ raise DNN_Error.new("The model is not loss_func setup complete.") unless @loss_func
169
143
  check_xy_type(x, y)
170
- before_train_on_batch_cbk&.call
144
+ call_callbacks(:before_train_on_batch)
171
145
  x = forward(x, true)
172
- loss_value = @loss_func.forward(x, y, layers)
173
- dy = @loss_func.backward(y, layers)
146
+ loss_value = @loss_func.loss(x, y, layers)
147
+ dy = @loss_func.backward(x, y)
174
148
  backward(dy)
175
149
  @optimizer.update(layers.uniq)
176
- after_train_on_batch_cbk&.call(loss_value)
150
+ @loss_func.regularizers_backward(layers)
151
+ call_callbacks(:after_train_on_batch, loss_value)
177
152
  loss_value
178
153
  end
179
154
 
180
- # Evaluate model and get accurate of test data.
155
+ # Evaluate model and get accuracy of test data.
181
156
  # @param [Numo::SFloat] x Input test data.
182
157
  # @param [Numo::SFloat] y Output test data.
183
- # @param [Lambda] before_test_on_batch_cbk Set the proc to be performed before test on batch processing.
184
- # @param [Lambda] after_test_on_batch_cbk Set the proc to be performed after test on batch processing.
185
- # @return [Array] Returns the test data accurate and mean loss in the form [accurate, mean_loss].
186
- def accurate(x, y, batch_size: 100, before_test_on_batch_cbk: nil, after_test_on_batch_cbk: nil)
158
+ # @return [Array] Returns the test data accuracy and mean loss in the form [accuracy, mean_loss].
159
+ def accuracy(x, y, batch_size: 100)
187
160
  check_xy_type(x, y)
188
- batch_size = batch_size >= x.shape[0] ? x.shape[0] : batch_size
161
+ num_test_datas = x.is_a?(Array) ? x[0].shape[0] : x.shape[0]
162
+ batch_size = batch_size >= num_test_datas[0] ? num_test_datas : batch_size
189
163
  iter = Iterator.new(x, y, random: false)
190
164
  total_correct = 0
191
165
  sum_loss = 0
192
- max_steps = (x.shape[0].to_f / batch_size).ceil
166
+ max_steps = (num_test_datas.to_f / batch_size).ceil
193
167
  iter.foreach(batch_size) do |x_batch, y_batch|
194
- correct, loss_value = test_on_batch(x_batch, y_batch, before_test_on_batch_cbk: before_test_on_batch_cbk,
195
- after_test_on_batch_cbk: after_test_on_batch_cbk)
168
+ correct, loss_value = test_on_batch(x_batch, y_batch)
196
169
  total_correct += correct
197
170
  sum_loss += loss_value.is_a?(Xumo::SFloat) ? loss_value.mean : loss_value
198
171
  end
199
172
  mean_loss = sum_loss / max_steps
200
- [total_correct.to_f / x.shape[0], mean_loss]
173
+ [total_correct.to_f / num_test_datas, mean_loss]
201
174
  end
202
175
 
203
176
  # Evaluate once.
204
177
  # @param [Numo::SFloat] x Input test data.
205
178
  # @param [Numo::SFloat] y Output test data.
206
- # @param [Lambda] before_test_on_batch_cbk Set the proc to be performed before test on batch processing.
207
- # @param [Lambda] after_test_on_batch_cbk Set the proc to be performed after test on batch processing.
208
- # @return [Array] Returns the test data accurate and mean loss in the form [accurate, mean_loss].
209
- def test_on_batch(x, y, before_test_on_batch_cbk: nil, after_test_on_batch_cbk: nil)
210
- before_test_on_batch_cbk&.call
179
+ # @return [Array] Returns the test data accuracy and mean loss in the form [accuracy, mean_loss].
180
+ def test_on_batch(x, y)
181
+ call_callbacks(:before_test_on_batch)
211
182
  x = forward(x, false)
212
183
  correct = evaluate(x, y)
213
- loss_value = @loss_func.forward(x, y, layers)
214
- after_test_on_batch_cbk&.call(loss_value)
184
+ loss_value = @loss_func.loss(x, y, layers)
185
+ call_callbacks(:after_test_on_batch, loss_value)
215
186
  [correct, loss_value]
216
187
  end
217
188
 
218
189
  private def evaluate(y, t)
219
- correct = 0
220
- y.shape[0].times do |i|
221
- if y.shape[1..-1] == [1]
190
+ if y.shape[1..-1] == [1]
191
+ correct = 0
192
+ y.shape[0].times do |i|
222
193
  if @loss_func.is_a?(Losses::SigmoidCrossEntropy)
223
194
  correct += 1 if (y[i, 0] < 0 && t[i, 0] < 0.5) || (y[i, 0] >= 0 && t[i, 0] >= 0.5)
224
195
  else
225
196
  correct += 1 if (y[i, 0] < 0 && t[i, 0] < 0) || (y[i, 0] >= 0 && t[i, 0] >= 0)
226
197
  end
227
- else
228
- correct += 1 if y[i, true].max_index == t[i, true].max_index
229
198
  end
199
+ else
200
+ correct = y.max_index(axis: 1).eq(t.max_index(axis: 1)).count
230
201
  end
231
202
  correct
232
203
  end
@@ -245,17 +216,37 @@ module DNN
245
216
  predict(x.reshape(1, *x.shape))[0, false]
246
217
  end
247
218
 
219
+ # Add callback function.
220
+ # @param [Symbol] event Callback event. The following can be used for event.
221
+ # before_epoch: Process: performed before one training.
222
+ # after_epoch: Process: performed after one training.
223
+ # before_train_on_batch: Set the proc to be performed before train on batch processing.
224
+ # after_train_on_batch: Set the proc to be performed after train on batch processing.
225
+ # before_test_on_batch: Set the proc to be performed before test on batch processing.
226
+ # after_test_on_batch: Set the proc to be performed after test on batch processing.
227
+ def add_callback(event, callback)
228
+ raise DNN_UnknownEventError.new("Unknown event #{event}.") unless @callbacks.has_key?(event)
229
+ @callbacks[event] << callback
230
+ end
231
+
232
+ # Clear the callback function registered for each event.
233
+ # @param [Symbol] event Callback event. The following can be used for event.
234
+ # before_epoch: Process: performed before one training.
235
+ # after_epoch: Process: performed after one training.
236
+ # before_train_on_batch: Set the proc to be performed before train on batch processing.
237
+ # after_train_on_batch: Set the proc to be performed after train on batch processing.
238
+ # before_test_on_batch: Set the proc to be performed before test on batch processing.
239
+ # after_test_on_batch: Set the proc to be performed after test on batch processing.
240
+ def clear_callbacks(event)
241
+ raise DNN_UnknownEventError.new("Unknown event #{event}.") unless @callbacks.has_key?(event)
242
+ @callbacks[event] = []
243
+ end
244
+
248
245
  # Save the model in marshal format.
249
246
  # @param [String] file_name Name to save model.
250
247
  def save(file_name)
251
- bin = Zlib::Deflate.deflate(Marshal.dump(self))
252
- begin
253
- File.binwrite(file_name, bin)
254
- rescue Errno::ENOENT
255
- dir_name = file_name.match(%r`(.*)/.+$`)[1]
256
- Dir.mkdir(dir_name)
257
- File.binwrite(file_name, bin)
258
- end
248
+ saver = Savers::MarshalSaver.new(self)
249
+ saver.save(file_name)
259
250
  end
260
251
 
261
252
  # @return [DNN::Models::Model] Return the copy this model.
@@ -267,6 +258,7 @@ module DNN
267
258
  # @return [Array] All layers array.
268
259
  def layers
269
260
  raise DNN_Error.new("This model is not built. You need build this model using predict or train.") unless built?
261
+ return @layers_cache if @layers_cache
270
262
  layers = []
271
263
  get_layers = -> link do
272
264
  return unless link
@@ -279,7 +271,7 @@ module DNN
279
271
  end
280
272
  end
281
273
  get_layers.(@last_link)
282
- layers
274
+ @layers_cache = layers
283
275
  end
284
276
 
285
277
  # Get the all has param layers.
@@ -289,38 +281,10 @@ module DNN
289
281
  end
290
282
 
291
283
  # Get the layer that the model has.
292
- # @overload get_layer(index)
293
- # @param [Integer] The index of the layer to get.
294
- # @return [DNN::Layers::Layer] Return the layer.
295
- # @overload get_layer(layer_class, index)
296
- # @param [Integer] The index of the layer to get.
297
- # @param [Class] The class of the layer to get.
298
- # @return [DNN::Layers::Layer] Return the layer.
299
- def get_layer(*args)
300
- if args.length == 1
301
- index = args[0]
302
- layers[index]
303
- else
304
- layer_class, index = args
305
- layers.select { |layer| layer.is_a?(layer_class) }[index]
306
- end
307
- end
308
-
309
- # @return [DNN::Optimizers::Optimizer] optimizer Return the optimizer to use for learning.
310
- def optimizer
311
- raise DNN_Error.new("The model is not setup complete.") unless setup_completed?
312
- @optimizer
313
- end
314
-
315
- # @return [DNN::Losses::Loss] loss_func Return the loss function to use for learning.
316
- def loss_func
317
- raise DNN_Error.new("The model is not setup complete.") unless setup_completed?
318
- @loss_func
319
- end
320
-
321
- # @return [Boolean] If model have already been setup completed then return true.
322
- def setup_completed?
323
- @setup_completed
284
+ # @param [Symbol] The name of the layer to get.
285
+ # @return [DNN::Layers::Layer] Return the layer.
286
+ def get_layer(name)
287
+ layers.find { |layer| layer.name == name }
324
288
  end
325
289
 
326
290
  # @return [Boolean] If model have already been built then return true.
@@ -331,31 +295,45 @@ module DNN
331
295
  private
332
296
 
333
297
  def forward(x, learning_phase)
334
- @built = true
335
- y, @last_link = call([x, nil, learning_phase])
298
+ DNN.learning_phase = learning_phase
299
+ @layers_cache = nil
300
+ y, @last_link = call(x)
301
+ unless @built
302
+ @built = true
303
+ naming
304
+ end
336
305
  y
337
306
  end
338
307
 
339
308
  def backward(dy)
340
- bwd = -> link, dy do
341
- return dy unless link
342
- if link.is_a?(TwoInputLink)
343
- dy1, dy2 = link.layer.backward(dy)
344
- [bwd.(link.prev1, dy1), bwd.(link.prev2, dy2)]
345
- else
346
- dy = link.layer.backward(dy)
347
- bwd.(link.prev, dy)
309
+ @last_link.backward(dy)
310
+ end
311
+
312
+ def call_callbacks(event, *args)
313
+ @callbacks[event].each do |callback|
314
+ callback.call(*args)
315
+ end
316
+ end
317
+
318
+ def naming
319
+ layers.uniq.each do |layer|
320
+ id = layers.uniq.select { |l| l.is_a?(layer.class) }.index(layer)
321
+ class_name = layer.class.name.split("::").last
322
+ layer.name = "#{class_name}_#{id}".to_sym unless layer.name
323
+ if layer.is_a?(Layers::HasParamLayer)
324
+ layer.get_params.each do |param_key, param|
325
+ param.name = "#{layer.name}__#{param_key}".to_sym unless param.name
326
+ end
348
327
  end
349
328
  end
350
- bwd.(@last_link, dy)
351
329
  end
352
330
 
353
331
  def check_xy_type(x, y = nil)
354
- unless x.is_a?(Xumo::SFloat)
355
- raise TypeError.new("x:#{x.class.name} is not an instance of #{Xumo::SFloat.name} class.")
332
+ if !x.is_a?(Xumo::SFloat) && !x.is_a?(Array)
333
+ raise TypeError.new("x:#{x.class.name} is not an instance of #{Xumo::SFloat.name} class or Array class.")
356
334
  end
357
- if y && !y.is_a?(Xumo::SFloat)
358
- raise TypeError.new("y:#{y.class.name} is not an instance of #{Xumo::SFloat.name} class.")
335
+ if y && !y.is_a?(Xumo::SFloat) && !x.is_a?(Array)
336
+ raise TypeError.new("y:#{y.class.name} is not an instance of #{Xumo::SFloat.name} class or Array class.")
359
337
  end
360
338
  end
361
339
  end
@@ -373,7 +351,7 @@ module DNN
373
351
  # Add layer to the model.
374
352
  # @param [DNN::Layers::Layer] layer Layer to add to the model.
375
353
  # @return [DNN::Models::Model] Return self.
376
- def <<(layer)
354
+ def add(layer)
377
355
  unless layer.is_a?(Layers::Layer) || layer.is_a?(Model)
378
356
  raise TypeError.new("layer: #{layer.class.name} is not an instance of the DNN::Layers::Layer class or DNN::Models::Model class.")
379
357
  end
@@ -381,6 +359,18 @@ module DNN
381
359
  self
382
360
  end
383
361
 
362
+ alias << add
363
+
364
+ # Remove layer to the model.
365
+ # @param [DNN::Layers::Layer] layer Layer to remove to the model.
366
+ # @return [Boolean] Return true if success for remove layer.
367
+ def remove(layer)
368
+ unless layer.is_a?(Layers::Layer) || layer.is_a?(Model)
369
+ raise TypeError.new("layer: #{layer.class.name} is not an instance of the DNN::Layers::Layer class or DNN::Models::Model class.")
370
+ end
371
+ @stack.delete(layer) ? true : false
372
+ end
373
+
384
374
  def call(x)
385
375
  @stack.each do |layer|
386
376
  x = layer.(x)
@@ -24,14 +24,6 @@ module DNN
24
24
  @eps = eps
25
25
  end
26
26
 
27
- def call(input)
28
- x, prev_link, learning_phase = *input
29
- build(x.shape[1..-1]) unless built?
30
- y = forward(x, learning_phase)
31
- link = Link.new(prev_link, self)
32
- [y, link, learning_phase]
33
- end
34
-
35
27
  def build(input_shape)
36
28
  super
37
29
  @gamma = Param.new(Xumo::SFloat.ones(*output_shape), 0)
@@ -40,8 +32,8 @@ module DNN
40
32
  @running_var = Param.new(Xumo::SFloat.zeros(*output_shape))
41
33
  end
42
34
 
43
- def forward(x, learning_phase)
44
- if learning_phase
35
+ def forward(x)
36
+ if DNN.learning_phase
45
37
  mean = x.mean(axis: @axis, keepdims: true)
46
38
  @xc = x - mean
47
39
  var = (@xc ** 2).mean(axis: @axis, keepdims: true)