ruby-dnn 0.15.3 → 0.16.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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +1 -9
  3. data/examples/api-examples/early_stopping_example.rb +1 -1
  4. data/examples/api-examples/initializer_example.rb +1 -1
  5. data/examples/api-examples/regularizer_example.rb +1 -1
  6. data/examples/api-examples/save_example.rb +1 -1
  7. data/examples/dcgan/dcgan.rb +3 -3
  8. data/examples/iris_example.rb +41 -17
  9. data/examples/mnist_define_by_run.rb +1 -1
  10. data/examples/pix2pix/dcgan.rb +157 -0
  11. data/examples/pix2pix/imgen.rb +27 -0
  12. data/examples/pix2pix/train.rb +52 -0
  13. data/lib/dnn.rb +2 -0
  14. data/lib/dnn/core/layers/activations.rb +37 -19
  15. data/lib/dnn/core/layers/basic_layers.rb +110 -25
  16. data/lib/dnn/core/layers/cnn_layers.rb +19 -21
  17. data/lib/dnn/core/layers/embedding.rb +3 -3
  18. data/lib/dnn/core/layers/math_layers.rb +169 -0
  19. data/lib/dnn/core/layers/merge_layers.rb +29 -24
  20. data/lib/dnn/core/layers/normalizations.rb +4 -2
  21. data/lib/dnn/core/layers/rnn_layers.rb +44 -36
  22. data/lib/dnn/core/link.rb +7 -2
  23. data/lib/dnn/core/losses.rb +54 -30
  24. data/lib/dnn/core/models.rb +47 -47
  25. data/lib/dnn/core/monkey_patch.rb +75 -0
  26. data/lib/dnn/core/optimizers.rb +10 -6
  27. data/lib/dnn/core/param.rb +17 -0
  28. data/lib/dnn/core/regularizers.rb +35 -33
  29. data/lib/dnn/core/tensor.rb +40 -0
  30. data/lib/dnn/core/utils.rb +1 -1
  31. data/lib/dnn/datasets/cifar10.rb +10 -9
  32. data/lib/dnn/datasets/cifar100.rb +10 -9
  33. data/lib/dnn/datasets/downloader.rb +1 -5
  34. data/lib/dnn/datasets/fashion-mnist.rb +4 -12
  35. data/lib/dnn/datasets/iris.rb +9 -9
  36. data/lib/dnn/datasets/mnist.rb +4 -12
  37. data/lib/dnn/datasets/stl-10.rb +6 -8
  38. data/lib/dnn/version.rb +1 -1
  39. data/ruby-dnn.gemspec +1 -1
  40. metadata +7 -5
  41. data/ext/cifar_loader/cifar_loader.c +0 -77
  42. data/ext/cifar_loader/extconf.rb +0 -3
@@ -1,6 +1,7 @@
1
1
  module DNN
2
2
  module Models
3
3
 
4
+ # This class is used to hold multiple layers in an array.
4
5
  class LayersList < Array
5
6
  def self.from_hash_list(hash_list)
6
7
  layers_list = new
@@ -19,7 +20,7 @@ module DNN
19
20
  end
20
21
 
21
22
  def to_hash_list
22
- map { |layer| layer.to_hash }
23
+ map(&:to_hash)
23
24
  end
24
25
 
25
26
  # Get the all layers.
@@ -38,8 +39,18 @@ module DNN
38
39
  end
39
40
 
40
41
  class Chain
41
- def call(x)
42
- raise NotImplementedError, "Class '#{self.class.name}' has implement method 'call'"
42
+ # Forward propagation.
43
+ # @param [Tensor] input_tensor Input tensor.
44
+ # @return [Tensor] Output tensor.
45
+ def forward(input_tensor)
46
+ raise NotImplementedError, "Class '#{self.class.name}' has implement method 'forward'"
47
+ end
48
+
49
+ # Forward propagation and create a link.
50
+ # @param [Tensor] input_tensor Input tensor.
51
+ # @return [Tensor] Output tensor.
52
+ def call(input_tensor)
53
+ forward(input_tensor)
43
54
  end
