深度学习和神经网络_深度学习之神经网络基础(前馈神经网络)

eb2243aff09407df6e0f2299540872a9.png

在之前的机器学习的分享中,我们一起去探索了关于传统机器学习的一些重要的模型,从接下来的分享中,我们开始把目光移到深度学习的领域中,看看现在大热的深度学习是如何在各行各业发挥至关重要的作用的。那今天的分享是关于深度学习的基础----前馈神经网络,内容包括:

  • 神经元
  • 激活函数(Activation Function)
  • 前馈神经网络
  • 反向传播算法

(图片与某些内容来自于邱锡鹏老师的《神经网络与深度学习》与李宏毅老师的深度学习课程)

1. 神经元

人工神经元(Artificial Neuron),简称神经元(Neuron),是构成神经网络 的基本单元,其主要是模拟生物神经元的结构和特性,接收一组输入信号并产生输出。

假设一个神经元接受D个输入

,令向量
来表示这组输入,并用净输入(Net Input)
来表示一个神经元所获得的的输入信号x的加权和:

净输入z在经过一个非线性函数

后,得到神经元的活性值(Activation)a:

其中非线性函数

叫做激活函数(Activation Function)

8b407c7494fce7074fcc8c7c2d72e77c.png

2. 激活函数(Activation Function)

激活函数 激活函数在神经元中非常重要的。为了增强网络的表示能力和学习能力,激活函数需要具备以下几点性质:

  • (1) 连续并可导(允许少数点上不可导)的非线性函数。可导的激活函数可以直接利用数值优化的方法来学习网络参数。
  • (2) 激活函数及其导函数要尽可能的简单,有利于提高网络计算效率。
  • (3) 激活函数的导函数的值域要在一个合适的区间内,不能太大也不能太小,否则会影响训练的效率和稳定性。

Sigmoid型函数

Sigmoid型函数是指一类S型曲线函数,为两端饱和函数.常用的Sigmoid型函数有Logistic函数和Tanh函数。

Logistic函数

Logistic函数定义为:

Logistic函数可以看成是一个“挤压”函数,把一个实数域的输入“挤压”到 (0, 1).当输入值在0附近时,Sigmoid型函数近似为线性函数;当输入值靠近两端时,对输入进行抑制。输入越小,越接近于0;输入越大,越接近于1。

Tanh函数

Tanh函数也是一种Sigmoid型函数.其定义为

Tanh函数可以看作放大并平移的Logistic函数,其值域是(−1, 1)。

f76e0cb94e13c4f8f1f7e4d55f9dbfbd.png

Tanh函数的输出是零中心化的(Zero-Centered),而Logistic函数的输出恒大于0.非零中心化的输出会使得其后一层的神经元的输入发生偏置偏移(Bias Shift),并进一步使得梯度下降的收敛速度变慢。

ReLU函数(Rectified Linear Unit,修正线性单元)

ReLU函数

ReLU(Rectified Linear Unit,修正线性单元)是目前深度神经网络中经常使用的激活函数。

ReLU实际上是一个斜坡(ramp)函数,定义为:

- 优点:采用ReLU的神经元只需要进行加、乘和比较的操作,计算上更加高效。Sigmoid型激活函数会导致一个非稀疏的神经网络,而ReLU却具有很好的稀疏性,大约50%的神经元会处于激活状态。

- 缺点:ReLU函数的输出是非零中心化的,给后一层的神经网络引入偏置偏移, 会影响梯度下降的效率。此外,ReLU神经元在训练时比较容易“死亡”.在训练时,如果参数在一次不恰当的更新后,第一个隐藏层中的某个ReLU神经元在所有的训练数据上都不能被激活,那么这个神经元自身参数的梯度永远都会是0,在以后的训练过程中都不会被激活。这种现象较死亡ReLU问题,且有可能发生在其他隐藏层位置。

带泄露的ReLU(Leaky ReLU)

带泄露的ReLU(Leaky ReLU)在输入 < 0时,保持一个很小的梯度

。这样当神经元非激活时也能有一个非零的梯度可以更新参数,避免永远不能被激活。带泄露的ReLU的定义如下:

其中

是一个很小的常数,比如0.01。

带参数的ReLU(Parametric ReLU,PReLU)

带参数的ReLU(Parametric ReLU,PReLU)引入一个可学习的参数,不 同神经元可以有不同的参数。对于第 个神经元,其PReLU的定义为:

PReLU可以允许不同神经元具有不同的参数,也可以一组神经元共享一个参数。

ELU函数(Exponential Linear Unit,指数线性单元)

ELU(Exponential Linear Unit,指数线性单元)是一个近似的零中心化的非线性函数,其定义为:

