ruby-dnn 0.10.4 → 0.12.4

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -2
  3. data/README.md +33 -6
  4. data/examples/cifar100_example.rb +3 -3
  5. data/examples/cifar10_example.rb +3 -3
  6. data/examples/dcgan/dcgan.rb +112 -0
  7. data/examples/dcgan/imgen.rb +20 -0
  8. data/examples/dcgan/train.rb +41 -0
  9. data/examples/iris_example.rb +3 -6
  10. data/examples/mnist_conv2d_example.rb +5 -5
  11. data/examples/mnist_define_by_run.rb +52 -0
  12. data/examples/mnist_example.rb +3 -3
  13. data/examples/mnist_lstm_example.rb +3 -3
  14. data/examples/xor_example.rb +4 -5
  15. data/ext/rb_stb_image/rb_stb_image.c +103 -0
  16. data/lib/dnn.rb +10 -10
  17. data/lib/dnn/cifar10.rb +1 -1
  18. data/lib/dnn/cifar100.rb +1 -1
  19. data/lib/dnn/core/activations.rb +21 -22
  20. data/lib/dnn/core/cnn_layers.rb +94 -111
  21. data/lib/dnn/core/embedding.rb +30 -9
  22. data/lib/dnn/core/initializers.rb +31 -21
  23. data/lib/dnn/core/iterator.rb +52 -0
  24. data/lib/dnn/core/layers.rb +99 -66
  25. data/lib/dnn/core/link.rb +24 -0
  26. data/lib/dnn/core/losses.rb +69 -59
  27. data/lib/dnn/core/merge_layers.rb +71 -0
  28. data/lib/dnn/core/models.rb +393 -0
  29. data/lib/dnn/core/normalizations.rb +27 -14
  30. data/lib/dnn/core/optimizers.rb +212 -134
  31. data/lib/dnn/core/param.rb +8 -6
  32. data/lib/dnn/core/regularizers.rb +10 -7
  33. data/lib/dnn/core/rnn_layers.rb +78 -85
  34. data/lib/dnn/core/utils.rb +6 -3
  35. data/lib/dnn/downloader.rb +3 -3
  36. data/lib/dnn/fashion-mnist.rb +89 -0
  37. data/lib/dnn/image.rb +57 -18
  38. data/lib/dnn/iris.rb +1 -3
  39. data/lib/dnn/mnist.rb +38 -34
  40. data/lib/dnn/version.rb +1 -1
  41. data/third_party/stb_image.h +16 -4
  42. data/third_party/stb_image_resize.h +2630 -0
  43. data/third_party/stb_image_write.h +4 -7
  44. metadata +12 -4
  45. data/lib/dnn/core/dataset.rb +0 -34
  46. data/lib/dnn/core/model.rb +0 -440
@@ -0,0 +1,24 @@
1
+ module DNN
2
+ class Link
3
+ attr_accessor :prev
4
+ attr_accessor :layer
5
+
6
+ def initialize(prev = nil, layer = nil)
7
+ @prev = prev
8
+ @layer = layer
9
+ end
10
+ end
11
+
12
+
13
+ class TwoInputLink
14
+ attr_accessor :prev1
15
+ attr_accessor :prev2
16
+ attr_accessor :layer
17
+
18
+ def initialize(prev1 = nil, prev2 = nil, layer = nil)
19
+ @prev1 = prev1
20
+ @prev2 = prev2
21
+ @layer = layer
22
+ end
23
+ end
24
+ end
@@ -2,39 +2,40 @@ module DNN
2
2
  module Losses
3
3
 
4
4
  class Loss
5
- def forward(x, y, layers)
6
- loss_value = forward_loss(x, y)
7
- regularizers = layers.select { |layer| layer.is_a?(Connection) }
8
- .map { |layer| layer.regularizers }.flatten
9
-
5
+ def forward(y, t, layers)
6
+ unless y.shape == t.shape
7
+ raise DNN_ShapeError.new("The shape of y does not match the t shape. y shape is #{y.shape}, but t shape is #{t.shape}.")
8
+ end
9
+ loss_value = forward_loss(y, t)
10
+ regularizers = layers.select { |layer| layer.is_a?(Layers::Connection) }
11
+ .map(&:regularizers).flatten
12
+
10
13
  regularizers.each do |regularizer|
