神经网络(二)

作者: kamidox | 来源:发表于2015-11-29 23:14 被阅读321次

    继续发公式。有兴趣阅读能正常显示公式的版本请移步 http://blog.kamidox.com/neural-networks-2.html

    成本函数

    与线性回归或逻辑回归类似,要使用神经网络对训练数据进行拟合时,需要有成本函数。这样只要针对训练数据,求解成本函数的最小值即可得出神经网络模型参数。

    针对 K 类分类问题的神经网络的输出层

    $$
    h_\Theta(x) \in R^K; \left( h_\Theta(x) \right)_k = k^{th} output
    $$

    其中 K 是输出层的的单元个数,K >= 3。因为如果 K < 3 则可以直接用一个单元表示。其成本函数是:

    $$
    J(\Theta) = - \frac{1}{m} \left[ \sum_{i=1}^m \sum_{k=1}^K y_k^{(i)} log(h_k^{(i)}) + (1 - y_k^{(i)}) log(1 - h_k^{(i)}) \right] + \frac{\lambda}{2m} \sum_{l=1}^{L-1} \sum_{i=1}^{s_l} \sum_{j=1}^{s_{l+1}} (\Theta_{ji}{(l)})2
    $$

    其中 $h_k^{(i)} = {h_\Theta(x^{(i)})}_k$ 是输出层的第 $k^{th}$ 个输出值。$L$ 是神经网络的层数,$s_l$ 是指第 $l$ 层的单元个数。公式的前半部分是未正则化的成本函数,后半部分是正则项,加起来就是正则化的成本公式。注意正则项部分求和时是从 $i=1$ 开始的,即我们不把偏置变量正则化。

    !!! warnning "MathJax 的缺陷"
    这个公式我写了 20 分钟。它已经复杂到我不得不把 $h_k^{(i)}$ 独立写出来了,如果全部写一个公式里,公式将无法正确显示。不服的人可以试看看。

    怎么理解神经网络的成本公式

    实际上不需要记住这么复杂的公式,但可以结合逻辑回归算法的成本公式来理解神经网络的成本公式。我们知道,神经网络中间层和输出层的每个神经元,都和其前面一层的神经网络的神经元构成逻辑回归关系。这个是神经网络的定义。而逻辑回归算法的成本函数是:

    $$
    J(\theta) = -\frac{1}{m} \left[ \sum_{i=1}^m y^{(i)} log(h_\theta(x^{(i)})) + (1 - y^{(i)}) log(1 - h_\theta(x^{(i)})) \right]
    $$

    跟神经网络成本函数对比,你会发现神经网络输出层有 K 个神经元。所以计算成本函数时,需要把输出层 K 个神经元的逻辑回归成本累加起来。

    怎么理解正则项呢?

    正则项有三个累加器,最前面那个是层累加器,典型地,对 3 层神经网络模型 $L=3$ ,正则项简化为:

    $$
    reg = \frac{\lambda}{2m} \left( \sum_{i=1}^{s_1} \sum_{j=1}^{s_2} \left( \Theta_{ji}^{(1)} \right)^2 + \sum_{i=1}^{s_2} \sum_{j=1}^{s_3} \left( \Theta_{ji}^{(2)} \right)^2 \right)
    $$

    向后传播算法

    我们把 $\delta_j^{(l)}$ ( $\delta$ 读作 delta ) 记作神经网络中第 $l$ 层,第 $j$ 个节点的误差。针对输出层,我们有

    $$
    \delta_j^{(L)} = a_j^{(L)} - y_j
    $$

    按照向量化写法,我们得到

    $$
    \delta^{(L)} = a^{(L)} - y
    $$

    此由可见,$\delta^{(L)}$ 是和 $y$ 一样维度的向量。针对第 $L-1$ 层,我们把误差定义为

    $$
    \delta^{(L-1)} = (\Theta{(L-1)})T \delta^{(L)} .* g'(z^{(L-1)})
    $$

    这个公式的前半部分 $ (\Theta{(L-1)})T \delta^{(L)}$ 样式很熟悉吧,就是线性回归算法的预测函数的样式。中间的 $.*$ 读作点乘,就是逐个元素相乘。$z{(L-1)}=\Theta{(L-2)} a^{(L-2)}$,$g'(z)$ 是 Sigmoid 函数的偏微分。

    可以从数学上证明 $g'(z^{(L-1)}) = a^{(L-1)} .* (1 - a^{(L-1)})$ 成立。证明过程可以参阅 常用的微分运算法则 里关于 Sigmoid Function 偏微分的推导过程。这样我们算出输出层的误差,然后一层层往前推导,算出各层的误差,就是我们向后传播算法名字的由来。需要注意的是,不存在 $\delta^{(1)}$,因为神经网络的第 1 层是我们的输入项,不存在误差问题。

    从数学上可以证明,如果忽略正则项,即 $\lambda = 0$时

    $$
    \frac{\partial}{\partial \Theta_{ij}^{(l)}} J(\Theta) = a_j^{(l)} \delta_i^{(l+1)}
    $$

    注意

    1. 计算微分项时,只需要计算 1, 2, ..., l+1 层的微分项
    2. 微分项 $\frac{\partial}{\partial \Theta_{ij}^{(l)}} J(\Theta)$ 是个和 $\Theta^{(l)}$ 尺寸相同的矩阵

    针对训练样本 ${ (x^{(1)}, y^{(1)}), (x^{(2)}, y^{(2)}), ... (x^{(m)}, y^{(m)}),}$,我们可以把向后传播算法用伪代码描述如下:

    • 初始化误差累加值 set $\Delta_{ij}^{(l)} = 0$, for all $l, i, j$
    • 遍历所有的训练样本 for i = 1 to m
      • 设置输入层的激励为第 $i$ 个训练样本的输入值 set $a^{(1)} = x^{(i)}$
      • 使用向前传播算法 $a^{(l+1)} = g\left( a^{(l)} * \left( \Theta^{(l)} \right)^T \right)$,算出所有层的激励 $a^{(l)}$ for $l = 2, 3, ... , L$
      • 使用输出层的激励,计算输出层的误差 $\delta^{(L)} = a^{(L)} - y^{(i)}$
      • 使用反向扩散的方法 $\delta^{(L-1)} = (\Theta{(L-1)})T \delta^{(L)} .* g'(z^{(L-1)})$ 计算每一层的误差 $\delta^{(L-1)}, \delta^{(L-2)}, ..., \delta^{(2)}$。
      • 累加 $(x^{(i)}, y^{(i)})$ 训练样本的误差 $\Delta_{ij}^{(l)} = \Delta_{ij}^{(l)} + a_j^{(l)} \delta_i^{(l+1)}$。
    • endfor
    • 累加的值除以 m 即得到无正则化的微分项 $\frac{\Delta_{ij}^{(l)}}{m}$

    最后一项可以用向量化的写法:

    $$
    \Delta^{(l)} = \Delta^{(l)} + \delta^{(l+1)} \left( a^{(l)} \right)^T
    $$

    注意:
    计算过程中,需要注意偏置单元。根据惯例,累加时不计算偏置单元。针对反向扩散公式 $\delta^{(L-1)} = (\Theta{(L-1)})T \delta^{(L)} . g'(z^{(L-1)})$,需要特别注意矩阵运算时的维度需要匹配。*

    加入正则项后,我们有

    $$
    D_{ij}^{(l)} = \frac{1}{m} \Delta_{ij}^{(l)} + \frac{\lambda}{m} \Theta_{ij}^{(l)}, if j \ne 0
    $$
    $$
    D_{ij}^{(l)} = \frac{1}{m} \Delta_{ij}^{(l)}, if j = 0
    $$

    从数学上可以证明

    $$
    \frac{\partial}{\partial \Theta_{ij}^{(l)}} J(\Theta) = D_{ij}^{(l)}
    $$

    这样我们就算出来了神经网络模型的成本函数微分项。有了成本函数和成本函数微分项,我们就可以使用线性回归或其他高级算法来计算神经网络成本函数的最小值,从而求解神经网络中各层激励的参数。

    在具体实现的时候,使用向量化的实现可以大幅提高算法效率。具体可以参考 Neural Network Vectorization

    实践中的向后传播算法

    参数折叠

    在线性回归或逻辑回归算法里,我们的参数是向量,我们使用的 fminunc 等函数也只接受向量作为参数。而神经网络算法里,参数是个向量,$\Theta^{(l)} \in R^{s_{l+1} \times s_l + 1}$。所以,在训练神经网络算法时,需要对参数进行折叠,即把矩阵转换为向量,而在使用时,可以再从向量里恢复矩阵数据。

    假设 Theta1 是 10x11 的矩阵,它是第一层的参数; Theta2 是 10x11 的矩阵,它是第二层的参数。可以使用下面的 matlab/octave 来转换:

    ThetaVec = [Theta1(:); Theta2(:)];
    

    在成本函数函数里,我们需要转换为矩阵进行计算:

    Theta1 = reshape(ThetaVec(1:110), 10, 11);
    Theta2 = reshape(ThetaVec(111:220), 10, 11);
    

    同理,针对成本函数的微分项,$D^{(1)} \in R^{10x11}, D^{(2)} \in R^{10x11}$,我们的成本函数返回这个微分项时,也需要把矩阵转换为向量:

    DVec = [D1(:); D2(:)]
    

    微分项检验

    神经网络的微分项特别复杂,有时候一些小的错误可能不会导致算法失败,这样就很难发现问题。这里介绍一个方法来验证微分项算法是否正确。我们使用的是微分的数值估算方法。

    $$
    \frac{d}{d\theta} J(\theta) \approx \frac{J(\theta + \varepsilon) + J(\theta - \varepsilon)}{2 \varepsilon}
    $$

    这里只要 $\varepsilon$ 足够小,则可以近似地计算出微分项的值。实际计算时,我们一般取 $\varepsilon = 0.0001$ 。这样算出来的值和微分项算出来的值应该非常近似,用这个方法我们可以验证微分项计算是否准确。需要特别注意的是,在验证完微分项计算的正确性后,数值近似计算必须关闭掉。否则会使算法效率严重降低。因为数值计算的成本是很高的。

    编程时需要注意

    微分项检查实际上是一种纯数学的做法。主要是检查我们使用向后传播算法 (backpropagation) 方法算出来的微分和用数值计算算出来的微分是否相同。它适用于其他算法,如线性回归或逻辑回归算法。有几点需要特别注意。

    • 由于计算很费时间,实际检查时,$\theta$ 可以选小一点的矩阵,比如 3 x 5,而不需要使用真正的机器学习时的 theta。因为 $\theta$ 太大不但费时间,还不利于观察。
    • 实际计算时,$\theta$ 往往是个列向量。这个时候我们需要让 $\varepsilon$ 也是一个和 $\theta$ 维度相同的向量,当检查 $\theta(i)$ 元素的偏微分项时,让 $\varepsilon$ 的的第 i 项的值为 0.0001,其他项都为 0 。这样进行矩阵来进行数值微分计算。

    用随机数初始化参数

    在进行线性回归和逻辑回归计算时,我们把参数全部初始化为零。但这个做法在神经网络里是不可行的,如果我们把参数全部初始化为零,那么隐藏层的神经单元的激励 $a_i^{(l)}$ 将是相同的,其误差 $\delta_i^{(l)}$ 也将是相同的,即我们计算的全部是相同的特征,这样神经网络就失去了其特征的覆盖度和丰富性。

    所以,我们需要把神经网络的每个参数 $\Theta_{ij}^{(l)}$ 初始化为 $[-\varepsilon, \varepsilon]$ 之间的一个随机数。例如:

    Theta1 = rand(10, 11) .* (2 .* INIT_VAREPSILON) - INIT_VAREPSILON;
    Theta2 = rand(10, 11) .* (2 .* INIT_VAREPSILON) - INIT_VAREPSILON;
    Theta3 = rand(1, 11) .* (2 .* INIT_VAREPSILON) - INIT_VAREPSILON;
    

    $\varepsilon$ 应该选择小一点,这样神经网络的学习最有效率。一个经验做法是

    $$
    \varepsilon^{(l)} = \frac{\sqrt{6}}{\sqrt{s_l} + \sqrt{s_{l+1}}}
    $$

    $s_l, s_{l+1}$ 分别表示 $l$ 层和 $l+1$ 层的神经单元个数。即每层的参数范围根据这层的神经单元个数及下一层的神经单元个数。

    总结

    使用神经网络解决问题时,需要经过两个步骤。一是设计神经网络的架构;二是训练出对应的神经网络参数。

    神经网络架构

    在进行神经网络计算时,需要先进行神经网络的架构设计。架构设计时需要考虑以下三个事情:

    1. 输入层的特征数量 (number of input unit)
    2. 输出层的单元个数 (number of output unit) ,针对多类别的分类问题,可以把输出层设计成一个向量
    3. 隐藏层的个数以及每个隐藏层的单元数目。一般来讲,隐藏层的个数越多越好,但会增加计算的工作量。另外,多个隐藏层的单元数目一般是相同的。

    训练神经网络

    训练神经网络总共有六个步骤

    • 按照随机数对初始权重 (参数) 进行初始化
    • 实现向前传播算法,以便针对任何的输入 $x^{(i)}$ 都能算出相应的 $h_\Theta(x^{(i)})$
    • 实现神经网络成本函数 $J(\Theta)$ 来计算成本
    • 实现向后传播算法,计算成本函数针对每个参数的偏微分 $\frac{\partial}{\partial \Theta_{ij}^{(l)}} J(\Theta)$
      • 需要遍历每个训练样本,即有个从 1 到 m 的循环
      • 针对每个训练样本 $(x^{(i)}, y^{(i)})$ 执行向前传播算法和向后传播算法,以便算出 $l$ 层的激励 (Activations) $a^{(l)}$ 和误差 $\delta^{(l)}$
      • 需要针对神经网络的每层算出*的值,这些层是 2, 3, ... , L
      • 最后,在循环外,算出成本函数的偏微分
    • 使用数值估计算法来验证神经网络成本函数的偏微分是否正确。验证通过后,关闭数值估计算法。
    • 使用梯度下降或其他优化过的高级算法来对成本函数 $J(\Theta)$ 进行最小化运算

    相关文章

      网友评论

        本文标题:神经网络(二)

        本文链接:https://www.haomeiwen.com/subject/gmhjhttx.html