44
55
 
45
56
  # Get the all layers.
@@ -109,13 +120,19 @@ module DNN
109
120
  def initialize
110
121
  @optimizer = nil
111
122
  @loss_func = nil
112
- @last_link = nil
113
123
  @built = false
114
124
  @callbacks = []
115
125
  @layers_cache = nil
116
126
  @last_log = {}
117
127
  end
118
128
 
129
+ def call(inputs)
130
+ @layers_cache = nil
131
+ output_tensor = forward(inputs)
132
+ @built = true unless @built
133
+ output_tensor
134
+ end
135
+
119
136
  # Set optimizer and loss_func to model.
120
137
  # @param [DNN::Optimizers::Optimizer] optimizer Optimizer to use for learning.
121
138
  # @param [DNN::Losses::Loss] loss_func Loss function to use for learning.
@@ -204,10 +221,10 @@ module DNN
204
221
 
205
222
  if test
206
223
  acc, loss = if test.is_a?(Array)
207
- evaluate(test[0], test[1], batch_size: batch_size)
208
- else
209
- evaluate_by_iterator(test, batch_size: batch_size)
210
- end
224
+ evaluate(test[0], test[1], batch_size: batch_size)
225
+ else
226
+ evaluate_by_iterator(test, batch_size: batch_size)
227
+ end
211
228
  print " " + metrics_to_str({ accuracy: acc, test_loss: loss }) if verbose
212
229
  end
213
230
  puts "" if verbose
@@ -242,15 +259,14 @@ module DNN
242
259
  raise DNN_Error, "The model is not loss_func setup complete." unless @loss_func
243
260
  check_xy_type(x, y)
244
261
  call_callbacks(:before_train_on_batch)
245
- x = forward(x, true)
246
- loss_value = @loss_func.loss(x, y, layers)
247
- dy = @loss_func.backward(x, y)
248
- backward(dy)
249
- @optimizer.update(layers)
250
- @loss_func.regularizers_backward(layers)
251
- @last_log[:train_loss] = loss_value
262
+ DNN.learning_phase = true
263
+ out = call(Tensor.convert(x))
264
+ loss = @loss_func.loss(out, Tensor.convert(y), layers)
265
+ loss.link.backward(Xumo::SFloat.zeros(y[0...1, false].shape))
266
+ @optimizer.update(get_all_trainable_params)
267
+ @last_log[:train_loss] = loss.data
252
268
  call_callbacks(:after_train_on_batch)
253
- loss_value
269
+ loss.data
254
270
  end
255
271
 
256
272
  # Evaluate model and get accuracy and loss of test data.
@@ -291,11 +307,12 @@ module DNN
291
307
  # @return [Array] Returns the test data accuracy and mean loss in the form [accuracy, mean_loss].
292
308
  def test_on_batch(x, y)
293
309
  call_callbacks(:before_test_on_batch)
294
- x = forward(x, false)
295
- correct = accuracy(x, y)
296
- loss_value = @loss_func.loss(x, y)
310
+ DNN.learning_phase = false
311
+ out = call(Tensor.convert(x))
312
+ correct = accuracy(out.data, y)
313
+ loss = @loss_func.(out, Tensor.convert(y))
297
314
  call_callbacks(:after_test_on_batch)
298
- [correct, loss_value]
315
+ [correct, loss.data]
299
316
  end
300
317
 
301
318
  # Implement the process to accuracy this model.
@@ -323,7 +340,9 @@ module DNN
323
340
  # @param [Boolean] use_loss_activation Use loss activation when loss has an activation.
324
341
  def predict(x, use_loss_activation: true)
325
342
  check_xy_type(x)
326
- y = forward(x, false)
343
+ DNN.learning_phase = false
344
+ out = call(Tensor.convert(x))
345
+ y = out.data
327
346
  if use_loss_activation && @loss_func.class.respond_to?(:activation)
328
347
  y = @loss_func.class.activation(y)
