首页 > 其他分享 >part2_01神经网络的准备

part2_01神经网络的准备

时间:2023-01-03 19:44:36浏览次数:35  
标签:感知器 函数 梯度 模型 神经网络 part2 01 激活

神经网络的概念由连接主义学派提出。每一个神经网络都由若干个互有交互、连接的感知器,或称为神经元,以网络连接层的形式构成,感知器与感知器之间在满足特定的条件下会互相传播信息。

1 神经网络概述

在大多数情况下,人工神经网络能在外界信息的基础上改变内部结构,是一种自适应系统。现代神经网络是一种非线性统计性数据建模工具。典型的神经网络具有以下3个部分。

  • 网络结构——网络结构指定了网络中的变量和它们的拓扑关系。通常情况下,神经网络中的变量可以是神经元连接的权重和神经元的激励值
  • 激励规则——大部分神经网络模型具有一个短时间尺度的动力学规则,从而定义神经元如何根据其他神经元的活动改变自己的激励值。一般激励函数依赖于网络中的权重(即该网络的参数)。
  • 学习规则——学习规则指定了网络中的权重如何随着时间推进而调整,一般视其为长时间尺度的动力学规则。一般情况下,学习规则依赖于神经元的激励值。此外,学习规则也可能依赖于监督者提供的目标值和当前权重的值。

一般情况下,网络模型的第一层是输入层,通常会将原始数据转化为数字矩阵,提交到输入层,输入层的神经受到激励响应,并传播到下面的隐藏层,最终在输出层呈现训练效果。

神经网络主要经历了两次传播——前向传播(Froward Propagation)和反向传播(Back Propagation)。

  1. 前向传播,又称激励传播

    假设一个两层的神经网络,其传播过程为:先将训练数据输入神经网络,训练数据通过隐藏层神经元时,通常为一个线性过程,$$y_{hidden}=ωx+b$$,其中$$x$$为输入的原始数据,$$y$$为隐藏层部分的线性输出,而$$ω、b$$为隐藏层中的相关参数;在得到$$y_{hidden}$$后,会通过一个激活函数,这一步十分重要,因为一个没有激活函数的神经网络永远是线性的。

    在前向传播过程中,输出又称作激励响应$$y_{out}$$,根据激励响应以及训练输入对应的目标输出$$y_{true}$$求损失值$$J$$,从而获得隐藏层与输出层的响应误差。至此,第一阶段的前向传播完成,损失值的公式为:
    $$
    J_i=\frac{[(y_{out}){i}-(y{true}){i}]^2}{n}
    $$
    则总的损失为
    $$
    J=\sum
    {i=0}^{n}J_{i}
    $$

  2. 反向传播

    反向传播的作用是更新每个神经元上的权重和偏移系数,当梯度下降到0时,便可以求得相应的参数。在前向传播过程中,最后会计算其损失$$J$$,在反向传播过程中,将损失$$J$$看作为一个相关系数的函数,记为$$J(ω,b)$$。在此,以网络中的单个神经元的反向传播为例,以更新$$ω_1$$为例,其公式为:
    $$
    \frac{\partial{J}}{\partial{\omega_{1}}}=\frac{\partial{J_1}}{\partial{\omega_{1}}}
    $$
    根据链式法则可知,存在一条反向传播路径,原求导公式可以表示为:
    $$
    \frac{\partial{J_{1}}}{\partial{\omega_{1}}}=
    \frac{\partial{J_{1}}}{\partial{y_{out1}}} \times
    \frac{\partial{y_{out1}}}{\partial{y_{hidden}}} \times
    \frac{\partial{y_{hidden}}}{\partial{\omega_{1}}}
    $$
    同理,对神经网络模型上每一个神经元中的权重和偏移系数做上述处理,于是权重得到更新,每次更新视为一个批量,将所有的数据完全训练一遍称为一轮(epoch),随着前向传播和反向传播交替进行,直至到达规定的轮数或者网络对输入的响应达到目标预期的范围内停止。

2 神经网络的初步实现

