lib/dnn/core/layers.rb in ruby-dnn-0.8.8 vs lib/dnn/core/layers.rb in ruby-dnn-0.9.0

- old
+ new

@@ -1,53 +1,47 @@ module DNN module Layers # Super class of all optimizer classes. class Layer + attr_reader :input_shape + def initialize @built = false end # Build the layer. - def build(model) - @model = model + def build(input_shape) + @input_shape = input_shape @built = true end # Does the layer have already been built? def built? @built end # Forward propagation. - # Classes that inherit from this class must implement this method. def forward(x) raise NotImplementedError.new("Class '#{self.class.name}' has implement method 'forward'") end # Backward propagation. - # Classes that inherit from this class must implement this method. def backward(dout) raise NotImplementedError.new("Class '#{self.class.name}' has implement method 'update'") end - - # Get the shape of the layer. - def shape - prev_layer.shape + + def output_shape + @input_shape end # Layer to a hash. def to_hash(merge_hash = nil) hash = {class: self.class.name} hash.merge!(merge_hash) if merge_hash hash end - - # Get the previous layer. - def prev_layer - @model.get_prev_layer(self) - end end # This class is a superclass of all classes with learning parameters. class HasParamLayer < Layer @@ -58,63 +52,67 @@ super() @params = {} @trainable = true end - def build(model) - @model = model + def build(input_shape) + @input_shape = input_shape unless @built @built = true init_params end end # Update the parameters. - def update - @model.optimizer.update(@params) if @trainable + def update(optimizer) + optimizer.update(@params) if @trainable end private # Initialize of the parameters. - # Classes that inherit from this class must implement this method. def init_params raise NotImplementedError.new("Class '#{self.class.name}' has implement method 'init_params'") end end class InputLayer < Layer - attr_reader :shape - def self.load_hash(hash) - self.new(hash[:shape]) + self.new(hash[:input_shape]) end - def initialize(dim_or_shape) + def initialize(input_dim_or_shape) super() - @shape = dim_or_shape.is_a?(Array) ? dim_or_shape : [dim_or_shape] + @input_shape = input_dim_or_shape.is_a?(Array) ? input_dim_or_shape : [input_dim_or_shape] end + def build + @built = true + @input_shape + end + def forward(x) x end def backward(dout) dout end def to_hash - super({shape: @shape}) + super({input_shape: @input_shape}) end end # It is a superclass of all connection layers. class Connection < HasParamLayer attr_reader :l1_lambda # L1 regularization attr_reader :l2_lambda # L2 regularization + attr_reader :weight_initializer + attr_reader :bias_initializer def initialize(weight_initializer: Initializers::RandomNormal.new, bias_initializer: Initializers::Zeros.new, l1_lambda: 0, l2_lambda: 0) @@ -141,19 +139,19 @@ else 0 end end - def dlasso + def d_lasso if @l1_lambda > 0 dlasso = Xumo::SFloat.ones(*@weight.data.shape) dlasso[@weight.data < 0] = -1 @weight.grad += @l1_lambda * dlasso end end - def dridge + def d_ridge if @l2_lambda > 0 @weight.grad += @l2_lambda * @weight.data end end @@ -176,12 +174,12 @@ class Dense < Connection attr_reader :num_nodes def self.load_hash(hash) self.new(hash[:num_nodes], - weight_initializer: Util.load_hash(hash[:weight_initializer]), - bias_initializer: Util.load_hash(hash[:bias_initializer]), + weight_initializer: Utils.load_hash(hash[:weight_initializer]), + bias_initializer: Utils.load_hash(hash[:bias_initializer]), l1_lambda: hash[:l1_lambda], l2_lambda: hash[:l2_lambda]) end def initialize(num_nodes, @@ -203,130 +201,104 @@ @weight.grad = @x.transpose.dot(dout) @bias.grad = dout.sum(0) dout.dot(@weight.data.transpose) end - def shape + def output_shape [@num_nodes] end def to_hash super({num_nodes: @num_nodes}) end private def init_params - num_prev_nodes = prev_layer.shape[0] + num_prev_nodes = @input_shape[0] @weight.data = Xumo::SFloat.new(num_prev_nodes, @num_nodes) @bias.data = Xumo::SFloat.new(@num_nodes) super() end end class Flatten < Layer def forward(x) - @shape = x.shape - x.reshape(x.shape[0], x.shape[1..-1].reduce(:*)) + x.reshape(x.shape[0], *output_shape) end def backward(dout) - dout.reshape(*@shape) + dout.reshape(dout.shape[0], *@input_shape) end - - def shape - [prev_layer.shape.reduce(:*)] + + def output_shape + [@input_shape.reduce(:*)] end end class Reshape < Layer - attr_reader :shape - - def initialize(shape) - super() - @shape = shape - @x_shape = nil + def self.load_hash(hash) + self.new(hash[:output_shape]) end - def self.load_hash(hash) - self.new(hash[:shape]) + def initialize(output_shape) + super() + @output_shape = output_shape end def forward(x) - @x_shape = x.shape - x.reshape(x.shape[0], *@shape) + x.reshape(x.shape[0], *@output_shape) end def backward(dout) - dout.reshape(*@x_shape) + dout.reshape(dout.shape[0], *@input_shape) end + def output_shape + @output_shape + end + def to_hash - super({shape: @shape}) + super({output_shape: @output_shape}) end end - - class OutputLayer < Layer - # Classes that inherit from this class must implement this method. - def loss(x) - raise NotImplementedError.new("Class '#{self.class.name}' has implement method 'forward'") - end - - def dloss - @model.get_all_layers.select { |layer| layer.is_a?(Connection) }.each do |layer| - layer.dlasso - layer.dridge - end - end - - private - - def lasso - @model.get_all_layers.select { |layer| layer.is_a?(Connection) } - .reduce(0) { |sum, layer| sum + layer.lasso } - end - def ridge - @model.get_all_layers.select { |layer| layer.is_a?(Connection) } - .reduce(0) { |sum, layer| sum + layer.ridge } - end - end - - class Dropout < Layer attr_reader :dropout_ratio def self.load_hash(hash) - self.new(hash[:dropout_ratio]) + self.new(hash[:dropout_ratio], hash[:seed]) end - def initialize(dropout_ratio = 0.5) + def initialize(dropout_ratio = 0.5, seed = rand(1 << 31)) super() @dropout_ratio = dropout_ratio + @seed = seed @mask = nil end - - def forward(x) - if @model.training? + + def forward(x, learning_phase) + if learning_phase + Xumo::SFloat.srand(@seed) @mask = Xumo::SFloat.ones(*x.shape).rand < @dropout_ratio x[@mask] = 0 else x *= (1 - @dropout_ratio) end x end - def backward(dout) - dout[@mask] = 0 if @model.training? + def backward(dout, learning_phase) + dout[@mask] = 0 if learning_phase dout end def to_hash - super({dropout_ratio: @dropout_ratio}) + super({dropout_ratio: @dropout_ratio, seed: @seed}) end end class BatchNormalization < HasParamLayer @@ -339,12 +311,12 @@ def initialize(momentum: 0.9) super() @momentum = momentum end - def forward(x) - if @model.training? + def forward(x, learning_phase) + if learning_phase mean = x.mean(0) @xc = x - mean var = (@xc**2).mean(0) @std = Xumo::NMath.sqrt(var + 1e-7) xn = @xc / @std @@ -356,11 +328,11 @@ xn = xc / Xumo::NMath.sqrt(@running_var.data + 1e-7) end @gamma.data * xn + @beta.data end - def backward(dout) + def backward(dout, learning_phase) batch_size = dout.shape[0] @beta.grad = dout.sum(0) @gamma.grad = (@xn * dout).sum(0) dxn = @gamma.data * dout dxc = dxn / @std @@ -376,13 +348,13 @@ end private def init_params - @params[:gamma] = @gamma = Param.new(Xumo::SFloat.ones(*shape)) - @params[:beta] = @beta = Param.new(Xumo::SFloat.zeros(*shape)) - @params[:running_mean] = @running_mean = Param.new(Xumo::SFloat.zeros(*shape)) - @params[:running_var] = @running_var = Param.new(Xumo::SFloat.zeros(*shape)) + @params[:gamma] = @gamma = Param.new(Xumo::SFloat.ones(*output_shape)) + @params[:beta] = @beta = Param.new(Xumo::SFloat.zeros(*output_shape)) + @params[:running_mean] = @running_mean = Param.new(Xumo::SFloat.zeros(*output_shape)) + @params[:running_var] = @running_var = Param.new(Xumo::SFloat.zeros(*output_shape)) end end end end