【神经网络DNN】算法原理 公式推导 python编程实现

 

1.前言

如图是一个神经网络的简化结构,隐藏层每一个节点都是一个神经元,比如下图的a1,a2,a3。机器学习中的神经网络是模拟生物神经网络结构,每个神经元与其他神经元相连,当神经元的电位超过了一个‘阈值’,那么它就会被激活,即‘兴奋’起来。

   机器学习的神经网络是怎么模拟大脑神经元‘兴奋’这个概念的?结合a1这个神经元做简要的分析:

首先对于a1的定义,我们给出如下的公式..

                      \large {a_{1}}=g(h(x)))={w_{11}}*{x_{1}}+{w_{21}}*{x_{2}}\cdot \cdot \cdot \cdot {w_{n1}}*{x_{n}}+{w_{m1}}*{x_{m}}+{b_{01}}*{x_{0}}

    剖析神经元兴奋的定义“当神经元的电位超过了一个‘阈值’,那么它就会被激活”,对应与上面公式,怎么使得上诉公式达到神经元的效果?

    当\small h(x)达到某一阀值时候\small {a_{1}}=g(h(x))=1,即兴奋状态,反之当未达到阀值时\small {a_{1}}=g(h(x))=0,即未兴奋状态。

所以理想情况下\small \small g(z)这个函数应该如下图的红色,因为起特性就是大于零被激活,小于零未激活,所以称之为激活函数。 :

但是红色函数存在不连续,不光滑等不太好的性质...所以在实际情况下,我们一般会使用sigmod函数来作为激活函数(即图中蓝线):

                                                                                \large sigmoid(z)=\frac{1}{1+e^{-z}}

其实我们可以发现,对于神经网络来说,每一个神经元我们都可以理解成一个逻辑回归(LR)模型。这样去理解它或许对你之后的理解有所帮助。对于逻辑回归算法模型的推到及编程可以查看我之前的一篇文章:点击此处跳转

2.前向传播的神经网络运算

          由图,输入层的输入矩阵(\large x_{i})可以表示为:                 隐藏层的输入矩阵(\large a_{j})可以表示为:

                                   \large \begin{bmatrix} x_{1}\\ x_{2}\\ x_{3} \end{bmatrix}                                                                        \large \large \begin{bmatrix} a_{1}\\ a_{2} \end{bmatrix}                        

          输入层的每个输入\large x_{i}对应每个隐藏层\large a_{j}的权重为\large w_{ij},所以权重的矩阵(\large w_{ij})可以表示为:

                                                                   \large \begin{bmatrix} w_{11} &w_{12} \\ w_{21} &w_{22} \\ w_{31}&w_{32} \end{bmatrix}

          由神经元的兴奋启发可以将上式表示为:

                                                                  \large h_{1}=\begin{bmatrix} w_{11} & w_{21}& w_{31} \end{bmatrix}*\begin{bmatrix} x_{1}\\ x_{2}\\ x_{3} \end{bmatrix}   

                                                                  \large a_{1}=g(h_{1})=\frac{1}{1+e^{-h_{1}}}

       由上式子可以整理得;

                                                                 \large h=w^{T}x+[b]    (b为偏置单元,对应图1的,x0,a0,在本节介绍中省略)

       将矩阵代入上式:

                                                               \large h_{j}=w^{T}x+[b]=\begin{bmatrix} w_{11} &w_{21} & w_{31} \\ w_{12} &w_{22}&w_{32} \end{bmatrix}*\begin{bmatrix} x_{1}\\ x_{2}\\ x_{3} \end{bmatrix}

                                                              \large a_{i}=g(h_{j})=\frac{1}{1+e^{-h_{j}}}

    同理,我们对于接下来的隐藏层和输出层也用同样的方法,那么我们最终的输出层就是一个在0,1之间分布的值,当其大于0时,即输入的x属于1这个类别。当其小于0时,输入的x属于0这个类别。

 

3.梯度下降与代价函数

 

   你可能会因为不清楚第二节的前向传播算法中的权重矩阵(\large w_{ij})是怎么来的,从而导致你对整个算法的过程有些不知所云。这一章节会为你解决这个疑惑。

     需要明白的是,在设计一个神经网络算法模型的时候,有些参数是提前人为定义的。

#参数1:
隐藏层的层数
#参数2:
每层隐藏层的神经元个数
#参数3:
输出层的神经元个数
#参数4
定义每次训练的样本数
#参数5
定义训练的次数
#参数6
定义学习率

当上诉参数设定后,将在神经网络中起到怎样的作用呢?结合下图来说明一下:

1.在定义了隐藏层的层数,每层神经元的个数,输出层神经元个数后我们能确定上图中w,b,v,b_,u,b__的矩阵大小。在神经网络第一轮训练时候,上诉矩阵的参数即w11,w12...等的值是人为定义的一系列满足正态分布的随机值。实质上之后你会发现,无论起先w,b的矩阵值如何,通过代价函数的反复迭代都能让其收敛到最低点。

2.定义每次训练的样本数即确定了输入层输入x的个数。

3.人为定义不同的训练的次数,学习率,通过观察准确率的变化,来选择最适合神经网络的参数。

 

在前言部分为大家介绍到,一个神经网络的每个神经元可以看做一个逻辑回归算法模型。

逻辑回归的代价函数如下:

                                     \large j(w)=\frac{1}{m}\sum_{i=1}^{m}[-y^{i}(h_{w}(x^{i}))-(1-y^{i})log(1-h_{w}(x^{i}))]

关于逻辑回归代价函数的推到,我简要说明一下:

1.在二分类问题中,对于每个观察样本:

                             \large p(x_{i},y_{i})=p(y_{i}=1|x_{i})^{y_{i}}(1-p(y_{i}=1|x_{i}))^{1-y_{i}}

这个公式很好理解,拆分开来可以这样表示:

                             \large p(x_{i},y_{i})=\left\{\begin{matrix} p(y_{i}=1|x_{i})& y_{i}=1\\ (1-p(y_{i}=1|x_{i})) &y_{i}=0\end{matrix}\right.

2.对于n个出现的样本,样本间相互独立,则n个出现的概率为每一个出现的概率的乘积。

                             \large L(w)=\prod p(y_{i}=1|x_{i})^{y_{i}}(1-p(y_{i}=1|x_{i}))^{1-y_{i}}

3.为了满足凸函数求最优解的思想,我们对L(w)取对数,并化简:

                              \large L(w)=\prod p(y_{i}=1|x_{i})^{y_{i}}(1-p(y_{i}=1|x_{i}))^{1-y_{i}}

                             \large j(w)=\sum_{i=1}^{n}[ y_{i}log(p(y_{i}=1|x_{i}))+(1-y_{i})log(1-p(y_{i}=1|x_{i}))]

4.由sigmod函数可以推到出如下结果:

                             \large p(y_{i}=1|x_{i})=\frac{1}{1+e^{-z}}               

                             \large 1-p(y_{i}=1|x_{i})=\frac{1}{1+e^{z}}

5.有次推导逻辑回归的代价函数:

                          \large j(w)=\frac{1}{m}\sum_{i=1}^{m}[-y^{i}(h_{w}(x^{i}))-(1-y^{i})log(1-h_{w}(x^{i}))]

    对于神经网络来说,我们可以由上一章节的前向传播的算法得到最后的输出,之后我们算出这个输出与真实值的误差,然后用同样的方式反向传播回去迭代修正之前w,b,v,b_,u,b__矩阵的值。

   而对于每个隐藏层来说,其每个神经元都可以作为一个逻辑回归单元,这样神经网络误差算法的代价函数就可以做出如下表示:

                           \large j(w)=\frac{1}{m}\sum_{i=1}^{m}\sum_{k=1}^{k}[-y_{k}^{i}(h_{w}(x^{i})_{k})-(1-y_{k}^{i})log(1-h_{w}(x^{i})_{k})](k表示神经元的序号)

同理我们可以用逻辑回归算法模型的方式来对\large j(w)做梯度下降算法,即找出满足:

                                                                 \large \frac{\partial j(w)}{\partial w_{ij}^{l}}=0

时,的w和b的值。对\small \frac{\partial j(w)}{\partial w_{ij}^{l}}的求解,可以使用链式法则:

                                                                 \large \frac{\partial y}{\partial x}=\frac{\partial y}{\partial u}\frac{\partial u}{\partial x}

                                                               \large \frac{\partial j(w)}{\partial w_{ij}^{l}}=\frac{\partial j(w)}{\partial z_{j}^{l+1}}\frac{\partial z_{j}^{l+1}}{\partial w_{ij}^{l}}

 对于神经元的求和:

                                                               \large z_{j}^{l+1}=\sum_{i=0}^{n}w_{ij}^{l}a_{j}^{l}(这个公式可由前向传播算法矩阵运算中得出)

所以

         \large \frac{\partial z_{j}^{l+1}}{\partial w_{ij}^{l}}=(\frac{\partial\sum_{i=0}^{n} w_{ij}^{l}a_{j}^{l} }{\partial w_{ij}^{l}})=a_{j}^{l}

假设:

        \bg_white \large \large \delta_{i}^{l+1}=\frac{\partial j(w)}{\partial z_{j}^{l+1}} 

那么:

         \large \frac{\partial j(w)}{\partial w_{ij}^{l}}=\delta_{i}^{l+1}a_{j}^{l}

 

所以由梯度下降算法可以求得:

                                             \bg_white \large \dpi{120} \large w_{j}:=w_{j}+ \eta \delta_{i}^{l+1}a_{j}^{l}           (\eta为人为设定的学习率。)

4.BP反向传播算法

通过第四节,我们对于神经网络更新参数w,b有了一定的认识,而在上节最后,原来的更新w,b的问题转为了求解\bg_white \large \large \delta_{i}^{l+1}

 用一张动态图表示前向(FP)和后向(BP)传播的全过程:

对于输出层来说:

\begin{align} \delta^{(n_l)}_i = \frac{\partial}{\partial z^{(n_l)}_i} \;\;         \frac{1}{2} \left\|y - h_{W,b}(x)\right\|^2 = - (y_i - a^{(n_l)}_i) \cdot f'(z^{(n_l)}_i) \end{align}

\begin{align} \delta^{(n_l)}_i &= \frac{\partial}{\partial z^{n_l}_i}J(W,b;x,y)  = \frac{\partial}{\partial z^{n_l}_i}\frac{1}{2} \left\|y - h_{W,b}(x)\right\|^2 \\  &= \frac{\partial}{\partial z^{n_l}_i}\frac{1}{2} \sum_{j=1}^{S_{n_l}} (y_j-a_j^{(n_l)})^2  = \frac{\partial}{\partial z^{n_l}_i}\frac{1}{2} \sum_{j=1}^{S_{n_l}} (y_j-f(z_j^{(n_l)}))^2 \\  &= - (y_i - f(z_i^{(n_l)})) \cdot f'(z^{(n_l)}_i)  = - (y_i - a^{(n_l)}_i) \cdot f'(z^{(n_l)}_i) \end{align}

 

(用均方误差来定义样本的y值和通过算法模型求解出来的y值的距离差距) 

其中

f(z_{j}^{(n_{l})})=sigmod(z)=\frac{1}{1+e^{-z}}

 f^{'}(z_{j}^{(n_{l})})=sigmod(z)*(1-sigmod(z))=\frac{e^{-z}}{(1+e^{-z})^{2}}

对于隐藏层来说:

\delta^{(l)}_i = \left( \sum_{j=1}^{s_{l+1}} W^{(l)}_{ji} \delta^{(l+1)}_j \right) f'(z^{(l)}_i)

\begin{align} \delta^{(n_l-1)}_i &=\frac{\partial}{\partial z^{n_l-1}_i}J(W,b;x,y)  = \frac{\partial}{\partial z^{n_l-1}_i}\frac{1}{2} \left\|y - h_{W,b}(x)\right\|^2   = \frac{\partial}{\partial z^{n_l-1}_i}\frac{1}{2} \sum_{j=1}^{S_{n_l}}(y_j-a_j^{(n_l)})^2 \\ &= \frac{1}{2} \sum_{j=1}^{S_{n_l}}\frac{\partial}{\partial z^{n_l-1}_i}(y_j-a_j^{(n_l)})^2  = \frac{1}{2} \sum_{j=1}^{S_{n_l}}\frac{\partial}{\partial z^{n_l-1}_i}(y_j-f(z_j^{(n_l)}))^2 \\ &= \sum_{j=1}^{S_{n_l}}-(y_j-f(z_j^{(n_l)})) \cdot \frac{\partial}{\partial z_i^{(n_l-1)}}f(z_j^{(n_l)})  = \sum_{j=1}^{S_{n_l}}-(y_j-f(z_j^{(n_l)})) \cdot  f'(z_j^{(n_l)}) \cdot \frac{\partial z_j^{(n_l)}}{\partial z_i^{(n_l-1)}} \\ &= \sum_{j=1}^{S_{n_l}} \delta_j^{(n_l)} \cdot \frac{\partial z_j^{(n_l)}}{\partial z_i^{n_l-1}}  = \sum_{j=1}^{S_{n_l}} \left(\delta_j^{(n_l)} \cdot \frac{\partial}{\partial z_i^{n_l-1}}\sum_{k=1}^{S_{n_l-1}}f(z_k^{n_l-1}) \cdot W_{jk}^{n_l-1}\right) \\ &= \sum_{j=1}^{S_{n_l}} \delta_j^{(n_l)} \cdot  W_{ji}^{n_l-1} \cdot f'(z_i^{n_l-1})  = \left(\sum_{j=1}^{S_{n_l}}W_{ji}^{n_l-1}\delta_j^{(n_l)}\right)f'(z_i^{n_l-1}) \end{align}

\large l替换调\large n_{l}-1,用\large l+1替换掉\large n_{l}

\delta^{(l)}_i = \left( \sum_{j=1}^{s_{l+1}} W^{(l)}_{ji} \delta^{(l+1)}_j \right) f'(z^{(l)}_i)

这个式子说明了反向传播算法的本质,我们先求得输入层\delta_{i}^{l},然后通过\delta_{i}^{l}不断后向推到\delta_{i}^{l-1}....\delta_{i}^{1}

通过一轮一轮反复迭代修正w,b。使得最终的w,b是最能完成分类的参数。

5.编程实现

 

如果需要更为详细的编程实现过程,点下面链接:

使用Python实现神经网络

import numpy as np
import random
import os, struct
from array import array as pyarray
from numpy import append, array, int8, uint8, zeros
 
class NeuralNet(object):
 
    # 初始化神经网络,sizes是神经网络的层数和每层神经元个数
    def __init__(self, sizes):
        self.sizes_ = sizes
        self.num_layers_ = len(sizes)  # 层数
        self.w_ = [np.random.randn(y, x) for x, y in zip(sizes[:-1], sizes[1:])]  # w_、b_初始化为正态分布随机数
        self.b_ = [np.random.randn(y, 1) for y in sizes[1:]]
 
    # Sigmoid函数,S型曲线,
    def sigmoid(self, z):
        return 1.0/(1.0+np.exp(-z))
    # Sigmoid函数的导函数
    def sigmoid_prime(self, z):
        return self.sigmoid(z)*(1-self.sigmoid(z))
 
    def feedforward(self, x):
        for b, w in zip(self.b_, self.w_):
            x = self.sigmoid(np.dot(w, x)+b)
        return x
 
    def backprop(self, x, y):
        nabla_b = [np.zeros(b.shape) for b in self.b_]
        nabla_w = [np.zeros(w.shape) for w in self.w_]
 
        activation = x
        activations = [x]
        zs = []
        for b, w in zip(self.b_, self.w_):
            z = np.dot(w, activation)+b
            zs.append(z)
            activation = self.sigmoid(z)
            activations.append(activation)
 
        delta = self.cost_derivative(activations[-1], y) * \
            self.sigmoid_prime(zs[-1])
        nabla_b[-1] = delta
        nabla_w[-1] = np.dot(delta, activations[-2].transpose())
 
        for l in range(2, self.num_layers_):
            z = zs[-l]
            sp = self.sigmoid_prime(z)
            delta = np.dot(self.w_[-l+1].transpose(), delta) * sp
            nabla_b[-l] = delta
            nabla_w[-l] = np.dot(delta, activations[-l-1].transpose())
        return (nabla_b, nabla_w)
 
    def update_mini_batch(self, mini_batch, eta):
        nabla_b = [np.zeros(b.shape) for b in self.b_]
        nabla_w = [np.zeros(w.shape) for w in self.w_]
        for x, y in mini_batch:
            delta_nabla_b, delta_nabla_w = self.backprop(x, y)
            nabla_b = [nb+dnb for nb, dnb in zip(nabla_b, delta_nabla_b)]
            nabla_w = [nw+dnw for nw, dnw in zip(nabla_w, delta_nabla_w)]
        self.w_ = [w-(eta/len(mini_batch))*nw for w, nw in zip(self.w_, nabla_w)]
        self.b_ = [b-(eta/len(mini_batch))*nb for b, nb in zip(self.b_, nabla_b)]
 
    # training_data是训练数据(x, y);epochs是训练次数;mini_batch_size是每次训练样本数;eta是learning rate
    def SGD(self, training_data, epochs, mini_batch_size, eta, test_data=None):
        if test_data:
            n_test = len(test_data)
 
        n = len(training_data)
        for j in range(epochs):
            random.shuffle(training_data)
            mini_batches = [training_data[k:k+mini_batch_size] for k in range(0, n, mini_batch_size)]
            for mini_batch in mini_batches:
                self.update_mini_batch(mini_batch, eta)
            if test_data:
                print("Epoch {0}: {1} / {2}".format(j, self.evaluate(test_data), n_test))
            else:
                print("Epoch {0} complete".format(j))
 
    def evaluate(self, test_data):
        test_results = [(np.argmax(self.feedforward(x)), y) for (x, y) in test_data]
        return sum(int(x == y) for (x, y) in test_results)
 
    def cost_derivative(self, output_activations, y):
        return (output_activations-y)
 
    # 预测
    def predict(self, data):
        value = self.feedforward(data)
        return value.tolist().index(max(value))
 
    # 保存训练模型
    def save(self):
        pass  # 把_w和_b保存到文件(pickle)
    def load(self):
        pass

打赏一下作者:

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值