329
348
  end
@@ -386,9 +405,7 @@ module DNN
386
405
  # @return [DNN::Layers::Layer] Return the layer.
387
406
  def get_layer(name)
388
407
  layer = instance_variable_get("@#{name}")
389
- if layer.is_a?(Layers::Layer) || layer.is_a?(Chain) || layer.is_a?(LayersList)
390
- return layer
391
- end
408
+ return layer if layer.is_a?(Layers::Layer) || layer.is_a?(Chain) || layer.is_a?(LayersList)
392
409
  nil
393
410
  end
394
411
 
@@ -398,11 +415,8 @@ module DNN
398
415
  end
399
416
 
400
417
  def clean_layers
401
- layers.each do |layer|
402
- layer.clean
403
- end
418
+ layers.each(&:clean)
404
419
  @loss_func.clean
405
- @last_link = nil
406
420
  @layers_cache = nil
407
421
  end
408
422
 
@@ -424,24 +438,10 @@ module DNN
424
438
 
425
439
  private
426
440
 
427
- def forward(x, learning_phase)
428
- DNN.learning_phase = learning_phase
429
- @layers_cache = nil
430
- inputs = if x.is_a?(Array)
431
- x.map { |a| Tensor.new(a, nil) }
432
- else
433
- Tensor.new(x, nil)
434
- end
435
- output_tensor = call(inputs)
436
- @last_link = output_tensor.link
437
- unless @built
438
- @built = true
439
- end
440
- output_tensor.data
441
- end
442
-
443
- def backward(dy)
444
- @last_link.backward(dy)
441
+ def get_all_trainable_params
442
+ layers.select { |layer| layer.is_a?(Layers::TrainableLayer) && layer.trainable }
443
+ .map { |layer| layer.get_params.values }.flatten.compact
444
+ .select(&:grad)
445
445
  end
446
446
 
447
447
  def call_callbacks(event)
@@ -512,7 +512,7 @@ module DNN
512
512
  @stack.delete(layer) ? true : false
513
513
  end
514
514
 
515
- def call(x)
515
+ def forward(x)
516
516
  @stack.each do |layer|
517
517
  x = layer.(x)
518
518
  end
@@ -0,0 +1,75 @@
1
+ class Integer
2
+ alias dnn__add +
3
+ def +(other)
4
+ if other.is_a?(DNN::Tensor)
5
+ DNN::Layers::Add.(self, other)
6
+ else
7
+ dnn__add(other)
8
+ end
9
+ end
10
+
11
+ alias dnn__sub -
12
+ def -(other)
13
+ if other.is_a?(DNN::Tensor)
14
+ DNN::Layers::Sub.(self, other)
15
+ else
16
+ dnn__sub(other)
17
+ end
18
+ end
19
+
20
+ alias dnn__mul *
21
+ def *(other)
22
+ if other.is_a?(DNN::Tensor)
23
+ DNN::Layers::Mul.(self, other)
24
+ else
25
+ dnn__mul(other)
26
+ end
27
+ end
28
+
29
+ alias dnn__div /
30
+ def /(other)
31
+ if other.is_a?(DNN::Tensor)
32
+ DNN::Layers::Div.(self, other)
33
+ else
34
+ dnn__div(other)
35
+ end
36
+ end
37
+ end
38
+
39
+ class Float
40
+ alias dnn__add +
41
+ def +(other)
42
+ if other.is_a?(DNN::Tensor)
43
+ DNN::Layers::Add.(self, other)
44
+ else
45
+ dnn__add(other)
46
+ end
47
+ end
48
+
49
+ alias dnn__sub -
50
+ def -(other)
51
+ if other.is_a?(DNN::Tensor)
52
+ DNN::Layers::Sub.(self, other)
53
+ else
54
+ dnn__sub(other)
55
+ end
56
+ end
57
+
58
+ alias dnn__mul *
59
+ def *(other)
60
+ if other.is_a?(DNN::Tensor)
61
+ DNN::Layers::Mul.(self, other)
62
+ else
63
+ dnn__mul(other)
64
+ end
65
+ end
66
+
67
+ alias dnn__div /
68
+ def /(other)
69
+ if other.is_a?(DNN::Tensor)
70
+ DNN::Layers::Div.(self, other)
71
+ else
72
+ dnn__div(other)
73
+ end
74
+ end
75
+ end
@@ -19,16 +19,20 @@ module DNN
19
19
  @clip_norm = clip_norm
