精华内容
下载资源
问答
  • 反向传播

    2020-11-04 09:43:37
    通过链式运算的规则,我们可以将输出端的损失函数通过反向传播(backward)(红线),来实现对权重w或者上一项x的偏导,从而实现梯度下降的目的。而从输入端到输出端的过程称为前馈(forward)(蓝线) 以简单的...

    反向传播(Back Propagation)

    在此做一些学习笔记的记录,视频来自link

    神经网络需要计算损失对权重的导数,以此来对权重进行更新在这里插入图片描述
    对于复杂的神经网络(即含有多个权重w),就不能直接套用梯度下降公式来计算权重w,否则计算量巨大,因此利用反向传播来解决复杂神经网络的问题
    在这里插入图片描述
    通过链式运算的规则,我们可以将输出端的损失函数通过反向传播(backward)(红线),来实现对权重w或者上一项x的偏导,从而实现梯度下降的目的。而从输入端到输出端的过程称为前馈(forward)(蓝线)
    在这里插入图片描述
    以简单的线性模型为例,大致的流程如下:
    在这里插入图片描述
    总结:正向前馈(forward)用于算出对应样本的损失,反向传播(backward)用于算出相应的梯度,根据梯度下降的情况来更新权重。

    展开全文
  • 背景 我们在 如何利用 C# 对神经网络模型进行抽象? 中完成了神经网络的抽象结构: 三个接口:激活函数、有监督学习、无监督学习 ...利用感知器学习规则实现有监督学习接口。 覆写了三个...

    背景

    我们在 如何利用 C# 对神经网络模型进行抽象? 中完成了神经网络的抽象结构:

    • 三个接口:激活函数、有监督学习、无监督学习

    接口

    • 三个抽象类:神经元、网络层、网络拓扑

    抽象类

    我们在 如何利用 C# 实现神经网络的感知器模型? 中对神经网络的结构进行了扩展,完成了感知器神经网络:

    • 实现了三个接口:利用符号函数和阈值函数实现激活函数接口;利用感知器学习规则实现有监督学习接口。

    接口实现

    • 覆写了三个抽象类中的抽象方法:用实体类 ActivationLayer 继承 Layer;用实体类 ActivationNeuron 继承 Neuron;用实体类 ActivationNetwork 继承 Network

    实体类继承

    实体类继承

    我们在 如何利用 C# 实现 Delta 学习规则? 中对神经网络进行进一步的扩展。

    • 实现了两个接口:利用Sigmoid函数实现激活函数接口;利用 Delta 学习规则实现有监督学习接口。

    接口实现


    技术分析

    今天,我们进一步扩展神经网络的学习规则,利用误差反向传播学习规则来实现有监督学习接口;增加BP神经网络另外一个常用的Sigmoid函数。

    接口实现

    有关BP神经网络的原理,参见图文 基于Matlab的BP神经网络在语音特征信号识别中的应用,这篇图文中有详细的权值、阈值推导过程。


    代码实现

    双极性Sigmoid函数

    public class BipolarSigmoidFunction : IActivationFunction
    {
        public double Alpha { get; set; }
        public BipolarSigmoidFunction(double alpha=2)
        {
            Alpha = alpha;
        }
    
        public double Function(double x)
        {
            return 2 / (1 + Math.Exp(-Alpha * x)) - 1;
        }
    
        public double Derivative(double x)
        {
            double y = Function(x);
    
            return Alpha * (1 - y * y) / 2;
        }
    
        public double Derivative2(double y)
        {
            return Alpha * (1 - y * y) / 2;
        }
    }
    

    误差反传算法的实现

    public class BackPropagationLearning : ISupervisedLearning
    {
        private readonly ActivationNetwork _network;
        private double _learningRate = 0.1;
        private readonly double[][] _neuronErrors;
        private readonly double[][][] _weightsUpdates;
        private readonly double[][] _thresholdsUpdates;
    
        public double LearningRate
        {
            get { return _learningRate; }
            set { _learningRate = Math.Max(0.0, Math.Min(1.0, value)); }
        }
    
        public BackPropagationLearning(ActivationNetwork network)
        {
            _network = network;
            _neuronErrors = new double[network.Layers.Length][];
            _weightsUpdates = new double[network.Layers.Length][][];
            _thresholdsUpdates = new double[network.Layers.Length][];
    
            for (int i = 0; i < network.Layers.Length; i++)
            {
                Layer layer = network.Layers[i];
    
                _neuronErrors[i] = new double[layer.Neurons.Length];
                _weightsUpdates[i] = new double[layer.Neurons.Length][];
                _thresholdsUpdates[i] = new double[layer.Neurons.Length];
    
                for (int j = 0; j < _weightsUpdates[i].Length; j++)
                {
                    _weightsUpdates[i][j] = new double[layer.InputsCount];
                }
            }
        }
    
        public double Run(double[] input, double[] output)
        {
            // 计算网络的输出
            _network.Compute(input);
            // 计算各层的误差
            double error = CalculateError(output);
            // 计算权值和阈值的改变量
            CalculateUpdates(input);
            // 更新整个网络
            UpdateNetwork();
            return error;
        }
    
        public double RunEpoch(double[][] input, double[][] output)
        {
            double error = 0.0;
    
            for (int i = 0; i < input.Length; i++)
            {
                error += Run(input[i], output[i]);
            }
    
            return error;
        }
    }    
    

    计算各层的误差与网络的总体误差

    private double CalculateError(double[] desiredOutput)
    {
        double error = 0;
        int layersCount = _network.Layers.Length;
        ActivationNeuron activationNeuron = _network.Layers[0].Neurons[0] as ActivationNeuron;
        
        if (activationNeuron != null)
        {
            IActivationFunction function = 
                activationNeuron.ActivationFunction;
                
            Layer layer = _network.Layers[layersCount - 1];
            double[] errors = _neuronErrors[layersCount - 1];
    
            for (int i = 0; i < layer.Neurons.Length; i++)
            {
                double output = layer.Neurons[i].Output;
                double e = desiredOutput[i] - output;
                errors[i] = e * function.Derivative2(output);
                error += e * e;
            }
            for (int j = layersCount - 2; j >= 0; j--)
            {
                layer = _network.Layers[j];
                Layer layerNext = _network.Layers[j + 1];
                errors = _neuronErrors[j];
                double[] errorsNext = _neuronErrors[j + 1];
    
                for (int i = 0; i < layer.Neurons.Length; i++)
                {
                    double sum = 0.0;
                    for (int k = 0; k < layerNext.Neurons.Length; k++)
                    {
                        sum += errorsNext[k] * layerNext.Neurons[k].Weights[i];
                    }
                    errors[i] = sum * function.Derivative2(layer.Neurons[i].Output);
                }
            }
        }
        return error / 2.0;
    }
    

    计算网络权值和阈值的调整值

    private void CalculateUpdates(double[] input)
    {
        Layer layer = _network.Layers[0];
        double[] errors = _neuronErrors[0];
        double[][] layerWeightsUpdates = _weightsUpdates[0];
        double[] layerThresholdUpdates = _thresholdsUpdates[0];
    
        for (int i = 0; i < layer.Neurons.Length; i++)
        {
            double error = errors[i];
            double[] neuronWeightUpdates = layerWeightsUpdates[i];
    
            for (int j = 0; j < neuronWeightUpdates.Length; j++)
            {
                neuronWeightUpdates[j] = _learningRate * error * input[j];
            }
            layerThresholdUpdates[i] = _learningRate * error;
        }
    
        for (int k = 1; k < _network.Layers.Length; k++)
        {
            Layer layerPrev = _network.Layers[k - 1];
            layer = _network.Layers[k];
            errors = _neuronErrors[k];
            layerWeightsUpdates = _weightsUpdates[k];
            layerThresholdUpdates = _thresholdsUpdates[k];
    
            for (int i = 0; i < layer.Neurons.Length; i++)
            {
                double error = errors[i];
                double[] neuronWeightUpdates = layerWeightsUpdates[i];
    
                for (int j = 0; j < neuronWeightUpdates.Length; j++)
                {
                    neuronWeightUpdates[j] = _learningRate * error * layerPrev.Neurons[j].Output;
                }
                layerThresholdUpdates[i] = _learningRate * error;
            }
        }
    }
    

    调整整个网络的权值和阈值

    private void UpdateNetwork()
    {
        for (int i = 0; i < _network.Layers.Length; i++)
        {
            Layer layer = _network.Layers[i];
            double[][] layerWeightsUpdates = _weightsUpdates[i];
            double[]  layerThresholdUpdates = _thresholdsUpdates[i];
    
            for (int j = 0; j < layer.Neurons.Length; j++)
            {
                double[] neuronWeightUpdates = layerWeightsUpdates[j];
                ActivationNeuron neuron = layer.Neurons[j] as ActivationNeuron;
                for (int k = 0; k < neuron.Weights.Length; k++)
                {
                    neuron.Weights[k] += neuronWeightUpdates[k];
                }
                neuron.Threshold += layerThresholdUpdates[j];
            }
        }
    }
    

    总结

    我们仍然应用 基于Matlab的BP神经网络在语音特征信号识别中的应用 中的语音信号分类的例子,来说明 BP 神经网络的应用。

    获取数据集的方法

    static List<double[]> GetData()
    {
        List<double[]> result = new List<double[]>();
        string[] files = Directory.GetFiles(@".\data");
    
        foreach (string file in files)
        {
            string[] dataset = File.ReadAllLines(file);
            foreach (string d in dataset)
            {
                if (string.IsNullOrEmpty(d))
                    continue;
                string[] data = d.Split(',');
                double[] item = new double[data.Length];
                for (int i = 0; i < data.Length; i++)
                {
                    item[i] = Convert.ToDouble(data[i]);
                }
                result.Add(item);
            }
        }
        result = result.OrderBy(c => Guid.NewGuid()).ToList();
        return result;
    }
    

    获取输入、输出样本的方法

    private static void GetDataset(List<double[]> lst, out double[][] inputs, out double[][] outputs)
    {
        int count = lst.Count;
        inputs = new double[count][];
        outputs = new double[count][];
        for (int i = 0; i < count; i++)
        {
            double[] data = lst[i];
            int k = (int) data[0];
            switch (k)
            {
                case 1:
                    outputs[i] = new double[] {1, 0, 0, 0};
                    break;
                case 2:
                    outputs[i] = new double[] {0, 1, 0, 0};
                    break;
                case 3:
                    outputs[i] = new double[] { 0, 0, 1, 0 };
                    break;
                case 4:
                    outputs[i] = new double[] { 0, 0, 0, 1 };
                    break;
            }
    
            inputs[i] = new double[data.Length - 1];
            for (int j = 0; j < inputs[i].Length; j++)
            {
                inputs[i][j] = data[j + 1];
            }
        }
    }
    

    主应用程序

    • 输入层 24 个神经元
    • 中间层 25 个神经元
    • 输出层 4 个神经元
    • 训练样本 1500 个
    • 测试样本 500 个
    • 最大迭代次数 1000 次
    static void Main(string[] args)
    {
        List<double[]> lst = GetData();
        double[][] input;
        double[][] output;
    
        int trainNum = 1500;
        List<double[]> trainData = lst.GetRange(0, trainNum);
        GetDataset(trainData, out input, out output);
    
        ActivationNetwork network = new ActivationNetwork(new SigmoidFunction(),
            24, new int[] {25, 4});
             
        ISupervisedLearning bp = new BackPropagationLearning(network);
                
        for (int i = 0; i < 1000; i++)
        {
            double error = bp.RunEpoch(input, output);
            Console.WriteLine("{0}:{1}",i,error);
            if (error <= 0.01)
                break;
        }
    
        List<double[]> testData = lst.GetRange(trainNum, lst.Count - trainNum);
        GetDataset(testData, out input, out output);
        int num = 0;
        for (int i = 0; i < testData.Count; i++)
        {
            double[] result = network.Compute(input[i]);
            int index = MaxIndex(result);
            if (output[i][index] == 1)
                num++;
        }
        Console.WriteLine("准确率为:{0}", 1.0*num/testData.Count);
    }
    

    最后得到的准确率为:91.2%,与用 Matlab 计算的近似。好了,今天就到这里吧!See You!


    对了,到目前为止已经有 10 名同学 通过解码 Huffman Code 得到团队的报名方式。

    团队的招新仍在进行中,对我们感兴趣的同学欢迎与我联系,我会亲自带着大家学习,一起成长!


    相关图文

    展开全文
  • 如图,共两部分,softmax一部分,cross-entropy一部分,a1...正向传播和反向传播基本规则(可以参考cs231n的教程): 正向就是正常运算,没什么说的,正向的多对一操作,求和,正向的一对多,每一条线都传播同样的...

    如图,共两部分,softmax一部分,cross-entropy一部分,a1、a2、a3是输入,y1、y2、y3是softmax的输出,t1、t2、t3是label,图中右侧输出Loss=cross entropy,输入gradient=1。

    正向传播和反向传播基本规则(可以参考cs231n的教程):

    正向就是正常运算,没什么说的,正向的多对一操作,求和,正向的一对多,每一条线都传播同样的值。关于汇聚和分散的原则,正向反向一样。

    反向传播操作:

    加法,grad_c=1,c=a+b,则grad_a=grad_b=grad_c。

    乘法,grad_c=1,c=a×b,则grad_a=grad_c*b和grad_b=grad_c*a。

    log、取导数等,都按正常导数就好了,需要注意反向传播求导过程的一个特点,y = w*x,dy/dw=x,所以想反向传播去更新网络参数w,前向传播过程是需要保留这个x的,x指每一个中间操作(例如图中的exp(a1)、y1、t1logy1等等),不特指输入数据x,所以关于神经网络的内存占用量等计算都少不了这些考量。

    神经网络两大内存占用:前向传播的缓存,网络自身的参数。

     

    参考图:

     

     

    参考实现:

    前向传播的loss并没有直接被后向传播使用,直接使用前边推导出的结果(y-t)作为梯度。。

     

    关于if-else,我刚开始也看蒙了,刚开始我以为ifelse是为了应对脏数据的容错,后来测了一下,代码是跑不通的。
    所以这段代码的意思是,如果size相等,说明你的label是onehot形式的,如果size不相等,说明你的label是index形式的,onehot直接相减就是误差,index实际是按label去取数据,如果一样,取出来就是1,就是误差0(公式里有个-1),如果label不一样,就是误差“2”,为什么,因为这个错误的y没被t指向,所以没减1,且t指向的那个0被减1,所以是-1和+1两个误差,因为这两个误差分属于两个index,也就是两个分类,所以虽然-1++1=0,但是误差不是0,onehot学的是分布。

    demo:

    import numpy as np
    
    a = np.array([
        [0,1,0,0],
        [0,0,0,1],
        [1,0,0,0],
    ])
    t = np.array([
        [0,1,0,0],
        [0,0,1,0],
        [1,0,0,0],
    ])
    t2 = np.array([1,2,0])
    t3 = np.array([1,3,0])
    
    print(a)
    a[np.arange(3),t2] -= 1
    a = a/3
    print(a)
    print(sum(a))

    t是one-hot形式的target,t2、t3代表非one-hot形式的target,t2对应t,a相应的有一个数据不正确,会得到如下结果。

    如果使用t3,结果就是全0。

    [[0 1 0 0]
     [0 0 0 1]
     [1 0 0 0]]
    [[ 0.          0.          0.          0.        ]
     [ 0.          0.         -0.33333333  0.33333333]
     [ 0.          0.          0.          0.        ]]
    [ 0.          0.         -0.33333333  0.33333333]

     

    展开全文
  • 6、反向传播

    2018-07-17 12:47:23
    6、反向传播 内容列表: 6.1简介 6.2简单表达式和理解梯度 6.3复合表达式,链式法则,反向...在本节中,我们将直观地理解反向传播,这是一种通过递归应用链规则来计算函数梯度的方法。理解这一过程及其微妙之...

    6、反向传播

    内容列表:

    • 6.1简介
    • 6.2简单表达式和理解梯度
    • 6.3复合表达式,链式法则,反向传播
    • 6.4直观理解反向传播
    • 6.5模块:Sigmoid例子
    • 6.6反向传播实践:分段计算
    • 6.7回传流中的模式
    • 6.8用户向量化操作的梯度
    • 6.9小结

    6.1 简介

    动机。在本节中,我们将直观地理解反向传播,这是一种通过递归应用链规则来计算函数梯度的方法。理解这一过程及其微妙之处对于理解和有效地开发、设计和调试神经网络至关重要。

    问题陈述。本节研究的核心问题是:给你函数f(x),其中x是输入向量,我们要计算即f在x处的梯度(即∇f(x))。

    动机。在神经网络的特定情况下,f 对应于损失函数(L),并且输入x由训练数据和神经网络权值组成。例如,损失可以是SVM损失函数,输入是训练数据(Xi,Yi),i=1…N,权重W和偏差b。注意,在机器学习领域,训练数据是给定的和固定的,而权重是我们可控制的变量。因此,我们通常只计算W、b等参数的梯度,这样我们就可以使用它来进行参数更新。当然,Xi的梯度有时也有用,例如为了可视化和解释神经网络正在做什么。

    6.2 简单函数的梯度

    让我们从简单开始,考虑两个数f(x,y)=XY的简单乘法函数。对于任一输入求偏导数比较简单的:

    解释。导数的含义:它们指示一个函数相对于围绕某个变量的在某个特定点无限小区域的变化率:

    左边的除法符号与右边的除法符号不同,不是除法。相反,该符号表示操作符d/dx被应用到函数f,并返回另外一个函数(导数)。咱们可以咱们理解导数:当h很小的时候,函数就被一条直线逼近,导数就是它的斜率。换句话说,每个变量上的导数告诉你整个函数在其值上的敏感度。例如,如果x=4,y=-3,则f(x,y)=-12,x上的导数∂f/∂x=-3。这告诉我们,如果我们将这个变量的值增加一个很小的量h,对整个表达式的影响将是减少(因为导数为负),并且是这个量的三倍。这可以通过重新排列上面的等式(f(x+h)=f(x)+h * ∂f/∂x)来看出。类似地,由于∂f /∂y=4,在y上增加非常小的h,则函数的输出会增加(因为导数为正),并且增加的值是4h。

    函数关于每个变量的导数指明了整个表达式对于该变量的敏感程度。

    如上所述,梯度∇f是偏导数的向量,因此我们有∇f=[∂f/∂x,∂f/∂y]=[y,x]。即使梯度在技术上是一个向量,我们将经常使用术语“x上的梯度”,而不是技术上正确的术语“x上的偏导数”。

    我们还可以导出用于加法运算的导数:

    f(x,y)=x+y → ∂f/∂x=1   ∂f/∂y=1

    上式说明,x,y上的导数是1,而不管x,y的值是什么。这是有意义的,因为增加任一个x,y会增加f的输出,并且该增加的速率将独立于x,y的实际值(与上面的乘法的情况不同)。我们在本章使用的最后一个函数是max操作:

    上式是说,如果该变量比另一个变量大,那么梯度是1,反之为0。直观地说,如果输入是x=4,y=2,则最大值是4,并且函数对y的取值不敏感。也就是说,如果我们将其增加一个很小的量h,函数将保持输出4,因此梯度为零:没有影响。当然,如果我们要改变一个大的量(例如大于2),那么f的值就会改变,但是导数没有告诉我们这些大的变化对函数的输入的影响,它们只是对输入的微小的、无穷小的变化的信息,因为定义就是lim h→0。

    6.3 使用链式法则计算复合函数的梯度

    现在让我们开始考虑组合函数,例如f(x,y,z)=(x+y)z。这个表达式可以直接微分,但是在此使用一种有助于读者直观理解反向传播的方法。注意到这个函数可以分解成两个函数:q= x+y和f= qz。此外,我们知道如何分别计算这两个表达式的导数。f只是q和z的乘法,所以∂f/∂q=z,∂f/∂z=q,q是x和y的加法,所以∂q/∂x=1,∂q/∂y=1。然而,我们并不一定关心中间值q的梯度 - ∂f/∂q的值是没有用的。我们最终感兴趣的是f相对于它的输入x,y,z的梯度。链式法则告诉我们,通过乘法可以地将这些梯度表达式“串”在一起。例如,∂f/∂x=∂f/∂q * ∂q/∂x。在实际操作中,只需要简单地将两个梯度数值相乘:

    # 设置输入值
    x = -2; y = 5; z = -4
    
    # 进行前向传播
    q = x + y    # q 的值是 3 了
    f = q * z     # f 的值是 -12 了
    
    # 进行反向传播:
    # 首先回传到 f = q * z
    dfdz = q   # df/dz = q, 所以关于z的梯度是3
    dfdq = z  # df/dq = z, 所以关于q的梯度是-4
    
    # 现在回传到q = x + y
    dfdx = 1.0 * dfdq # dq/dx = 1. 这里的乘法是就是链式法则
    dfdy = 1.0 * dfdq # dq/dy = 1

    最后我们将梯度保存在变量 [dfdx,dfdy,dfdz]中,他们告诉我们函数 f对于变量 x,y,z 的敏感程度。这是反向传播最简单的例子。接下来,我们希望使用一个更简洁的符号,这样我们就不用继续写 df 部分了。也就是说,例如,代替dfdq ,我们将简单地写为 dq, 并且总是假设梯度是相对于最终输出。

    下图展示了这次计算的线路:

    绿色值表示从输入到输出的正向计算。红色值表示从末端到开始的反向传播,递归地应用链式法规则来计算梯度。梯度可以被认为是通过网络的反向流动。

    6.4 反向传播的直观理解

    反向传播是一个漂亮的局部过程。网络中的每个关口都得到一些输入,并且可以立即计算两件事:1、其输出值 2、其输入相对于输出值的局部梯度。注意,每一个关口都可以完全独立地完成这一操作,而不必知道它们嵌入的完整电路的其他细节。然而,一旦正向传递结束,在反向传播期间,关口将最终获得整个网络的最终输出值在自己的输出值上的梯度。链式法则指出,关口应该将回传的梯度乘以它的局部梯度,从而得到整个网络的输出对该关口的每个输入值的梯度。

    这种基于链式法则额外的乘法(对于每个输入)能够将单个和相对无用的关口转换成复杂网络(如整个神经网络)中的关键节点  。

    让我们通过引用这个例子来了解这是如何工作的。加法关口接收输入[-2, 5 ]和计算输出3。由于该关口是计算加法运算,它的两个输入的局部梯度是1。网络的其余部分计算出最终值为-12。在反向传播时将递归地使用链式法则,算到加法关口(是乘法门的输入)的时候,知道加法门的输出的梯度是-4。如果网络想要输出值更高,那么可以认为它会想要加法关口的输出更小一点(因为负号),而且还有一个4的倍数。继续递归并对梯度使用链式法则,加法关口拿到梯度,然后把这个梯度分别乘到每个输入值的局部梯度(就是让-4乘以x和y的局部梯度,x和y的局部梯度都是1,所以最终都是-4)。可以看到得到了想要的效果:如果x,y减小(它们的梯度为负),那么加法关口的输出值减小,这会让乘法关口的输出值增大。

    因此,反向传播可以看做是关口之间在通过梯度信号相互通信,只要让它们的输入沿着梯度方向变化,无论它们自己的输出值在何种程度上升或降低,都是为了让整个网络的输出值更高。

    6.5 模块:Sigmoid例子

    我们上面介绍的关口相对来说是随意的。任何一种可微函数都可以作为一个关口,我们可以将多个关口组合成一个单独的关口,或者在方便的时候将一个函数分解成多个关口。让我们看看另一个表达这一点的表达式:

    后续会指出,这个表达式描述了一个使用S形激活函数的二维神经元(带有输入X和权值W)。但现在让我们简单地把它看作是从输入w,x到单个数的函数。该函数由多个关口组成。除了上面已经描述的(加法,乘法,取最大值)之外,还有四个:

    其中函数fc,fa 用常量c 对输入值进行了的平移数,用a的常数对输入进行缩放。它们是加法和乘法的特例,但是这里将其看做一元关口,因为确实需要计算常量c,a的梯度。整个计算线路如下::

    math.exp(-1)*-.53 的结果为-0.19497610382086444,约等于-0.2

    math.exp(-1)*-.53*2 的结果为-0.3899522076417289,约等于-0.39

    math.exp(-1)*-.53*3 的结果为-0.5849283114625934,约等于-0.59

    使用S形激活函数的2维神经元的图示。输入是[x0,x1],神经元(可学习)的权重是[W0,W1,W2]。神经元用输入来计算点积,然后其结果被S形函数轻轻地压缩到0到1的范围内。

    在上面的例子中,我们看到了一个长链的函数操作,操作的是在W、x之间的点积的结果。这些操作实现的函数称为sigmoid 函数σ(x)。sigmoid函数求导是可以简化的(使用了在分子上先加后减1的技巧)::

    你看,sigmoid的导数变得非常简单。例如,sigmoid表达式接收输入1并在正向传递期间计算输出0.73。上面的推导表明,局部梯度将简单地(1 - 0.73)* 0.73~=0.2。因此,在实际的应用中将这些操作装进一个单独的关口中将会非常有用。该神经元反向传播的代码实现如下:

    dx:[0.3932238664829637, -0.5898357997244456]

    dw:[-0.19661193324148185, -0.3932238664829637, 0.19661193324148185]

    实现提示:分段反向传播。上述代码表明,为了使反向传播过程更加简洁,把向前传播分成不同的阶段将很有帮助。比如我们创造了中间变量dot,它装着w和x的点乘结果。在反向传播的时,就可以(反向地)计算出装着w和x等的梯度的对应的变量(比如ddot,dx和dw)。

    本节主要是阐明,如何进行反向传播,以及出于便利的考虑,我们要将前向函数中的哪些部分当中关口来看待。知道表达式中哪部分的局部梯度计算比较简洁非常有用,这样他们可以“链”在一起,让代码量更少,效率更高。

    6.6 反向传播实践:分段计算

    再看另外一个例子,假设我们有这样一个函数:

    这个函数实际上是完全无用的,它只是练习反向传播的一个很好的例子。很重要的一点是,如果你要开始对X或Y进行微分,你会得到非常复杂的表达式。然而,事实证明这样做是完全不必要的,因为我们不需要有一个显式函数来评估梯度。我们只需要知道如何计算它。下面是构建这个函数的正向传递代码:

    x = 3 # 例子数值
    y = -4
    
    # 前向传播
    sigy = 1.0 / (1 + math.exp(-y)) # 分子中的sigmoid #(1)
    num = x + sigy # 分子 #(2)
    sigx = 1.0 / (1 + math.exp(-x)) # 分母中的sigmoid #(3)
    xpy = x + y #(4)
    xpysqr = xpy**2 #(5)
    den = sigx + xpysqr # 分母 #(6)
    invden = 1.0 / den #(7)
    f = num * invden # 搞定! #(8)

    唷,在表达式结束时,我们计算了正向传递。注意,我们以这样的方式构造了代码,它包含多个中间变量,每个中间变量都是我们已经知道局部梯度的简单表达式。因此,计算反向传播是很容易的:我们将往回走,对于前进方向上的每个变量(sigy, num, sigx, xpy, xpysqr, den, invden),我们定义一个对应的变量,加一个d打头,用来保存对应变量在网络中的梯度。注意在反向传播的每一小块中都将包含了表达式的局部梯度,然后根据链式法则乘以上游梯度。对于每行代码,我们将指明其对应的是前向传播的哪部分:

    # 回传 f = num * invden
    dnum = invden # 分子的梯度 #(8)
    dinvden = num #(8)
    
    # 回传 invden = 1.0 / den
    dden = (-1.0 / (den**2)) * dinvden #(7)
    
    # 回传 den = sigx + xpysqr
    dsigx = (1) * dden #(6)
    dxpysqr = (1) * dden #(6)
    
    # 回传 xpysqr = xpy**2
    dxpy = (2 * xpy) * dxpysqr #(5)
    
    # 回传 xpy = x + y
    dx = (1) * dxpy #(4)
    dy = (1) * dxpy #(4)
    
    # 回传 sigx = 1.0 / (1 + math.exp(-x))
    dx += ((1 - sigx) * sigx) * dsigx # 注意使用累加符号 += #(3)
    
    # 回传 num = x + sigy
    dx += (1) * dnum #(2)
    dsigy = (1) * dnum #(2)
    
    # 回传 sigy = 1.0 / (1 + math.exp(-y))
    dy += ((1 - sigy) * sigy) * dsigy #(1)
    
    # 完成! 嗷~~

    注意事项:

    缓存前向传递变量。为了计算向后传播,有一些在向前传播中使用的变量是非常有帮助的。在实践中,您需要构造代码,以便缓存这些变量,以便它们在反向传播过程中可用。如果重新计算它们也可以,就是浪费计算资源。

    在不同分支的梯度要相加。正向表达式涉及变量x,y多次,所以当我们执行反向传播时,我们必须小心地使用+=而不是=来积累这些变量上的梯度(否则我们会覆盖它)。这是微积分中的多元链式法则,该法则指出如果变量在线路中分支走向不同的部分,那么梯度在回传的时候,就应该进行累加。

    6.7 反向流中的规律

    有趣的是,在许多情况下,反向传播中的梯度可以得到很直观的解释。例如,神经网络中的三个最常用的关口(加,乘,取最大值),在它们如何在反向传播过程中起作用,都有非常简单的解释。考虑这个回路例子:

    说明:加法操作将梯度相等地分发给它的输入。取最大操作将梯度传递给更大的输入。乘法节点取输入数据,对它们进行交换,然后乘以梯度。

    以上面的图表为例,我们可以看到:

    加法节点总是在其输出上取梯度,并将其直接分配给所有输入,而不管它们在向前传递中的值是多少。这是因为加法运算的局部梯度是简单的1,因此所有输入上的梯度将完全等于输出上的梯度,因为它将乘以X1.0(并且保持不变)。在上面的示例电路中,注意到+节点对其两个输入都相等地和不变地直接使用梯度2.00。

    取最大值节点路由梯度。取最大值节点直接将梯度传递给它的一个输入(在向前传球中具有最高值的输入)。这是因为最大门的局部梯度是最高值的1,对于所有其他值的局部梯度是0。在上面的示例电路中,MAX运算将2的梯度路由到Z变量,因为Z变量的值比W的值高,W上的梯度保持为零。

    乘法节点。输入值互换后就是它的局部梯度,然后根据链式法则乘以输出值的梯度。在上面的例子中,x上的局部梯度是-4.00,结果梯度是-8 00,即-4.00 x 2。

    非直觉效应及其后果。注意,乘法节点中,如果其中一个输入值非常小,另一个则非常大,那么乘法节点将做一些不直观的事情:它将给小的输入分配一个相对大的梯度,并且给大的输入分配一个微小的梯度。注意,在线性分类器中,权重与输入取点积W.T*Xi,这意味着数据的规模对权重的梯度的大小有影响。例如,如果在预处理期间将所有输入数据示例Xi乘以1000,那么权重的梯度将是1000倍大,那么您必须通过降低学习率来弥补。这就是为什么预处理非常重要,有时是微妙的方式!并且对梯度流如何有直观的理解可以帮助您调试其中一些情况。

    6.8 向量化操作的梯度

    上述计算都说的是单个变量,但是所有概念都可以直接扩展到矩阵和向量运算。但是需要注意维度数和转置操作。

    矩阵*矩阵乘法的梯度。可能最棘手的操作是矩阵 与 矩阵的乘法运算(也适用于矩阵和向量,向量和向量相乘):

    # 前向传播
    W = np.random.randn(5, 10)
    X = np.random.randn(10, 3)
    D = W.dot(X)
    
    # 假设我们得到了D的梯度
    dD = np.random.randn(*D.shape) # 和D一样的尺寸
    dW = dD.dot(X.T) #.T就是对矩阵进行转置

    提示:使用维度分析!请注意,您不需要记住dW和dX的表达式,因为它们很容易基于维度重新推导。例如,我们知道权重dW上的梯度必须在计算之后与W大小相同,并且它必须依赖于X和DD的矩阵乘法(如当X、W都是单数而不是矩阵)的情况下。总有一种方法可以做到这一点,这样维度就可以计算出来。例如,x是大小[ 10×3 ]和dD的大小[ 5×3 ],所以如果我们想要dW和W具有形状[5×10 ],那么实现这一点的唯一方法就是dD.dot(X.T),如上文所示。

    使用小的、明确的例子。有些人可能会发现很难在一开始就推导出一些向量化表达式的梯度更新。我们的建议是显式地写出一个最小向量化的例子,在纸上导出梯度,然后对其一般化,得到一个高效的向量化操作形式。

    Erik Learned Miller还写了一篇较长的有关矩阵/向量导数的相关文档,你可能会发现它是有用的。在这里找到它

    6.9小结

    • 我们就梯度的含义做了直观的说明,它们是如何在网络中回流的,知道了它们是如何与网络的不同部分通信并控制其升高或者降低,并使得最终输出值更高的。
    • 我们讨论了分段计算对于反向传播的实际实现的重要性。你总是想把你的函数分解成 可以很容易地得到局部梯度 的模块,然后用链规则将它们链接起来。重要的是,不需要把这些表达式写在纸上然后演算它的完整求导公式,因为实际上并不需要关于输入变量的梯度的数学公式。只需要将表达式分成不同的可以求导的模块(模块可以是矩阵向量的乘法操作,或者取最大值操作,或者加法操作等),然后在反向传播中一步一步地计算梯度。

    在下一节中,我们将开始定义神经网络,并且反向传播将允许我们有效地计算神经网络连接上各节点关于损失函数的的梯度。换言之,我们现在已经准备好训练神经网络,这个概念中最困难的部分已经过去了!向前走一小步就是卷积神经网络。

     

    斯坦福大学计算机视图课程,青星大学 翻译整理

    1、数据驱动的图像分类方法

    2、最近邻分类器

    3、k - 最近邻分类器及使用验证集取得超参数

    4、线性分类: SVM, Softmax

    5、优化方法:随机梯度下降法

    6、反向传播

    7、神经网络一: 建立网络架构

    8、神经网络二:设置数据和损失

    9、神经网络 三:学习与评价

    10、神经网络案例学习

    11、卷积神经网络:结构、卷积/汇集层

    12、理解与可视化卷积神经网络

    13、传承学习与卷积神经网络调谐

     

    原文地址 http://cs231n.github.io/

     

     

    展开全文
  • 深度学习-反向传播

    2021-02-03 17:40:15
    文章目录反向传播(BP)梯度梯度下降算法局部最优解 反向传播(BP) 上一篇文章讲了感应器,将把这些单独的单元按照一定的规则相互连接在一起形成神经网络,从而获得了强大的学习能力。称之为反向传播算法 反向传播...
  • 弹性反向传播算法

    2020-05-10 16:19:17
    Rprop( resilient backpropagation ):弹性反向传播算法。分为两步,一是调整步长,而是根据步长调整权重。+表示权重会回退,-表示不会回退。 公式中字符的表示: 步长调整规则:(以下算法通用) Rprop+ Rprop-...
  • 反向传播求梯度的矩阵推导 反向传播(back propagation)是深度学习的灵魂。以前只是大致知道是利用了链式规则求导,但是公式太负责不好看懂。而网上的各种讲解对于矩阵求导的定义、神经网络和神经元的定义和各种...
  • 在求出前向传播和反向传播前要先确定参数和输入输出符号的表达形式 最普通的DNN就可以看做是一个多层感知机MLP,感知机的输出其实就是对输入的加权求和:,再经过一个非线性激活函数 首先来定义权值矩阵W,按照下图...
  • BP反向传播算法

    千次阅读 2014-01-20 12:12:31
    反向传播BP模型 学习是神经网络一种最重要也最令人注目的特点。在神经网络的发展进程中,学习算法的研究有着十分重要的地位。目前,人们所提出的神经网络模型都是和学习算 法相应的。所以,有时人们并不去...
  • 03—BP算法推导3.1 公式1如下图所示,有个精灵...可以看出在输出层 L, 误差项的定义表达为如下,第一个公式 上式是根据链式规则可以推导得出,成本函数的改变首先是有第L层第j个神经元的输出项影响的,然后第 j 个...
  • 专栏中《零神经网络实战》系列持续更新介绍神经元怎么工作,最后使用python从0到1不调用任何依赖神经网络框架(不使用tensorflow等框架)来实现神经网络,梯度下降、反向传播、卷积神经网络CNN、循环神经网络RNN。...
  • 反向传播神经网络 反向传播 问题陈述:这节的核心问题是,给定函数f(x),其中x是输入数据的向量,需要计算函数f关于x的梯度,也就是∇f(x)。 反向传播是利用链式法则递归计算表达式的梯度的方法。理解反向传播过程...
  • 往期回顾 在上一篇文章中,我们已经掌握了机器学习的基本套路,对模型...我们还将介绍这种网络的训练算法:反向传播算法。最后,我们依然用代码实现一个神经网络。如果您能坚持到本文的结尾,将会看到我们用自己实现...
  • Back Propagation - Wikipedia 反向传播(backpropagation)是一种用于人工神经网络的方法,用于计算在计算网络中...反向传播是将delta规则推广到多层前馈网络,通过使用链规则迭代计算每个层的梯度来实现。它与...
  • 计算机视觉05:神经网络与误差反向传播算法 文章目录计算机视觉05:神经网络与误差反向传播算法1. 神经网络1. 前馈神经网络(无反馈)2. Delta学习规则3. 梯度下降4. 权重如何改变5. 示例6.其他梯度下降7.典型的机器...
  • 问题:假设有一个全连接神经网络的结构如图1所示,我们现在来推导其中的反向传播算法。一、初始:设:C:网络的误差:第L层上第j个节点与第L-1层上第k个节点的权值:第L层上第j个节点的偏置值:第L层第j个节点的带权...
  • 我们还将介绍这种网络的训练算法:反向传播算法。最后,我们依然用代码实现一个神经网络。如果您能坚持到本文的结尾,将会看到我们用自己实现的神经网络去识别手写数字。现在请做好准备,您即将双手触及到深度学习的...
  • CNN反向传播公式推导

    千次阅读 2016-12-06 19:33:05
    CNN架构的连接比权值要多很多,这实际上就隐含着实现了某种形式的规则化。这种特别的网络假定了我们希望通过数据驱动的方式学习到一些滤波器,作为提取输入的特征的一种方法。  本文中,我们先对训练全连接网络的...

空空如也

空空如也

1 2 3 4 5 ... 16
收藏数 306
精华内容 122
关键字:

反向传播规则