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
@@ -2,30 +2,42 @@ module DNN
2
2
  module Layers
3
3
 
4
4
  class Embedding < HasParamLayer
5
- # @return [Integer] Return the input length.
6
5
  attr_reader :input_length
7
- # @return [Initializers::Initializer] Return the weight initializer.
6
+ attr_reader :weight
8
7
  attr_reader :weight_initializer
8
+ attr_reader :weight_regularizer
9
9
 
10
10
  def self.from_hash(hash)
11
11
  self.new(hash[:input_shape], hash[:input_length],
12
- weight_initializer: DNN::Utils.from_hash(hash[:weight_initializer]))
12
+ weight_initializer: DNN::Utils.hash_to_obj(hash[:weight_initializer]),
13
+ weight_regularizer: DNN::Utils.hash_to_obj(hash[:weight_regularizer]))
13
14
  end
14
-
15
+
15
16
  # @param [Integer | Array] input_dim_or_shape Set input data dimension or shape.
16
- # @param [Integer] input_length input Set the time series length of input data.
17
- def initialize(input_dim_or_shape, input_length, weight_initializer: Initializers::RandomUniform.new)
17
+ # @param [Integer] input_length Set the time series length of input data.
18
+ # @param [DNN::Initializers::Initializer] weight_initializer Weight initializer.
19
+ # @param [DNN::Regularizers::Regularizer | NilClass] weight_regularizer Weight regularizer.
20
+ def initialize(input_dim_or_shape, input_length,
21
+ weight_initializer: Initializers::RandomUniform.new,
22
+ weight_regularizer: nil)
18
23
  super()
19
24
  @input_shape = input_dim_or_shape.is_a?(Array) ? input_dim_or_shape : [input_dim_or_shape]
20
25
  @input_length = input_length
21
26
  @weight_initializer = weight_initializer
27
+ @weight_regularizer = weight_regularizer
28
+ end
29
+
30
+ def call(input)
31
+ x, *, learning_phase = *input
32
+ build unless built?
33
+ [forward(x), Link.new(nil, self), learning_phase]
22
34
  end
23
35
 
24
36
  def build
25
37
  @built = true
26
- @params[:weight] = @weight = Param.new(Xumo::SFloat.new(@input_length), 0)
38
+ @weight = Param.new(Xumo::SFloat.new(@input_length), 0)
27
39
  @weight_initializer.init_param(self, @weight)
28
- @input_shape
40
+ @weight_regularizer.param = @weight if @weight_regularizer
29
41
  end
30
42
 
31
43
  def forward(x)
@@ -47,8 +59,17 @@ module DNN
47
59
  nil
48
60
  end
49
61
 
62
+ def regularizers
63
+ @weight_regularizer ? [@weight_regularizer] : []
64
+ end
65
+
50
66
  def to_hash
51
- super(input_shape: @input_shape, input_length: @input_length, weight_initializer: @weight_initializer.to_hash)
67
+ super(input_shape: @input_shape, input_length: @input_length,
68
+ weight_initializer: @weight_initializer.to_hash, weight_regularizer: @weight_regularizer&.to_hash)
69
+ end
70
+
71
+ def get_params
72
+ { weight: @weight }
52
73
  end
53
74
  end
54
75
 
@@ -2,16 +2,21 @@ module DNN
2
2
  module Initializers
3
3
 
4
4
  class Initializer
5
- def initialize(seed = false)
5
+ # @param [Boolean | Integer] seed Seed of random number used for masking.
6
+ # Set true to determine seed as random.
7
+ def initialize(seed: false)
6
8
  @seed = seed == true ? rand(1 << 31) : seed
7
9
  end
8
10
 
11
+ # Initialization of learning parameters.
12
+ # @param [DNN::Layers::Layer] layer Layer that owns learning parameters.
13
+ # @param [DNN::Param] param Learning parameter to be initialized.
9
14
  def init_param(layer, param)
10
- raise NotImplementedError.new("Class '#{self.class.name}' has implement method 'init_params'")
15
+ raise NotImplementedError.new("Class '#{self.class.name}' has implement method 'init_param'")
11
16
  end
12
17
 
13
18
  def to_hash(merge_hash = nil)
14
- hash = {class: self.class.name, seed: @seed}
19
+ hash = { class: self.class.name, seed: @seed }
15
20
  hash.merge!(merge_hash) if merge_hash
16
21
  hash
17
22
  end
@@ -32,6 +37,7 @@ module DNN
32
37
  self.new(hash[:const])
33
38
  end
34
39
 
