modern-fortran / neural-fortran

A parallel framework for deep learning
MIT License
395 stars 82 forks source link

Support of a method `network % evaluate` #179

Open jvdp1 opened 4 months ago

jvdp1 commented 4 months ago

I propose to introduce the method evaluate that will returns the loss value for the trained model:


function evaluate(input_data, output_data, loss) result(res)
  real, intent(in) :: input_data(:,:)
  real, intent(in) :: output_data(:,:)
  class(loss_type), intent(in), optional :: loss
  real :: res(size(output_data, 1))
end function
milancurcic commented 4 months ago

I agree this would be useful. Python frameworks distinguish between losses and metrics, e.g. see https://keras.io/api/metrics/. It could be a good idea that we differentiate here as well, if we envision providing a broad range of losses and metrics. Losses can be used as metrics, but not all metrics functions are differentiable. How to best implement this without duplicating too much code from losses? The simplest approach could be to simple have a completely new nf_metrics.f90 with nf_metrics module that follows the same model as nf_loss and its abstract derived type and function implementations, but without derivatives.

milancurcic commented 4 months ago

In this case, I would make the metric/loss function non-optional, for better readability of the client code.

jvdp1 commented 4 months ago

Would somenthing like this work:


  type, abstract :: metrics_type
  contains
    procedure(loss_interface), nopass, deferred :: eval
  end type loss_type

  type, extends(metrics_type), abstract :: loss_type
  contains
    procedure(loss_derivative_interface), nopass, deferred :: derivative
  end type loss_type

witth following API:

subroutine evaluate(self, input, output, loss, metrics) result(val)
  class(network), intent(inout) :: self
  real, intent(in) :: input(:)
  real, intent(in) :: output(:)
  class(loss_type), intent(in), optional :: loss
  class(metric_type), intent(in), optional :: metrics(:) !this would also accept loss types, and therefore we don't need to duplicate them.
end subroutine
milancurcic commented 4 months ago

Sounds good, in that case, is it not just:

subroutine evaluate(self, input, output, metrics) result(val)
  class(network), intent(inout) :: self
  real, intent(in) :: input(:)
  real, intent(in) :: output(:)
  class(metric_type), intent(in), optional :: metrics(:) !this would also accept loss types, and therefore we don't need to duplicate them.
end subroutine
milancurcic commented 4 months ago

As discussed, the scalar variant would be

pure real function evaluate(self, input, output, metric) result(val)
  class(network), intent(in) :: self
  real, intent(in) :: input(:)
  real, intent(in) :: output(:)
  class(metric_type), intent(in), optional :: metric ! this would also accept loss types, and therefore we don't need to duplicate them.
end subroutine evaluate

If optional, the default metric can be MSE.