lib/dnn/core/model.rb in ruby-dnn-0.9.1 vs lib/dnn/core/model.rb in ruby-dnn-0.9.2

- old
+ new

@@ -4,17 +4,24 @@ module DNN # This class deals with the model of the network. class Model - attr_accessor :layers # All layers possessed by the model - attr_accessor :trainable # Setting false prevents learning of parameters. + # @return [Array] All layers possessed by the model. + attr_accessor :layers + # @return [Bool] Setting false prevents learning of parameters. + attr_accessor :trainable + # Load marshal model. + # @param [String] file_name File name of marshal model to load. def self.load(file_name) Marshal.load(Zlib::Inflate.inflate(File.binread(file_name))) end + # Load json model. + # @param [String] json_str json string to load model. + # @return [DNN::Model] 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 @@ -31,10 +38,12 @@ @trainable = true @optimizer = nil @compiled = false end + # Load json model parameters. + # @param [String] json_str json string to load model parameters. 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) } @@ -47,10 +56,12 @@ end has_param_layers_index += 1 end end + # Save the model in marshal format. + # @param [String] file_name name to save model. def save(file_name) bin = Zlib::Deflate.deflate(Marshal.dump(self)) begin File.binwrite(file_name, bin) rescue Errno::ENOENT => ex @@ -58,16 +69,20 @@ Dir.mkdir(dir_name) File.binwrite(file_name, bin) end end + # Convert model to json string. + # @return [String] json string. def to_json hash = self.to_hash hash[:version] = VERSION JSON.pretty_generate(hash) end + # Convert model parameters to json string. + # @return [String] json string. 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) @@ -76,10 +91,13 @@ end hash = {version: VERSION, params: has_param_layers_params} JSON.dump(hash) end + # Add layer to the model. + # @param [DNN::Layers::Layer] layer Layer to add to the model. + # @return [DNN::Model] return self. 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 @@ -88,11 +106,15 @@ end @layers << layer self end + # Set optimizer and loss to model and build all layers. + # @param [DNN::Optimizers::Optimizer] optimizer Optimizer to use for learning. + # @param [DNN::Losses::Loss] loss Lptimizer to use for learning. 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.") @@ -103,10 +125,27 @@ @loss = loss build layers_shape_check end + # Set optimizer and loss to model and recompile. But does not build layers. + # @param [DNN::Optimizers::Optimizer] optimizer Optimizer to use for learning. + # @param [DNN::Losses::Loss] loss Lptimizer to use for learning. + 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 + def build(super_model = nil) @super_model = super_model shape = if super_model super_model.output_shape else @@ -120,32 +159,48 @@ end shape = layer.output_shape end end + # @return [Array] Return the input shape of the model. def input_shape @layers.first.input_shape end + # @return [Array] Return the output shape of the model. def output_shape @layers.last.output_shape end + # @return [DNN::Optimizers::Optimizer] optimizer Return the optimizer to use for learning. def optimizer raise DNN_Error.new("The model is not compiled.") unless compiled? @optimizer ? @optimizer : @super_model.optimizer end + # @return [DNN::Losses::Loss] loss Return the loss to use for learning. def loss raise DNN_Error.new("The model is not compiled.") unless compiled? @loss ? @loss : @super_model.loss end + # @return [Bool] Returns whether the model is learning. def compiled? @compiled end + # Start training. + # Compile the model before use this method. + # @param [Numo::SFloat] x Input training data. + # @param [Numo::SFloat] y Output training data. + # @param [Integer] epochs Number of training. + # @param [Integer] batch_size Batch size used for one training. + # @param [Array or NilClass] test If you to test the model for every 1 epoch, + # specify [x_test, y_test]. Don't test to the model, specify nil. + # @param [Bool] verbose Set true to display the log. If false is set, the log is not displayed. + # @param [Proc] batch_proc Set proc to process per batch. + # @yield [epoch] Process performed before one training. def train(x, y, epochs, batch_size: 1, test: nil, verbose: true, batch_proc: nil, @@ -185,28 +240,33 @@ puts "" if verbose epoch_proc.call(epoch) if epoch_proc end end + # Training once. + # Compile the model before use this method. + # @param [Numo::SFloat] x Input training data. + # @param [Numo::SFloat] y Output training data. + # @yield [x, y] batch_proc Set proc to process per batch. 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 = if @loss.is_a?(HuberLoss) - @loss.forward(out, y, get_all_layers) - else - @loss.forward(out, y) + @loss.regularize(get_all_layers) - end + loss_value = @loss.forward(out, y, get_all_layers) dout = @loss.backward(y) - backward(dout, true) + backward(dout) @loss.d_regularize(get_all_layers) update loss_value end + # Evaluate model and get accurate of test data. + # @param [Numo::SFloat] x Input test data. + # @param [Numo::SFloat] y Output test data. + # @yield [x, y] batch_proc Set proc to process per batch. 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 @@ -229,60 +289,66 @@ end end end correct.to_f / x.shape[0] end - + + # Predict data. + # @param [Numo::SFloat] x Input data. def predict(x) check_xy_type(x) input_data_shape_check(x) forward(x, false) end + # Predict one data. + # @param [Numo::SFloat] x Input data. However, x is single data. def predict1(x) check_xy_type(x) predict(Xumo::SFloat.cast([x]))[0, false] end + # @return [DNN::Model] Copy this model. def copy Marshal.load(Marshal.dump(self)) end + # Get the layer that the model has. 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 the all layers. + # @return [Array] all layers array. def get_all_layers @layers.map { |layer| layer.is_a?(Model) ? layer.get_all_layers : layer }.flatten end - def forward(x, learning_phase) + # TODO + # It is not good to write the Layer class name directly in the Model class. I will fix it later. + def forward(x, learning_phase)01 @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 - def backward(dout, learning_phase) + def backward(dout) @layers.reverse.each do |layer| - if layer.is_a?(Layers::Dropout) || layer.is_a?(Layers::BatchNormalization) || layer.is_a?(Model) - dout = layer.backward(dout, learning_phase) - else - dout = layer.backward(dout) - end + dout = layer.backward(dout) end dout end def update @@ -360,15 +426,9 @@ unless x.is_a?(Xumo::SFloat) raise TypeError.new("x:#{x.class.name} is not an instance of #{Xumo::SFloat.name} class.") end if y && !y.is_a?(Xumo::SFloat) raise TypeError.new("y:#{y.class.name} is not an instance of #{Xumo::SFloat.name} class.") - end - end - - def type_check(var_name, var, type) - unless var.is_a?(type) - raise TypeError.new("#{var_name}:#{var.class} is not an instance of #{type} class.") end end end end