一、 一层的数据结构
多层神经网络结构的示意图如上图所示,首先需要定义每一层的数据。每一层中的权值、阈值是需要保存的信息,因此各自单独定义了一个结构体 Layer_Weight,Layer_Threshold。
每一层中还包含了其他信息有:
- S:每一层的输入;
- R:每一层激活函数的输入 ;
- Z:每一层的输出 ;
- d_Matrix_part:上篇博客中公式(26)-(31)中向量 eiT后面的部分;
- dW:损失函数对权值的导数;
- avg_dW:所有样本损失函数对权值的导数的平均;
- dTheta:损失函数对阈值的导数
- avg_dTheta:所有样本损失函数对阈值的导数的平均;
- act_fun:每一层对应的激活函数,这里的激活函数 BaseActivationFunction 是一个抽象类,因为激活函数常用的有许多种,比如:sigmoid、tanh、ReLU等等。
!* 该模块定义了神经网络各层用到的数组结构,
!* 具体的参数意义参见PDF文档。
module mod_NNParameter
use mod_Precision
use mod_BaseActivationFunction
implicit none
!---------------------------------------------------------
! 层与层之间的权重
!---------------------------------------------------------
type :: Layer_Weight
!* 注意:W连接两层的结点数目分别为 M,N
!* 则W为 N×M 的矩阵.
real(kind=PRECISION), dimension(:,:), allocatable :: W
end type
!=========================================================
!---------------------------------------------------------
! 层的阈值
!---------------------------------------------------------
type :: Layer_Threshold
!* 数组的大小是该阈值对应层的节点数目
real(kind=PRECISION), dimension(:), allocatable :: Theta
end type
!=========================================================
!---------------------------------------------------------
! 层中用到的局部数组:输入、输出等
!---------------------------------------------------------
type :: Layer_Local_Array
!* 数组的大小是该阈值对应层的节点数目
!* S是输入数组,R=S-theta,Z是输出数组,Z=f(R)=f(S-Theta)
real(kind=PRECISION), dimension(:), allocatable :: S
real(kind=PRECISION), dimension(:), allocatable :: R
real(kind=PRECISION), dimension(:), allocatable :: Z
!* (Gamma^{k+1} W^{k+1})^T ... (Gamma^{n} W^{n}) p_zeta/p_zn
real(kind=PRECISION), dimension(:), allocatable :: d_Matrix_part
!* 以下zeta表示误差函数。
!* zeta对W的导数
!* 数组行、列大小分别是该权重W连接的两层的节点数目
real(kind=PRECISION), dimension(:,:), allocatable :: dW
!* 所有样本zeta对W的导数的求平均
real(kind=PRECISION), dimension(:,:), allocatable :: avg_dW
!* zeta对Theta的导数
!* 数组的大小是该阈值对应层的节点数目
real(kind=PRECISION), dimension(:), allocatable :: dTheta
!* 所有样本zeta对Theta的导数的求平均
real(kind=PRECISION), dimension(:), allocatable :: avg_dTheta
!* 激活函数
!* 注:BaseActivationFunction 是抽象类,不能使用动态数组.
class(BaseActivationFunction), pointer :: act_fun
end type
!=========================================================
!* W、Theta 数组也可以统一放到 Layer_Local_Array 结构体中,
!* 这里单独放置是为了应对未来代码可能的修改。
end module
二、多层神经网络结构
使用指针数组即可构造出整个神经网络的结构,需要增加的一些数据为:
- X:为神经网络的输入;
- t:为神经网络的目标输出;
- loss_function:为损失函数,这里的 BaseLossFunction 是一个抽象类,因为损失函数需要根据具体的需求定义,常用的损失函数有MSE、交叉熵等。
另外最重要的是需要将神经网络的前向计算,以及反向误差计算等方法实现。
!* 该模块定义了神经网络结构,以及神经网络结构相应的运算,
!* 如:前向传播计算各层值、反向传播计算误差导数等。
!* 具体的参数、函数意义参见PDF文档。
module mod_NNStructure
use mod_Precision
use mod_NNParameter
use mod_Log
use mod_BaseActivationFunction
use mod_BaseLossFunction
implicit none
!-------------------------
! 工作类:网络结构的数据 |
!-------------------------
type, public :: NNStructure
! 是否初始化完成的标识
logical, private :: is_init_basic = .false.
! 是否初始化内存空间
logical, private :: is_allocate_done = .false.
! 是否初始化权值矩阵、阈值
logical, private :: is_init_weight = .false.
logical, private :: is_init_threshold = .false.
!* 是否初始化损失函数
logical, private :: is_init_loss_fun = .false.
! 层的数目,不含输入层
integer, public :: layers_count
! 每层节点数目构成的数组:
! 数组的大小是所有层的数目(含输入层)
integer, dimension(:), allocatable, public :: layers_node_count
! 指向所有层权重的指针数组:
! 数组的大小是所有层的数目(不含输入层)
type (Layer_Weight), dimension(:), pointer, public :: pt_W
! 指向所有层阈值的指针数组:
! 数组的大小是所有层的数目(不含输入层)
type (Layer_Threshold), dimension(:), pointer, public :: pt_Theta
! 指向所有层局部数组结构的指针数组:
! 数组的大小是所有层的数目(不含输入层)
type (Layer_Local_Array), dimension(:), pointer, public :: pt_Layer
! 网络的目标输入
real(PRECISION), dimension(:), allocatable, private :: X
! 网络的目标输出
real(PRECISION), dimension(:), allocatable, private :: t
!* 损失函数
class(BaseLossFunction), pointer, private :: loss_function
!||||||||||||
contains !|
!||||||||||||
!* 初始化:
!* (1). 给定网络基本结构、申请内存空间;
!* (2). 随机初始化权值、阈值.
procedure, public :: init_basic => m_init_basic
!* 是否初始化
procedure, public :: get_init_basic_status => c_is_init_basic
!* 前向计算,根据输入值,计算神经网络各层的值,
!* 并返回预测值
!* Tips:需要初始化结构、设置各层激活函数.
procedure, public :: forward_propagation => m_forward_propagation
!* 反向计算,计算误差函数对神经网络各层的导数
!* Tips:需要初始化结构、设置各层激活函数、设置损失函数.
procedure, public :: backward_propagation => m_backward_propagation
!* 计算参数的平均梯度
!* 完成一次m_backward_propagation计算后调用
procedure, public :: calc_average_gradient => m_calc_avg_gradient
!* 将平均梯度置 0
procedure, public :: set_average_gradient_zero => m_set_average_gradient_zero
!* 设置损失函数
procedure, public :: set_loss_function => m_set_loss_function
!* 设置指定层的激活函数
procedure, public :: set_activation_function_layer => m_set_act_fun_layer
!* 计算所有求导变量的值
procedure, private :: get_all_derivative_variable => m_get_all_derivative_variable
!* 设置输入层
procedure, private :: set_input_layer => m_set_input_layer
!* 设置输出层
procedure, private :: set_output_layer => m_set_output_layer
!* 随机初始化阈值,默认初始化到(-1,1)
!* 在Train方法中,可以重新设置初始化.
procedure, private :: init_layer_weight => m_init_layer_weight
!* 随机初始化阈值,默认初始化到(-1,1)
!* 在Train方法中,可以重新设置初始化.
procedure, private :: init_layer_threshold => m_init_layer_threshold
!* 计算所有层中的局部变量 S、R、Z:
!* 激活函数、输入层、W、Theta已随机初始化
procedure, private :: get_all_layer_local_var => m_get_all_layer_local_var
!* 计算所有的d_Matrix_part:
!* 目标输出层已初始化.
procedure, private :: get_all_d_Matrix_part => m_get_all_d_Matrix_part
!* 计算目标层的dW
procedure, private :: get_layer_dW => m_get_layer_dW
!* 计算所有的dW
procedure, private :: get_all_dW => m_get_all_dW
!* 计算目标层的 dTheta
procedure, private :: get_layer_dTheta => m_get_layer_dTheta
!* 计算所有的 dTheta
procedure, private :: get_all_dTheta => m_get_all_dTheta
!* 输出网络结构信息到日志
procedure, private :: print_NN_Structrue => m_print_NN_Structrue
!* 申请NNStructure包含的指针所需空间
procedure, private :: allocate_pointer => m_allocate_pointer
!* 申请每层所需的内存空间
procedure, private :: allocate_memory => m_allocate_memory
!* 销毁指针
procedure, private :: deallocate_pointer => m_deallocate_pointer
!* 销毁内存空间
procedure, private :: deallocate_memory => m_deallocate_memory
!* 析构函数,清理内存空间
final :: NNStructure_clean_space
end type NNStructure
!--------------------------------------------------------
!-------------------------
!* 略... ...
!-------------------------
!||||||||||||
contains !|
!||||||||||||
!* 具体实现略...
end module
附录
多层神经网络,从零开始——(一)、Fortran读取MNIST数据集
多层神经网络,从零开始——(二)、Fortran随机生成“双月”分类问题数据
多层神经网络,从零开始——(三)、BP神经网络公式的详细推导
多层神经网络,从零开始——(四)、多层BP神经网络的矩阵形式
多层神经网络,从零开始——(五)、定义数据结构
多层神经网络,从零开始——(六)、激活函数
多层神经网络,从零开始——(七)、损失函数
多层神经网络,从零开始——(八)、分类问题中为什么使用交叉熵作为损失函数
多层神经网络,从零开始——(九)、优化函数
多层神经网络,从零开始——(十)、参数初始化
多层神经网络,从零开始——(十一)、实现训练类
多层神经网络,从零开始——(十二)、实现算例类
多层神经网络,从零开始——(十三)、关于并行计算的简单探讨