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.
- checksums.yaml +4 -4
- data/.travis.yml +1 -2
- data/README.md +33 -6
- data/examples/cifar100_example.rb +3 -3
- data/examples/cifar10_example.rb +3 -3
- data/examples/dcgan/dcgan.rb +112 -0
- data/examples/dcgan/imgen.rb +20 -0
- data/examples/dcgan/train.rb +41 -0
- data/examples/iris_example.rb +3 -6
- data/examples/mnist_conv2d_example.rb +5 -5
- data/examples/mnist_define_by_run.rb +52 -0
- data/examples/mnist_example.rb +3 -3
- data/examples/mnist_lstm_example.rb +3 -3
- data/examples/xor_example.rb +4 -5
- data/ext/rb_stb_image/rb_stb_image.c +103 -0
- data/lib/dnn.rb +10 -10
- data/lib/dnn/cifar10.rb +1 -1
- data/lib/dnn/cifar100.rb +1 -1
- data/lib/dnn/core/activations.rb +21 -22
- data/lib/dnn/core/cnn_layers.rb +94 -111
- data/lib/dnn/core/embedding.rb +30 -9
- data/lib/dnn/core/initializers.rb +31 -21
- data/lib/dnn/core/iterator.rb +52 -0
- data/lib/dnn/core/layers.rb +99 -66
- data/lib/dnn/core/link.rb +24 -0
- data/lib/dnn/core/losses.rb +69 -59
- data/lib/dnn/core/merge_layers.rb +71 -0
- data/lib/dnn/core/models.rb +393 -0
- data/lib/dnn/core/normalizations.rb +27 -14
- data/lib/dnn/core/optimizers.rb +212 -134
- data/lib/dnn/core/param.rb +8 -6
- data/lib/dnn/core/regularizers.rb +10 -7
- data/lib/dnn/core/rnn_layers.rb +78 -85
- data/lib/dnn/core/utils.rb +6 -3
- data/lib/dnn/downloader.rb +3 -3
- data/lib/dnn/fashion-mnist.rb +89 -0
- data/lib/dnn/image.rb +57 -18
- data/lib/dnn/iris.rb +1 -3
- data/lib/dnn/mnist.rb +38 -34
- data/lib/dnn/version.rb +1 -1
- data/third_party/stb_image.h +16 -4
- data/third_party/stb_image_resize.h +2630 -0
- data/third_party/stb_image_write.h +4 -7
- metadata +12 -4
- data/lib/dnn/core/dataset.rb +0 -34
- 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
|
data/lib/dnn/core/losses.rb
CHANGED
@@ -2,39 +2,40 @@ module DNN
|
|
2
2
|
module Losses
|
3
3
|
|
4
4
|
class Loss
|
5
|
-
def forward(
|
6
|
-
|
7
|
-
|
8
|
-
|
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(
|
17
|
-
layers.select { |layer| layer.
|
18
|
-
layer.regularizers.each
|
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(
|
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(
|
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(
|
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(
|
46
|
-
@
|
47
|
-
batch_size =
|
48
|
-
0.5 * ((
|
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(
|
52
|
-
@
|
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(
|
61
|
-
@
|
62
|
-
batch_size =
|
63
|
-
(
|
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(
|
67
|
-
dy = @
|
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
|
76
|
-
|
77
|
-
|
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(
|
83
|
-
@
|
84
|
-
|
85
|
-
loss_value > 1 ?
|
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(
|
89
|
-
dy = @
|
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(
|
98
|
-
batch_size =
|
99
|
-
(@
|
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(
|
103
|
-
batch_size =
|
104
|
-
0.5 * ((@
|
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
|
-
|
126
|
+
self.new(eps: hash[:eps])
|
115
127
|
end
|
116
128
|
|
117
|
-
def self.softmax(
|
118
|
-
NMath.exp(
|
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(
|
133
|
-
@
|
134
|
-
batch_size =
|
135
|
-
-(
|
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(
|
139
|
-
@
|
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
|
-
|
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(
|
164
|
-
@
|
165
|
-
|
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(
|
170
|
-
@
|
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
|