多层神经网络,从零开始——(十一)、实现训练类

Train 类最重要的就是读取参数文件(在CFD程序中,习惯使用配置文件调整计算参数,在Fortran中,这部分内容叫做namelist,类似于JSON、XML这种方式),配置神经网络结构,以及训练神经网络。

最重要的三个函数是:

  • init:初始化
    • (1). 从文件中读取网络参数、训练参数;
    • (2). 申请内存空间;
    • (3). 初始化网络结构.
  • train:训练函数.
  • sim:神经网络计算函数.
!* 该模块定义了神经网络训练的数据和方法。
!* 具体的参数、函数意义参见PDF文档。   
module mod_NNTrain
use mod_Precision
use mod_ActivationFunctionList
use mod_BaseActivationFunction
use mod_NNStructure
use mod_Log
use mod_BaseGradientOptimizationMethod
implicit none    

!-----------------------------------
! 工作类:神经网络训练的方法和数据 |
!-----------------------------------
type, public :: NNTrain

    ! 层的数目,不含输入层
    integer, public :: layers_count    
        
    ! 每层节点数目构成的数组: 
    !     数组的大小是所有层的数目(含输入层)
    integer, dimension(:), allocatable, public :: layers_node_count
    
    !* 网络结构
    type(NNStructure), pointer, public :: my_NNStructure
    
    !* 默认为空,即使用NNStructure中定义的(-1,1)
    character(len=30), private :: weight_threshold_init_methods_name = ''
    
    !* 调用者标识,可用于读取指定的配置信息等。
    character(len=180), private :: caller_name = ''

    !* 是否初始化完成的标识
    logical, private :: is_init = .false.
        
    !* 是否初始化内存空间
    logical, private :: is_allocate_done = .false.
    
    character(len=180), private :: NNParameter_path = &
        './ParameterSetting/'
    
    !* 网络参数信息    
    character(len=180), private :: NNParameter_file = &
        './ParameterSetting/NNParameter.nml'
        
    !* 隐藏层每层结点数目的数组
    character(len=180), private :: NNLayerNodeCount_file = &
        './ParameterSetting/NNHiddenLayerNodeCount.parameter'
    
    !* 每层的激活函数s
    character(len=180), private :: NNActivationFunctionList_file = &
        './ParameterSetting/NNActivationFunctionList.parameter'    
        
    !* 激活函数列表
    character(len=20), dimension(:), allocatable, private :: act_fun_name_list
        
    !* 内置的训练步数计数器
    integer, private :: train_step_counter = 0
    
    !* 使用的BP训练算法
    character(len=20), private :: bp_algorithm
       
    !* 优化方法
    class(BaseGradientOptimizationMethod), pointer, private :: gradient_optimization_method
    
!||||||||||||    
contains   !|
!||||||||||||

    procedure, public :: init => m_init
    
    procedure, public :: set_caller_name   => m_set_caller_name
    procedure, public :: set_loss_function => m_set_loss_function
    procedure, public :: set_weight_threshold_init_methods_name => &
        m_set_weight_threshold_init_methods_name
    procedure, public :: set_optimization_method => m_set_optimization_method
    procedure, public :: set_train_step_counter  => m_set_train_step_counter
    
    procedure, public :: train => m_train
    procedure, public :: sim   => m_sim
    
    procedure, private :: allocate_memory   => m_allocate_memory
    procedure, private :: deallocate_memory => m_deallocate_memory
    
    procedure, private :: init_NNParameter                => m_init_NNParameter
    procedure, private :: load_NNParameter                => m_load_NNParameter
    procedure, private :: load_NNParameter_array          => m_load_NNParameter_array
    procedure, private :: load_NNActivation_Function_List => m_load_NNActivation_Function_List
    
    final :: NNTrain_clean_space
    
end type NNTrain
!===================

    !-------------------------
    private :: m_init
    private :: m_train
    private :: m_sim    

    private :: m_set_caller_name
    private :: m_set_weight_threshold_init_methods_name
    private :: m_set_loss_function
    private :: m_set_optimization_method
    private :: m_set_train_step_counter
    
    private :: m_init_NNParameter
    private :: m_load_NNParameter
    private :: m_load_NNParameter_array
    private :: m_load_NNActivation_Function_List
    
    private :: m_allocate_memory
    private :: m_deallocate_memory
    !-------------------------
    
