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