zoukankan      html  css  js  c++  java
  • 机器学习之五:神经网络、反向传播算法推导

    一、逻辑回归的局限

    在逻辑回归一节中,使用逻辑回归的多分类,实现了识别20*20的图片上的数字。

    但所使用的是一个一阶的模型,并没有使用多项式,为什么?

    可以设想一下,在原有400个特征的数据样本中,增加二次、三次、四次多项式,会是什么情形?

    很显然,训练样本的特征数量将会拔高多个数量级,而且,更重要的,要在一个式子中拟合这么多的特征,其难度是非常大的,可能无法收敛到一个比较理想的状态。

    也就是说,逻辑回归没法提供很复杂的模型。

    因为其本质上是一个线性的分类器,擅长解决的是线性可分的问题。

    那么非线性可分问题,要怎么解决?

    解决思路

    如果有一种方法,将非线性可分问题先进行特征提取,变为接近线性可分,那么再应用一次逻辑回归,是否就能解决非线性问题了?

    这便是神经网络的思想。

    二、神经网络

    1、结构

    神经网络的结构,如下图所示

    ![image](https://wx4.sinaimg.cn/mw690/ec98cc4agy1fqavy256mdj207r08bwf1.jpg)
    上面是一个最简单的模型,分为三层:输入层、隐藏层、输出层。

    其中,隐藏层可以是多层结构,通过扩展隐藏层的结构,可以构建更得杂的模型,例如下面的模型:

    ![image](https://wx2.sinaimg.cn/mw690/ec98cc4agy1fqazaso0uzj20ek0943zr.jpg)
    每一层的输出,皆是下一层的输出,层层连接而成,形成一个网络。

    网络中的节点,称为神经元。每个神经元,其实就是进行一次类似逻辑回归的运算(之所以说是"类似",是因为可以使用逻辑回归,也有别的算法代替,但可以使用逻逻回归来理解它的运算机理)。

    根据上面前言中的分析,显然,隐藏层是进行特征的提取,而输出层,其实就是进行逻辑回归。

    为何说隐藏层是进行特征提取?

    为方便理解,这里假设所有神经元执行逻辑回归。

    一次逻辑回归,可以将平面一分为二。神经网络中,执行的是 N 多个逻辑回归,那么可以将平面切割为 N 多个区域,这些区域最后由输出层进行综合后做为结果。

    如果只关注输出层,那么这些前面切割出来的区域,其实可以当作是一种特征,是一种更高级的特征,由原始样本提取出来的。这就是特征的提取。

    2、计算原理

    2.1 前向传播,计算输出

    下面求解当一个样本从输入层输入时,如何得到最终结果。

    假设每个神经元,都执行逻辑回归的计算,则第 (i) 层网络的输出为:$$a^{(i)} = g(z^{(i)}) = g(Theta^Ta^{(i-1)}) ag{1}$$

    以如下三层网络为例:

    ![image](https://wx4.sinaimg.cn/mw690/ec98cc4agy1fqavy256mdj207r08bwf1.jpg)
    各层的输入输出如下:

    Input layer:

    [a^{(1)} = x ]

    Hidden layer:

    [egin{split} z^{(2)} &= Theta^{(1)}a^{(1)} \ a^{(2)} &= g(z^{(2)}) end{split} ]

    Output layer:

    [egin{split} z^{(3)} &= Theta^{(2)}a^{(2)} \ a^{(3)} &= g(z^{(2)}) end{split} ]

    即整个网络的最终结果为:

    [h_ heta(x) = a^{(3)} ]

    上述流程:以上一层的输出,作为下一层的输入,一层一层叠加运算后,得到最终的输出,这个计算方法,称为“前向传播”

    2.2 反向传播,求theta矩阵

    训练算法的目的是“求取使得误差函数最小化的参数矩阵”,用梯度下降法处理最小化误差,需要计算误差函数J、以及J对theta的偏导数。

    2.2.1 误差函数J

    [J(Theta) = -frac{1}{m} sum_{i=1}^{m}sum_{k=1}^{K}[y_k^{(i)}log(h_Theta(x^{(i)}))_k + (1-y_k^{(i)})log(1-h_ heta(x^{(i)}))_k] + frac{lambda}{2m}sum_{l=1}^{L-1}sum_{i=1}^{S_l}sum_{j=1}^{S_l+1}(Theta_{ji}^{(l)})^2 ag{2} ]

    其中 (K) 为输出层的单元数,即类别数。在计算误差的时候,需要将每一类都计算进去。后面的正则项是整个神经网络中所有的参数 (Theta) 的值之和。

    2.2.2 J对theta偏导数

    这里先给结果,后面再做推导:

    [frac{partial}{partialTheta_{ij}^{(l)}}J(Theta) = frac{1}{m}sum_{t=1}^{m}delta_i^{(t)(l+1)}a_j^{(t)(l)} + frac{lambda}{m}sum_{l=1}^{L-1}sum_{i=1}^{S_l}sum_{j=1}^{S_l+1}(Theta_{ji}^{(l)}) ag{3} ]

    其中

    [egin{cases} delta^{(L)} &=& a^{(L)}-y \ \ delta^{(l)} &=& delta^{(l+1)}*(Theta^{(l+1)})^T*g'(z^{(l)}) \ \ delta^{(0)} &=& 0 \ end{cases} ag{4} ]

    上述公式描述的内容

    (l) 层的误差,可以通过第 (l+1) 层的误差计算出来,而最后一层的误差,就是系统通过前向传播计算出的值与样本 (Y) 值的差。
    也就是说,从输出层开始,各层误差能通过一层一层反向迭代的方式得到,确定误差之后,偏导数便也随之计算出来,进而可进行模型的调整。这就是,“反向传播算法”

    而反向传播的内容,其实是误差。

    • 关于误差的直观理解:

    输出层的误差,即为系统的总误差;

    中间层的误差,即为每一层对总误差的贡献值(所以,( heta) 矩阵,在前向传播中,是特征权重,而在反向传播中,就是误差权重);

    而输入层,其输出即为原始数据,即无误差。

    2.2.3 反向传播算法的推导过程

    (1) 第一部分,推导偏导数

    上面给出了反向传播的结论,以下进行推导。

    矩阵形式计算第 (l) 层的偏导数:

    [egin{split} frac{partial J(Theta)}{partialTheta^{(l)}} &= frac{partial J(Theta)}{partial z^{(l+1)}} * frac{partial z^{(l+1)}}{partial Theta^{(l)}} \ \ &= frac{partial J(Theta)}{partial z^{(l+1)}} * frac{partial (Theta^{(l)}*a^{(l)})}{partial Theta^{(l)}} \ \ &= frac{partial J(Theta)}{partial z^{(l+1)}} * a^{(l)} end{split} ag{5} ]

    令 $$delta^{(l)} = frac{partial J(Theta)}{partial z^{(l)}} ag{6}$$

    则有

    [egin{split} frac{partial J(Theta)}{partialTheta^{(l)}} &=& frac{partial J(Theta)}{partial z^{(l+1)}} * a^{(l)} \ \ &=& delta^{(l+1)} * a^{(l)} \ end{split} ag{7} ]

    (2) 第二部分,推导误差delta

    上面推导过程中,有这个式子:

    [delta^{(l)} = frac{partial J(Theta)}{partial z^{(l)}} ]

    表示了什么意思?下面分别从输出层、及中间层来推导、解释这个式子。

    • 输出层

    因误差函数如下(这里省略掉正则项)

    [J(Theta) = -frac{1}{m} sum_{i=1}^{m}sum_{k=1}^{K}[y_k^{(i)}log(h_Theta(x^{(i)}))_k + (1-y_k^{(i)})log(1-h_ heta(x^{(i)}))_k] ]

    此式表达的是总误差,那么,对于输出层的每个神经元的误差,可用矩阵表示为:

    [C = - [ylog(h_Theta(x)) + (1-y)log(1-h_ heta(x))] ag{8} ]

    故输出层的误差为:

    [egin{split} delta^{(L)} &=& frac{partial J(Theta)}{partial z^{(L)}} = frac{partial C}{partial z^{(L)}} \ \ &=& frac{partial }{partial z^{(L)}} [ylog(h_Theta(x)) + (1-y)log(1-h_ heta(x))] \ \ &=& -frac{y}{g(z^{(L)})}g'(z^{(L)}) - frac{1-y}{1-g(z^{(L)})}(-g'(z^{(L)})) \ \ &=& frac{g(z^{(L)})-y}{g(z^{(L)})(1-g(z^{(L)}))}(g'(z^{(L)})) \ \ &=& g(z^{(L)})-y \ \ &=& a^{(L)}-y end{split} ag{9} ]

    这个结果,有点意思了,表示出输层的 (delta) 值,就是系统输出值与样本 (Y) 值的差。所以,我们称 (delta) 为神经系统各层结构的各个神经元的误差。

    • 中间层误差推导

    对于第 (l)

    [egin{split} delta^{(l)} &=& frac{partial J(Theta)}{partial z^{(l)}} \ \ &=& frac{partial J(Theta)}{partial z^{(l+1)}} * frac{partial z^{(l+1)}}{partial z^{(l)}} \ \ &=& delta^{(l+1)} * frac{partial [(Theta^{(l+1)})^T*g(z^{(l)})]}{partial z^{(l)}} \ \ &=& delta^{(l+1)} * (Theta^{(l+1)})^T*g'(z^{(l)}) \ end{split} ag{10}]

    即第 (l) 层的误差,能用第 (l+1) 层的误差计算得到,与先前所定的结论完全一致。

    这就是反向传播的所有推导的内容。

    三、程序实现

    例子来源于,吴恩达的机器学习编程题。样本与逻辑回归中的多分类的数字识别相同。

    1、计算损失函数、及梯度

    function [J grad] = nnCostFunction(nn_params, ...
                                       input_layer_size, ...
                                       hidden_layer_size, ...
                                       num_labels, ...
                                       X, y, lambda)
    Theta1 = reshape(nn_params(1:hidden_layer_size * (input_layer_size + 1)), ...
                     hidden_layer_size, (input_layer_size + 1));
    
    Theta2 = reshape(nn_params((1 + (hidden_layer_size * (input_layer_size + 1))):end), ...
                     num_labels, (hidden_layer_size + 1));
    
    % Setup some useful variables
    m = size(X, 1);
             
    % You need to return the following variables correctly 
    J = 0;
    Theta1_grad = zeros(size(Theta1));
    Theta2_grad = zeros(size(Theta2));
    
    
    % ------ 前向传播计算输出 ------
    
    % input layer
    a1 = [ones(m, 1) X]; %add +1 to X;
    % hidden layer
    a2 = sigmoid(a1 * Theta1');
    a2 = [ones(m, 1) a2];
    % output layer
    a3 = sigmoid(a2 * Theta2');
    
    % ------ 样本的Y值 ------
    % [1 0 0 0 0 0 0 0 0 0] -- the value is 1
    % [0 1 0 0 0 0 0 0 0 0] -- the value is 2
    Y = zeros(m,num_labels);
    for i = 1 : m
        Y(i,y(i)) = 1;
    end
    
    % ------ 损失函数J ------
    J = (sum(sum(-Y .* log(a3))) - sum(sum((1-Y) .* log(1-a3)))) / m ;
    % remove theta0
    t1 = Theta1(:,2:end);
    t2 = Theta2(:,2:end);
    regularize = lambda / 2 / m * (sum(sum(t1.^2)) + sum(sum(t2.^2)));
    J = J + regularize;
    
    % ------ 反向传播计算各层误差 ------
    delta3 = a3 - Y;
    delta2 = delta3 * Theta2 .* a2 .* (1 - a2);
    delta2 = delta2(:,2:end);
    
    % ------ 计算梯度 ------
    Theta1_grad = ( delta2' * a1 + [zeros(size(t1,1),1) t1] * lambda) / m;
    Theta2_grad = ( delta3' * a2 + [zeros(size(t2,1),1) t2] * lambda) / m;
    
    % Unroll gradients
    grad = [Theta1_grad(:) ; Theta2_grad(:)];
    
    end
    
    

    2、前向传播及计算delta中,需要用到sigmoid函数及其导数

    2.1 sigmoid函数

    function g = sigmoid(z)
    g = 1.0 ./ (1.0 + exp(-z));
    end
    
    

    2.2 sigmoid函数的导数

    function g = sigmoidGradient(z)
    g = sigmoid(z) .* (1 - sigmoid(z));
    end
    
    

    3、训练过程

    3.1、随机初始化theta参数矩阵

    initial_Theta1 = randInitializeWeights(input_layer_size, hidden_layer_size);
    initial_Theta2 = randInitializeWeights(hidden_layer_size, num_labels);
    
    % Unroll parameters
    initial_nn_params = [initial_Theta1(:) ; initial_Theta2(:)];
    
    

    逻辑回归中,theta矩阵可以初始化为同一个值,如全0或全1。但神经网络中却不行。

    原因在于:神经网络中,神经元是以全连接的形式组织起来的,即n-1层的任意一个节点,都与第n层所有节点相连接。

    若是初始化时theta矩阵初始化为同一个值,同一个层的每一个神经元都进行相同的运算,多个神经元进行相同的运算,这对于数据的拟合没有任何用处,只是浪费资源,造成冗余。此为对称现象。

    随机初始化参数的实现如下:

    function W = randInitializeWeights(L_in, L_out)
    W = zeros(L_out, 1 + L_in);
    epsilon_init = 0.12;
    W = rand(L_out, 1 + L_in) * 2 * epsilon_init - epsilon_init;
    end
    
    

    3.2、初始化参数

    options = optimset('MaxIter', 100);
    
    % 正则项参数
    lambda = 1;
    
    % 损失函数
    costFunction = @(p) nnCostFunction(p, ...
                                       input_layer_size, ...
                                       hidden_layer_size, ...
                                       num_labels, X, y, lambda);
    % 梯度下降计算参数
    [nn_params, cost] = fmincg(costFunction, initial_nn_params, options);
    
    % 获取两层神经网络的参数
    Theta1 = reshape(nn_params(1:hidden_layer_size * (input_layer_size + 1)), ...
                     hidden_layer_size, (input_layer_size + 1));
    
    Theta2 = reshape(nn_params((1 + (hidden_layer_size * (input_layer_size + 1))):end), ...
                     num_labels, (hidden_layer_size + 1));
    
    

    4、预测

    pred = predict(Theta1, Theta2, X);
    
    fprintf('
    Training Set Accuracy: %f
    ', mean(double(pred == y)) * 100);
    

    可以看到,其预测结果,比逻辑回归准确率高接近3个点。

    原因在于:神经网络所能构建的模型,比逻辑回归更为复杂,其对数据的拟合能力也更强。

    predict函数,使用训练得到的参数矩阵,前向传播计算得到结果即为输出层,输出层表示一个输入样本经过经神网络计算之后,其可能属于各个分类的概率值。与逻辑回归类似,取最大值即为最终的结果。

    function p = predict(Theta1, Theta2, X)
    
    m = size(X, 1);
    num_labels = size(Theta2, 1);
    
    p = zeros(size(X, 1), 1);
    
    h1 = sigmoid([ones(m, 1) X] * Theta1');
    h2 = sigmoid([ones(m, 1) h1] * Theta2');
    [dummy, p] = max(h2, [], 2);
    
    end
    
    
  • 相关阅读:
    快速排序就这么简单
    Shiro入门这篇就够了【Shiro的基础知识、回顾URL拦截】
    SpringDataJPA入门就这么简单
    递归就这么简单
    SpringBoot就是这么简单
    Activiti就是这么简单
    Lucene就是这么简单
    过来人告诉你,去工作前最好还是学学Git
    给女朋友讲解什么是Git
    我终于看懂了HBase,太不容易了...
  • 原文地址:https://www.cnblogs.com/Fordestiny/p/8819978.html
Copyright © 2011-2022 走看看