40
+ # @param [Float] const Constant value of initialization.
35
41
  def initialize(const)
36
42
  super()
37
43
  @const = const
@@ -42,21 +48,23 @@ module DNN
42
48
  end
43
49
 
44
50
  def to_hash
45
- super({const: @const})
51
+ super(const: @const)
46
52
  end
47
53
  end
48
-
49
-
54
+
55
+
50
56
  class RandomNormal < Initializer
51
57
  attr_reader :mean
52
58
  attr_reader :std
53
-
59
+
54
60
  def self.from_hash(hash)
55
- self.new(hash[:mean], hash[:std], hash[:seed])
61
+ self.new(hash[:mean], hash[:std], seed: hash[:seed])
56
62
  end
57
63
 
58
- def initialize(mean = 0, std = 0.05, seed = true)
59
- super(seed)
64
+ # @param [Float] mean Average of initialization value.
65
+ # @param [Float] std Variance of initialization value.
66
+ def initialize(mean = 0, std = 0.05, seed: true)
67
+ super(seed: seed)
60
68
  @mean = mean
61
69
  @std = std
62
70
  end
@@ -67,7 +75,7 @@ module DNN
67
75
  end
68
76
 
69
77
  def to_hash
70
- super({mean: @mean, std: @std})
78
+ super(mean: @mean, std: @std)
71
79
  end
72
80
  end
73
81
 
@@ -77,11 +85,13 @@ module DNN
77
85
  attr_reader :max
78
86
 
79
87
  def self.from_hash(hash)
80
- self.new(hash[:min], hash[:max], hash[:seed])
88
+ self.new(hash[:min], hash[:max], seed: hash[:seed])
81
89
  end
82
90
 
83
- def initialize(min = -0.05, max = 0.05, seed = true)
84
- super(seed)
91
+ # @param [Float] min Min of initialization value.
92
+ # @param [Float] max Max of initialization value.
93
+ def initialize(min = -0.05, max = 0.05, seed: true)
94
+ super(seed: seed)
85
95
  @min = min
86
96
  @max = max
87
97
  end
@@ -92,13 +102,13 @@ module DNN
92
102
  end
93
103
 
94
104
  def to_hash
95
- super({min: @min, max: @max})
105
+ super(min: @min, max: @max)
96
106
  end
97
107
  end
98
-
99
-
108
+
109
+
100
110
  class Xavier < Initializer
101
- def initialize(seed = true)
111
+ def initialize(seed: true)
102
112
  super
103
113
  end
104
114
 
@@ -108,10 +118,10 @@ module DNN
108
118
  param.data = param.data.rand_norm / Math.sqrt(num_prev_nodes)
109
119
  end
110
120
  end
111
-
112
-
121
+
122
+
113
123
  class He < Initializer
114
- def initialize(seed = true)
124
+ def initialize(seed: true)
115
125
  super
116
126
  end
117
127
 
@@ -0,0 +1,52 @@
1
+ module DNN
2
+ # This class manages input datas and output datas together.
3
+ class Iterator
4
+ # @param [Numo::SFloat] x_datas input datas.
5
+ # @param [Numo::SFloat] y_datas output datas.
6
+ # @param [Boolean] random Set true to return batches randomly. Setting false returns batches in order of index.
7
+ def initialize(x_datas, y_datas, random: true)
8
+ @x_datas = x_datas
9
+ @y_datas = y_datas
10
+ @random = random
11
+ @num_datas = x_datas.shape[0]
12
+ reset
13
+ end
14
+
15
+ # Return the next batch.
16
+ # @param [Integer] batch_size Required batch size.
17
+ def next_batch(batch_size)
18
+ raise DNN_Error.new("This iterator has not next batch. Please call reset.") unless has_next?
19
+ if @indexes.length <= batch_size
20
+ batch_indexes = @indexes
21
+ @has_next = false
22
+ else
23
+ batch_indexes = @indexes.shift(batch_size)
24
+ end
25
+ x_batch = @x_datas[batch_indexes, false]
26
+ y_batch = @y_datas[batch_indexes, false]
27
+ [x_batch, y_batch]
28
+ end
29
+
30
+ # Reset input datas and output datas.
31
+ def reset
32
+ @has_next = true
33
+ @indexes = @num_datas.times.to_a
34
+ @indexes.shuffle! if @random
35
+ end
36
+
37
+ # Return the true if has next batch.
38
+ def has_next?
39
+ @has_next
40
+ end
41
+
42
+ def foreach(batch_size, &block)
43
+ step = 0
44
+ while has_next?
45
+ x_batch, y_batch = next_batch(batch_size)
46
+ block.call(x_batch, y_batch, step)
47
+ step += 1
48
+ end
49
+ reset
50
+ end
51
+ end
52
+ end
@@ -1,37 +1,48 @@
1
1
  module DNN