其中

是一个超参数,决定
时的饱和曲线,并调整输出均值在0附近

Softplus函数

Softplus函数可以看作Rectifier函数的平滑版本,其定义为:

Softplus函数其导数刚好是Logistic函数。Softplus函数虽然也具有单侧抑制、宽兴奋边界的特性,却没有稀疏激活性。

60b8556d2261ecf6860008e4b0bdd461.png

Swish函数

Swish函数是一种自门控(Self-Gated)激活函数,定义为:

e824179abd3cd2595a1378d788f5049a.png

Maxout单元

Maxout单元也是一种分段线性函数。Sigmoid型函数、ReLU等激活函数的输入是神经元的净输入 ,是一个标量.而Maxout单元的输入是上一层神经元的全部原始输出,是一个向量

Maxout单元的过程是:

- 1.输入x与w,得到

- 2.对下一层的神经元分组,如图为两两分组。

- 3.取每个组的最大值作为下一层神经元的输出。

2510e0eba1218e8e24732d12614991d3.png

Maxout单元不单是净输入到输出之间的非线性映射,而是整体学习输入到输出之间的非线性映射关系。Maxout激活函数可以看作任意凸函数的分段线性近似,并且在有限的点上是不可微的。

931368d608d7a65b698a5fa640a8585b.png

3. 前馈神经网络

前馈网络可以看作一个函数,通过简单非线性函数的多次复合,实现输入空间到输出空间的复杂映射。 在前馈神经网络中,各神经元分别属于不同的层。每一层的神经元可以接收前一层神经元的信号,并产生信号输出到下一层。第0层称为输入层,最后一层称为输出层,其他中间层称为隐藏层.整个网络中无反馈,信号从输入层向输出层单向传播,可用一个有向无环图表示。

19094806e5652d16e84b64984c2ed561.png

bbdfdaa27f0d91f64b514c0fc14dad3f.png

这样,前馈神经网络可以通过逐层的信息传递,得到网络最后的输出

。 整个网络可以看作一个复合函数 ( ; , ),将向量 作为第1层的输入
,将第 层的输出
作为整个函数的输出。

其中 , 表示网络中所有层的连接权重和偏置。

通用近似定理

前馈神经网络具有很强的拟合能力,常见的连续非线性函数都可以用前馈神经网络来近似。

fefc2394308c75f5ed96d558debecd0d.png

根据通用近似定理,对于具有线性输出层和至少一个使用“挤压”性质的激活函数的隐藏层组成的前馈神经网络,只要其隐藏层神经元的数量足够,它可以以任意的精度来近似任何一个定义在实数空间ℝ 中的有界闭集函数。但是这个定理只给出了任意近似的可行性,但是没有说明近似的效率与是否是最优的,也没有给出找到最优结构的方法,因此我们需要考虑深度的网络而不是用很多个神经元的单个隐藏层的神经网络。

参数学习

给定训练集为

,将每个样本
输入给前馈神经网络,得到网络输出为
,其在数据集D上的结构化风险函数为:

其中 和 分别表示网络中所有的权重矩阵和偏置向量;

来防止过拟合;
t;0 为超参数.
越大, 越接近于0。

Frobenius范数:

有了学习准则和训练样本,网络参数可以通过梯度下降法来进行学习.在梯度下降方法的每次迭代中,

的参数
参数更新方式为:

参数更新方式为

参数更新方式为:

其中

为学习率。

梯度下降法需要计算损失函数对参数的偏导数,如果通过链式法则逐一对 每个参数进行求偏导比较低效.在神经网络的训练中经常使用反向传播算法来高效地计算梯度。

4. 反向传播算法

假设采用随机梯度下降进行神经网络参数学习,给定一个样本( , ),将 输入到神经网络模型中,得到网络输出为

根据链式法则:

其中,

是目标函数关于第
层的神经元
的偏导数,称为
误差项,可以一次计算得到。这样我们只需要计算三个偏导数,分别为:

- 1.计算偏导数

:

由于

,偏导数:

- 2.计算偏导数

由于

,偏导数:

的单位阵。

- 3.计算偏导数

偏导数

表示第
层神经元对最终损失的影响,也反映了最终损失对第 层神经元的敏感程度,因此一般称为第
层神经元的误差项,用
来表示。

误差项

也间接反映了不同神经元对网络能力的贡献程度,从而比较好地解决了贡献度分配问题(Credit Assignment Problem,CAP)。

因此:

其中

是向量的点积运算符,表示每个元素相乘。

可以看出,第

