Class: DNN::Model
- Inherits:
-
Object
- Object
- DNN::Model
- Defined in:
- lib/dnn/core/model.rb
Overview
This class deals with the model of the network.
Instance Attribute Summary collapse
-
#layers ⇒ Array
All layers possessed by the model.
-
#trainable ⇒ Bool
Setting false prevents learning of parameters.
Class Method Summary collapse
-
.load(file_name) ⇒ Object
Load marshal model.
- .load_hash(hash) ⇒ Object
-
.load_json(json_str) ⇒ DNN::Model
Load json model.
Instance Method Summary collapse
-
#<<(layer) ⇒ DNN::Model
Add layer to the model.
-
#accurate(x, y, batch_size = 100) {|x, y| ... } ⇒ Object
Evaluate model and get accurate of test data.
- #backward(dout) ⇒ Object
- #build(super_model = nil) ⇒ Object
-
#compile(optimizer, loss) ⇒ Object
Set optimizer and loss to model and build all layers.
-
#compiled? ⇒ Bool
Returns whether the model is learning.
-
#copy ⇒ DNN::Model
Copy this model.
-
#forward(x, learning_phase) ⇒ Object
TODO It is not good to write the Layer class name directly in the Model class.
-
#get_all_layers ⇒ Array
Get the all layers.
-
#get_layer(*args) ⇒ Object
Get the layer that the model has.
- #get_prev_layer(layer) ⇒ Object
-
#initialize ⇒ Model
constructor
A new instance of Model.
-
#input_shape ⇒ Array
Return the input shape of the model.
-
#load_json_params(json_str) ⇒ Object
Load json model parameters.
-
#loss ⇒ DNN::Losses::Loss
Loss Return the loss to use for learning.
-
#optimizer ⇒ DNN::Optimizers::Optimizer
Optimizer Return the optimizer to use for learning.
-
#output_shape ⇒ Array
Return the output shape of the model.
-
#params_to_json ⇒ String
Convert model parameters to json string.
-
#predict(x) ⇒ Object
Predict data.
-
#predict1(x) ⇒ Object
Predict one data.
-
#recompile(optimizer, loss) ⇒ Object
Set optimizer and loss to model and recompile.
-
#save(file_name) ⇒ Object
Save the model in marshal format.
- #to_hash ⇒ Object
-
#to_json ⇒ String
Convert model to json string.
-
#train(x, y, epochs, batch_size: 1, test: nil, verbose: true, batch_proc: nil) {|epoch| ... } ⇒ Object
Start training.
-
#train_on_batch(x, y) {|x, y| ... } ⇒ Float | Numo::SFloat
Training once.
- #update ⇒ Object
Constructor Details
#initialize ⇒ Model
Returns a new instance of Model.
36 37 38 39 40 41 |
# File 'lib/dnn/core/model.rb', line 36 def initialize @layers = [] @trainable = true @optimizer = nil @compiled = false end |
Instance Attribute Details
#layers ⇒ Array
Returns All layers possessed by the model.
10 11 12 |
# File 'lib/dnn/core/model.rb', line 10 def layers @layers end |
#trainable ⇒ Bool
Returns Setting false prevents learning of parameters.
12 13 14 |
# File 'lib/dnn/core/model.rb', line 12 def trainable @trainable end |
Class Method Details
.load(file_name) ⇒ Object
Load marshal model.
16 17 18 |
# File 'lib/dnn/core/model.rb', line 16 def self.load(file_name) Marshal.load(Zlib::Inflate.inflate(File.binread(file_name))) end |
.load_hash(hash) ⇒ Object
30 31 32 33 34 |
# File 'lib/dnn/core/model.rb', line 30 def self.load_hash(hash) model = self.new model.layers = hash[:layers].map { |hash_layer| Utils.load_hash(hash_layer) } model end |
.load_json(json_str) ⇒ DNN::Model
Load json model.
23 24 25 26 27 28 |
# File 'lib/dnn/core/model.rb', line 23 def self.load_json(json_str) hash = JSON.parse(json_str, symbolize_names: true) model = self.load_hash(hash) model.compile(Utils.load_hash(hash[:optimizer]), Utils.load_hash(hash[:loss])) model end |
Instance Method Details
#<<(layer) ⇒ DNN::Model
Add layer to the model.
99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/dnn/core/model.rb', line 99 def <<(layer) # Due to a bug in saving nested models, temporarily prohibit model nesting. # if !layer.is_a?(Layers::Layer) && !layer.is_a?(Model) # raise TypeError.new("layer is not an instance of the DNN::Layers::Layer class or DNN::Model class.") # end unless layer.is_a?(Layers::Layer) raise TypeError.new("layer:#{layer.class.name} is not an instance of the DNN::Layers::Layer class.") end @layers << layer self end |
#accurate(x, y, batch_size = 100) {|x, y| ... } ⇒ Object
Evaluate model and get accurate of test data.
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 |
# File 'lib/dnn/core/model.rb', line 271 def accurate(x, y, batch_size = 100, &batch_proc) check_xy_type(x, y) input_data_shape_check(x, y) batch_size = batch_size >= x.shape[0] ? x.shape[0] : batch_size correct = 0 (x.shape[0].to_f / batch_size).ceil.times do |i| x_batch = Xumo::SFloat.zeros(batch_size, *x.shape[1..-1]) y_batch = Xumo::SFloat.zeros(batch_size, *y.shape[1..-1]) batch_size.times do |j| k = i * batch_size + j break if k >= x.shape[0] x_batch[j, false] = x[k, false] y_batch[j, false] = y[k, false] end x_batch, y_batch = batch_proc.call(x_batch, y_batch) if batch_proc out = forward(x_batch, false) batch_size.times do |j| if @layers.last.output_shape == [1] correct += 1 if out[j, 0].round == y_batch[j, 0].round else correct += 1 if out[j, true].max_index == y_batch[j, true].max_index end end end correct.to_f / x.shape[0] end |
#backward(dout) ⇒ Object
350 351 352 353 354 355 |
# File 'lib/dnn/core/model.rb', line 350 def backward(dout) @layers.reverse.each do |layer| dout = layer.backward(dout) end dout end |
#build(super_model = nil) ⇒ Object
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
# File 'lib/dnn/core/model.rb', line 147 def build(super_model = nil) @super_model = super_model shape = if super_model super_model.output_shape else @layers.first.build end @layers[1..-1].each do |layer| if layer.is_a?(Model) layer.build(self) else layer.build(shape) end shape = layer.output_shape end end |
#compile(optimizer, loss) ⇒ Object
Set optimizer and loss to model and build all layers.
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/dnn/core/model.rb', line 114 def compile(optimizer, loss) raise DNN_Error.new("The model is already compiled.") if compiled? unless optimizer.is_a?(Optimizers::Optimizer) raise TypeError.new("optimizer:#{optimizer.class} is not an instance of DNN::Optimizers::Optimizer class.") end unless loss.is_a?(Losses::Loss) raise TypeError.new("loss:#{loss.class} is not an instance of DNN::Losses::Loss class.") end @compiled = true layers_check @optimizer = optimizer @loss = loss build layers_shape_check end |
#compiled? ⇒ Bool
Returns whether the model is learning.
187 188 189 |
# File 'lib/dnn/core/model.rb', line 187 def compiled? @compiled end |
#copy ⇒ DNN::Model
Returns Copy this model.
314 315 316 |
# File 'lib/dnn/core/model.rb', line 314 def copy Marshal.load(Marshal.dump(self)) end |
#forward(x, learning_phase) ⇒ Object
TODO It is not good to write the Layer class name directly in the Model class. I will fix it later.
339 340 341 342 343 344 345 346 347 348 |
# File 'lib/dnn/core/model.rb', line 339 def forward(x, learning_phase) @layers.each do |layer| x = if layer.is_a?(Layers::Dropout) || layer.is_a?(Layers::BatchNormalization) || layer.is_a?(Model) layer.forward(x, learning_phase) else layer.forward(x) end end x end |
#get_all_layers ⇒ Array
Get the all layers.
331 332 333 334 335 |
# File 'lib/dnn/core/model.rb', line 331 def get_all_layers @layers.map { |layer| layer.is_a?(Model) ? layer.get_all_layers : layer }.flatten end |
#get_layer(*args) ⇒ Object
Get the layer that the model has.
319 320 321 322 323 324 325 326 327 |
# File 'lib/dnn/core/model.rb', line 319 def get_layer(*args) if args.length == 1 index = args[0] @layers[index] else layer_class, index = args @layers.select { |layer| layer.is_a?(layer_class) }[index] end end |
#get_prev_layer(layer) ⇒ Object
368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 |
# File 'lib/dnn/core/model.rb', line 368 def get_prev_layer(layer) layer_index = @layers.index(layer) prev_layer = if layer_index == 0 if @super_model @super_model.layers[@super_model.layers.index(self) - 1] else self end else @layers[layer_index - 1] end if prev_layer.is_a?(Layers::Layer) prev_layer elsif prev_layer.is_a?(Model) prev_layer.layers.last end end |
#input_shape ⇒ Array
Return the input shape of the model.
165 166 167 |
# File 'lib/dnn/core/model.rb', line 165 def input_shape @layers.first.input_shape end |
#load_json_params(json_str) ⇒ Object
Load json model parameters.
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/dnn/core/model.rb', line 45 def load_json_params(json_str) hash = JSON.parse(json_str, symbolize_names: true) has_param_layers_params = hash[:params] has_param_layers_index = 0 has_param_layers = get_all_layers.select { |layer| layer.is_a?(Layers::HasParamLayer) } has_param_layers.each do |layer| hash_params = has_param_layers_params[has_param_layers_index] hash_params.each do |key, (shape, base64_param)| bin = Base64.decode64(base64_param) data = Xumo::SFloat.from_binary(bin).reshape(*shape) layer.params[key].data = data end has_param_layers_index += 1 end end |
#loss ⇒ DNN::Losses::Loss
Returns loss Return the loss to use for learning.
181 182 183 184 |
# File 'lib/dnn/core/model.rb', line 181 def loss raise DNN_Error.new("The model is not compiled.") unless compiled? @loss ? @loss : @super_model.loss end |
#optimizer ⇒ DNN::Optimizers::Optimizer
Returns optimizer Return the optimizer to use for learning.
175 176 177 178 |
# File 'lib/dnn/core/model.rb', line 175 def optimizer raise DNN_Error.new("The model is not compiled.") unless compiled? @optimizer ? @optimizer : @super_model.optimizer end |
#output_shape ⇒ Array
Return the output shape of the model.
170 171 172 |
# File 'lib/dnn/core/model.rb', line 170 def output_shape @layers.last.output_shape end |
#params_to_json ⇒ String
Convert model parameters to json string.
84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/dnn/core/model.rb', line 84 def params_to_json has_param_layers = get_all_layers.select { |layer| layer.is_a?(Layers::HasParamLayer) } has_param_layers_params = has_param_layers.map do |layer| layer.params.map { |key, param| base64_data = Base64.encode64(param.data.to_binary) [key, [param.data.shape, base64_data]] }.to_h end hash = {version: VERSION, params: has_param_layers_params} JSON.dump(hash) end |
#predict(x) ⇒ Object
Predict data.
300 301 302 303 304 |
# File 'lib/dnn/core/model.rb', line 300 def predict(x) check_xy_type(x) input_data_shape_check(x) forward(x, false) end |
#predict1(x) ⇒ Object
Predict one data.
308 309 310 311 |
# File 'lib/dnn/core/model.rb', line 308 def predict1(x) check_xy_type(x) predict(x.reshape(1, *x.shape))[0, false] end |
#recompile(optimizer, loss) ⇒ Object
Set optimizer and loss to model and recompile. But does not build layers.
133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/dnn/core/model.rb', line 133 def recompile(optimizer, loss) unless optimizer.is_a?(Optimizers::Optimizer) raise TypeError.new("optimizer:#{optimizer.class} is not an instance of DNN::Optimizers::Optimizer class.") end unless loss.is_a?(Losses::Loss) raise TypeError.new("loss:#{loss.class} is not an instance of DNN::Losses::Loss class.") end @compiled = true layers_check @optimizer = optimizer @loss = loss layers_shape_check end |
#save(file_name) ⇒ Object
Save the model in marshal format.
63 64 65 66 67 68 69 70 71 72 |
# File 'lib/dnn/core/model.rb', line 63 def save(file_name) bin = Zlib::Deflate.deflate(Marshal.dump(self)) begin File.binwrite(file_name, bin) rescue Errno::ENOENT => ex dir_name = file_name.match(%r`(.*)/.+$`)[1] Dir.mkdir(dir_name) File.binwrite(file_name, bin) end end |
#to_hash ⇒ Object
386 387 388 389 |
# File 'lib/dnn/core/model.rb', line 386 def to_hash hash_layers = @layers.map { |layer| layer.to_hash } {class: Model.name, layers: hash_layers, optimizer: @optimizer.to_hash, loss: @loss.to_hash} end |
#to_json ⇒ String
Convert model to json string.
76 77 78 79 80 |
# File 'lib/dnn/core/model.rb', line 76 def to_json hash = self.to_hash hash[:version] = VERSION JSON.pretty_generate(hash) end |
#train(x, y, epochs, batch_size: 1, test: nil, verbose: true, batch_proc: nil) {|epoch| ... } ⇒ Object
Start training. Compile the model before use this method.
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 |
# File 'lib/dnn/core/model.rb', line 202 def train(x, y, epochs, batch_size: 1, test: nil, verbose: true, batch_proc: nil, &epoch_proc) raise DNN_Error.new("The model is not compiled.") unless compiled? check_xy_type(x, y) dataset = Dataset.new(x, y) num_train_datas = x.shape[0] (1..epochs).each do |epoch| puts "【 epoch #{epoch}/#{epochs} 】" if verbose (num_train_datas.to_f / batch_size).ceil.times do |index| x_batch, y_batch = dataset.get_batch(batch_size) loss_value = train_on_batch(x_batch, y_batch, &batch_proc) if loss_value.is_a?(Numo::SFloat) loss_value = loss_value.mean elsif loss_value.nan? puts "\nloss is nan" if verbose return end num_trained_datas = (index + 1) * batch_size num_trained_datas = num_trained_datas > num_train_datas ? num_train_datas : num_trained_datas log = "\r" 40.times do |i| if i < num_trained_datas * 40 / num_train_datas log << "=" elsif i == num_trained_datas * 40 / num_train_datas log << ">" else log << "_" end end log << " #{num_trained_datas}/#{num_train_datas} loss: #{sprintf('%.8f', loss_value)}" print log if verbose end if verbose && test acc = accurate(test[0], test[1], batch_size, &batch_proc) print " accurate: #{acc}" end puts "" if verbose epoch_proc.call(epoch) if epoch_proc end end |
#train_on_batch(x, y) {|x, y| ... } ⇒ Float | Numo::SFloat
Training once. Compile the model before use this method.
253 254 255 256 257 258 259 260 261 262 263 264 265 |
# File 'lib/dnn/core/model.rb', line 253 def train_on_batch(x, y, &batch_proc) raise DNN_Error.new("The model is not compiled.") unless compiled? check_xy_type(x, y) input_data_shape_check(x, y) x, y = batch_proc.call(x, y) if batch_proc out = forward(x, true) loss_value = @loss.forward(out, y, get_all_layers) dout = @loss.backward(y) backward(dout) @loss.regularizes_backward(get_all_layers) update loss_value end |
#update ⇒ Object
357 358 359 360 361 362 363 364 365 366 |
# File 'lib/dnn/core/model.rb', line 357 def update return unless @trainable @layers.each do |layer| if layer.is_a?(Layers::HasParamLayer) layer.update(@optimizer) elsif layer.is_a?(Model) layer.update end end end |