11
14
  loss_value = regularizer.forward(loss_value)
12
15
  end
13
16
  loss_value
14
17
  end
15
18
 
16
- def backward(y, layers)
17
- layers.select { |layer| layer.is_a?(Connection) }.each do |layer|
18
- layer.regularizers.each do |regularizer|
19
- regularizer.backward
20
- end
19
+ def backward(t, layers)
20
+ layers.select { |layer| layer.respond_to?(:regularizers) }.each do |layer|
21
+ layer.regularizers.each(&:backward)
21
22
  end
22
- backward_loss(y)
23
+ backward_loss(t)
23
24
  end
24
25
 
25
26
  def to_hash(merge_hash = nil)
26
- hash = {class: self.class.name}
27
+ hash = { class: self.class.name }
27
28
  hash.merge!(merge_hash) if merge_hash
28
29
  hash
29
30
  end
30
31
 
31
32
  private
32
33
 
33
- def forward_loss(x, y)
34
+ def forward_loss(y, t)
34
35
  raise NotImplementedError.new("Class '#{self.class.name}' has implement method 'forward_loss'")
35
36
  end
36
37
 
37
- def backward_loss(y)
38
+ def backward_loss(t)
38
39
  raise NotImplementedError.new("Class '#{self.class.name}' has implement method 'backward_loss'")
39
40
  end
40
41
  end
@@ -42,14 +43,14 @@ module DNN
42
43
  class MeanSquaredError < Loss
43
44
  private
44
45
 
45
- def forward_loss(x, y)
46
- @x = x
47
- batch_size = y.shape[0]
48
- 0.5 * ((x - y)**2).sum / batch_size
46
+ def forward_loss(y, t)
47
+ @y = y
48
+ batch_size = t.shape[0]
49
+ 0.5 * ((y - t) ** 2).sum / batch_size
49
50
  end
50
51
 
51
- def backward_loss(y)
52
- @x - y
52
+ def backward_loss(t)
53
+ @y - t
53
54
  end
54
55
  end
55
56
 
@@ -57,14 +58,14 @@ module DNN
57
58
  class MeanAbsoluteError < Loss
58
59
  private
59
60
 
60
- def forward_loss(x, y)
61
- @x = x
62
- batch_size = y.shape[0]
63
- (x - y).abs.sum / batch_size
61
+ def forward_loss(y, t)
62
+ @y = y
63
+ batch_size = t.shape[0]
64
+ (y - t).abs.sum / batch_size
64
65
  end
65
66
 
66
- def backward_loss(y)
67
- dy = @x - y
67
+ def backward_loss(t)
68
+ dy = @y - t
68
69
  dy[dy >= 0] = 1
69
70
  dy[dy < 0] = -1
70
71
  dy
@@ -72,21 +73,33 @@ module DNN
72
73
  end
73
74
 
74
75
 
75
- class HuberLoss < Loss
76
- def forward(x, y, layers)
77
- @loss_value = super(x, y, layers)
76
+ class Hinge < Loss
77
+ private
78
+
79
+ def forward_loss(y, t)
80
+ @a = 1 - y * t
81
+ Xumo::SFloat.maximum(0, @a)
78
82
  end
79
83
 
84
+ def backward_loss(t)
85
+ a = Xumo::SFloat.ones(*@a.shape)
86
+ a[@a <= 0] = 0
87
+ a * -t
88
+ end
89
+ end
90
+
91
+
92
+ class HuberLoss < Loss
80
93
  private
81
94
 
82
- def forward_loss(x, y)
83
- @x = x
84
- loss_value = loss_l1(y)
85
- loss_value > 1 ? loss_value : loss_l2(y)
95
+ def forward_loss(y, t)
96
+ @y = y
97
+ loss_l1_value = loss_l1(t)
98
+ @loss_value = loss_l1_value > 1 ? loss_l1_value : loss_l2(t)
86
99
  end