层的误差项可以通过第
层的误差项计算得到,这就是误差的反向传播(BackPropagation,BP)。反向传播算法的含义是: 第
层的一个神经元的误差项(或敏感性)是所有与该神经元相连的第
层的神经元的误差项的权重和。然后,再乘上该神经元激活函数的梯度。

因此, 使用误差反向传播算法的前馈神经网络训练过程可以分为以下三步:

- (1) 前馈计算每一层的净输入 ( ) 和激活值 ( ),直到最后一层;

- (2) 反向传播计算每一层的误差项 ( );

- (3) 计算每一层参数的偏导数,并更新参数。

给出使用反向传播算法的随机梯度下降训练过程:

1d456eaa38dbdc063be6111511e44a3a.png

下面给出部分代码:

import numpy as np
import sys
import os

class NeuralNetMLP(object):
    """ Feedforward neural network / Multi-layer perceptron classifier.

    Parameters
    ------------
    n_hidden : int (default: 30)
        Number of hidden units.
    l2 : float (default: 0.)
        Lambda value for L2-regularization.
        No regularization if l2=0. (default)
    epochs : int (default: 100)
        Number of passes over the training set.
    eta : float (default: 0.001)
        Learning rate.
    shuffle : bool (default: True)
        Shuffles training data every epoch if True to prevent circles.
    minibatch_size : int (default: 1)
        Number of training samples per minibatch.
    seed : int (default: None)
        Random seed for initalizing weights and shuffling.

    Attributes
    -----------
    eval_ : dict
      Dictionary collecting the cost, training accuracy,
      and validation accuracy for each epoch during training.

    """
    def __init__(self, n_hidden=30,
                 l2=0., epochs=100, eta=0.001,
                 shuffle=True, minibatch_size=1, seed=None):

        self.random = np.random.RandomState(seed)
        self.n_hidden = n_hidden
        self.l2 = l2
        self.epochs = epochs
        self.eta = eta
        self.shuffle = shuffle
        self.minibatch_size = minibatch_size

    def _onehot(self, y, n_classes):
        """Encode labels into one-hot representation

        Parameters
        ------------
        y : array, shape = [n_samples]
            Target values.

        Returns
        -----------
        onehot : array, shape = (n_samples, n_labels)

        """
        onehot = np.zeros((n_classes, y.shape[0]))
        for idx, val in enumerate(y.astype(int)):
            onehot[val, idx] = 1.
        return onehot.T

    def _sigmoid(self, z):
        """Compute logistic function (sigmoid)"""
        return 1. / (1. + np.exp(-np.clip(z, -250, 250)))

    def _forward(self, X):
        """Compute forward propagation step"""

        # step 1: net input of hidden layer
        # [n_samples, n_features] dot [n_features, n_hidden]
        # -> [n_samples, n_hidden]
        z_h = np.dot(X, self.w_h) + self.b_h

        # step 2: activation of hidden layer
        a_h = self._sigmoid(z_h)

        # step 3: net input of output layer
        # [n_samples, n_hidden] dot [n_hidden, n_classlabels]
        # -> [n_samples, n_classlabels]

        z_out = np.dot(a_h, self.w_out) + self.b_out

        # step 4: activation output layer
        a_out = self._sigmoid(z_out)

        return z_h, a_h, z_out, a_out

    def _compute_cost(self, y_enc, output):
        """Compute cost function.

        Parameters
        ----------
        y_enc : array, shape = (n_samples, n_labels)
            one-hot encoded class labels.
        output : array, shape = [n_samples, n_output_units]
            Activation of the output layer (forward propagation)

        Returns
        ---------
        cost : float
            Regularized cost

        """
        L2_term = (self.l2 *
                   (np.sum(self.w_h ** 2.) +
                    np.sum(self.w_out ** 2.)))

        term1 = -y_enc * (np.log(output))
        term2 = (1. - y_enc) * np.log(1. - output)
        cost = np.sum(term1 - term2) + L2_term
        
        # If you are applying this cost function to other
        # datasets where activation
        # values maybe become more extreme (closer to zero or 1)
        # you may encounter "ZeroDivisionError"s due to numerical
        # instabilities in Python & NumPy for the current implementation.
        # I.e., the code tries to evaluate log(0), which is undefined.
        # To address this issue, you could add a small constant to the
        # activation values that are passed to the log function.
        #
        # For example:
        #
        # term1 = -y_enc * (np.log(output + 1e-5))
        # term2 = (1. - y_enc) * np.log(1. - output + 1e-5)
        
        return cost

    def predict(self, X):
        """Predict class labels

        Parameters
        -----------
        X : array, shape = [n_samples, n_features]
            Input layer with original features.

        Returns:
        ----------
        y_pred : array, shape = [n_samples]
            Predicted class labels.

        """
        z_h, a_h, z_out, a_out = self._forward(X)
        y_pred = np.argmax(z_out, axis=1)
        return y_pred

    def fit(self, X_train, y_train, X_valid, y_valid):
        """ Learn weights from training data.

        Parameters
        -----------
        X_train : array, shape = [n_samples, n_features]
            Input layer with original features.
        y_train : array, shape = [n_samples]
            Target class labels.
        X_valid : array, shape = [n_samples, n_features]
            Sample features for validation during training
        y_valid : array, shape = [n_samples]
            Sample labels for validation during training

        Returns:
        ----------
        self

        """
        n_output = np.unique(y_train).shape[0]  # number of class labels
        n_features = X_train.shape[1]

        ########################
        # Weight initialization
        ########################

        # weights for input -> hidden
        self.b_h = np.zeros(self.n_hidden)
        self.w_h = self.random.normal(loc=0.0, scale=0.1,
                                      size=(n_features, self.n_hidden))

        # weights for hidden -> output
        self.b_out = np.zeros(n_output)
        self.w_out = self.random.normal(loc=0.0, scale=0.1,
                                        size=(self.n_hidden, n_output))

        epoch_strlen = len(str(self.epochs))  # for progress formatting
        self.eval_ = {'cost': [], 'train_acc': [], 'valid_acc': []}

        y_train_enc = self._onehot(y_train, n_output)

        # iterate over training epochs
        for i in range(self.epochs):

            # iterate over minibatches
            indices = np.arange(X_train.shape[0])

            if self.shuffle:
                self.random.shuffle(indices)

            for start_idx in range(0, indices.shape[0] - self.minibatch_size +
                                   1, self.minibatch_size):
                batch_idx = indices[start_idx:start_idx + self.minibatch_size]

                # forward propagation
                z_h, a_h, z_out, a_out = self._forward(X_train[batch_idx])

                ##################
                # Backpropagation
                ##################

                # [n_samples, n_classlabels]
                sigma_out = a_out - y_train_enc[batch_idx]

                # [n_samples, n_hidden]
                sigmoid_derivative_h = a_h * (1. - a_h)

                # [n_samples, n_classlabels] dot [n_classlabels, n_hidden]
                # -> [n_samples, n_hidden]
                sigma_h = (np.dot(sigma_out, self.w_out.T) *
                           sigmoid_derivative_h)

                # [n_features, n_samples] dot [n_samples, n_hidden]
                # -> [n_features, n_hidden]
                grad_w_h = np.dot(X_train[batch_idx].T, sigma_h)
                grad_b_h = np.sum(sigma_h, axis=0)

                # [n_hidden, n_samples] dot [n_samples, n_classlabels]
                # -> [n_hidden, n_classlabels]
                grad_w_out = np.dot(a_h.T, sigma_out)
                grad_b_out = np.sum(sigma_out, axis=0)

                # Regularization and weight updates
                delta_w_h = (grad_w_h + self.l2*self.w_h)
                delta_b_h = grad_b_h # bias is not regularized
                self.w_h -= self.eta * delta_w_h
                self.b_h -= self.eta * delta_b_h

                delta_w_out = (grad_w_out + self.l2*self.w_out)
                delta_b_out = grad_b_out  # bias is not regularized
                self.w_out -= self.eta * delta_w_out
                self.b_out -= self.eta * delta_b_out

            #############
            # Evaluation
            #############

            # Evaluation after each epoch during training
            z_h, a_h, z_out, a_out = self._forward(X_train)
            
            cost = self._compute_cost(y_enc=y_train_enc,
                                      output=a_out)

            y_train_pred = self.predict(X_train)
            y_valid_pred = self.predict(X_valid)

            train_acc = ((np.sum(y_train == y_train_pred)).astype(np.float) /
                         X_train.shape[0])
            valid_acc = ((np.sum(y_valid == y_valid_pred)).astype(np.float) /
                         X_valid.shape[0])

            sys.stderr.write('r%0*d/%d | Cost: %.2f '
                             '| Train/Valid Acc.: %.2f%%/%.2f%% ' %
                             (epoch_strlen, i+1, self.epochs, cost,
                              train_acc*100, valid_acc*100))
            sys.stderr.flush()

            self.eval_['cost'].append(cost)
            self.eval_['train_acc'].append(train_acc)
            self.eval_['valid_acc'].append(valid_acc)

        return self

5. 结语

本期的深度学习基础(前馈神经网络)分享的内容就结束啦,那么从下一期开始,我们就进入卷积神经网络的分享啦,后面会有非常有趣的小案例分享给大家哦,敬请期待。

邱锡鹏:《神经网络与深度学习》
李宏毅:机器学习课程
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值