神经网络——梯度下降&反向传播

本文深入探讨神经网络的反向传播算法,介绍了误差的概念,通过建立损失函数来统一误差符号,并选择了平方误差作为损失函数。进一步解释了为何采用梯度下降法寻找损失函数的最小值,详细阐述了二元凸函数的梯度下降过程。最后,展示了反向传播的具体步骤,包括如何计算权重和偏置的梯度,并给出了多层感知机的反向传播实现代码片段。
摘要由CSDN通过智能技术生成

前言

本篇是根据上一篇博文《神经网络–前向传播》续写的。
https://blog.csdn.net/weixin_43955293/article/details/120297427
本文中的数学知识有点,需要一点微分的知识。(会求导)就可以。

基本概念

在上文我们已经知道了什么是前向传播。就是当前层的输入是上一层的输出,当前层的输出是下一层的输入。然后一层一层的传递下去。

反向传播也叫反向更新。根据感知机的原理。神经元产生输出之后要与正确的标签做一个对比然后更新调整自己的参数,经过反复训练达到一个很好的效果。

我们最后一层叫做输出层,这一层产生的输出就要与正确的输出做比较,然后更新自己的参数。那么神经网络有那么多层怎么去更新呢?答案就是反向更新。就是先最后一层的参数然后更新倒数第二层的参数、倒数第三层一直到第一层。这个就叫反向传播

下面会一步一步解释为什么要这么做。

误差

根据前向传播原理
每一层的输出y是这样的到的。
z=wx+b
y=f(z)
其中 f(z)是激励函数。
我们可以观察到参数有 权重w,和偏置b。这个也是我们需要反向更新的参数。

因为反向传播主要是讨论参数问题,我们可以发现激励函数没有任何其他新的参数。(z还是w,b)组成。

我们暂且忽略激励函数的存在。
我们会得到公式:

y预测=wx+b

这里引入新的一个参数e(误差),这个e就是残差。
很容易得到公式
y真实=y预测+e
那么e=y真实-y预测
e=y真实-(wx+b)

对于每个表达式都是这样。所以我们加和之后变成了。
∑ i = 1 n e i = ∑ i = 1 n [ y i − ( w x i + b ) ] \sum_{i=1}^n{e_i}=\sum_{i=1}^n[{y_i}-(wx_i+b)] i=1nei=i=1n[yi(wxi+b)]

损失函数

因为e可能有正也有负,这样加和会导致相互抵消,所以我们要统一e的符号。统一e的符号只有两种方法。
1、取绝对值
2、取平方
我们使用第二种思路就是取平方。那么我们可以定义出一个损失函数。取名叫loss或者cost都可以。
L o s s = ∑ i = 1 n e i 2 Loss = \sum_{i=1}^n{e_i^2} Loss=i=1nei2
L o s s = ∑ i = 1 n [ y i − ( w x i + b ) ] 2 Loss = \sum_{i=1}^n[y_i-(wx_i+b)]^2 Loss=i=1n[yi(wxi+b)]2
很容易就能得到loss的公式。

等等。。得到这个有啥用呢?有的人可能已经被公式绕晕了,看到这里不妨回去看看,我们为什么要做出这个loss函数。对!我们要找到它的最小值。

梯度下降

要取loss 的最小值,我首先想到的就是高数的求偏导之后,算极值点,这貌似对一元函数,二元函数比较靠谱,但是神经网络有那么多维度。显然不现实。(求偏导要玩疯)。
那么就整出了一个梯度下降算法。
我们首先看看loss函数
L o s s = ∑ i = 1 n [ y i − ( w x i + b ) ] 2 Loss = \sum_{i=1}^n[y_i-(wx_i+b)]^2 Loss=i=1n[yi(wxi+b)]2
⇒ L o s s = A w 2 + B b 2 + C w b + D w + E b + F \Rightarrow Loss = Aw^2+Bb^2+Cwb+Dw+Eb+F Loss=Aw2+Bb2+Cwb+Dw+Eb+F
图像为
在这里插入图片描述
我们很容易看出,这个是像个碗(有点尖)既然是个碗的话,那么肯定有碗底,那么碗底就是我们要找到那个极小点。(不难理解吧)。
这就是一个二元凸函数。
怎么去找碗底现在就是我们要解决的问题。

二元凸函数的梯度下降

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
那么以其中某一层中的权重参数矩阵w来说,更新公式就是
w n + 1 = w n − η ∇ w_{n+1} = w_n - \eta\nabla wn+1=wnη
也就是说参数的更新就是当前值减去梯度

模型构建