2
2
  module Layers
3
3
 
4
- # Super class of all optimizer classes.
4
+ # Super class of all layer classes.
5
5
  class Layer
6
- # @return [Bool] learning_phase Return the true if learning.
7
- attr_accessor :learning_phase
8
- # @return [Array] Return the shape of the input data.
9
6
  attr_reader :input_shape
10
7
 
8
+ def self.call(x, *args)
9
+ self.new(*args).(x)
10
+ end
11
+
11
12
  def initialize
12
13
  @built = false
13
14
  end
14
15
 
16
+ # Forward propagation and create a link.
17
+ # @param [Array] input Array of the form [x_input_data, prev_link, learning_phase].
18
+ def call(input)
19
+ x, prev_link, learning_phase = *input
20
+ build(x.shape[1..-1]) unless built?
21
+ y = forward(x)
22
+ link = Link.new(prev_link, self)
23
+ [y, link, learning_phase]
24
+ end
25
+
15
26
  # Build the layer.
16
27
  # @param [Array] input_shape Setting the shape of the input data.
17
28
  def build(input_shape)
18
29
  @input_shape = input_shape
19
- @learning_phase = true
20
30
  @built = true
21
31
  end
22
-
23
- # Does the layer have already been built?
24
- # @return [Bool] If layer have already been built then return true.
32
+
33
+ # @return [Boolean] If layer have already been built then return true.
25
34
  def built?
26
35
  @built
27
36
  end
28
37
 
29
38
  # Forward propagation.
39
+ # @param [Numo::SFloat] x Input data.
30
40
  def forward(x)
31
41
  raise NotImplementedError.new("Class '#{self.class.name}' has implement method 'forward'")
32
42
  end
33
43
 
34
44
  # Backward propagation.
45
+ # @param [Numo::SFloat] dy Differential value of output data.
35
46
  def backward(dy)
36
47
  raise NotImplementedError.new("Class '#{self.class.name}' has implement method 'backward'")
37
48
  end
@@ -45,73 +56,88 @@ module DNN
45
56
 
46
57
  # Layer to a hash.
47
58
  def to_hash(merge_hash = nil)
48
- hash = {class: self.class.name}
59
+ hash = { class: self.class.name }
49
60
  hash.merge!(merge_hash) if merge_hash
50
61
  hash
51
62
  end
52
63
  end
53
-
54
-
64
+
65
+
55
66
  # This class is a superclass of all classes with learning parameters.
56
67
  class HasParamLayer < Layer
57
- # @return [Bool] trainable Setting false prevents learning of parameters.
68
+ # @return [Boolean] Setting false prevents learning of parameters.
58
69
  attr_accessor :trainable
59
- # @return [Array] The parameters of the layer.
60
- attr_reader :params
61
-
70
+
62
71
  def initialize
63
72
  super()
64
- @params = {}
65
73
  @trainable = true
66
74
  end
75
+
76
+ # @return [Array] The parameters of the layer.
77
+ def get_params
78
+ raise NotImplementedError.new("Class '#{self.class.name}' has implement method 'get_params'")
79
+ end
67
80
  end
68
-
69
-
81
+
82
+
70
83
  class InputLayer < Layer
84
+ def self.call(input)
85
+ shape = input.is_a?(Array) ? input[0].shape : input.shape
86
+ self.new(shape[1..-1]).(input)
87
+ end
88
+
71
89
  def self.from_hash(hash)
72
90
  self.new(hash[:input_shape])
73
91
  end
74
92
 
93
+ # @param [Array] input_dim_or_shape Setting the shape or dimension of the input data.
75
94
  def initialize(input_dim_or_shape)
76
95
  super()
77
96
  @input_shape = input_dim_or_shape.is_a?(Array) ? input_dim_or_shape : [input_dim_or_shape]
78
97
  end
79
98
 
99
+ def call(input)
100
+ build unless built?
101
+ x, prev_link, learning_phase = *input
102
+ link = prev_link ? Link.new(prev_link, self) : Link.new(nil, self)
103
+ [forward(x), link, learning_phase]
104
+ end
105
+
80
106
  def build
81
107
  @built = true
82
- @input_shape
83
108
  end
84
109
 
85
110
  def forward(x)