!||||||||||||    
contains   !|
!||||||||||||

    !* 初始化:
    !* (1). 从文件中读取网络参数、训练参数;
    !* (2). 申请内存空间;
    !* (3). 初始化网络结构.
    subroutine m_init( this, caller_name, count_input_node, count_output_node )
    use mod_NNWeightThresholdInitMethods
    implicit none
        class(NNTrain), intent(inout) :: this
        character(len=*), intent(in) :: caller_name
        integer, intent(in) :: count_input_node, count_output_node

        class(BaseActivationFunction), pointer :: pt_act_fun
        type(ActivationFunctionList),  pointer :: pt_act_fun_list
        integer :: i
        
        if( .not. this % is_init ) then
        
            this % caller_name = caller_name
            call this % init_NNParameter(caller_name)
        
            !* 从文件读取参数信息
            call this % load_NNParameter()          
            call this % allocate_memory()
            call this % load_NNParameter_array()
            call this % load_NNActivation_Function_List()
            
            associate (                                       &
                layers_count      => this % layers_count,     &
                layers_node_count => this % layers_node_count & 
            )   
            
            !* 输入层结点数目
            layers_node_count(0) = count_input_node
            !* 输出层结点数目
            layers_node_count(layers_count) = count_output_node                
            
            !* 初始化 my_NNStructure
            allocate( this % my_NNStructure )
            
            allocate( pt_act_fun_list )
            
            call this % my_NNStructure % init_basic( layers_count,layers_node_count)
        
            !* 给每层设置激活函数
            do i=1, this % layers_count
                call pt_act_fun_list % get_activation_function_by_name( &
                    this % act_fun_name_list(i),                        &
                    this % my_NNStructure % pt_Layer(i) % act_fun)           
            end do
            
            !* 给每层权值、阈值按指定方式初始化
            call NN_weight_threshold_init_main(            &
                this % weight_threshold_init_methods_name, &
                this % my_NNStructure)
                
            this % is_init = .true.
            
            end associate
            
            call LogDebug("NNTrain: SUBROUTINE m_init")
            
        end if

        return
    end subroutine m_init
    !====

    !* 训练函数
    subroutine m_train( this, X, t, y )
    implicit none
        class(NNTrain), intent(inout) :: this
        !* X 是输入值,t 是实际输出,y 是网络预测输出
        real(PRECISION), dimension(:,:), intent(in) :: X
        real(PRECISION), dimension(:,:), intent(in) :: t
        real(PRECISION), dimension(:,:), intent(inout) :: y
        
        integer :: sample_index
        integer :: X_shape(2)
        
        associate (                                                 &
            step            => this % train_step_counter,           &
            grad_opt_method => this % gradient_optimization_method, &
            my_NN           => this % my_NNStructure,               &
            bp_algorithm    => this % bp_algorithm                  &           
        )   
        
        step = step + 1
        
        X_shape = SHAPE(X)        
        
        !* Some gradient optimization methods need preprocessing 
        !* before start iteration, like Adam e.t.c.
        call grad_opt_method % pre_process()
        !* Some gradient optimization methods need the iteration step value 
        !* to adjustment the algorithm intrinsic parameter 
        !* before start iteration, like Adam e.t.c.     
        call grad_opt_method % set_iterative_step( step )
            
        do sample_index=1, X_shape(2)

            call my_NN % backward_propagation( X(:, sample_index), &
                t(:, sample_index), y(:, sample_index) )
        
            !* 标准BP算法在此处更新网络权值和阈值
            if (TRIM(ADJUSTL(bp_algorithm)) == 'standard') then
                call grad_opt_method % update_NN(bp_algorithm)
            end if
                
            call my_NN % calc_average_gradient( X_shape(2) )
            
        end do
            
        !* 累积BP算法在此处更新网络权值和阈值 
        if (TRIM(ADJUSTL(bp_algorithm)) == 'accumulation') then
            call grad_opt_method % update_NN(bp_algorithm)
        end if
            
        call grad_opt_method % post_process()
            
        call my_NN % set_average_gradient_zero()
        
        end associate
        
        return
    end subroutine m_train
    !====
    
    !* 拟合函数
    subroutine m_sim( this, X, t, y )
    implicit none
        class(NNTrain), intent(inout) :: this
        !* X 是输入值,t 是实际输出,y 是网络预测输出
        real(PRECISION), dimension(:,:), intent(in) :: X
        real(PRECISION), dimension(:,:), intent(in) :: t
        real(PRECISION), dimension(:,:), intent(out) :: y
        
        integer :: sample_index
        integer :: X_shape(2)
        
        if( .not. this % is_init ) then
            call LogErr("NNTrain: SUBROUTINE m_sim, &
                NNTrain need init first.")
        end if
        
        X_shape = SHAPE(X)
        
        do sample_index=1, X_shape(2)
            call this % my_NNStructure % forward_propagation( X(:, sample_index), &
                t(:, sample_index), y(:, sample_index) )
        end do
        
        return
    end subroutine m_sim
    !====     

    !* 初始化各参数文件的完整路径
    subroutine m_init_NNParameter( this, caller_name )
    implicit none
        class(NNTrain), intent(inout) :: this
        !* 调用者信息,值可以为 '',此时使用默认配置信息
        character(len=*), intent(in) :: caller_name
        
        if (caller_name /= '') then
            this % NNParameter_file = &
                TRIM(ADJUSTL(this % NNParameter_path)) // &
                TRIM(ADJUSTL(caller_name)) // '_' // &
                'NNParameter.nml'
                
            this % NNLayerNodeCount_file = &
                TRIM(ADJUSTL(this % NNParameter_path)) // &
                TRIM(ADJUSTL(caller_name)) // '_' // &
                'NNHiddenLayerNodeCount.parameter'

            this % NNActivationFunctionList_file = &
                TRIM(ADJUSTL(this % NNParameter_path)) // &
                TRIM(ADJUSTL(caller_name)) // '_' // &
                'NNActivationFunctionList.parameter'
        end if
    
        call LogDebug("NNTrain: SUBROUTINE m_init_NNParameter")
        
        return
    end subroutine
    !====
    
    !* 设置权值、阈值处理方法的名字
    subroutine m_set_caller_name( this, caller_name )
    implicit none
        class(NNTrain), intent(inout) :: this
        character(len=*), intent(in) :: caller_name
    
        this % caller_name = caller_name
        
        call LogDebug("mod_NNWeightThresholdInitMethods: &
            SUBROUTINE m_set_caller_name")
        
        return
    end subroutine m_set_caller_name
    !====
    
    !* 设置权值、阈值处理方法的名字
    subroutine m_set_weight_threshold_init_methods_name( this, name )
    use mod_NNWeightThresholdInitMethods
    implicit none
        class(NNTrain), intent(inout) :: this
        character(len=*), intent(in) :: name
    
        this % weight_threshold_init_methods_name = name
        
        call NN_weight_threshold_init_main(            &
            this % weight_threshold_init_methods_name, &
            this % my_NNStructure)
        
        call LogDebug("mod_NNWeightThresholdInitMethods: &
            SUBROUTINE set_weight_threshold_init_methods_name.")
        
        return
    end subroutine
    !====   
 
    !* 设置激活函数
    subroutine m_set_loss_function( this, loss_fun )
    implicit none
        class(NNTrain), intent(inout) :: this
        class(BaseLossFunction), target, intent(in) :: loss_fun
        
        call this % my_NNStructure % set_loss_function( loss_fun )
        
        call LogDebug("NNTrain: SUBROUTINE m_set_loss_function")
        
        return
    end subroutine m_set_loss_function
    !====  
    
    !* 设置优化方法
    subroutine m_set_optimization_method( this, opt_method )
    implicit none
        class(NNTrain), intent(inout) :: this
        class(BaseGradientOptimizationMethod), target, intent(in) :: opt_method
        
        this % gradient_optimization_method => opt_method
        
        call LogDebug("NNTrain: SUBROUTINE m_set_optimization_method")
        
        return
    end subroutine m_set_optimization_method
    !====   
        
    !* 重置训练步数计数器
    subroutine m_set_train_step_counter( this, step )
    implicit none
        class(NNTrain), intent(inout) :: this
        integer, optional, intent(in) :: step
        
        if (PRESENT(step)) then
            this % train_step_counter = step
        else
            this % train_step_counter = 0
        end if
        
        call LogDebug("NNTrain: SUBROUTINE m_set_train_step_counter")
        
        return
    end subroutine m_set_train_step_counter
    !====   
    
    !* 读取网络的参数
    subroutine m_load_NNParameter( this )
    implicit none
        class(NNTrain), intent(inout) :: this
        
        integer :: HIDDEN_LAYERS_COUNT
        character(len=20) :: BP_ALGORITHM
        namelist / NNParameter_NameList / HIDDEN_LAYERS_COUNT, BP_ALGORITHM
            
        integer :: l_count  
        
        !* 读取参数信息,比如隐藏层的数量
        open( UNIT=30, FILE=this % NNParameter_file, &
            form='formatted', status='old' )            
        read( unit=30, nml=NNParameter_NameList )        
        close(unit=30)
        
        l_count = HIDDEN_LAYERS_COUNT + 1
        this % layers_count = l_count
        this % bp_algorithm = TRIM(ADJUSTL(BP_ALGORITHM))   
        
        call LogDebug("NNTrain: SUBROUTINE m_load_NNParameter")
        
        return
    end subroutine m_load_NNParameter
    !====

    !* 读取网络的参数
    subroutine m_load_NNParameter_array( this )
    implicit none
        class(NNTrain), intent(inout) :: this
        
        integer :: l_count, hidden_l_count
        
        l_count = this % layers_count
        hidden_l_count = l_count - 1
        
        !* 读取每个隐藏层的结点数目
        open( UNIT=30, FILE=this % NNLayerNodeCount_file, &
            form='formatted', status='old' )            
        read( 30, * ) this % layers_node_count(1:hidden_l_count)       
        close(unit=30)
        
        call LogDebug("NNTrain: SUBROUTINE m_load_NNParameter_array")
           
        return
    end subroutine m_load_NNParameter_array
    !====
    
    !* 读取各层激活函数名字
    subroutine m_load_NNActivation_Function_List( this )
    implicit none
        class(NNTrain), intent(inout) :: this
        
        integer :: l_count
        integer :: i
        character(len=180) :: msg
        character(len=20) :: index_to_string
        
        l_count = this % layers_count
        
        !* 读取每个隐藏层的结点数目
        open( UNIT=30, FILE=this % NNActivationFunctionList_file, &
            form='formatted', status='old' )  
            
        do i=1, l_count
            read( 30, * ) this % act_fun_name_list(i)  
        end do
        
        call LogInfo("Activation Function List: ")
        do i=1, l_count    
            write(UNIT=index_to_string, FMT='(I15)') i
            msg = "--> layer index = " // TRIM(ADJUSTL(index_to_string)) // &
                ", activation function = " // &
                TRIM(ADJUSTL(this % act_fun_name_list(i)))
            call LogInfo(msg)
        end do
        
        close(unit=30)
        
        call LogDebug("NNTrain: SUBROUTINE m_load_NNActivation_Function_List")
           
        return
    end subroutine m_load_NNActivation_Function_List
    !====
    
    !* 申请内存空间
    subroutine m_allocate_memory( this )
    implicit none
        class(NNTrain), intent(inout) :: this
        
        integer :: l_count
        
        l_count = this % layers_count
        
        allocate( this % layers_node_count(0:l_count)     ) 
        allocate( this % act_fun_name_list(l_count)       )       
        
        this % is_allocate_done = .true.
        
        call LogDebug("NNTrain: SUBROUTINE m_allocate_memory")
        
        return
    end subroutine m_allocate_memory
    !====
    

    !* 销毁内存空间
    subroutine m_deallocate_memory( this )
    implicit none
        class(NNTrain), intent(inout)  :: this  
        
        deallocate( this % layers_node_count       )
        deallocate( this % act_fun_name_list       )  
        
        this % is_allocate_done = .false.
        
        return
    end subroutine m_deallocate_memory 
    !====

    
    !* 析构函数,清理内存空间
    subroutine NNTrain_clean_space( this )
    implicit none
        type(NNTrain), intent(inout) :: this
    
        call this % deallocate_memory()
        
        call LogInfo("NNTrain: SUBROUTINE clean_space.")
        
        return
    end subroutine NNTrain_clean_space
    !====

end module

附录

多层神经网络,从零开始——(一)、Fortran读取MNIST数据集
多层神经网络,从零开始——(二)、Fortran随机生成“双月”分类问题数据
多层神经网络,从零开始——(三)、BP神经网络公式的详细推导
多层神经网络,从零开始——(四)、多层BP神经网络的矩阵形式
多层神经网络,从零开始——(五)、定义数据结构
多层神经网络,从零开始——(六)、激活函数
多层神经网络,从零开始——(七)、损失函数
多层神经网络,从零开始——(八)、分类问题中为什么使用交叉熵作为损失函数
多层神经网络,从零开始——(九)、优化函数
多层神经网络,从零开始——(十)、参数初始化
多层神经网络,从零开始——(十一)、实现训练类
多层神经网络,从零开始——(十二)、实现算例类
多层神经网络,从零开始——(十三)、关于并行计算的简单探讨

你可能感兴趣的:(多层神经网络,从零开始——(十一)、实现训练类)