感知器是神经网络的基本单元,本质上是一个过滤器,可以类比逻辑回归算法的思想,存在一个阈值n(通常为0),当变量大于或者小于该阈值时,返回的映射值就是1或者-1,其公式为:
$$
sign(x)=\left{\begin{array}{rcl}
1,x>n
\-1,x \leqslant n
\end{array}\right.
$$
假设数据集是线性可分的,感知器的目标是求一个能够将训练集正、负实例点完全分开的超平面。为了找出这样的超平面,即确定感知器的模型参数$$\omega、b$$,其超平面公式为$$y=\omega x+b$$。

实际上,感知器也是一个类似的过滤算法,假设存在一个$$m$$维的输入向量$$X_{m}{x_{1},x_{2},...,x_{m}}$$,在神经网络模型的场景中称为$$m$$维输入特征,输出为1或0,其公式为:
$$
f(x)=\left{\begin{array}{rcl}
1,\omega x+b>0
\0,\omega x+b \leqslant 0
\end{array}\right.
$$
式中,$$\omega$$代表向量的权重;$$\omega x$$代表向量与权重的点积$$\sum_{i=1}^{m}{\omega_i}{x_i}$$;$$b$$代表偏差。可以看出,当输入值$$X_m$$经过公式$$y=\omega x+b$$计算后,如果$$y>0$$(在超平面之上),则输出为1(真);反之,输出为0(假)。

可以发现,感知器是线性函数$$y=\omega x+b$$与非线性函数(这里也就是激活函数)的组合。

3 感知器层

感知器层(Perceptron Layer),就是将多个感知器合并为一个层,该层以全连接或者部分连接的方式,将上一层感知器的输出或者原始数据的输入作为输入,其输出则直接作为下一层的输入或者整个模型的输出。多个感知器层的叠加组成多感知器层(Multilayer Perceptron,MLP)。

当首层接收到输入数据$$X_m$$时,通过预先定义的感知器函数进行计算(其权重$$\omega$$和偏差$$b$$会在之后的训练中进行更新,使其逼近实际输出),同时触发产生0或者1,再作为输入传输到下一层,直到最终作为模型的输出传出。

需要注意的是,输入数据的维度与首层感知器数量的多少没有绝对关系,输入数据的维度仅仅作用于单个感知器的输入位数。下面构造一个网络模型——假设输入维度为m,首层感知器有3个,第二层感知器有4个,最后输出位数为1,所有的层均为全连接层。

# 引用 keras.models库中的 Sequential类,引用 keras.layers.core库中的 Dense模型
from keras.models import Sequential
from keras.layers.core import Dense
# 引用 keras.utils中的 plot_model类,画出神经网络的结构图
from keras.utils.vis_utils import plot_model
# m 为输入的维度
m=8
# 声明序列模型model
model=Sequential()
# 添加第一层全连接层,输入维度为 m,感知器有3个,权重初始化为 random_uniform
model.add(Dense(3,input_dim=m,kernel_initializer='random_uniform'))
# 添加第二层全连接层,在序列模型中,如果不是首层,则不需要设置输入维数,感知器有4个
# 权重初始化为 random_uniform
model.add(Dense(4,kernel_initializer='random_uniform'))
# summary()用于显示模型的详细信息
model.summary()
plot_model(model,to_file='model01.png',show_shapes=True)
model01

每个感知器都可以采用特定的方式对其进行权重初始化,keras中提供如下几种方法:

random_uniform:随机对称概率分布,简单来说,权重会在对称概率分布(-0.5,0.5)中被赋予初始值。

random_normal:随机高斯分布,使权重的初始值的随机数服从高斯分布,其均值为0,标准差为0.05

zero:将权重全部置为0

glorot_uniform(默认):设置了一个对称概率分布,其在(-a,a)中随机分布,其中
$$
a=(\frac{\sigma}{n_{in}+n_{out}})^\frac{1}{2}
$$
其中,$$n_{in}$$表示权重向量中的输入单元;$$n_{out}$$表示权重向量中的输出单元。值得注意的是,该方法是Glorot和Bengio提出的Xavier方法。对于神经网络来说,是一种很有效的初始化方法,能够使网络中的信息更好地流动,每一层输出的方差应尽量相等。

3.1 梯度消失/爆炸问题

反向传播算法通过从输出层到输入层的方向传播梯度误差;通过计算在网络中对应的权重下的损失函数的梯度,并利用这些梯度信息更新每个单元的权重,至此一个梯度下降的步进即可完成,通过多次梯度下降的步进,最终每个单元的权值会收敛于某个固定的值域范围,模型训练就此完成。

在实际应用场景中,尤其是在深度网络中,梯度通过逐层传播之后,可能会越来越小,当传播到足够低的层时,该层的权重值由于梯度过小,所以几乎不会改变,并且在增加训练轮数或者样本的情况下,拟合效果仍然没有很好的改观,这种情况称为梯度消失。同时,也存在与之相反的情况,随之传播层数的变低,其梯度越来越大,造成神经网络的权重在训练过程中会发生较大程度的更新,甚至到训练周期结束时,都没有形成一个稳定的模型。

无论是梯度消失还是梯度爆炸,通过增加训练轮数或者样本的方法模型都无法得到优化,因为这种情况是模型内生性的结构性问题,所以需要对模型本身加以优化和诊断。

解决方法是Xavier方法——Sigmoid激活函数和权重初始化的结合,在前向传播上,我们希望每一层作为输出信号的方差与每一层作为输入信号的方差相等;在反向传播上,我们同样希望每一层梯度方差在每次进出层时都是相等的。尽管无法保证输入和输出的连接数量是一样的,但是对权重赋予随机值(需要满足一定的条件)的方式,可以使数据信号在通过激活函数时不至于消失,或者引起信号饱和甚至爆炸。

随机值需要满足的条件为:随机值满足均值为0,标准差为$$\sigma=(\frac{2}{n_{in}+n_{out}})\frac{1}{2}$$的正态分布。或者满足范围在(-a,a)的对称概率分布,其中$$a=(\frac{6}{n_{in}+n_{out}})\frac{1}{2}$$。

采用Xavier方法可以相对快速的加快训练过程,随着深度网络的发展,出现了在其基础上的变种,如He方法,其随机分布满足均值为0,标准差$$\sigma=(\frac{4}{n_{in}+n_{out}})\frac{1}{2}$$的正态分布,或者需要满足范围在(-a,a)的对称概率分布,其中$$a=(\frac{12}{n_{in}+n_{out}})\frac{1}{2}$$。

3.2 激活函数及其进化

感知器本质上激活函数(非线性函数)和线性函数的组合。引入激活函数是在于,线性函数表达数据的能力不够。比如,在支持向量机中线性不可分的情况下,可以通过将低维数据映射到高维上,从而使其线性可分。神经网络模型实际上也面临许多非线性场合,加入激活函数则可以增加模型的表达能力。

或者假设一个感知器不存在激活函数,这个感知器仅用$$y=\omega x+b$$进行描述,多个感知器之间通过特定的权重组合在一起,实际上这个神经网络模型就成了多个线性方程的组合:$$y_n=\omega_n x_n+b_n$$。

因此,没有激活函数的神经网络,仅仅是线性模型。

  1. 阶跃函数

    阶跃函数的公式为:
    $$
    f(x)=\left{\begin{array}{rcl}
    1,x>0
    \0,x\leqslant 0
    \end{array}\right.
    $$
    在神经网络中,如果存在两个阈值附近的十分接近的数据,如0.0001和-0.0001,在实际场景中,二者的差别并不大,但通过阶跃函数之后,两个数据具有完全不同的输出1和0。在模型学习的过程中,每个神经元的权重将会存在一定程度的更新,但是由于阶跃函数过于刚性,尽管每层的数据输出绝对变化幅度很大,但只要没有达到阈值触发跃迁,模型的拟合效果就不会随着训练的增加而相应增加,这种现象称为死神经元(Dead Neurons)。

    基于阶跃函数这样过于刚性的函数缺陷,激活函数在非线性的基础上,同时还有平滑性的需求,从而避免类似0.0001和-0.0001这样的值被激活函数处理后存在较大的差异,在(0,1)的值域空间,输出是渐进的、连续的。

  2. Sigmoid函数

    Sigmoid函数的公式为:
    $$
    f(x)=\frac{1}{1+e^{-x}}
    $$
    式中,输入变量$$x \in (-\infin,\infin)$$,其输出值是(0,1),这是一个连续函数。但值得注意的是,当输入值的绝对值特别大时,如果为正,则该函数的输出无限接近1;如果为负,则无限接近0。这两个区域称为饱和区,如果对这些饱和区进行求导,会发现其导数值无限接近0,也就意味着,在这些区域进行反向传播时,梯度会变得十分小,接近0,随着反向传播层递进,梯度被逐层稀释,到最后一层(模型的首层)时,梯度被忽略为0。所以Sigmoid函数解决了梯度爆炸问题,但不能规避梯度消失问题。

  3. tanh函数

    tanh函数的公式为:
    $$
    tan h(x)=\frac{2}{1+e^{-2x}}-1
    $$
    tanh函数是神经网络模型主流采用的激活函数之一。因为它比Sigmoid函数收敛更快——对tanh函数求导,在相同的值域区间,具有更大的梯度;同时在相同的值域范围内,tanh函数的取值范围更大,为(-1,1);但是仍然存在Sigmoid函数固有的弊端,存在饱和区。

  4. ReLU(Rectified Linear Unit)函数

    全称是调整线性单元,是神经网络模型采用的主流激活函数之一。在负区间采用的是阶跃函数(输入值需要超过阈值0才能激活),而在正区间的函数存在非零导数,从而保证整个值域范围内仍然存在梯度的可能。

    ReLU函数的公式为:
    $$
    f(x)=\left{\begin{array}{rcl}
    x,x>0
    \0,x\leqslant 0
    \end{array}\right.
    $$
    ReLU函数能够以较快的速度计算其结果。但是相较于Sigmoid函数和tanh函数,ReLU函数在0值附近的值域收敛存在一定的劣势——在负区间容易引发死神经元问题,该区间的导数为0,从而无法更新其感知器的权重,导致其学习能力变低。

  5. Leaky ReLU函数

    上述问题的解决方案就是替换掉负区间阶跃函数的部分,当然不能直接换成$$f(x)=x$$,这就变为了线性函数,Leaky ReLU函数的公式为:
    $$
    f(x)=\left{\begin{array}{rcl}
    x,x>0
    \0.1x,x\leqslant 0
    \end{array}\right.
    $$
    当然,即使是这样,在某些特定情况下依然无法完全消除梯度消失的问题,仅仅能够起到缓解作用,因负区间的导数为0.1,负区间的梯度变化明显小于正区间的梯度变化,负区间的梯度在传播过程中会逐层变小,如果神经网络模型足够深,依然会出现梯度消失的情况。

  6. PReLU函数

    Parametric Rectified Linear Unit函数,全程是参数调整线性单元。其公式为:
    $$
    f(x)=\left{\begin{array}{rcl}
    x,x>0
    \ \alpha x,x\leqslant 0
    \end{array}\right.
    $$
    PReLU函数可以视作ReLU系列函数的父函数,相当于PReLU函数中的$$\alpha$$选择了不同的值。在ReLU中$$\alpha$$为0,在Leaky ReLU中$$\alpha$$为0.1。

    所谓的梯度问题,在ReLU系列的函数上是导数$$f'(x)$$大于1还是小于1的问题,在负区间导数$$f'(x)=\alpha$$,于是当$$\alpha>1$$时,随着网络逐层传播,只要有足够的深度,必然梯度爆炸;当$$0<\alpha<1$$时,随着网络逐层传播,只要有足够的深度,必然会导致梯度消失。

    因此,选取合适的$$\alpha$$值需要综合考虑神经网络模型的深度,以及输入数据的分布情况。

    除了以上的激活函数,还有以下几种。

    Hard tanh函数:
    $$
    f(x)=\left{\begin{array}{rcl}
    -1,x<-1
    \0,-1\leqslant x\leqslant1
    \1,x>1
    \end{array}\right.
    $$
    ELU(Exponential Linear Unit)函数:
    $$
    f(x)=\left{\begin{array}{rcl}
    x,x\geqslant0
    \\alpha(e^x-1),x< 0
    \end{array}\right.
    $$
    Softplus函数:
    $$
    f(x)=ln(1+e^x)
    $$
    ⭐激活函数存在以下几个选择标准:

    • 非线性:激活函数的主要意义在于其非线性,所以避免选取线性函数作为激活函数;当然,还需要考虑数据的分布情况,假设在ReLU函数中,数据仅仅分布在负区间,则会有潜在的死神经元问题;或者仅在正区间,激活函数的非线性特性也无法表达。

    • 梯度问题:在反向传播时,如果梯度长期大于1,而被训练的神经网络模型足够深,则必然会引起梯度爆炸;如果长期小于1甚至为0,同样在被训练的神经网络模型足够深的情况下,则必然会引起梯度消失。

      那么,多深的神经网络模型可以称其为足够深呢?

    • 神经元死亡问题:该情况主要发生于阶跃函数及有饱和区的激活函数,此处的激活函数的输出值恒为常数或者收敛于常数,其梯度恒为0,即梯度没有变化,各个感知器的权重不会更新,无法达到训练预期。

3.3 激活函数的代码实现

激活函数在keras中有两种实现方式:一是直接作为神经网络层的属性进行设置;二是将激活函数作为一个模型序列添加到相应的层中。

以全连接层添加激活函数为例,以下两种代码的实现是等价的。

  • 作为属性的实现方式:

    from keras.models import Sequential
    from keras.layers.core import Dense,Activation
    m=8
    model=Sequential()
    # 在感知器层,本例中是全连接层Dense,有激活函数属性activation,在此可以设置激活函数
    model.add(Dense(3,input_dim=m,kernel_initializer='random_uniform',activation='relu'))
    model.summary()
    '''
    Model: "sequential"
    _________________________________________________________________
     Layer (type)                Output Shape              Param #   
    =================================================================
     dense (Dense)               (None, 3)                 27        
                                                                     
    =================================================================
    Total params: 27
    Trainable params: 27
    Non-trainable params: 0
    _________________________________________________________________
    '''
    
  • ⭐作为模型序列的实现方式:

    # 在感知器层,本例中是全连接层Dense
    model.add(Dense(3,input_dim=m,kernel_initializer='random_uniform'))
    # 为序列Dense层添加激活函数ReLU
    model.add(Activation('relu'))
    '''
    Model: "sequential_1"
    _________________________________________________________________
     Layer (type)                Output Shape              Param #   
    =================================================================
     dense_1 (Dense)             (None, 3)                 27        
                                                                     
     activation (Activation)     (None, 3)                 0         
                                                                     
    =================================================================
    Total params: 27
    Trainable params: 27
    Non-trainable params: 0
    _________________________________________________________________
    '''
    
  • activation在序列中并不计参数数量(Param列,activation的为0)

3.4 批量规范化

除了激活函数以外,数据分布的重要性主要体现在以下几个方面。

机器学习中存在一个经典假设,即源空间(Source Domain)和目标空间(Target Domain)的数据分布(Distribution)是一致的。如果两者之间的分布不一致,则会引发一系列的问题,比如模型不能很好的泛化。

假设存在一组带训练的数据,其分布十分不平衡,完全不满足高斯分布,并且分布范围大多数在坐标象限上不对称,这样的分布数据在通过诸如ReLU激活函数之后,其输出特性并没有很好地表现出非线性(因为ReLU在正区间段的函数是线性函数)。因此,需要将数据各个维度上的均值设为0,且规范到单位区间中。

这种将各个维度数据减去其所在维度数据的平均值的方法称为均值减除(Mean Substraction),其公式为:
$$
\overline x=x-\mu
$$
式中,$$\overline x$$是均值为0的数据;$$x$$为原始数据;$$\mu$$为均值。

此外,当数据不同维度的尺度、相关系数不一致时,可能会使损失函数的等高线变得复杂、扭曲,从而使梯度下降的速度变慢,故需要将数据在不同维度上的尺度统一在一个相同的标准之下。因此,在均值减除的基础上,将数据统一在一个标准差之下,这个过程称为规范化(Normalization),也可以称为Z-Score,其公式为:
$$
\overline x=\frac{x-\mu}{\sigma}
$$
式中,$$\sigma$$是该数据的标准差。

在实际模型开发过程中,数据分为训练集和测试集,当训练集和测试集的效果不一样,即两者的分布不一致时,这种现象被称为协变量移位(Covariate Shift)。更为规范的描述如下:如果引入测试集,存在$$p_{1}(x)$$是测试集中的一个样本点的概率密度,以及训练集中的一个样本点的概率密度$$p_{0}(x)$$,于是估计一个条件概率密度$$p(y|x,\theta)$$,就可以通过训练集得到的损失函数$$f_{loss}(\theta)$$的最优解$$\theta_{train}$$,而最优解$$\theta_{train}$$带入测试集中的损失函数不是最优解,于是协变量移位就此发生。

总之,数据本身存在不对称、标准差不统一及协变量移位的问题,批量规范化可以解决前两个问题。对于协变量移位,则提出新的概念——内部协变量移位(Internal Covariate Shift,ICS)。具体来说,对于深度网络模型在其中的某一层,在训练过程中梯度下降使参数改变,从而该层的输出数据分布也随之改变;于是,对下一层来说,输入数据的分布已经改变。这是层与层之间的协变量移位,在模型内部产生,因此称为内部协变量移位。

那么,批量规范化如何解决内部协变量移位问题?

感知器是线性函数和激活函数的组合,而批量规范化(Batch Normalization)需要对线性函数的输出进行处理,之后再将处理后的数据输出到激活函数

普通感知器:输入→线性函数→激活函数→输出

引入批量规范化的感知器:输入→线性函数→批量规范化→激活函数→输出

存在输入$$x$$大小为$$m$$的小批量(Mini Batch)$$B={x_{1},x_{2},...,x_{m}}$$,有变量$$\beta、\gamma$$,则对线性函数$$y=\omega x+b$$进行批量规范化输出:
$$
y_{BN}=BN_{\beta,\gamma}(x)
$$
可以求得小批量的均值$$\mu_{BN}$$和标准差$$\sigma_{BN}$$,具体如下:
$$
\mu_{BN}=\frac{1}{m} \sum_{i=1}^{m} x_i
$$

$$
\sigma_{BN}^2=\frac{1}{m} \sum_{i=1}^{m} (x_i-\mu_{BN})^2
$$

对输入$$x$$正则化后得到$$\overline x$$:
$$
\overline x=\frac{x-\mu_{BN}}{\sigma_{BN}}
$$
$$BN_{\beta,\gamma}(x)$$的输出为
$$
y_{BN}=\gamma \overline x+\beta
$$
由公式的结构可以看出,$$\gamma$$是正则化函数的缩放系数,$$\beta$$是正则化函数的偏移系数。$$\beta,\gamma$$都是在网络中学习得到的。

其整个步骤可以概括为两步:①对批量中的数据$$x_i$$进行Z-Score转换;②通过调整$$\beta$$和$$\gamma$$对$$\overline x$$进行变换,从而生成相对于激活函数合适的输入。

批量规范化不在激活函数之后或者线性函数之前的原因有以下两点。

  • 如果批量规范化在激活函数之后,内部协变量移位已经发生,再进行批量规范化意义不大。
  • 在线性函数之前进行进行批量规范化也不行,因为规范化后的输入数据(相对于线性函数)在经过线性函数处理后,仍然有可能分布不平衡。

所以应当将批量正则化操作放在线性函数和激活函数之间,从而杜绝内部协变量移位问题。

批量规范化适用于哪些场景,需要在什么时候使用。

  • 梯度下降很慢——梯度在各个维度上的尺度、相关度等因素不一致,会使损失函数的等高线变得扭曲且复杂,从而降低梯度下降的速度。批量规范化通过Z-Score的正则化方法使转化之后的特征的各个维度统一在一个标准差之内,从而使损失函数的等高线变得平滑,进而提升了梯度下降的速度。
  • 梯度消失/爆炸——如果数据分布不平衡,那么大部分数据落在饱和区间、阶跃区间以及激活函数导数大于1的区间,如果这种分布不平衡没有及时的纠正,随着神经网络模型深度的逐层积累,最终会引发梯度消失/爆炸,如$$0.9{10}≈0.3487$$,$$1.1{10}≈2.594$$,而在2014年提出的用于处理大尺度图形识别的VGG-16卷积神经网络有21层感知器层,现在的神经网络模型的规模早已突破1000层,其中谷歌拥有多达1370亿个参数的超大规模神经网络。

同样,由于规范化的作用,特征在各个维度上的数据均值和方差一致,所以激活函数处理的区间范围也间接变大。

一般情况下的调优——在不存在梯度相关问题的神经网络模型中,批量规范化仍然可以作为梯度下降速度和模型精度的方法。最后,在代码的实现过程中,如果需要用到批量规范化,则激活函数不要以属性的方式设置到相应的感知器层,而是应以模型序列的方式加入,因为感知器层与激活函数层之间需要添加批量规范化层。

from keras.models import Sequential
from keras.layers.core import Dense,Activation
from keras.layers import BatchNormalization
m=8
model=Sequential()
# 在感知器层,本例中是全连接层Dense,有激活函数属性activation,在此可以设置激活函数
model.add(Dense(3,input_dim=m,kernel_initializer='random_uniform'))
# 添加正则化层
model.add(BatchNormalization())
# 为序列Dense层添加激活函数ReLU
model.add(Activation('relu'))
model.summary()
'''
Model: "sequential_1"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 dense_1 (Dense)             (None, 3)                 27        
                                                                 
 batch_normalization (BatchN  (None, 3)                12        
 ormalization)                                                   
                                                                 
 activation (Activation)     (None, 3)                 0         
                                                                 
=================================================================
Total params: 39
Trainable params: 33
Non-trainable params: 6
_________________________________________________________________
'''

4 准备训练模型

这里搭建的神经网络模型主要用于识别手写数字。按照模型开发的应有流程实施,其步骤如下。

首先,进行数据预处理,本例中的数据样本不需要特别的预处理,但是其标签需要做预处理。

其次,模型搭建。通过叠加相应的感知器层,激活函数来构建一个神经网路架构,初步搭建的神经网络不一定是最终确定的结构,所以在这个过程中需要对结构进行改进。

再次,模型的验证。通过测试集对已经训练好的数据进行验证。

最后,模型的优化。神经网络模型的优化不仅涉及参数调整,还涉及对网络结构的调整。

除了上面的标准流程外,模型的学习效率、性能指标以及拟合程度等方面也在考虑范围之内。

在本例中,尽管对样本数据已经进行中心化、规范化的方式预处理,但是数据标签是以0~9这种方式标注的。这种标签的数据性质是数字型的,这种数字型的隐患在于会使神经网络认为样本与标签存在线性关系。

但是类别标签是不存在线性关系的,一个像3又像5的手写数字字体,如果按照线性关系处理中位数或者平均值,则会被处理为4,但是4的形状和3、5是完全不同的,所以必须打破这种潜在的线性关系。

所以需要将类别标签以非线性关系呈现出来,可以采用独热编码预处理的方式:将标签的种类数10,转化为维度是10的系数向量ylabel,每个种类m对应ylabel第m位上数值为1,其余为0。

5 定义神经网络模型

这里将使用keras实现神经网络模型并用它识别MNIST的手写字体,从一个最简单的模型开始搭建,再逐步优化它的输出。对用到的库尽心简单介绍:

  • keras.datasets 为数据源库,在本例中需要引用MNIST数据
  • 在keras.model中选取序列模型
  • 在模型的核心层keras.layers.core中,选取全连接层Dense和激活函数层Activation
  • 在模型的训练过程中,需要配置相应的优化器用于梯度下降,所用到的库为keras.layers.core
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers.core import Dense,Activation
from tensorflow.keras.optimizers import SGD
from keras.utils import np_utils
import numpy as np
import tensorflow as tf

然后配置一些变量,分别是随机数种子、训练轮数、训练量批数、训练输出级别、训练样本类别、优化器、隐藏层感知器数目、验证集占比。

np.random.seed(17)
NB_EPOCH=80
BATCH_SIZE=128
VERBOSE=1
NB_CLASSES=10
OPTIMIZER=SGD()
N_HIDDEN=128
VALIDATION_SPLIT=0.2

再对数据进行预处理

  • 通过引用MNIST,导入数据,该函数的返回结果,分别为训练集和测试集。
  • 其中需要做一个数据结构化的转换,原来的图片数据是28px*28px,直接转化为一维数据,784
  • 为了支持GPU运算,将数据设置为float32,并将数据在每个维度上的值缩放到(0,1)
  • 将标签的值用独热编码的方式进行转化
(X_train,y_train),(X_test,y_test)=mnist.load_data()
RESHAPED=784
X_train=X_train.reshape(60000,RESHAPED)
X_test=X_test.reshape(10000,RESHAPED)
X_train=X_train.astype('float32')
X_test=X_test.astype('float32')
X_train/=255
X_test/=255
y_train=np_utils.to_categorical(y_train,NB_CLASSES)
y_test=np_utils.to_categorical(y_test,NB_CLASSES)

最后构建神经网络模型

  • 创造一个序列模型model
  • 逐层添加全连接层,首层比较特殊,需要设置输入数据尺寸,这里是784,其输出维度为128(N_HIDDEN);接着依次是激活函数层ReLU,再连接一个全连接层维度是10和数据的种类相同(NB_CLASSES),最后连接一个激活层softmax。至此,一个神经网络模型已经搭建完毕。
model=Sequential()
model.add(Dense(N_HIDDEN,input_shape=(RESHAPED,)))
model.add(Activation('relu'))
model.add(Dense(NB_CLASSES))
model.add(Activation('softmax'))
model.summary()
'''
Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 dense (Dense)               (None, 128)               100480    
                                                                 
 activation (Activation)     (None, 128)               0         
                                                                 
 dense_1 (Dense)             (None, 10)                1290      
                                                                 
 activation_1 (Activation)   (None, 10)                0         
                                                                 
=================================================================
Total params: 101,770
Trainable params: 101,770
Non-trainable params: 0
_________________________________________________________________
'''

模型编译(model.compile)

编译模型时,需要配置以下几项:

  • 损失函数,本例中配置的是类别交叉熵。

  • 优化函数,本例中配置的是随机梯度下降(SGD)。

  • 配置验证所需要的性能指标。

模型训练(model.fit)

模型训练时,需要配置如下信息。

  • 训练集(x_train)——这些数据用于训练模型,虽然模型的雏形已经搭建起来,但是其参数(感知器内部的权重)等都还是初始化状态,当然这些需要和标签配合使用。

  • 标签(y_train)——需要将训练模型的输出标签与实际标签相对比,从而产生反向传播时的梯度。

  • 批量大小(batch_size)——每次迭代(iteration)都需要的数据量。

  • 训练轮数(epoch)——每一轮是指将所用的训练集训练完的这个周期,它包含一个前向传播和一个反向传播,但是需要注意和迭代之间关系。例如,有10000个样本的训练集,设置批量大小(batch_size)为100,那么在一轮(epoch)中,将有100个迭代(iteration)。

    迭代(iteration)次数*一次批量(batch_size)大小=一轮(epoch)总的训练样本数。

  • 输出级别(verbose)——可选项为0~2,0代表不输出任何日志信息,1代表输出进度条信息,2代表每轮的输出信息。

  • 验证占比(validation_split)——在原有训练集的样本中,随机抽取一定占比的样本作为测试集;当然,也可以设置validation_data直接指定测试集。

此外,为了验证模型各个层之间的输出及最终的收敛情况,配置了相应的回调函数类,在本例中采用的是keras.callbacks.TensorBoard()

模型验证(model.evaluate)

检测试集用于模型测试,并且给出评分,其返回值分别是评分和准确率。需要注意的是,模型训练过程中所用到的验证数据和这里的验证数据不一样,前者是对每轮的训练结果进行验证,而这里是独立于验证数据的数据样本,并且是对已经完成训练的模型进行验证。

import keras
tbCallBack=keras.callbacks.TensorBoard(log_dir='./ann_graph',histogram_freq=1,write_graph=True,write_images=True)
model.compile(loss='categorical_crossentropy',optimizer=OPTIMIZER,metrics=['accuracy'])
model.fit(X_train,y_train,batch_size=BATCH_SIZE,epochs=NB_EPOCH,verbose=VERBOSE,validation_split=VALIDATION_SPLIT,callbacks=[tbCallBack])
score=model.evaluate(X_test,y_test,verbose=VERBOSE)
print('Test score:',score[0])
print('Test accuracy:',score[1])
'''
Test score: 0.12912768125534058
Test accuracy: 0.9623000025749207
'''

神经网络模型的准确率提高的两个优化方向:

  • 准确率的提高,当然还有其他指标作为度量模型效果的标准,本例中只考虑了准确率。
  • 模型训练的耗时,在实际工作中,模型训练不仅看重准确率、召回率之类的指标,实际上,对模型的收敛速度也有一定的要求。

6 隐藏层对模型的影响

仅用一个全连接层实现的神经网络对MNIST数据集进行训练,训练准确率为97%以上,测试准确率为96%左右。

回到整个模型训练的过程中来看,模型训练的本质就是调整各个参数(权重$$\omega$$和偏置b),而模型的表现实际上是指模型是否能够很好地描述样本。那么,增加感知器的数量,实际上会增加需要调整的参数量,增加了模型的自由度,从而增强了模型的描述能力,但是这种做法不完全正确。此外,激活函数本身不具备描述能力,而是一个作为切分线性函数段的工具(ReLU函数的效果极为明显,其本身就是两段线性函数的组合);起到描述作用的实际上是线性函数,通过激活函数的切分,线性函数在不同的值域范围内有了不同的线性表示。

因此,有第二个问题,在增加相等数量的感知器时,增加宽度或者深度,其效果是否一样?

先讨论增加宽度的情况,假设一个极端的情况,假设层数为1,将增加的所有感知器都放在同一层,根据感知器的结构来看,线性函数和激活函数的结合可以增加其宽度,实际上,仅仅是增加了模型的线性,此时类似于多项式回归,增加多项式在一定程度上可以提高模型的表达能力,但是并不是增加的多项式越多越好,因为项数达到一定阈值后的增长反而会导致模型的不稳定。

在感知器增加的过程中,线性函数的参数也随之增加,需要调整的也是线性函数的参数,激活函数仅仅是在更高的维度上对线性函数的数据进行切分,但是其不同线性区域的数量并未增加,增加的仅仅是维度。

增加宽度时,虽然在训练时准确率上升,但是验证准确率到一定程度后略微下降,这说明一味地增加模型的宽度并不能显著地提高模型的准确性。

一个网络层上一层的输出会成为该网络层的输入,并且通过该网络层的激活函数做非线性切分,在维度足够的情况下,网络深度的增加意味着线性函数的线性表示通过的非线性切分增多,其描述能力增强。

较之于增加模型的宽度,增加模型的深度的效果尤甚。但注意,不是一味地增加模型深度就可以提高模型的性能,因为涉及梯度问题——梯度消失和梯度爆炸;此外,还涉及模型生成效率的问题,如果一个模型的层数不同,在同等性能负载的情况下,耗时也会存在不同。

综上,一味地增加模型的深度或者宽度都不是一个好策略,实际上,模型在某个方向上增加神经元提高模型性能是存在阈值的,如果超过该阈值,性能可能不升反降;在模型调优过程中,存在类似”第一性原则“,即向模型提升效率最快的那个方向调整模型的结构或者参数,在本例中,建议的是先调整深度,再调整宽度。

标签:感知器,函数,梯度,模型,神经网络,part2,01,激活
From: https://www.cnblogs.com/olin25/p/17023201.html

相关文章