20
20
  end
21
21
 
22
+ def update(params)
23
+ clip_grads(params) if @clip_norm
24
+ update_params(params)
25
+ params.each do |param|
26
+ param.grad = Xumo::SFloat[0]
27
+ end
28
+ end
29
+
22
30
  # Update layers has params.
23
- def update(layers)
31
+ def update_layers(layers)
24
32
  target_params = layers.select { |layer| layer.is_a?(Layers::TrainableLayer) && layer.trainable }
25
33
  .map { |layer| layer.get_params.values }.flatten.compact
26
34
  .select(&:grad)
27
- clip_grads(target_params) if @clip_norm
28
- update_params(target_params)
29
- target_params.each do |param|
30
- param.grad = Xumo::SFloat[0]
31
- end
35
+ update(target_params)
32
36
  end
33
37
 
34
38
  def to_hash(merge_hash = nil)
@@ -1,11 +1,28 @@
1
1
  module DNN
2
2
  class Param
3
+ attr_accessor :trainable
3
4
  attr_accessor :data
4
5
  attr_accessor :grad
5
6
 
6
7
  def initialize(data = nil, grad = nil)
7
8
  @data = data
8
9
  @grad = grad
10
+ @trainable = true
11
+ end
12
+
13
+ def backward(grad)
14
+ if @trainable
15
+ @grad ||= Xumo::SFloat[0]
16
+ if @data.shape == grad.shape
17
+ @grad += grad
18
+ elsif @data.shape == grad.shape[1..-1]
19
+ @grad += grad.sum(0)
20
+ else
21
+ raise DNN_Error, "Shape is missmatch."
22
+ end
23
+ else
24
+ @grad = Xumo::SFloat[0]
25
+ end
9
26
  end
10
27
  end
11
28
  end
@@ -17,10 +17,6 @@ module DNN
17
17
  raise NotImplementedError, "Class '#{self.class.name}' has implement method 'forward'"
18
18
  end
19
19
 
20
- def backward
21
- raise NotImplementedError, "Class '#{self.class.name}' has implement method 'backward'"
22
- end
23
-
24
20
  def to_hash(merge_hash)
25
21
  hash = { class: self.class.name }
26
22
  hash.merge!(merge_hash)
@@ -33,25 +29,25 @@ module DNN
33
29
  end
34
30
 
35
31
  class L1 < Regularizer
36
- attr_accessor :l1_lambda
37
-
38
32
  # @param [Float] l1_lambda L1 regularizer coefficient.
39
33
  def initialize(l1_lambda = 0.01)
40
- @l1_lambda = l1_lambda
34
+ @l1 = Layers::Lasso.new(l1_lambda)
41
35
  end
42
36
 
43
37
  def forward(x)
44
- x + @l1_lambda * @param.data.abs.sum
38
+ x + @l1.(@param)
39
+ end
40
+
41
+ def l1_lambda
42
+ @l1.l1_lambda
45
43
  end
46
44
 
47
- def backward
48
- dparam = Xumo::SFloat.ones(*@param.data.shape)
49
- dparam[@param.data < 0] = -1
50
- @param.grad += @l1_lambda * dparam
45
+ def l1_lambda=(lam)
46
+ @l1.l1_lambda = lam
51
47
  end
52
48
 
53
49
  def to_hash
54
- super(l1_lambda: @l1_lambda)
50
+ super(l1_lambda: l1_lambda)
55
51
  end
56
52
 
57
53
  def load_hash(hash)
@@ -60,23 +56,25 @@ module DNN
60
56
  end