87
100
 
88
- def backward_loss(y)
89
- dy = @x - y
101
+ def backward_loss(t)
102
+ dy = @y - t
90
103
  if @loss_value > 1
91
104
  dy[dy >= 0] = 1
92
105
  dy[dy < 0] = -1
@@ -94,28 +107,27 @@ module DNN
94
107
  dy
95
108
  end
96
109
 
97
- def loss_l1(y)
98
- batch_size = y.shape[0]
99
- (@x - y).abs.sum / batch_size
110
+ def loss_l1(t)
111
+ batch_size = t.shape[0]
112
+ (@y - t).abs.sum / batch_size
100
113
  end
101
114
 
102
- def loss_l2(y)
103
- batch_size = y.shape[0]
104
- 0.5 * ((@x - y)**2).sum / batch_size
115
+ def loss_l2(t)
116
+ batch_size = t.shape[0]
117
+ 0.5 * ((@y - t) ** 2).sum / batch_size
105
118
  end
106
119
  end
107
120
 
108
121
 
109
122
  class SoftmaxCrossEntropy < Loss
110
- # @return [Float] Return the eps value.
111
123
  attr_accessor :eps
112
124
 
113
125
  def self.from_hash(hash)
114
- SoftmaxCrossEntropy.new(eps: hash[:eps])
126
+ self.new(eps: hash[:eps])
115
127
  end
116
128
 
117
- def self.softmax(x)
118
- NMath.exp(x) / NMath.exp(x).sum(1).reshape(x.shape[0], 1)
129
+ def self.softmax(y)
130
+ Xumo::NMath.exp(y) / Xumo::NMath.exp(y).sum(1).reshape(y.shape[0], 1)
119
131
  end
120
132
 
121
133
  # @param [Float] eps Value to avoid nan.
@@ -129,24 +141,23 @@ module DNN
129
141
 
130
142
  private
131
143
 
132
- def forward_loss(x, y)
133
- @x = SoftmaxCrossEntropy.softmax(x)
134
- batch_size = y.shape[0]
135
- -(y * NMath.log(@x + @eps)).sum / batch_size
144
+ def forward_loss(y, t)
145
+ @y = SoftmaxCrossEntropy.softmax(y)
146
+ batch_size = t.shape[0]
147
+ -(t * Xumo::NMath.log(@y + @eps)).sum / batch_size
136
148
  end
137
149
 
138
- def backward_loss(y)
139
- @x - y
150
+ def backward_loss(t)
151
+ @y - t
140
152
  end
141
153
  end
142
154
 
143
155
 
144
156
  class SigmoidCrossEntropy < Loss
145
- # @return [Float] Return the eps value.
146
157
  attr_accessor :eps
147
158
 
148
159
  def self.from_hash(hash)
149
- SigmoidCrossEntropy.new(eps: hash[:eps])
160
+ self.new(eps: hash[:eps])
150
161
  end
151
162
 
152
163
  # @param [Float] eps Value to avoid nan.
@@ -160,14 +171,13 @@ module DNN
160
171
 
161
172
  private
162
173
 
163
- def forward_loss(x, y)
164
- @x = Sigmoid.new.forward(x)
165
- batch_size = y.shape[0]
166
- -(y * NMath.log(@x) + (1 - y) * NMath.log(1 - @x))
174
+ def forward_loss(y, t)
175
+ @y = Activations::Sigmoid.new.forward(y)
176
+ -(t * Xumo::NMath.log(@y) + (1 - t) * Xumo::NMath.log(1 - @y))
167
177
  end
168
178
 
169
- def backward_loss(y)
170
- @x - y
179
+ def backward_loss(t)
180
+ @y - t
171
181
  end
172
182
  end
173
183
 