111
+ unless x.shape[1..-1] == @input_shape
112
+ raise DNN_ShapeError.new("The shape of x does not match the input shape. input shape is #{@input_shape}, but x shape is #{x.shape[1..-1]}.")
113
+ end
86
114
  x
87
115
  end
88
-
116
+
89
117
  def backward(dy)
90
118
  dy
91
119
  end
92
120
 
93
121
  def to_hash
94
- super({input_shape: @input_shape})
122
+ super(input_shape: @input_shape)
95
123
  end
96
124
  end
97
125
 
98
126
 
99
127
  # It is a superclass of all connection layers.
100
128
  class Connection < HasParamLayer
101
- # @return [DNN::Initializers::Initializer] Weight initializer.
129
+ attr_reader :weight
130
+ attr_reader :bias
102
131
  attr_reader :weight_initializer
103
- # @return [DNN::Initializers::Initializer] Bias initializer.
104
132
  attr_reader :bias_initializer
105
- # @return [DNN::Regularizers::Regularizer] Weight regularization.
106
133
  attr_reader :weight_regularizer
107
- # @return [DNN::Regularizers::Regularizer] Bias regularization.
108
134
  attr_reader :bias_regularizer
109
135
 
110
136
  # @param [DNN::Initializers::Initializer] weight_initializer Weight initializer.
111
137
  # @param [DNN::Initializers::Initializer] bias_initializer Bias initializer.
