diff --git a/example/concurrent-inferences.f90 b/example/concurrent-inferences.f90 index 65e35d0f8..8a350e848 100644 --- a/example/concurrent-inferences.f90 +++ b/example/concurrent-inferences.f90 @@ -3,7 +3,7 @@ program concurrent_inferences !! This program demonstrates how to read a neural network from a JSON file !! and use the network to perform concurrent inferences. - use inference_engine_m, only : inference_engine_t, tensor_t, infer + use inference_engine_m, only : inference_engine_t, tensor_t, infer, numerics_t use sourcery_m, only : string_t, command_line_t, file_t use assert_m, only : assert use iso_fortran_env, only : int64, real64 @@ -40,10 +40,12 @@ program concurrent_inferences block integer(int64) t_start, t_finish, clock_rate + type(numerics_t) :: numerics + numerics = inference_engine%to_numerics() print *,"Performing elemental inferences" call system_clock(t_start, clock_rate) - outputs = inference_engine%infer(inputs) ! implicit allocation of outputs array + outputs = numerics%infer(inputs) ! implicit allocation of outputs array call system_clock(t_finish) print *,"Elemental inference time: ", real(t_finish - t_start, real64)/real(clock_rate, real64) @@ -54,7 +56,7 @@ program concurrent_inferences do k=1,lev do j=1,lon do i=1,lat - outputs(i,j,k) = inference_engine%infer(inputs(i,j,k)) + outputs(i,j,k) = numerics%infer(inputs(i,j,k)) end do end do end do @@ -64,7 +66,7 @@ program concurrent_inferences print *,"Performing concurrent inference" call system_clock(t_start) do concurrent(i=1:lat, j=1:lon, k=1:lev) - outputs(i,j,k) = inference_engine%infer(inputs(i,j,k)) + outputs(i,j,k) = numerics%infer(inputs(i,j,k)) end do call system_clock(t_finish) print *,"Concurrent inference time: ", real(t_finish - t_start, real64)/real(clock_rate, real64) @@ -72,7 +74,7 @@ program concurrent_inferences print *,"Performing concurrent inference with a non-type-bound inference procedure" call system_clock(t_start) do concurrent(i=1:lat, j=1:lon, k=1:lev) - outputs(i,j,k) = infer(inference_engine, inputs(i,j,k)) + outputs(i,j,k) = infer(numerics, inputs(i,j,k)) end do call system_clock(t_finish) print *,"Concurrent inference time with non-type-bound procedure: ", real(t_finish - t_start, real64)/real(clock_rate, real64) diff --git a/example/write-read-infer.F90 b/example/write-read-infer.F90 index ff9688760..c688e15df 100644 --- a/example/write-read-infer.F90 +++ b/example/write-read-infer.F90 @@ -7,7 +7,7 @@ program write_read_infer !! perform inference. The network performs an identity mapping from any !! non-negative inputs to the corresponding outputs using a RELU activation !! function. - use inference_engine_m, only : inference_engine_t, relu_t, tensor_t + use inference_engine_m, only : inference_engine_t, relu_t, tensor_t, numerics_t use sourcery_m, only : string_t, command_line_t, file_t use kind_parameters_m, only : rkind implicit none @@ -82,7 +82,11 @@ subroutine write_read_query_infer(output_file_name) print *, "Performing inference:" inputs = tensor_t([2.,3.]) print *, "Inputs: ", inputs%values() - outputs = inference_engine%infer(inputs) + block + type(numerics_t) :: numerics + numerics = inference_engine%to_numerics() + outputs = numerics%infer(inputs) + end block print *, "Actual outputs: ", outputs%values() print *, "Correct outputs: ", inputs%values() end subroutine write_read_query_infer diff --git a/src/inference_engine/inference_engine_m_.f90 b/src/inference_engine/inference_engine_m_.f90 index 9edc2af85..f7c4f9691 100644 --- a/src/inference_engine/inference_engine_m_.f90 +++ b/src/inference_engine/inference_engine_m_.f90 @@ -15,13 +15,13 @@ module inference_engine_m_ public :: inference_engine_t public :: difference_t public :: exchange_t - public :: infer + public :: numerics_t, infer character(len=*), parameter :: key(*) = [character(len=len("usingSkipConnections")) :: & "modelName", "modelAuthor", "compilationDate", "activationFunction", "usingSkipConnections"] type inference_engine_t - !! Encapsulate the minimal information needed to perform inference + !! Encapsulate the information needed to perform inference private type(tensor_range_t) input_range_, output_range_ type(string_t) metadata_(size(key)) @@ -29,7 +29,7 @@ module inference_engine_m_ integer, allocatable :: nodes_(:) class(activation_strategy_t), allocatable :: activation_strategy_ ! Strategy Pattern facilitates elemental activation contains - procedure :: infer + procedure :: to_numerics procedure :: to_json procedure :: map_to_input_range procedure :: map_from_output_range @@ -44,6 +44,17 @@ module inference_engine_m_ procedure :: to_exchange end type + type numerics_t + !! Encapsulate the minimal information needed to perform inference + private + type(tensor_range_t) input_range_, output_range_ + real(rkind), allocatable :: weights_(:,:,:), biases_(:,:) + integer, allocatable :: nodes_(:) + class(activation_strategy_t), allocatable :: activation_strategy_ ! Strategy Pattern facilitates elemental activation + contains + procedure :: infer + end type + type exchange_t type(tensor_range_t) input_range_, output_range_ type(string_t) metadata_(size(key)) @@ -104,6 +115,12 @@ pure module function to_exchange(self) result(exchange) type(exchange_t) exchange end function + pure elemental module function to_numerics(self) result(numerics) + implicit none + class(inference_engine_t), intent(in) :: self + type(numerics_t) numerics + end function + impure elemental module function to_json(self) result(json_file) implicit none class(inference_engine_t), intent(in) :: self @@ -131,7 +148,7 @@ elemental module subroutine assert_conformable_with(self, inference_engine) elemental module function infer(self, inputs) result(outputs) implicit none - class(inference_engine_t), intent(in) :: self + class(numerics_t), intent(in) :: self type(tensor_t), intent(in) :: inputs type(tensor_t) outputs end function diff --git a/src/inference_engine/inference_engine_s.F90 b/src/inference_engine/inference_engine_s.F90 index ae7172833..84f46a691 100644 --- a/src/inference_engine/inference_engine_s.F90 +++ b/src/inference_engine/inference_engine_s.F90 @@ -42,7 +42,7 @@ integer, parameter :: input_layer = 0 integer k, l - call assert_consistency(self) + call numerics_consistency(self%weights_, self%biases_, self%nodes_, self%activation_strategy_) associate(w => self%weights_, b => self%biases_, n => self%nodes_, output_layer => ubound(self%nodes_,1)) @@ -86,24 +86,30 @@ end procedure pure subroutine inference_engine_consistency(self) - type(inference_engine_t), intent(in) :: self + call numerics_consistency(self%weights_, self%biases_, self%nodes_, self%activation_strategy_) + end subroutine + + pure subroutine numerics_consistency(weights, biases, nodes, activation_strategy) + real(rkind), allocatable, intent(in) :: weights(:,:,:), biases(:,:) + integer, allocatable, intent(in) :: nodes(:) + class(activation_strategy_t), allocatable, intent(in) :: activation_strategy integer, parameter :: input_layer=0 associate( & - all_allocated=>[allocated(self%weights_),allocated(self%biases_),allocated(self%nodes_),allocated(self%activation_strategy_)]& + all_allocated=>[allocated(weights),allocated(biases),allocated(nodes),allocated(activation_strategy)]& ) call assert(all(all_allocated),"inference_engine_s(inference_engine_consistency): fully_allocated", & intrinsic_array_t(all_allocated)) end associate - associate(max_width=>maxval(self%nodes_), component_dims=>[size(self%biases_,1), size(self%weights_,1), size(self%weights_,2)]) + associate(max_width=>maxval(nodes), component_dims=>[size(biases,1), size(weights,1), size(weights,2)]) call assert(all(component_dims == max_width), "inference_engine_s(inference_engine_consistency): conformable arrays", & intrinsic_array_t([max_width,component_dims])) end associate - associate(input_subscript => lbound(self%nodes_,1)) + associate(input_subscript => lbound(nodes,1)) call assert(input_subscript == input_layer, "inference_engine_s(inference_engine_consistency): n base subsscript", & input_subscript) end associate @@ -390,6 +396,15 @@ function get_key_value(line) result(value_) node_count = self%nodes_ end procedure + module procedure to_numerics + numerics%input_range_ = self%input_range_ + numerics%output_range_ = self%output_range_ + numerics%weights_ = self%weights_ + numerics%biases_ = self%biases_ + numerics%nodes_ = self%nodes_ + numerics%activation_strategy_ = self%activation_strategy_ + end procedure + module procedure to_json type(string_t), allocatable :: lines(:) diff --git a/src/inference_engine_m.f90 b/src/inference_engine_m.f90 index 02a3868ec..436016c56 100644 --- a/src/inference_engine_m.f90 +++ b/src/inference_engine_m.f90 @@ -6,7 +6,7 @@ module inference_engine_m use differentiable_activation_strategy_m, only : differentiable_activation_strategy_t use hyperparameters_m, only : hyperparameters_t use input_output_pair_m, only : input_output_pair_t, shuffle - use inference_engine_m_, only : inference_engine_t, difference_t, infer + use inference_engine_m_, only : inference_engine_t, difference_t, numerics_t, infer use kind_parameters_m, only : rkind use mini_batch_m, only : mini_batch_t use network_configuration_m, only : network_configuration_t diff --git a/test/asymmetric_engine_test_m.F90 b/test/asymmetric_engine_test_m.F90 index bf6a04f15..06a5b8400 100644 --- a/test/asymmetric_engine_test_m.F90 +++ b/test/asymmetric_engine_test_m.F90 @@ -9,7 +9,7 @@ module asymmetric_engine_test_m test_t, test_result_t, vector_test_description_t, test_description_substring, string_t, vector_function_strategy_t ! Internal dependencies - use inference_engine_m, only : inference_engine_t, tensor_t + use inference_engine_m, only : inference_engine_t, tensor_t, numerics_t use kind_parameters_m, only : rkind implicit none @@ -122,11 +122,13 @@ function xor_and_2nd_input_truth_table() result(test_passes) block real(rkind), parameter :: tolerance = 1.E-08_rkind, false = 0._rkind, true = 1._rkind type(tensor_t) true_true, true_false, false_true, false_false + type(numerics_t) :: numerics + numerics = asymmetric%to_numerics() - true_true = asymmetric%infer(tensor_t([true,true])) - true_false = asymmetric%infer(tensor_t([true,false])) - false_true = asymmetric%infer(tensor_t([false,true])) - false_false = asymmetric%infer(tensor_t([false,false])) + true_true = numerics%infer(tensor_t([true,true])) + true_false = numerics%infer(tensor_t([true,false])) + false_true = numerics%infer(tensor_t([false,true])) + false_false = numerics%infer(tensor_t([false,false])) associate( & true_true_outputs => true_true%values(), & diff --git a/test/inference_engine_test_m.F90 b/test/inference_engine_test_m.F90 index d351842ee..c7f23ef1a 100644 --- a/test/inference_engine_test_m.F90 +++ b/test/inference_engine_test_m.F90 @@ -12,7 +12,7 @@ module inference_engine_test_m #endif ! Internal dependencies - use inference_engine_m, only : inference_engine_t, tensor_t, difference_t + use inference_engine_m, only : inference_engine_t, tensor_t, difference_t, numerics_t implicit none @@ -132,9 +132,11 @@ function elemental_infer_with_1_hidden_layer_xor_net() result(test_passes) type(tensor_t), allocatable :: truth_table(:) real(rkind), parameter :: tolerance = 1.E-08_rkind, false = 0._rkind, true = 1._rkind integer i + type(numerics_t) :: numerics + numerics = inference_engine%to_numerics() associate(array_of_inputs => [tensor_t([true,true]), tensor_t([true,false]), tensor_t([false,true]), tensor_t([false,false])]) - truth_table = inference_engine%infer(array_of_inputs) + truth_table = numerics%infer(array_of_inputs) end associate test_passes = all( & abs(truth_table(1)%values() - false) < tolerance .and. abs(truth_table(2)%values() - true) < tolerance .and. & @@ -153,9 +155,11 @@ function elemental_infer_with_2_hidden_layer_xor_net() result(test_passes) type(tensor_t), allocatable :: truth_table(:) real(rkind), parameter :: tolerance = 1.E-08_rkind, false = 0._rkind, true = 1._rkind integer i + type(numerics_t) :: numerics + numerics = inference_engine%to_numerics() associate(array_of_inputs => [tensor_t([true,true]), tensor_t([true,false]), tensor_t([false,true]), tensor_t([false,false])]) - truth_table = inference_engine%infer(array_of_inputs) + truth_table = numerics%infer(array_of_inputs) end associate test_passes = all( & abs(truth_table(1)%values() - false) < tolerance .and. abs(truth_table(2)%values() - true) < tolerance .and. &