ruby-dnn 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Rakefile +10 -6
- data/lib/dnn/core/layers/basic_layers.rb +5 -14
- data/lib/dnn/core/layers/embedding.rb +22 -5
- data/lib/dnn/core/link.rb +20 -2
- data/lib/dnn/core/models.rb +201 -36
- data/lib/dnn/core/savers.rb +1 -1
- data/lib/dnn/core/tensor.rb +4 -0
- data/lib/dnn/keras-model-convertor.rb +170 -0
- data/lib/dnn/numo2numpy.rb +72 -0
- data/lib/dnn/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 62482330aab914fc53313fa7a1170a53ea3644e72dfe1cecb2fffca1160f6f7e
|
4
|
+
data.tar.gz: e21d0e28ba2c603179ece6fe9cb2c4722ea8bbaa85c72cc357f8b5fa691e33d0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5f7a34efbba1465aa4e245b83928bbbe764d67708e9e6c3faef7030b13873dc27f775adbf8b32b0f3015280f1b3bbb600a1e4c4bff9c1a5be40387217efc2e26
|
7
|
+
data.tar.gz: 9bba32d0b4142df90ab07b75191bb7590fa000f38470f9d9149f4cd95c346f5ba90fbf4549d714dee5374d1024184cf45eb61bcd1ba3e10e1ab0aa5eda398011
|
data/Rakefile
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
2
|
require "rake/testtask"
|
3
|
+
require "yard"
|
4
|
+
require "yard/rake/yardoc_task"
|
3
5
|
|
4
6
|
Rake::TestTask.new(:test) do |t|
|
5
7
|
t.libs << "test"
|
@@ -18,10 +20,12 @@ end
|
|
18
20
|
|
19
21
|
task :default => [:test, :build_rb_stb_image]
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
YARD::Rake::YardocTask.new do |t|
|
24
|
+
t.files = [
|
25
|
+
"lib/dnn.rb",
|
26
|
+
"lib/dnn/core/*.rb",
|
27
|
+
"lib/dnn/core/layers/*.rb",
|
28
|
+
"lib/dnn/*.rb",
|
29
|
+
"lib/dnn/datasets/*.rb",
|
30
|
+
]
|
27
31
|
end
|
@@ -7,6 +7,7 @@ module DNN
|
|
7
7
|
prev = (input.is_a?(Tensor) ? input.link : input)
|
8
8
|
y = forward_node(x)
|
9
9
|
link = Link.new(prev, self)
|
10
|
+
prev.next = link if prev.is_a?(Link)
|
10
11
|
Tensor.new(y, link)
|
11
12
|
end
|
12
13
|
|
@@ -77,6 +78,10 @@ module DNN
|
|
77
78
|
@input_shape
|
78
79
|
end
|
79
80
|
|
81
|
+
def <<(tensor)
|
82
|
+
self.(tensor)
|
83
|
+
end
|
84
|
+
|
80
85
|
# Layer to a hash.
|
81
86
|
def to_hash(merge_hash = nil)
|
82
87
|
hash = { class: self.class.name }
|
@@ -154,20 +159,6 @@ module DNN
|
|
154
159
|
method(:call).to_proc
|
155
160
|
end
|
156
161
|
|
157
|
-
def >>(layer)
|
158
|
-
if RUBY_VERSION < "2.6.0"
|
159
|
-
raise DNNError, "Function composition is not supported before ruby version 2.6.0."
|
160
|
-
end
|
161
|
-
to_proc >> layer
|
162
|
-
end
|
163
|
-
|
164
|
-
def <<(layer)
|
165
|
-
if RUBY_VERSION < "2.6.0"
|
166
|
-
raise DNNError, "Function composition is not supported before ruby version 2.6.0."
|
167
|
-
end
|
168
|
-
to_proc << layer
|
169
|
-
end
|
170
|
-
|
171
162
|
def to_hash
|
172
163
|
super(input_shape: @input_shape)
|
173
164
|
end
|
@@ -8,6 +8,7 @@ module DNN
|
|
8
8
|
attr_reader :weight
|
9
9
|
attr_reader :weight_initializer
|
10
10
|
attr_reader :weight_regularizer
|
11
|
+
attr_reader :mask_zero
|
11
12
|
|
12
13
|
# @param [Integer | Array] input_dim_or_shape Set input data dimension or shape.
|
13
14
|
# @param [Integer] input_length Set the time series length of input data.
|
@@ -15,13 +16,15 @@ module DNN
|
|
15
16
|
# @param [DNN::Regularizers::Regularizer | NilClass] weight_regularizer Weight regularizer.
|
16
17
|
def initialize(input_dim_or_shape, input_length,
|
17
18
|
weight_initializer: Initializers::RandomUniform.new,
|
18
|
-
weight_regularizer: nil
|
19
|
+
weight_regularizer: nil,
|
20
|
+
mask_zero: false)
|
19
21
|
super()
|
20
22
|
@input_shape = input_dim_or_shape.is_a?(Array) ? input_dim_or_shape : [input_dim_or_shape]
|
21
23
|
@input_length = input_length
|
22
24
|
@weight_initializer = weight_initializer
|
23
25
|
@weight_regularizer = weight_regularizer
|
24
26
|
@weight = Param.new(nil, Xumo::SFloat[0])
|
27
|
+
@mask_zero = mask_zero
|
25
28
|
end
|
26
29
|
|
27
30
|
def build(input_shape)
|
@@ -35,7 +38,14 @@ module DNN
|
|
35
38
|
@x = x
|
36
39
|
y = Xumo::SFloat.zeros(*x.shape)
|
37
40
|
x.shape[0].times do |i|
|
38
|
-
|
41
|
+
if @mask_zero
|
42
|
+
x.shape[1].times do |j|
|
43
|
+
index = x[i, j]
|
44
|
+
y[i, j] = index == 0 ? 0 : @weight.data[index]
|
45
|
+
end
|
46
|
+
else
|
47
|
+
y[i, false] = @weight.data[x[i, false]]
|
48
|
+
end
|
39
49
|
end
|
40
50
|
y
|
41
51
|
end
|
@@ -44,7 +54,12 @@ module DNN
|
|
44
54
|
@weight.grad += Xumo::SFloat.zeros(*@weight.data.shape)
|
45
55
|
@x.shape[0].times do |i|
|
46
56
|
@x.shape[1].times do |j|
|
47
|
-
@
|
57
|
+
index = @x[i, j]
|
58
|
+
if @mask_zero
|
59
|
+
@weight.grad[index] += dy[i, j] unless index == 0
|
60
|
+
else
|
61
|
+
@weight.grad[index] += dy[i, j]
|
62
|
+
end
|
48
63
|
end
|
49
64
|
end
|
50
65
|
nil
|
@@ -56,13 +71,15 @@ module DNN
|
|
56
71
|
|
57
72
|
def to_hash
|
58
73
|
super(input_shape: @input_shape, input_length: @input_length,
|
59
|
-
weight_initializer: @weight_initializer.to_hash, weight_regularizer: @weight_regularizer&.to_hash
|
74
|
+
weight_initializer: @weight_initializer.to_hash, weight_regularizer: @weight_regularizer&.to_hash,
|
75
|
+
mask_zero: @mask_zero)
|
60
76
|
end
|
61
77
|
|
62
78
|
def load_hash(hash)
|
63
79
|
initialize(hash[:input_shape], hash[:input_length],
|
64
80
|
weight_initializer: Initializers::Initializer.from_hash(hash[:weight_initializer]),
|
65
|
-
weight_regularizer: Regularizers::Regularizer.from_hash(hash[:weight_regularizer])
|
81
|
+
weight_regularizer: Regularizers::Regularizer.from_hash(hash[:weight_regularizer]),
|
82
|
+
mask_zero: hash[:mask_zero])
|
66
83
|
end
|
67
84
|
|
68
85
|
def get_params
|
data/lib/dnn/core/link.rb
CHANGED
@@ -1,14 +1,21 @@
|
|
1
1
|
module DNN
|
2
2
|
class Link
|
3
3
|
attr_accessor :prev
|
4
|
+
attr_accessor :next
|
4
5
|
attr_accessor :layer_node
|
5
6
|
|
6
7
|
def initialize(prev = nil, layer_node = nil)
|
7
8
|
@prev = prev
|
8
9
|
@layer_node = layer_node
|
10
|
+
@next = nil
|
9
11
|
end
|
10
12
|
|
11
|
-
def
|
13
|
+
def forward(x)
|
14
|
+
x = @layer_node.(x)
|
15
|
+
@next ? @next.forward(x) : x
|
16
|
+
end
|
17
|
+
|
18
|
+
def backward(dy = Xumo::SFloat[1])
|
12
19
|
dy = @layer_node.backward_node(dy)
|
13
20
|
@prev&.backward(dy)
|
14
21
|
end
|
@@ -17,15 +24,26 @@ module DNN
|
|
17
24
|
class TwoInputLink
|
18
25
|
attr_accessor :prev1
|
19
26
|
attr_accessor :prev2
|
27
|
+
attr_accessor :next
|
20
28
|
attr_accessor :layer_node
|
21
29
|
|
22
30
|
def initialize(prev1 = nil, prev2 = nil, layer_node = nil)
|
23
31
|
@prev1 = prev1
|
24
32
|
@prev2 = prev2
|
25
33
|
@layer_node = layer_node
|
34
|
+
@next = nil
|
35
|
+
@hold = []
|
36
|
+
end
|
37
|
+
|
38
|
+
def forward(x)
|
39
|
+
@hold << x
|
40
|
+
return if @hold.length < 2
|
41
|
+
x = @layer_node.(*@hold)
|
42
|
+
@hold = []
|
43
|
+
@next ? @next.forward(x) : x
|
26
44
|
end
|
27
45
|
|
28
|
-
def backward(dy =
|
46
|
+
def backward(dy = Xumo::SFloat[1])
|
29
47
|
dys = @layer_node.backward_node(dy)
|
30
48
|
if dys.is_a?(Array)
|
31
49
|
dy1, dy2 = *dys
|
data/lib/dnn/core/models.rb
CHANGED
@@ -72,12 +72,43 @@ module DNN
|
|
72
72
|
end
|
73
73
|
@layers_cache = layers_array
|
74
74
|
end
|
75
|
+
|
76
|
+
def to_hash
|
77
|
+
layers_hash = { class: self.class.name }
|
78
|
+
instance_variables.sort.each do |ivar|
|
79
|
+
obj = instance_variable_get(ivar)
|
80
|
+
if obj.is_a?(Layers::Layer) || obj.is_a?(Chain)
|
81
|
+
layers_hash[ivar] = obj.to_hash
|
82
|
+
elsif obj.is_a?(LayersList)
|
83
|
+
layers_hash[ivar] = obj.to_hash_list
|
84
|
+
end
|
85
|
+
end
|
86
|
+
layers_hash
|
87
|
+
end
|
88
|
+
|
89
|
+
def load_hash(layers_hash)
|
90
|
+
instance_variables.sort.each do |ivar|
|
91
|
+
hash_or_array = layers_hash[ivar]
|
92
|
+
if hash_or_array.is_a?(Array)
|
93
|
+
instance_variable_set(ivar, LayersList.from_hash_list(hash_or_array))
|
94
|
+
elsif hash_or_array.is_a?(Hash)
|
95
|
+
obj_class = DNN.const_get(hash_or_array[:class])
|
96
|
+
obj = obj_class.allocate
|
97
|
+
if obj.is_a?(Chain)
|
98
|
+
obj = obj_class.new
|
99
|
+
obj.load_hash(hash_or_array)
|
100
|
+
instance_variable_set(ivar, obj)
|
101
|
+
else
|
102
|
+
instance_variable_set(ivar, Layers::Layer.from_hash(hash_or_array))
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
75
107
|
end
|
76
108
|
|
77
109
|
# This class deals with the model of the network.
|
78
110
|
class Model < Chain
|
79
111
|
attr_accessor :optimizer
|
80
|
-
attr_accessor :loss_func
|
81
112
|
attr_reader :last_log
|
82
113
|
|
83
114
|
# Load marshal model.
|
@@ -100,9 +131,9 @@ module DNN
|
|
100
131
|
end
|
101
132
|
|
102
133
|
def call(input_tensors)
|
103
|
-
|
134
|
+
output_tensors = forward(input_tensors)
|
104
135
|
@built = true unless @built
|
105
|
-
|
136
|
+
output_tensors
|
106
137
|
end
|
107
138
|
|
108
139
|
# Set optimizer and loss_func to model.
|
@@ -112,11 +143,29 @@ module DNN
|
|
112
143
|
unless optimizer.is_a?(Optimizers::Optimizer)
|
113
144
|
raise TypeError, "optimizer:#{optimizer.class} is not an instance of DNN::Optimizers::Optimizer class."
|
114
145
|
end
|
115
|
-
unless loss_func.is_a?(Losses::Loss)
|
116
|
-
raise TypeError, "loss_func:#{loss_func.class} is not an instance of DNN::Losses::Loss class."
|
146
|
+
unless loss_func.is_a?(Losses::Loss) || loss_func.is_a?(Array)
|
147
|
+
raise TypeError, "loss_func:#{loss_func.class} is not an instance of DNN::Losses::Loss or Array class."
|
117
148
|
end
|
118
149
|
@optimizer = optimizer
|
119
|
-
|
150
|
+
self.loss_func = loss_func
|
151
|
+
end
|
152
|
+
|
153
|
+
def loss_func
|
154
|
+
@loss_func
|
155
|
+
end
|
156
|
+
|
157
|
+
def loss_func=(lfs)
|
158
|
+
if lfs.is_a?(Array)
|
159
|
+
@loss_func = []
|
160
|
+
lfs.each.with_index do |lf, i|
|
161
|
+
unless lf.is_a?(Losses::Loss)
|
162
|
+
raise TypeError, "loss_func[#{i}]:#{lf.class} is not an instance of DNN::Losses::Loss class."
|
163
|
+
end
|
164
|
+
@loss_func << lf
|
165
|
+
end
|
166
|
+
else
|
167
|
+
@loss_func = lfs
|
168
|
+
end
|
120
169
|
end
|
121
170
|
|
122
171
|
# Start training.
|
@@ -232,13 +281,28 @@ module DNN
|
|
232
281
|
check_xy_type(x, y)
|
233
282
|
call_callbacks(:before_train_on_batch)
|
234
283
|
DNN.learning_phase = true
|
235
|
-
|
236
|
-
|
237
|
-
|
284
|
+
output_tensors = call(Tensor.convert(x))
|
285
|
+
if output_tensors.is_a?(Array)
|
286
|
+
loss_data = []
|
287
|
+
output_tensors.each.with_index do |out, i|
|
288
|
+
loss = if i == 0
|
289
|
+
@loss_func[i].loss(out, Tensor.convert(y[i]), layers)
|
290
|
+
else
|
291
|
+
@loss_func[i].loss(out, Tensor.convert(y[i]))
|
292
|
+
end
|
293
|
+
loss_data << loss.data.to_f
|
294
|
+
loss.link.backward(Xumo::SFloat.ones(y[i][0...1, false].shape[0], 1))
|
295
|
+
end
|
296
|
+
else
|
297
|
+
out = output_tensors
|
298
|
+
loss = @loss_func.loss(out, Tensor.convert(y), layers)
|
299
|
+
loss_data = loss.data.to_f
|
300
|
+
loss.link.backward(Xumo::SFloat.ones(y[0...1, false].shape[0], 1))
|
301
|
+
end
|
238
302
|
@optimizer.update(get_all_trainable_params)
|
239
|
-
@last_log[:train_loss] =
|
303
|
+
@last_log[:train_loss] = loss_data
|
240
304
|
call_callbacks(:after_train_on_batch)
|
241
|
-
|
305
|
+
loss_data
|
242
306
|
end
|
243
307
|
|
244
308
|
# Evaluate model and get accuracy and loss of test data.
|
@@ -258,33 +322,66 @@ module DNN
|
|
258
322
|
def evaluate_by_iterator(test_iterator, batch_size: 100)
|
259
323
|
num_test_datas = test_iterator.num_datas
|
260
324
|
batch_size = batch_size >= num_test_datas ? num_test_datas : batch_size
|
261
|
-
|
262
|
-
|
325
|
+
if @loss_func.is_a?(Array)
|
326
|
+
total_correct = Array.new(@loss_func.length, 0)
|
327
|
+
sum_loss = Array.new(@loss_func.length, 0)
|
328
|
+
else
|
329
|
+
total_correct = 0
|
330
|
+
sum_loss = 0
|
331
|
+
end
|
263
332
|
max_steps = (num_test_datas.to_f / batch_size).ceil
|
264
333
|
test_iterator.foreach(batch_size) do |x_batch, y_batch|
|
265
334
|
correct, loss_value = test_on_batch(x_batch, y_batch)
|
266
|
-
|
267
|
-
|
335
|
+
if @loss_func.is_a?(Array)
|
336
|
+
@loss_func.each_index do |i|
|
337
|
+
total_correct[i] += correct[i]
|
338
|
+
sum_loss[i] += loss_value[i]
|
339
|
+
end
|
340
|
+
else
|
341
|
+
total_correct += correct
|
342
|
+
sum_loss += loss_value
|
343
|
+
end
|
344
|
+
end
|
345
|
+
if @loss_func.is_a?(Array)
|
346
|
+
mean_loss = Array.new(@loss_func.length, 0)
|
347
|
+
acc = Array.new(@loss_func.length, 0)
|
348
|
+
@loss_func.each_index do |i|
|
349
|
+
mean_loss[i] += sum_loss[i] / max_steps
|
350
|
+
acc[i] += total_correct[i].to_f / num_test_datas
|
351
|
+
end
|
352
|
+
else
|
353
|
+
mean_loss = sum_loss / max_steps
|
354
|
+
acc = total_correct.to_f / num_test_datas
|
268
355
|
end
|
269
|
-
mean_loss = sum_loss / max_steps
|
270
|
-
acc = total_correct.to_f / num_test_datas
|
271
356
|
@last_log[:test_loss] = mean_loss
|
272
357
|
@last_log[:test_accuracy] = acc
|
273
358
|
[acc, mean_loss]
|
274
359
|
end
|
275
360
|
|
276
361
|
# Evaluate once.
|
277
|
-
# @param [Numo::SFloat] x Input test data.
|
278
|
-
# @param [Numo::SFloat] y Output test data.
|
362
|
+
# @param [Numo::SFloat | Array] x Input test data.
|
363
|
+
# @param [Numo::SFloat | Array] y Output test data.
|
279
364
|
# @return [Array] Returns the test data accuracy and mean loss in the form [accuracy, mean_loss].
|
280
365
|
def test_on_batch(x, y)
|
281
366
|
call_callbacks(:before_test_on_batch)
|
282
367
|
DNN.learning_phase = false
|
283
|
-
|
284
|
-
|
285
|
-
|
368
|
+
output_tensors = call(Tensor.convert(x))
|
369
|
+
if output_tensors.is_a?(Array)
|
370
|
+
correct = []
|
371
|
+
loss_data = []
|
372
|
+
output_tensors.each.with_index do |out, i|
|
373
|
+
correct << accuracy(out.data, y[i])
|
374
|
+
loss = @loss_func[i].(out, Tensor.convert(y[i]))
|
375
|
+
loss_data << loss.data.to_f
|
376
|
+
end
|
377
|
+
else
|
378
|
+
out = output_tensors
|
379
|
+
correct = accuracy(out.data, y)
|
380
|
+
loss = @loss_func.(out, Tensor.convert(y))
|
381
|
+
loss_data = loss.data.to_f
|
382
|
+
end
|
286
383
|
call_callbacks(:after_test_on_batch)
|
287
|
-
[correct,
|
384
|
+
[correct, loss_data]
|
288
385
|
end
|
289
386
|
|
290
387
|
# Implement the process to accuracy this model.
|
@@ -313,12 +410,23 @@ module DNN
|
|
313
410
|
def predict(x, use_loss_activation: true)
|
314
411
|
check_xy_type(x)
|
315
412
|
DNN.learning_phase = false
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
413
|
+
output_tensors = call(Tensor.convert(x))
|
414
|
+
if output_tensors.is_a?(Array)
|
415
|
+
lfs = @loss_func
|
416
|
+
ary_output_tensors = output_tensors
|
417
|
+
else
|
418
|
+
lfs = [@loss_func]
|
419
|
+
ary_output_tensors = [output_tensors]
|
420
|
+
end
|
421
|
+
ys = []
|
422
|
+
ary_output_tensors.each.with_index do |out, i|
|
423
|
+
y = out.data
|
424
|
+
if use_loss_activation && lfs[i].class.respond_to?(:activation)
|
425
|
+
y = lfs[i].class.activation(y)
|
426
|
+
end
|
427
|
+
ys << y
|
320
428
|
end
|
321
|
-
|
429
|
+
output_tensors.is_a?(Array) ? ys : ys.first
|
322
430
|
end
|
323
431
|
|
324
432
|
# Predict one data.
|
@@ -433,16 +541,38 @@ module DNN
|
|
433
541
|
end
|
434
542
|
|
435
543
|
def metrics_to_str(mertics)
|
436
|
-
mertics.map { |key,
|
544
|
+
mertics.map { |key, values|
|
545
|
+
str_values = if values.is_a?(Array)
|
546
|
+
values_fmt = values.map { |v| sprintf('%.4f', v) }
|
547
|
+
"[#{values_fmt.join(", ")}]"
|
548
|
+
else
|
549
|
+
sprintf('%.4f', values)
|
550
|
+
end
|
551
|
+
"#{key}: #{str_values}"
|
552
|
+
}.join(", ")
|
437
553
|
end
|
438
554
|
|
439
555
|
def check_xy_type(x, y = nil)
|
440
556
|
if !x.is_a?(Xumo::SFloat) && !x.is_a?(Array)
|
441
557
|
raise TypeError, "x:#{x.class.name} is not an instance of #{Xumo::SFloat.name} class or Array class."
|
442
558
|
end
|
559
|
+
if x.is_a?(Array)
|
560
|
+
x.each.with_index do |v, i|
|
561
|
+
unless v.is_a?(Xumo::SFloat)
|
562
|
+
raise TypeError, "x[#{i}]:#{v.class.name} is not an instance of #{Xumo::SFloat.name} class."
|
563
|
+
end
|
564
|
+
end
|
565
|
+
end
|
443
566
|
if y && !y.is_a?(Xumo::SFloat) && !y.is_a?(Array)
|
444
567
|
raise TypeError, "y:#{y.class.name} is not an instance of #{Xumo::SFloat.name} class or Array class."
|
445
568
|
end
|
569
|
+
if y.is_a?(Array)
|
570
|
+
y.each.with_index do |v, i|
|
571
|
+
unless v.is_a?(Xumo::SFloat)
|
572
|
+
raise TypeError, "x[#{i}]:#{v.class.name} is not an instance of #{Xumo::SFloat.name} class."
|
573
|
+
end
|
574
|
+
end
|
575
|
+
end
|
446
576
|
end
|
447
577
|
end
|
448
578
|
|
@@ -459,14 +589,14 @@ module DNN
|
|
459
589
|
end
|
460
590
|
|
461
591
|
# Add layer to the model.
|
462
|
-
# @param [DNN::Layers::Layer] layer Layer to add to the model.
|
592
|
+
# @param [DNN::Layers::Layer | DNN::Models::Chain] layer Layer or Chain to add to the model.
|
463
593
|
# @return [DNN::Models::Model] Return self.
|
464
594
|
def add(layer)
|
465
595
|
if layer.is_a?(Layers::MergeLayer)
|
466
596
|
raise TypeError, "layer: #{layer.class.name} should not be a DNN::Layers::MergeLayer class."
|
467
597
|
end
|
468
|
-
unless layer.is_a?(Layers::Layer) || layer.is_a?(
|
469
|
-
raise TypeError, "layer: #{layer.class.name} is not an instance of the DNN::Layers::Layer class or DNN::Models::
|
598
|
+
unless layer.is_a?(Layers::Layer) || layer.is_a?(Chain)
|
599
|
+
raise TypeError, "layer: #{layer.class.name} is not an instance of the DNN::Layers::Layer class or DNN::Models::Chain class."
|
470
600
|
end
|
471
601
|
@stack << layer
|
472
602
|
self
|
@@ -475,20 +605,20 @@ module DNN
|
|
475
605
|
alias << add
|
476
606
|
|
477
607
|
# Insert layer to the model by index position.
|
478
|
-
# @param [DNN::Layers::Layer] layer Layer to add to the model.
|
608
|
+
# @param [DNN::Layers::Layer | DNN::Models::Chain] layer Layer or Chain to add to the model.
|
479
609
|
# @return [DNN::Models::Model] Return self.
|
480
610
|
def insert(index, layer)
|
481
611
|
if layer.is_a?(Layers::MergeLayer)
|
482
612
|
raise TypeError, "layer: #{layer.class.name} should not be a DNN::Layers::MergeLayer class."
|
483
613
|
end
|
484
|
-
unless layer.is_a?(Layers::Layer) || layer.is_a?(
|
485
|
-
raise TypeError, "layer: #{layer.class.name} is not an instance of the DNN::Layers::Layer class or DNN::Models::
|
614
|
+
unless layer.is_a?(Layers::Layer) || layer.is_a?(Chain)
|
615
|
+
raise TypeError, "layer: #{layer.class.name} is not an instance of the DNN::Layers::Layer class or DNN::Models::Chain class."
|
486
616
|
end
|
487
617
|
@stack.insert(index, layer)
|
488
618
|
end
|
489
619
|
|
490
620
|
# Remove layer to the model.
|
491
|
-
# @param [DNN::Layers::Layer] layer Layer to remove to the model.
|
621
|
+
# @param [DNN::Layers::Layer | DNN::Models::Chain] layer Layer to remove to the model.
|
492
622
|
# @return [Boolean] Return true if success for remove layer.
|
493
623
|
def remove(layer)
|
494
624
|
@stack.delete(layer) ? true : false
|
@@ -502,5 +632,40 @@ module DNN
|
|
502
632
|
end
|
503
633
|
end
|
504
634
|
|
635
|
+
class FixedModel < Model
|
636
|
+
attr_reader :layers
|
637
|
+
|
638
|
+
def initialize(output_tensor, layers)
|
639
|
+
super()
|
640
|
+
@input_link = get_input_link(output_tensor.link)
|
641
|
+
@layers = layers
|
642
|
+
end
|
643
|
+
|
644
|
+
def forward(input_tensors)
|
645
|
+
if input_tensors.is_a?(Array)
|
646
|
+
input_tensors.each do |tensor|
|
647
|
+
@input_link.forward(tensor)
|
648
|
+
end
|
649
|
+
else
|
650
|
+
@input_link.forward(input_tensors)
|
651
|
+
end
|
652
|
+
end
|
653
|
+
|
654
|
+
private
|
655
|
+
|
656
|
+
def get_input_link(last_link)
|
657
|
+
get_input_link = -> link do
|
658
|
+
if link.is_a?(Link)
|
659
|
+
return link unless link.prev
|
660
|
+
get_input_link.(link.prev)
|
661
|
+
else
|
662
|
+
return link unless link.prev1
|
663
|
+
get_input_link.(link.prev1)
|
664
|
+
end
|
665
|
+
end
|
666
|
+
get_input_link.(last_link)
|
667
|
+
end
|
668
|
+
end
|
669
|
+
|
505
670
|
end
|
506
671
|
end
|
data/lib/dnn/core/savers.rb
CHANGED
@@ -97,7 +97,7 @@ module DNN
|
|
97
97
|
if @include_model
|
98
98
|
@model.clean_layers
|
99
99
|
data = {
|
100
|
-
version: VERSION, class: @model.class.name,
|
100
|
+
version: VERSION, class: @model.class.name,
|
101
101
|
params: params_data, model: @model
|
102
102
|
}
|
103
103
|
else
|
data/lib/dnn/core/tensor.rb
CHANGED
@@ -0,0 +1,170 @@
|
|
1
|
+
# This library is not yet complete.
|
2
|
+
|
3
|
+
# This library converts keras models to ruby-dnn models.
|
4
|
+
# Use of the library requires the installation of PyCall.
|
5
|
+
|
6
|
+
require "pycall/import"
|
7
|
+
require "numpy"
|
8
|
+
require_relative "numo2numpy"
|
9
|
+
|
10
|
+
include PyCall::Import
|
11
|
+
|
12
|
+
pyimport :numpy, as: :np
|
13
|
+
pyimport :keras
|
14
|
+
pyfrom :"keras.models", import: :Sequential
|
15
|
+
pyfrom :"keras.layers", import: [:Dense, :Dropout, :Conv2D, :Activation, :MaxPooling2D, :Flatten]
|
16
|
+
pyfrom :"keras.layers.normalization", import: :BatchNormalization
|
17
|
+
|
18
|
+
class DNNKerasModelConvertError < DNN::DNNError; end
|
19
|
+
|
20
|
+
class KerasModelConvertor
|
21
|
+
pyfrom :"keras.models", import: :load_model
|
22
|
+
|
23
|
+
def self.k_load_model(k_model_name, k_weights_name)
|
24
|
+
model = load_model(k_model_name)
|
25
|
+
model.load_weights(k_weights_name) if k_weights_name
|
26
|
+
model
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(k_model_name, k_weights_name = nil)
|
30
|
+
@k_model = KerasModelConvertor.k_load_model(k_model_name, k_weights_name)
|
31
|
+
end
|
32
|
+
|
33
|
+
def convert
|
34
|
+
unless @k_model.__class__.__name__ == "Sequential"
|
35
|
+
raise DNNKerasModelConvertError.new("#{@k_model.__class__.__name__} models do not support convert.")
|
36
|
+
end
|
37
|
+
dnn_model = DNN::Models::Sequential.new
|
38
|
+
@k_model.layers.each do |k_layer|
|
39
|
+
dnn_layer = layer_convert(k_layer)
|
40
|
+
dnn_model << dnn_layer if dnn_layer
|
41
|
+
end
|
42
|
+
input_shape = @k_model.layers[0].input_shape.to_a[1..-1]
|
43
|
+
input_layer = DNN::Layers::InputLayer.new(input_shape)
|
44
|
+
input_layer.build(input_shape)
|
45
|
+
dnn_model.insert(0, input_layer)
|
46
|
+
dnn_model
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def layer_convert(k_layer)
|
52
|
+
k_layer_name = k_layer.__class__.__name__
|
53
|
+
method_name = "convert_" + k_layer_name
|
54
|
+
if respond_to?(method_name, true)
|
55
|
+
send(method_name, k_layer)
|
56
|
+
else
|
57
|
+
raise DNNKerasModelConvertError.new("#{k_layer_name} layer do not support convert.")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def get_k_layer_shape(k_layer)
|
62
|
+
input_shape = k_layer.input_shape.to_a[1..-1]
|
63
|
+
output_shape = k_layer.output_shape.to_a[1..-1]
|
64
|
+
[input_shape, output_shape]
|
65
|
+
end
|
66
|
+
|
67
|
+
def build_dnn_layer(k_layer, dnn_layer)
|
68
|
+
input_shape, output_shape = get_k_layer_shape(k_layer)
|
69
|
+
dnn_layer.build(input_shape)
|
70
|
+
end
|
71
|
+
|
72
|
+
def convert_Dense(k_dense)
|
73
|
+
input_shape, output_shape = get_k_layer_shape(k_dense)
|
74
|
+
dense = DNN::Layers::Dense.new(output_shape[0])
|
75
|
+
dense.build(input_shape)
|
76
|
+
dense.weight.data = Numpy.to_na(k_dense.get_weights[0])
|
77
|
+
dense.bias.data = Numpy.to_na(k_dense.get_weights[1])
|
78
|
+
dense
|
79
|
+
end
|
80
|
+
|
81
|
+
def convert_Activation(k_activation)
|
82
|
+
activation_name = k_activation.get_config[:activation].to_s
|
83
|
+
case k_activation.get_config[:activation].to_s
|
84
|
+
when "sigmoid"
|
85
|
+
activation = DNN::Layers::Sigmoid.new
|
86
|
+
when "tanh"
|
87
|
+
activation = DNN::Layers::Tanh.new
|
88
|
+
when "relu"
|
89
|
+
activation = DNN::Layers::ReLU.new
|
90
|
+
when "softmax"
|
91
|
+
return nil
|
92
|
+
else
|
93
|
+
raise DNNKerasModelConvertError.new("#{activation_name} activation do not support convert.")
|
94
|
+
end
|
95
|
+
build_dnn_layer(k_activation, activation)
|
96
|
+
activation
|
97
|
+
end
|
98
|
+
|
99
|
+
def convert_Dropout(k_dropout)
|
100
|
+
dropout_ratio = k_dropout.get_config[:rate]
|
101
|
+
dropout = DNN::Layers::Dropout.new(dropout_ratio, use_scale: false)
|
102
|
+
build_dnn_layer(k_dropout, dropout)
|
103
|
+
dropout
|
104
|
+
end
|
105
|
+
|
106
|
+
def convert_BatchNormalization(k_batch_norm)
|
107
|
+
momentum = k_batch_norm.get_config[momentum]
|
108
|
+
batch_norm = DNN::Layers::BatchNormalization.new(momentum: momentum)
|
109
|
+
build_dnn_layer(k_batch_norm, batch_norm)
|
110
|
+
batch_norm.gamma.data = Numpy.to_na(k_batch_norm.get_weights[0])
|
111
|
+
batch_norm.beta.data = Numpy.to_na(k_batch_norm.get_weights[1])
|
112
|
+
batch_norm.running_mean.data = Numpy.to_na(k_batch_norm.get_weights[2])
|
113
|
+
batch_norm.running_var.data = Numpy.to_na(k_batch_norm.get_weights[3])
|
114
|
+
batch_norm
|
115
|
+
end
|
116
|
+
|
117
|
+
def convert_Conv2D(k_conv2d)
|
118
|
+
padding = k_conv2d.get_config[:padding].to_s == "same" ? true : false
|
119
|
+
filter_size = k_conv2d.get_config[:kernel_size].to_a
|
120
|
+
strides = k_conv2d.get_config[:strides].to_a
|
121
|
+
num_filters = k_conv2d.get_config[:filters]
|
122
|
+
conv2d = DNN::Layers::Conv2D.new(num_filters, filter_size, padding: padding, strides: strides)
|
123
|
+
build_dnn_layer(k_conv2d, conv2d)
|
124
|
+
conv2d.filters = Numpy.to_na(k_conv2d.get_weights[0])
|
125
|
+
conv2d.bias.data = Numpy.to_na(k_conv2d.get_weights[1])
|
126
|
+
conv2d
|
127
|
+
end
|
128
|
+
|
129
|
+
def convert_Conv2DTranspose(k_conv2d)
|
130
|
+
padding = k_conv2d.get_config[:padding].to_s == "same" ? true : false
|
131
|
+
filter_size = k_conv2d.get_config[:kernel_size].to_a
|
132
|
+
strides = k_conv2d.get_config[:strides].to_a
|
133
|
+
num_filters = k_conv2d.get_config[:filters]
|
134
|
+
conv2d = DNN::Layers::Conv2DTranspose.new(num_filters, filter_size, padding: padding, strides: strides)
|
135
|
+
build_dnn_layer(k_conv2d, conv2d)
|
136
|
+
conv2d.filters = Numpy.to_na(k_conv2d.get_weights[0])
|
137
|
+
conv2d.bias.data = Numpy.to_na(k_conv2d.get_weights[1])
|
138
|
+
conv2d
|
139
|
+
end
|
140
|
+
|
141
|
+
def convert_MaxPooling2D(k_max_pool2d)
|
142
|
+
padding = k_max_pool2d.get_config[:padding].to_s == "same" ? true : false
|
143
|
+
pool_size = k_max_pool2d.get_config[:pool_size].to_a
|
144
|
+
strides = k_max_pool2d.get_config[:strides].to_a
|
145
|
+
max_pool2d = DNN::Layers::MaxPool2D.new(pool_size, padding: padding, strides: strides)
|
146
|
+
build_dnn_layer(k_max_pool2d, max_pool2d)
|
147
|
+
max_pool2d
|
148
|
+
end
|
149
|
+
|
150
|
+
def convert_UpSampling2D(k_upsampling2d)
|
151
|
+
input_shape, output_shape = get_k_layer_shape(k_upsampling2d)
|
152
|
+
unpool_size = k_upsampling2d.get_config[:size].to_a
|
153
|
+
unpool2d = DNN::Layers::UnPool2D.new(unpool_size)
|
154
|
+
build_dnn_layer(k_upsampling2d, unpool2d)
|
155
|
+
unpool2d
|
156
|
+
end
|
157
|
+
|
158
|
+
def convert_Flatten(k_flatten)
|
159
|
+
flatten = DNN::Layers::Flatten.new
|
160
|
+
build_dnn_layer(k_flatten, flatten)
|
161
|
+
flatten
|
162
|
+
end
|
163
|
+
|
164
|
+
def convert_Reshape(k_reshape)
|
165
|
+
input_shape, output_shape = get_k_layer_shape(k_reshape)
|
166
|
+
reshape = DNN::Layers::Reshape.new(output_shape)
|
167
|
+
build_dnn_layer(k_reshape, reshape)
|
168
|
+
reshape
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# This library is a performs mutual conversion between Numo and Numpy.
|
2
|
+
# You need to install PyCall to use this library.
|
3
|
+
# [Usage]
|
4
|
+
# numpy to numo: Numpy.to_na(np_array)
|
5
|
+
# numo to numpy: Numpy.from_na(narray)
|
6
|
+
|
7
|
+
require "pycall/import"
|
8
|
+
require "numpy"
|
9
|
+
|
10
|
+
include PyCall::Import
|
11
|
+
|
12
|
+
class NumpyToNumoError < StandardError; end
|
13
|
+
|
14
|
+
module Numpy
|
15
|
+
def self.from_na(narray)
|
16
|
+
bin = narray.to_binary
|
17
|
+
bin.force_encoding("ASCII-8BIT")
|
18
|
+
case
|
19
|
+
when narray.is_a?(Numo::Int8)
|
20
|
+
Numpy.frombuffer(bin, dtype: "int8").reshape(*narray.shape)
|
21
|
+
when narray.is_a?(Numo::UInt8)
|
22
|
+
Numpy.frombuffer(bin, dtype: "uint8").reshape(*narray.shape)
|
23
|
+
when narray.is_a?(Numo::Int16)
|
24
|
+
Numpy.frombuffer(bin, dtype: "int16").reshape(*narray.shape)
|
25
|
+
when narray.is_a?(Numo::UInt16)
|
26
|
+
Numpy.frombuffer(bin, dtype: "uint16").reshape(*narray.shape)
|
27
|
+
when narray.is_a?(Numo::Int32)
|
28
|
+
Numpy.frombuffer(bin, dtype: "int32").reshape(*narray.shape)
|
29
|
+
when narray.is_a?(Numo::UInt32)
|
30
|
+
Numpy.frombuffer(bin, dtype: "uint32").reshape(*narray.shape)
|
31
|
+
when narray.is_a?(Numo::Int64)
|
32
|
+
Numpy.frombuffer(bin, dtype: "int64").reshape(*narray.shape)
|
33
|
+
when narray.is_a?(Numo::UInt64)
|
34
|
+
Numpy.frombuffer(bin, dtype: "uint64").reshape(*narray.shape)
|
35
|
+
when narray.is_a?(Numo::SFloat)
|
36
|
+
Numpy.frombuffer(bin, dtype: "float32").reshape(*narray.shape)
|
37
|
+
when narray.is_a?(Numo::DFloat)
|
38
|
+
Numpy.frombuffer(bin, dtype: "float64").reshape(*narray.shape)
|
39
|
+
else
|
40
|
+
raise NumpyToNumoError.new("#{narray.class.name} is not support convert.")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.to_na(ndarray)
|
45
|
+
shape = ndarray.shape
|
46
|
+
bin = ndarray.flatten.tobytes
|
47
|
+
case ndarray.dtype.to_s
|
48
|
+
when "int8"
|
49
|
+
Numo::Int8.from_binary(bin).reshape(*shape)
|
50
|
+
when "uint8"
|
51
|
+
Numo::UInt8.from_binary(bin).reshape(*shape)
|
52
|
+
when "int16"
|
53
|
+
Numo::Int16.from_binary(bin).reshape(*shape)
|
54
|
+
when "uint16"
|
55
|
+
Numo::UInt16.from_binary(bin).reshape(*shape)
|
56
|
+
when "int32"
|
57
|
+
Numo::Int32.from_binary(bin).reshape(*shape)
|
58
|
+
when "uint32"
|
59
|
+
Numo::UInt32.from_binary(bin).reshape(*shape)
|
60
|
+
when "int64"
|
61
|
+
Numo::Int64.from_binary(bin).reshape(*shape)
|
62
|
+
when "uint64"
|
63
|
+
Numo::UInt64.from_binary(bin).reshape(*shape)
|
64
|
+
when "float32"
|
65
|
+
Numo::SFloat.from_binary(bin).reshape(*shape)
|
66
|
+
when "float64"
|
67
|
+
Numo::DFloat.from_binary(bin).reshape(*shape)
|
68
|
+
else
|
69
|
+
raise NumpyToNumoError.new("#{ndarray.dtype} is not support convert.")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/lib/dnn/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-dnn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- unagiootoro
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-02-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: numo-narray
|
@@ -150,6 +150,8 @@ files:
|
|
150
150
|
- lib/dnn/datasets/mnist.rb
|
151
151
|
- lib/dnn/datasets/stl-10.rb
|
152
152
|
- lib/dnn/image.rb
|
153
|
+
- lib/dnn/keras-model-convertor.rb
|
154
|
+
- lib/dnn/numo2numpy.rb
|
153
155
|
- lib/dnn/version.rb
|
154
156
|
- ruby-dnn.gemspec
|
155
157
|
- third_party/stb_image.h
|