112
- # @param [DNN::Regularizers::Regularizer] weight_regularizer Weight regularization.
113
- # @param [DNN::Regularizers::Regularizer] bias_regularizer Bias regularization.
114
- # @param [Bool] use_bias whether to use bias.
138
+ # @param [DNN::Regularizers::Regularizer | NilClass] weight_regularizer Weight regularizer.
139
+ # @param [DNN::Regularizers::Regularizer | NilClass] bias_regularizer Bias regularizer.
140
+ # @param [Boolean] use_bias Whether to use bias.
115
141
  def initialize(weight_initializer: Initializers::RandomNormal.new,
116
142
  bias_initializer: Initializers::Zeros.new,
117
143
  weight_regularizer: nil,
@@ -122,12 +148,8 @@ module DNN
122
148
  @bias_initializer = bias_initializer
123
149
  @weight_regularizer = weight_regularizer
124
150
  @bias_regularizer = bias_regularizer
125
- @params[:weight] = @weight = Param.new(nil, 0)
126
- if use_bias
127
- @params[:bias] = @bias = Param.new(nil, 0)
128
- else
129
- @bias = nil
130
- end
151
+ @weight = Param.new(nil, 0)
152
+ @bias = use_bias ? Param.new(nil, 0) : nil
131
153
  end
132
154
 
133
155
  def regularizers
@@ -137,17 +159,21 @@ module DNN
137
159
  regularizers
138
160
  end
139
161
 
140
- # @return [Bool] Return whether to use bias.
162
+ # @return [Boolean] Return whether to use bias.
141
163
  def use_bias
142
164
  @bias ? true : false
143
165
  end
144
166
 
145
167
  def to_hash(merge_hash)
146
- super({weight_initializer: @weight_initializer.to_hash,
147
- bias_initializer: @bias_initializer.to_hash,
148
- weight_regularizer: @weight_regularizer&.to_hash,
149
- bias_regularizer: @bias_regularizer&.to_hash,
150
- use_bias: use_bias}.merge(merge_hash))
168
+ super({ weight_initializer: @weight_initializer.to_hash,
169
+ bias_initializer: @bias_initializer.to_hash,
170
+ weight_regularizer: @weight_regularizer&.to_hash,
171
+ bias_regularizer: @bias_regularizer&.to_hash,
172
+ use_bias: use_bias }.merge(merge_hash))
173
+ end
174
+
175
+ def get_params
176
+ { weight: @weight, bias: @bias }
151
177
  end
152
178
 
153
179
  private def init_weight_and_bias
@@ -159,23 +185,21 @@ module DNN
159
185
  end
160
186
  end
161
187
  end
162
-
163
-
164
- # Full connnection layer.
188
+
189
+
165
190
  class Dense < Connection
166
- # @return [Integer] number of nodes.
167
191
  attr_reader :num_nodes
168
192
 
169
193
  def self.from_hash(hash)
170
194
  self.new(hash[:num_nodes],
171
- weight_initializer: Utils.from_hash(hash[:weight_initializer]),
172
- bias_initializer: Utils.from_hash(hash[:bias_initializer]),
173
- weight_regularizer: Utils.from_hash(hash[:weight_regularizer]),
174
- bias_regularizer: Utils.from_hash(hash[:bias_regularizer]),
195
+ weight_initializer: Utils.hash_to_obj(hash[:weight_initializer]),
196
+ bias_initializer: Utils.hash_to_obj(hash[:bias_initializer]),
197
+ weight_regularizer: Utils.hash_to_obj(hash[:weight_regularizer]),
198
+ bias_regularizer: Utils.hash_to_obj(hash[:bias_regularizer]),
175
199
  use_bias: hash[:use_bias])
176
200
  end
177
201
 
178
- # @param [Integer] num_nodes number of nodes.
202
+ # @param [Integer] num_nodes Number of nodes.
179
203
  def initialize(num_nodes,
180
204
  weight_initializer: Initializers::RandomNormal.new,
181
205
  bias_initializer: Initializers::Zeros.new,
@@ -204,7 +228,7 @@ module DNN
204
228
  y += @bias.data if @bias
205
229
  y
206
230
  end
207
-
231
+
208
232
  def backward(dy)
209
233
  if @trainable
210
234
  @weight.grad += @x.transpose.dot(dy)
@@ -212,22 +236,22 @@ module DNN
212
236
  end
213
237
  dy.dot(@weight.data.transpose)
214
238
  end
215
-
239
+
216
240
  def output_shape
217
241
  [@num_nodes]
218
242
  end
219
243
 
220
244
  def to_hash
221
- super({num_nodes: @num_nodes})
245
+ super(num_nodes: @num_nodes)
222
246
  end
223
247
  end
224
-
248
+
225
249
 
226
250
  class Flatten < Layer
227
251
  def forward(x)
228
252
  x.reshape(x.shape[0], *output_shape)
229
253
  end
230
-
254
+
231
255
  def backward(dy)
232
256
  dy.reshape(dy.shape[0], *@input_shape)
233
257
  end
@@ -261,50 +285,59 @@ module DNN
261
285
  end
262
286
 
263
287
  def to_hash
264
- super({output_shape: @output_shape})
288
+ super(output_shape: @output_shape)
265
289
  end
266
290
  end
267
291
 
268
-
292
+
269
293
  class Dropout < Layer
270
- # @return [Float] dropout ratio.
271
294
  attr_accessor :dropout_ratio
272
- # @return [Float] Use 'weight scaling inference rule'.
273
295
  attr_reader :use_scale
274
296
 
275
297
  def self.from_hash(hash)
276
298
  self.new(hash[:dropout_ratio], seed: hash[:seed], use_scale: hash[:use_scale])
277
299
  end
278
300
 
301
+ # @param [Float] dropout_ratio Nodes dropout ratio.
302
+ # @param [Integer] seed Seed of random number used for masking.
303
+ # @param [Boolean] use_scale Set to true to scale the output according to the dropout ratio.
279
304
  def initialize(dropout_ratio = 0.5, seed: rand(1 << 31), use_scale: true)
280
305
  super()
281
306
  @dropout_ratio = dropout_ratio
282
307
  @seed = seed
283
308
  @use_scale = use_scale
284
309
  @mask = nil
310
+ @rnd = Random.new(@seed)
285
311
  end
286
312
 
287
- def forward(x)
313
+ def call(input)
314
+ x, prev_link, learning_phase = *input
315
+ build(x.shape[1..-1]) unless built?
316
+ y = forward(x, learning_phase)
317
+ link = Link.new(prev_link, self)
318
+ [y, link, learning_phase]
319
+ end
320
+
321
+ def forward(x, learning_phase)
288
322
  if learning_phase
289
- Xumo::SFloat.srand(@seed)
323
+ Xumo::SFloat.srand(@rnd.rand(1 << 31))
290
324
  @mask = Xumo::SFloat.ones(*x.shape).rand < @dropout_ratio
291
325
  x[@mask] = 0
292
- else
293
- x *= (1 - @dropout_ratio) if @use_scale
326
+ elsif @use_scale
327
+ x *= (1 - @dropout_ratio)
294
328
  end
295
329
  x
296
330
  end
297
-
331
+
298
332
  def backward(dy)
299
333
  dy[@mask] = 0
300
334
  dy
301
335
  end
302
336
 
303
337
  def to_hash
304
- super({dropout_ratio: @dropout_ratio, seed: @seed, use_scale: @use_scale})
338
+ super(dropout_ratio: @dropout_ratio, seed: @seed, use_scale: @use_scale)
305
339
  end
306
340
  end
307
-
341
+
308
342
  end
309
-
310
343
  end