@@ -0,0 +1,71 @@
1
+ module DNN
2
+ module MergeLayers
3
+
4
+ class MergeLayer < Layers::Layer
5
+ def self.call(x1, x2, *args)
6
+ self.new(*args).call(x1, x2)
7
+ end
8
+
9
+ def call(input1, input2)
10
+ x1, prev_link1, learning_phase = *input1
11
+ x2, prev_link2, * = *input2
12
+ build(x1.shape[1..-1]) unless built?
13
+ y = forward(x1, x2)
14
+ link = TwoInputLink.new(prev_link1, prev_link2, self)
15
+ [y, link, learning_phase]
16
+ end
17
+ end
18
+
19
+
20
+ class Add < MergeLayer
21
+ def forward(x1, x2)
22
+ x1 + x2
23
+ end
24
+
25
+ def backward(dy)
26
+ [dy, dy]
27
+ end
28
+ end
29
+
30
+
31
+ class Mul < MergeLayer
32
+ def forward(x1, x2)
33
+ @x1, @x2 = x1, x2
34
+ x1 * x2
35
+ end
36
+
37
+ def backward(dy)
38
+ [dy * @x2, dy * @x1]
39
+ end
40
+ end
41
+
42
+
43
+ class Concatenate < MergeLayer
44
+ attr_reader :axis
45
+
46
+ def self.from_hash(hash)
47
+ self.new(axis: hash[:axis])
48
+ end
49
+
50
+ def initialize(axis: 1)
51
+ super()
52
+ @axis = axis
53
+ end
54
+
55
+ def forward(x1, x2)
56
+ @x1_dim = x1.shape[@axis]
57
+ @x2_dim = x2.shape[@axis]
58
+ x1.concatenate(x2, axis: @axis)
59
+ end
60
+
61
+ def backward(dy)
62
+ dy.split([@x1_dim, @x1_dim + @x2_dim], axis: @axis)
63
+ end
64
+
65
+ def to_hash
66
+ super(axis: @axis)
67
+ end
68
+ end
69
+
70
+ end
71
+ end
@@ -0,0 +1,393 @@
1
+ require "zlib"
2
+ require "json"
3
+ require "base64"
4
+
5
+ module DNN
6
+ module Models
7
+
8
+ # This class deals with the model of the network.
9
+ class Model
10
+ # Load marshal model.
11
+ # @param [String] file_name File name of marshal model to load.
12
+ def self.load(file_name)
13
+ Marshal.load(Zlib::Inflate.inflate(File.binread(file_name)))
14
+ end
15
+
16
+ def initialize
17
+ @optimizer = nil
18
+ @last_link = nil
19
+ @setup_completed = false
20
+ @built = false
21
+ end
22
+
23
+ # Load hash model parameters.
24
+ # @param [Hash] hash Hash to load model parameters.
25
+ def load_hash_params(hash)
26
+ has_param_layers_params = hash[:params]
27
+ has_param_layers_index = 0
28
+ has_param_layers.uniq.each do |layer|
29
+ hash_params = has_param_layers_params[has_param_layers_index]
30
+ hash_params.each do |key, (shape, bin)|
31
+ data = Xumo::SFloat.from_binary(bin).reshape(*shape)
32
+ layer.get_params[key].data = data
33
+ end
34
+ has_param_layers_index += 1
35
+ end
36
+ end
37
+
38
+ # Load json model parameters.
39
+ # @param [String] json_str JSON string to load model parameters.
40
+ def load_json_params(json_str)
41
+ hash = JSON.parse(json_str, symbolize_names: true)
42
+ has_param_layers_params = hash[:params]
43
+ has_param_layers_index = 0
44
+ has_param_layers.uniq.each do |layer|
45
+ hash_params = has_param_layers_params[has_param_layers_index]
46
+ hash_params.each do |key, (shape, base64_param)|
47
+ bin = Base64.decode64(base64_param)
48
+ data = Xumo::SFloat.from_binary(bin).reshape(*shape)
49
+ layer.get_params[key].data = data
50
+ end
51
+ has_param_layers_index += 1
52
+ end
53
+ end
54
+
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
+ # Set optimizer and loss_func to model.
80
+ # @param [DNN::Optimizers::Optimizer] optimizer Optimizer to use for learning.
81
+ # @param [DNN::Losses::Loss] loss_func Loss function to use for learning.
82
+ def setup(optimizer, loss_func)
83
+ unless optimizer.is_a?(Optimizers::Optimizer)
84
+ raise TypeError.new("optimizer:#{optimizer.class} is not an instance of DNN::Optimizers::Optimizer class.")
85
+ end
86
+ unless loss_func.is_a?(Losses::Loss)
87
+ raise TypeError.new("loss_func:#{loss_func.class} is not an instance of DNN::Losses::Loss class.")
88
+ end
89
+ @setup_completed = true
90
+ @optimizer = optimizer
91
+ @loss_func = loss_func
92
+ end
93
+
94
+ # Start training.
95
+ # Setup the model before use this method.
96
+ # @param [Numo::SFloat] x Input training data.
97
+ # @param [Numo::SFloat] y Output training data.
98
+ # @param [Integer] epochs Number of training.
99
+ # @param [Integer] batch_size Batch size used for one training.
100
+ # @param [Array | NilClass] test If you to test the model for every 1 epoch,
101
+ # specify [x_test, y_test]. Don't test to the model, specify nil.
102
+ # @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
+ def train(x, y, epochs,
110
+ batch_size: 1,
111
+ 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?
120
+ check_xy_type(x, y)
121
+ iter = Iterator.new(x, y)
122
+ num_train_datas = x.shape[0]
123
+ (1..epochs).each do |epoch|
124
+ before_epoch_cbk&.call(epoch)
125
+ puts "【 epoch #{epoch}/#{epochs} 】" if verbose
126
+ 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)
129
+ if loss_value.is_a?(Xumo::SFloat)
130
+ loss_value = loss_value.mean
131
+ elsif loss_value.nan?
132
+ puts "\nloss is nan" if verbose
133
+ return
134
+ end
135
+ num_trained_datas = (index + 1) * batch_size
136
+ num_trained_datas = num_trained_datas > num_train_datas ? num_train_datas : num_trained_datas
137
+ log = "\r"
138
+ 40.times do |i|
139
+ if i < num_trained_datas * 40 / num_train_datas
140
+ log << "="
141
+ elsif i == num_trained_datas * 40 / num_train_datas
142
+ log << ">"
143
+ else
144
+ log << "_"
145
+ end
146
+ end
147
+ log << " #{num_trained_datas}/#{num_train_datas} loss: #{sprintf('%.8f', loss_value)}"
148
+ print log if verbose
149
+ end
150
+ 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
154
+ end
155
+ puts "" if verbose
156
+ after_epoch_cbk&.call(epoch)
157
+ end
158
+ end
159
+
160
+ # Training once.
161
+ # Setup the model before use this method.
162
+ # @param [Numo::SFloat] x Input training data.
163
+ # @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
+ # @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?
169
+ check_xy_type(x, y)
170
+ before_train_on_batch_cbk&.call
171
+ x = forward(x, true)
172
+ loss_value = @loss_func.forward(x, y, layers)
173
+ dy = @loss_func.backward(y, layers)
174
+ backward(dy)
175
+ @optimizer.update(layers.uniq)
176
+ after_train_on_batch_cbk&.call(loss_value)
177
+ loss_value
178
+ end
179
+
180
+ # Evaluate model and get accurate of test data.
181
+ # @param [Numo::SFloat] x Input test data.
182
+ # @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)
187
+ check_xy_type(x, y)
188
+ batch_size = batch_size >= x.shape[0] ? x.shape[0] : batch_size
189
+ iter = Iterator.new(x, y, random: false)
190
+ total_correct = 0
191
+ sum_loss = 0
192
+ max_steps = (x.shape[0].to_f / batch_size).ceil
193
+ 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)
196
+ total_correct += correct
197
+ sum_loss += loss_value.is_a?(Xumo::SFloat) ? loss_value.mean : loss_value
198
+ end
199
+ mean_loss = sum_loss / max_steps
200
+ [total_correct.to_f / x.shape[0], mean_loss]
201
+ end
202
+
203
+ # Evaluate once.
204
+ # @param [Numo::SFloat] x Input test data.
205
+ # @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
211
+ x = forward(x, false)
212
+ correct = evaluate(x, y)
213
+ loss_value = @loss_func.forward(x, y, layers)
214
+ after_test_on_batch_cbk&.call(loss_value)
215
+ [correct, loss_value]
216
+ end
217
+
218
+ private def evaluate(y, t)
219
+ correct = 0
220
+ y.shape[0].times do |i|
221
+ if y.shape[1..-1] == [1]
222
+ if @loss_func.is_a?(Losses::SigmoidCrossEntropy)
223
+ correct += 1 if (y[i, 0] < 0 && t[i, 0] < 0.5) || (y[i, 0] >= 0 && t[i, 0] >= 0.5)
224
+ else
225
+ correct += 1 if (y[i, 0] < 0 && t[i, 0] < 0) || (y[i, 0] >= 0 && t[i, 0] >= 0)
226
+ end
227
+ else
228
+ correct += 1 if y[i, true].max_index == t[i, true].max_index
229
+ end
230
+ end
231
+ correct
232
+ end
233
+
234
+ # Predict data.
235
+ # @param [Numo::SFloat] x Input data.
236
+ def predict(x)
237
+ check_xy_type(x)
238
+ forward(x, false)
239
+ end
240
+
241
+ # Predict one data.
242
+ # @param [Numo::SFloat] x Input data. However, x is single data.
243
+ def predict1(x)
244
+ check_xy_type(x)
245
+ predict(x.reshape(1, *x.shape))[0, false]
246
+ end
247
+
248
+ # Save the model in marshal format.
249
+ # @param [String] file_name Name to save model.
250
+ 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
259
+ end
260
+
261
+ # @return [DNN::Models::Model] Return the copy this model.
262
+ def copy
263
+ Marshal.load(Marshal.dump(self))
264
+ end
265
+
266
+ # Get the all layers.
267
+ # @return [Array] All layers array.
268
+ def layers
269
+ raise DNN_Error.new("This model is not built. You need build this model using predict or train.") unless built?
270
+ layers = []
271
+ get_layers = -> link do
272
+ return unless link
273
+ layers.unshift(link.layer)
274
+ if link.is_a?(TwoInputLink)
275
+ get_layers.(link.prev1)
276
+ get_layers.(link.prev2)
277
+ else
278
+ get_layers.(link.prev)
279
+ end
280
+ end
281
+ get_layers.(@last_link)
282
+ layers
283
+ end
284
+
285
+ # Get the all has param layers.
286
+ # @return [Array] All has param layers array.
287
+ def has_param_layers
288
+ layers.select { |layer| layer.is_a?(Layers::HasParamLayer) }
289
+ end
290
+
291
+ # 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
324
+ end
325
+
326
+ # @return [Boolean] If model have already been built then return true.
327
+ def built?
328
+ @built
329
+ end
330
+
331
+ private
332
+
333
+ def forward(x, learning_phase)
334
+ @built = true
335
+ y, @last_link = call([x, nil, learning_phase])
336
+ y
337
+ end
338
+
339
+ 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)
348
+ end
349
+ end
350
+ bwd.(@last_link, dy)
351
+ end
352
+
353
+ 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.")
356
+ 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.")
359
+ end
360
+ end
361
+ end
362
+
363
+
364
+ class Sequential < Model
365
+ attr_reader :stack
366
+
367
+ # @param [Array] stack All layers possessed by the model.
368
+ def initialize(stack = [])
369
+ super()
370
+ @stack = stack.clone
371
+ end
372
+
373
+ # Add layer to the model.
374
+ # @param [DNN::Layers::Layer] layer Layer to add to the model.
375
+ # @return [DNN::Models::Model] Return self.
376
+ def <<(layer)
377
+ unless layer.is_a?(Layers::Layer) || layer.is_a?(Model)
378
+ raise TypeError.new("layer: #{layer.class.name} is not an instance of the DNN::Layers::Layer class or DNN::Models::Model class.")
379
+ end
380
+ @stack << layer
381
+ self
382
+ end
383
+
384
+ def call(x)
385
+ @stack.each do |layer|
386
+ x = layer.(x)
387
+ end
388
+ x
389
+ end
390
+ end
391
+
392
+ end
393
+ end