Compile the network
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| class(network_type), | intent(inout) | :: | this |
Instance of network |
||
| class(base_optimiser_type), | intent(in), | optional | :: | optimiser |
Optimiser to use for training |
|
| class(*), | intent(in), | optional | :: | loss_method |
Loss method |
|
| character(len=*), | intent(in), | optional | :: | accuracy_method |
Accuracy method |
|
| class(*), | intent(in), | optional, | dimension(..) | :: | metrics |
Metrics |
| integer, | intent(in), | optional | :: | batch_size |
Batch size |
|
| integer, | intent(in), | optional | :: | verbose |
Verbosity level |
module subroutine compile( & this, optimiser, loss_method, accuracy_method, & metrics, batch_size, verbose & ) !! Compile the network implicit none ! Arguments class(network_type), intent(inout) :: this !! Instance of network class(base_optimiser_type), optional, intent(in) :: optimiser !! Optimiser to use for training class(*), optional, intent(in) :: loss_method !! Loss method character(*), optional, intent(in) :: accuracy_method !! Accuracy method class(*), dimension(..), optional, intent(in) :: metrics !! Metrics integer, optional, intent(in) :: batch_size !! Batch size integer, optional, intent(in) :: verbose !! Verbosity level ! Local variables integer :: i, j, k, child_id, parent_id, layer_id, num_inputs, input_rank !! Loop index integer :: parent_vertex, vertex_idx !! Vertex indices integer :: layer_rank, parent_rank, operator !! Ranks of layers integer :: verbose_ = 0 !! Verbosity level logical :: use_graph_input = .false. !! Boolean whether to use graph input logical :: l_flatten_child, l_set_input_shape !! Booleans whether to flatten child or set input shape integer, dimension(:), allocatable :: input_shape, & child_vertices, parent_vertices, output_ranks, parent_ids !! Shapes of the input and output of the layers integer, dimension(:,:), allocatable :: merge_shape !! Shapes of the inputs to merge layers class(base_layer_type), allocatable :: & t_input_layer, t_flatten_layer, t_merge_layer !! Temporary input, flatten, and merge layers !--------------------------------------------------------------------------- ! Initialise optional arguments !--------------------------------------------------------------------------- if(present(verbose)) verbose_ = verbose !--------------------------------------------------------------------------- ! Initialise metrics !--------------------------------------------------------------------------- if(present(metrics)) call this%set_metrics(metrics) !--------------------------------------------------------------------------- ! Initialise loss and accuracy methods !--------------------------------------------------------------------------- if(present(loss_method)) call this%set_loss(loss_method, verbose_) if(present(accuracy_method)) & call this%set_accuracy(accuracy_method, verbose_) !--------------------------------------------------------------------------- ! Check for input layers at root vertices !--------------------------------------------------------------------------- this%auto_graph%directed = .true. call this%build_root_vertices() do i = 1, size(this%root_vertices) layer_id = this%auto_graph%vertex(this%root_vertices(i))%id if(.not.allocated(this%model(layer_id)%layer%input_shape))then call stop_program("input_shape of first layer not defined") return end if use_graph_input = .false. select type( root => this%model(layer_id)%layer) class is(input_layer_type) cycle class is(learnable_layer_type) input_shape = root%input_shape use_graph_input = root%use_graph_input class default input_shape = root%input_shape end select t_input_layer = input_layer_type(& input_shape = input_shape, & index = i, & use_graph_input = use_graph_input, & verbose=verbose_ & ) call this%add( & t_input_layer, output_list = [ this%model(layer_id)%layer%id ] & ) ! NEED TO CALL layer%init? deallocate(input_shape) deallocate(t_input_layer) this%root_vertices(i) = this%num_layers if(i.eq.1)then do j = 1, this%auto_graph%num_edges if(this%auto_graph%edge(j)%index(1).eq.0) & this%auto_graph%edge(j)%index(1) = this%num_layers end do end if end do call this%auto_graph%generate_adjacency() !--------------------------------------------------------------------------- ! Identify whether input is graph type !--------------------------------------------------------------------------- if( & this%model( & this%auto_graph%vertex(this%root_vertices(1))%id & )%layer%use_graph_input & )then this%use_graph_input = .true. else this%use_graph_input = .false. end if !--------------------------------------------------------------------------- ! Check for zero input rank layers !--------------------------------------------------------------------------- do i = 1, size(this%auto_graph%vertex, dim = 1) layer_id = this%auto_graph%vertex(i)%id if(this%model(layer_id)%layer%input_rank.eq.0)then parent_ids = pack( & [ ( & this%auto_graph%vertex(j)%id, & j = 1, size(this%auto_graph%adjacency(:,i)) & ) ], & this%auto_graph%adjacency(:,i) .ne. 0 & ) if(size(parent_ids).eq.0) cycle output_ranks = [ ( this%model(parent_ids(j))%layer%output_rank, & j=1,size(parent_ids) ) ] if(any(output_ranks.ne.output_ranks(1)))then write(0,*) output_ranks call stop_program( & "input rank of layer "//trim(this%model(layer_id)%layer%name) // & " is zero, but multiple parents with different output ranks" & ) return end if input_rank = this%model(parent_ids(1))%layer%output_rank call this%model(layer_id)%layer%set_rank( & input_rank = input_rank, & output_rank = input_rank & ) end if end do !--------------------------------------------------------------------------- ! Check for required flatten layers !--------------------------------------------------------------------------- i = 0 flatten_loop: do i = i + 1 if(i.gt.this%auto_graph%num_vertices) exit flatten_loop layer_id = this%auto_graph%vertex(i)%id ! get all child vertices child_vertices = pack( & [(j, j=1,size(this%auto_graph%adjacency(i,:)))], & this%auto_graph%adjacency(i,:) .ne. 0 & ) child_loop: do j = 1, size(child_vertices) ! Get layer ID (needed for add() function's output_list parameter) child_id = this%auto_graph%vertex(child_vertices(j))%id if(trim(this%model(layer_id)%layer%type).eq."flat") cycle child_loop if( this%model(layer_id)%layer%output_rank .eq. & this%model(child_id)%layer%input_rank ) cycle child_loop if(this%model(layer_id)%layer%output_rank.eq.0) cycle child_loop ! get all parent vertices of the child vertex parent_vertices = pack( & [(k, k=1,size(this%auto_graph%adjacency(:,child_vertices(j))))], & this%auto_graph%adjacency(:,child_vertices(j)) .ne. 0 & ) l_flatten_child = .true. do k = 1, size(parent_vertices) parent_id = this%auto_graph%vertex(parent_vertices(k))%id !check if ranks match, rather than input and output shapes if( this%model(layer_id)%layer%output_rank .ne. & this%model(parent_id)%layer%input_rank & ) l_flatten_child = .false. end do t_flatten_layer = flatten_layer_type( & input_rank = this%model(layer_id)%layer%output_rank & ) if(l_flatten_child)then ! add flatten layer in the place of the child layer operator = this%auto_graph%edge( & this%auto_graph%adjacency(parent_vertices(1),child_vertices(j)) & )%id call this%auto_graph%remove_edges( & indices = [ & this%auto_graph%adjacency( & parent_vertices(:),child_vertices(j) & ) & ] & ) call this%add( & t_flatten_layer, & input_list=[parent_vertices(:)], output_list=[child_id], & operator=operator & ) else ! add flatten layer between the current layer and the child layer operator = this%auto_graph%edge( & this%auto_graph%adjacency(i,child_vertices(j)) & )%id call this%auto_graph%remove_edges( & indices = [this%auto_graph%adjacency(i,child_vertices(j))] & ) call this%add( & t_flatten_layer, input_list = [layer_id], & output_list = [child_id], & operator=operator & ) end if deallocate(t_flatten_layer) deallocate(child_vertices) cycle flatten_loop end do child_loop deallocate(child_vertices) end do flatten_loop call this%build_vertex_order() !--------------------------------------------------------------------------- ! Check for required merge layers !--------------------------------------------------------------------------- i = 0 merge_loop: do i = i + 1 if(i.gt.this%auto_graph%num_vertices) exit merge_loop layer_id = this%auto_graph%vertex(i)%id if(this%model(layer_id)%layer%type.eq."merg") cycle merge_loop ! get all child vertices parent_vertices = pack( & [(j, j=1,size(this%auto_graph%adjacency(:,i)))], & this%auto_graph%adjacency(:,i) .ne. 0 & ) if(size(parent_vertices).le.1) cycle merge_loop ! get edge id for merge layer operator = this%auto_graph%edge( & this%auto_graph%adjacency(parent_vertices(1),i) & )%id ! remove edges from parents to this layer do j = 1, size(parent_vertices) call this%auto_graph%remove_edges( & indices = [this%auto_graph%adjacency(parent_vertices(j),i)] & ) end do parent_ids = & [ ( & this%auto_graph%vertex(parent_vertices(k))%id, & k = 1, size(parent_vertices) & ) ] select case(operator) case(1) ! concatenate t_merge_layer = concat_layer_type( & input_layer_ids = parent_ids, & input_rank = this%model(layer_id)%layer%input_rank & ) case(2) ! add t_merge_layer = add_layer_type( & input_layer_ids = parent_ids, & input_rank = this%model(layer_id)%layer%input_rank & ) ! case(3) ! multiply ! t_merge_layer = multiply_layer_type( & ! input_layer_ids = parent_vertices & ! ) case default write(0,*) "invalid merge operator: ", operator call stop_program("invalid merge operator") return end select t_merge_layer%use_graph_input = this%model(layer_id)%layer%use_graph_input t_merge_layer%use_graph_output = t_merge_layer%use_graph_input call this%add( & t_merge_layer, & input_list = parent_ids, & output_list = [layer_id] & ) deallocate(t_merge_layer) end do merge_loop call this%build_vertex_order() ! Update number of layers !--------------------------------------------------------------------------- this%num_layers = size(this%model,dim=1) !--------------------------------------------------------------------------- ! Initialise layers !--------------------------------------------------------------------------- do i = 1, size(this%vertex_order, dim = 1) vertex_idx = this%vertex_order(i) layer_id = this%auto_graph%vertex(vertex_idx)%id if(allocated(this%model(layer_id)%layer%input_shape))then l_set_input_shape = .false. else l_set_input_shape = .true. end if if(l_set_input_shape)then layer_rank = this%model(layer_id)%layer%input_rank parent_rank = 0 select type( layer => this%model(layer_id)%layer ) class is(merge_layer_type) ! loop over all parent layers allocate( & merge_shape( & this%model(layer_id)%layer%input_rank, & size(layer%input_layer_ids) & ) & ) do k = 1, size(layer%input_layer_ids) merge_shape(:,k) = & this%model(layer%input_layer_ids(k))%layer%output_shape end do input_shape = layer%calc_input_shape(merge_shape) deallocate(merge_shape) class default allocate( & input_shape(this%model(layer_id)%layer%input_rank), & source = 0 & ) do j = 1, this%auto_graph%num_vertices if(this%auto_graph%adjacency(j,vertex_idx).eq.0) cycle parent_id = this%auto_graph%vertex(j)%id parent_rank = this%model(parent_id)%layer%output_rank if(layer_rank .eq. parent_rank)then input_shape(:) = input_shape(:) + & this%model(parent_id)%layer%output_shape elseif(layer_rank .eq. 1)then input_shape(1) = input_shape(1) + & product( this%model(parent_id)%layer%output_shape ) end if end do end select call this%model(layer_id)%layer%init( & input_shape = input_shape, & verbose = verbose_ & ) deallocate(input_shape) end if if(verbose_.gt.0)then write(*,*) "layer: ", layer_id, this%model(layer_id)%layer%type write(*,*) this%model(layer_id)%layer%input_shape write(*,*) this%model(layer_id)%layer%output_shape end if end do ! Set number of outputs !--------------------------------------------------------------------------- this%num_outputs = 0 call this%build_leaf_vertices() do i = 1, size(this%leaf_vertices,1) this%num_outputs = this%num_outputs + & product( & this%model( & this%auto_graph%vertex(this%leaf_vertices(i))%id & )%layer%output_shape & ) end do if( & this%model( & this%auto_graph%vertex(this%leaf_vertices(1))%id & )%layer%use_graph_output & )then this%use_graph_output = .true. else this%use_graph_output = .false. end if !--------------------------------------------------------------------------- ! Confirm input_shape of each layer matches data going into it !--------------------------------------------------------------------------- do i = 1, size(this%vertex_order, dim = 1) vertex_idx = this%vertex_order(i) layer_id = this%auto_graph%vertex(vertex_idx)%id if(this%model(layer_id)%layer%type.eq."inpt") cycle ! Get all parent vertices that feed into this layer parent_vertices = pack( & [(j, j=1,size(this%auto_graph%adjacency(:,vertex_idx)))], & this%auto_graph%adjacency(:,vertex_idx) .ne. 0 & ) if(size(parent_vertices).eq.0) cycle select type( layer => this%model(layer_id)%layer ) class is(merge_layer_type) operator = layer%merge_mode class default if(size(parent_vertices).gt.1)then call stop_program( & "layer "//trim(layer%name)// & " is not a merge layer but has multiple inputs" & ) return end if end select ! Calculate expected input size from parent layers num_inputs = 0 do j = 1, size(parent_vertices) parent_vertex = parent_vertices(j) select case(operator) case(1) ! pointwise - all inputs should have same size if(num_inputs.eq.0)then if(this%model(layer_id)%layer%use_graph_input)then num_inputs = this%model(parent_vertex)%layer%output_shape(1) else num_inputs = product(this%model(parent_vertex)%layer%output_shape) end if end if case(2) ! concatenate if(this%model(layer_id)%layer%use_graph_input)then num_inputs = num_inputs + & this%model(parent_vertex)%layer%output_shape(1) else num_inputs = num_inputs + & product(this%model(parent_vertex)%layer%output_shape) end if end select end do ! Verify calculated input size matches layer's expected input size if(this%model(layer_id)%layer%use_graph_input)then if(num_inputs.ne.this%model(layer_id)%layer%input_shape(1) .and. & num_inputs.ne.0)then write(*,*) "Expected:", num_inputs, "Got:", & this%model(layer_id)%layer%input_shape(1) call stop_program( & "input_shape of layer "//& trim(this%model(layer_id)%layer%name)// & " does not match data going into it" & ) end if else if(num_inputs.ne.product(this%model(layer_id)%layer%input_shape) .and. & num_inputs.ne.0)then write(*,*) "Expected:", num_inputs, "Got:", & product(this%model(layer_id)%layer%input_shape) call stop_program( & "input_shape of layer "//& trim(this%model(layer_id)%layer%name)// & " does not match data going into it" & ) end if end if end do !--------------------------------------------------------------------------- ! Initialise optimiser !--------------------------------------------------------------------------- this%num_params = this%get_num_params() if(present(optimiser))then this%optimiser = optimiser end if if(.not.allocated(this%optimiser))then call stop_program("No optimiser is defined for the network") return else call this%optimiser%init(num_params=this%num_params) end if !--------------------------------------------------------------------------- ! Pre-compute forward pass navigation !--------------------------------------------------------------------------- block integer :: nv, l_idx, v_idx, lid, parent_v nv = size(this%vertex_order, 1) if(allocated(this%fwd_layer_id)) deallocate(this%fwd_layer_id) if(allocated(this%fwd_num_inputs)) deallocate(this%fwd_num_inputs) if(allocated(this%fwd_parent_id)) deallocate(this%fwd_parent_id) if(allocated(this%fwd_layer_type)) deallocate(this%fwd_layer_type) allocate(this%fwd_layer_id(nv)) allocate(this%fwd_num_inputs(nv)) allocate(this%fwd_parent_id(nv)) allocate(this%fwd_layer_type(nv)) this%fwd_parent_id = 0 do l_idx = 1, nv v_idx = this%vertex_order(l_idx) lid = this%auto_graph%vertex(v_idx)%id this%fwd_layer_id(l_idx) = lid this%fwd_num_inputs(l_idx) = & count(this%auto_graph%adjacency(:,v_idx).gt.0) if(this%fwd_num_inputs(l_idx).eq.1)then parent_v = maxloc( & this%auto_graph%adjacency(:,v_idx), dim=1) this%fwd_parent_id(l_idx) = & this%auto_graph%vertex(parent_v)%id end if ! Determine layer type: 0=input, 1=merge, 2=default select type(layer => this%model(lid)%layer) class is(input_layer_type) this%fwd_layer_type(l_idx) = 0 class is(merge_layer_type) this%fwd_layer_type(l_idx) = 1 class default this%fwd_layer_type(l_idx) = 2 end select end do end block !--------------------------------------------------------------------------- ! Pre-compute parameter segment layout !--------------------------------------------------------------------------- block integer :: l_idx, p_idx, seg_count, s_idx, e_idx ! First pass: count segments seg_count = 0 do l_idx = 1, this%num_layers select type(current => this%model(l_idx)%layer) class is(learnable_layer_type) seg_count = seg_count + size(current%params) end select end do this%param_num_segments = seg_count if(allocated(this%param_seg_layer)) deallocate(this%param_seg_layer) if(allocated(this%param_seg_pidx)) deallocate(this%param_seg_pidx) if(allocated(this%param_seg_start)) deallocate(this%param_seg_start) if(allocated(this%param_seg_end)) deallocate(this%param_seg_end) allocate(this%param_seg_layer(seg_count)) allocate(this%param_seg_pidx(seg_count)) allocate(this%param_seg_start(seg_count)) allocate(this%param_seg_end(seg_count)) ! Second pass: fill layout seg_count = 0 e_idx = 0 do l_idx = 1, this%num_layers select type(current => this%model(l_idx)%layer) class is(learnable_layer_type) do p_idx = 1, size(current%params) seg_count = seg_count + 1 s_idx = e_idx + 1 e_idx = e_idx + size(current%params(p_idx)%val, 1) this%param_seg_layer(seg_count) = l_idx this%param_seg_pidx(seg_count) = p_idx this%param_seg_start(seg_count) = s_idx this%param_seg_end(seg_count) = e_idx end do end select end do end block !--------------------------------------------------------------------------- ! Set batch size, if provided !--------------------------------------------------------------------------- if(present(batch_size)) this%batch_size = batch_size end subroutine compile