61
57
 
62
58
  class L2 < Regularizer
63
- attr_accessor :l2_lambda
64
-
65
59
  # @param [Float] l2_lambda L2 regularizer coefficient.
66
60
  def initialize(l2_lambda = 0.01)
67
- @l2_lambda = l2_lambda
61
+ @l2 = Layers::Ridge.new(l2_lambda)
68
62
  end
69
63
 
70
64
  def forward(x)
71
- x + 0.5 * @l2_lambda * (@param.data**2).sum
65
+ x + @l2.(@param)
66
+ end
67
+
68
+ def l2_lambda
69
+ @l2.l2_lambda
72
70
  end
73
71
 
74
- def backward
75
- @param.grad += @l2_lambda * @param.data
72
+ def l2_lambda=(lam)
73
+ @l2.l2_lambda = lam
76
74
  end
77
75
 
78
76
  def to_hash
79
- super(l2_lambda: @l2_lambda)
77
+ super(l2_lambda: l2_lambda)
80
78
  end
81
79
 
82
80
  def load_hash(hash)
@@ -85,27 +83,31 @@ module DNN
85
83
  end
86
84
 
87
85
  class L1L2 < Regularizer
88
- attr_accessor :l1_lambda
89
- attr_accessor :l2_lambda
90
-
91
86
  # @param [Float] l1_lambda L1 regularizer coefficient.
92
87
  # @param [Float] l2_lambda L2 regularizer coefficient.
93
88
  def initialize(l1_lambda = 0.01, l2_lambda = 0.01)
94
- @l1_lambda = l1_lambda
95
- @l2_lambda = l2_lambda
89
+ @l1 = Layers::Lasso.new(l1_lambda)
90
+ @l2 = Layers::Ridge.new(l2_lambda)
96
91
  end
97
92
 
98
93
  def forward(x)
99
- l1 = @l1_lambda * @param.data.abs.sum
100
- l2 = 0.5 * @l2_lambda * (@param.data**2).sum
101
- x + l1 + l2
94
+ x + @l1.(@param) + @l2.(@param)
95
+ end
96
+
97
+ def l1_lambda
98
+ @l1.l1_lambda
99
+ end
100
+
101
+ def l1_lambda=(lam)
102
+ @l1.l1_lambda = lam
103
+ end
104
+
105
+ def l2_lambda
106
+ @l2.l2_lambda
102
107
  end
103
108
 
104
- def backward
105
- dparam = Xumo::SFloat.ones(*@param.data.shape)
106
- dparam[@param.data < 0] = -1
107
- @param.grad += @l1_lambda * dparam
108
- @param.grad += @l2_lambda * @param.data
109
+ def l2_lambda=(lam)
110
+ @l2.l2_lambda = lam
109
111
  end
110
112
 
111
113
  def to_hash
@@ -3,9 +3,49 @@ module DNN
3
3
  attr_reader :data
4
4
  attr_accessor :link
5
5
 
6
+ def self.convert(inputs)
7
+ if inputs.is_a?(Array)
8
+ inputs.map { |input| Tensor.new(input) }
9
+ else
10
+ Tensor.new(inputs)
11
+ end
12
+ end
13
+
6
14
  def initialize(data, link = nil)
7
15
  @data = data
8
16
  @link = link
9
17
  end
18
+
19
+ def shape
20
+ @data.shape
21
+ end
22
+
23
+ def +@
24
+ self
25
+ end
26
+
27
+ def -@
28
+ self * -1
29
+ end
30
+
31
+ def +(other)
32
+ Layers::Add.(self, other)
33
+ end
34
+
35
+ def -(other)
36
+ Layers::Sub.(self, other)
37
+ end
38
+
39
+ def *(other)
40
+ Layers::Mul.(self, other)
41
+ end
42
+
43
+ def /(other)
44
+ Layers::Div.(self, other)
45
+ end
46
+
47
+ def **(index)
48
+ Layers::Pow.new(index).(self)
49
+ end
10
50
  end
11
51
  end