我们还是从多层感知机看起。
假设我们每一层只有一个神经元,只要把这个做出来其他都是一样的。(这个层的神经元多,了只不过是多维度基本代码不会变)。
在这里插入图片描述
根据前向传播原理
从隐藏层开始算
z h = w h x + b h z_h = w_hx+b_h zh=whx+bh
y h = 1 1 + e − z h y_h = \frac{1}{1+e^{-z_h}} yh=1+ezh1
隐藏层到输出层
z o = w o y o + b o z_o = w_oy_o+b_o zo=woyo+bo
y o = 1 1 + e − z o y_o = \frac{1}{1+e^{-z_o}} yo=1+ezo1
根据我们损失函数定义,为求导方便Loss前面加一个常数项
L o s s = 1 2 ∑ i = 1 n ( y o i − y i ) 2 Loss = \frac{1}{2}\sum_{i=1}^n(y_{oi}-y_i)^2 Loss=21i=1n(yoiyi)2
根据反向传播原理我们需要更新
wh,bh, wo,bo的值。
由梯度下降算法可知,于是有了四个表达式
在这里插入图片描述
其中的wh,bh,wo,bo是我们已经知道的值。而wh+1,bh+1,wo+1,bo+1是我们需要更新的值。因为wh,bh,wo,bo已经确定,所以只要我们求出。Loss 对每个待定系数的偏导数。

因为是反向传播,我们需先看输出层的wo和bo.
根据公式和求导链式法则()
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

算法设计

前向传播在上一文中已经实现了。本文就实现出反向传播。

1、计算最后一层的delta
由公式推导可以知道,我们想求出最后一层的delta必须知道yo的值和zo的值。

我们要知道当前层的z
我们要知道当前层的y
我们要知道上一层的y

于是我们吧所有的层产生的y和z保存到两个列表里面。
在这里插入图片描述
2、计算出最后一层的权重和偏置的偏导数

3、计算所有隐藏层到第一层权重和偏置的偏导数。(反向更新)

我们要知道当前层的Z
我们要知道下一层的w
我们要知道下一层的delta
我们要知道前一层的y
按照公式来。

利用两个列表进行操作。每一层更新自己的delta供上一层用。这个delta就是在传递。从最后一层传到第一层。这就是反向传播。

代码部分

# -*- coding: utf-8 -*-
# @Time    : 2017/11/24 下午2:34
# @Author  : SkullFang
# @Email   : yzhang.private@gmail.com
# @File    : MnistDemo.py
# @Software: PyCharm
import random
import numpy as np
class NetWork(object):
    def __init__(self,sizes):
        """
        初始化
        :param sizes: 
        """
        self.num_layers=len(sizes)
        self.sizes=sizes
        #偏置
        self.biases=[np.random.randn(y,1)
                     for y in sizes[1:]]
        #权重

        self.weights=[np.random.randn(y,x)
                      for x,y in zip(sizes[:-1],sizes[1:])]

    def GD(self,training_data,epochs,eta):
        """
        梯度下降
        :param training_data: 训练数据
        :param epochs: 训练次数
        :param eta: 学习率
        :return: 
        """
        for j in xrange(epochs):
            random.shuffle(training_data)

            # 保存每层偏导
            nabla_b = [np.zeros(b.shape) for b in self.biases]
            nabla_w = [np.zeros(w.shape) for w in self.weights]

            for x,y in training_data:
                delta_nable_b, delta_nabla_w = self.update(x, y)

                # 保存一次训练网络中每层的偏导
                nabla_b = [nb + dnb for nb, dnb in zip(nabla_b, delta_nable_b)]
                nabla_w = [nw + dnw for nw, dnw in zip(nabla_w, delta_nabla_w)]

            self.weights=[w-(eta)*nw
                          for w,nw in zip(self.weights,nabla_w)]

            self.biases=[b-(eta)*nb 
            			  for b,nb in zip(self.biases,nabla_b)]


            print "Epoch {0} complete".format(j)

    def update(self,x,y):
        """
        正向传播和反向传播
        :param x: 
        :param y: 
        :return: 
        """

        # 初始化一个矩阵用于保存每层偏导
        nabla_b = [np.zeros(b.shape) for b in self.biases]
        nabla_w = [np.zeros(w.shape) for w in self.weights]
        #输入层
        activation=x

        #保存每一层的激励值
        activations=[x]

        #保存每一层的z=wx+b
        zs=[]

        for b,w in zip(self.biases,self.weights):

            #重复更新activation,即激励值(经过激励函数过后的值)
            z=np.dot(w,activation)+b

            zs.append(z)

            activation=sigmoid(z)

            activations.append(activation)

        #计算最后一层的delta
        delta=self.cost_derivative(activations[-1],y)*sigmoid_prime(zs[-1])

        #计算最后一层的的w和b的偏导数
        nabla_b[-1]=delta
        nabla_w[-1]=np.dot(delta,activations[-2].transpose())

        #计算倒数第二层到第一层的w和b的偏导数
        for l in range(2,self.num_layers):
            #当前层的z
            z=zs[-l]
            #自己层的偏导
            sp=sigmoid_prime(z)
            #自己层的delta 按照公式来。每次更新delta 供上一层用。
            delta=np.dot(self.weights[-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 cost_derivative(self, output_activation, y):
        """
        计算y-yi
        :param output_activation: 
        :param y: 
        :return: 
        """
        return (output_activation - y)


def sigmoid_prime(z):
    """
    sigmoid的偏导数y' = y*(1-y)
    :param z: 
    :return: 
    """
    return sigmoid(z)*(1-sigmoid(z))


def sigmoid(z):
    """
    激励函数
    :param z: 
    :return: 
    """
    return 1.0/(1.0+np.exp(-z))
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值