[关闭]
@natsumi 2017-04-17T10:15:36.000000Z 字数 4954 阅读 815

BP算法在encog中的实现

机器学习


http://blog.csdn.net/yt7589/article/details/52277407

反向传播过程的梯度计算

符号:
第0层表示输出层,第l-1层表示输入层。
j表示0层神经元下标,i表示1层神经元下标,h表示第2层神经元下标……
表示输出节点j对应的理想输出值。
E表示所有输出节点的误差之和,即

输出层

后面三项偏导分别可以求出来,若节点j的激活函数为sigmod,则计算结果如下。

第1层

其中,可以由第一层的计算得到。

由此我们可以总结出误差对权值偏导数的求解方法。除了第0层(输出层)外,层l+1的节点x到层l的节点y的权值对应的偏导为:
系数 × 误差对l-1层输出加权和 × 节点y激活函数导数 × 节点x的输出

encog中的实现

  1. //StochasticGradientDescent.java
  2. private void processLevel(final int currentLevel) {
  3. final int fromLayerIndex = flat.getLayerIndex()[currentLevel + 1];
  4. final int toLayerIndex = flat.getLayerIndex()[currentLevel];
  5. final int fromLayerSize = flat.getLayerCounts()[currentLevel + 1];
  6. final int toLayerSize = flat.getLayerFeedCounts()[currentLevel];
  7. double dropoutRate = 0;
  8. final int index = this.flat.getWeightIndex()[currentLevel];
  9. final ActivationFunction activation = this.flat
  10. .getActivationFunctions()[currentLevel];
  11. // handle weights
  12. // array references are made method local to avoid one indirection
  13. final double[] layerDelta = this.layerDelta;
  14. final double[] weights = this.flat.getWeights();
  15. final double[] gradients = this.gradients;
  16. final double[] layerOutput = this.flat.getLayerOutput();
  17. final double[] layerSums = this.flat.getLayerSums();
  18. int yi = fromLayerIndex;
  19. //...循环计算梯度
  20. }

方法中先是初始化了一些局部变量。layerOutput[i]是节点i的输出(激活后的值),对应上面公式中的y。layerSums[i]是节点i获得的所有输入之和,也就是激活之前的值,对应上面公式中的z。另外一个值得注意的数组是layerDelta,也就是这个累的成员变量this.layerDelta,这个数组大小是和网络节点数相同的,所以可以理解为每个节点对应这个数组中的一个double数值。

给这个数组赋值的代码第8行和第23行。在encog给出的异或的例子(org.encog.Test)中,没有进入17行的if语句,先忽略23行。

  1. public void process(final MLDataPair pair) {
  2. errorCalculation = new ErrorCalculation();
  3. double[] actual = new double[this.flat.getOutputCount()];
  4. flat.compute(pair.getInputArray(), actual);
  5. errorCalculation.updateError(actual, pair.getIdealArray(), pair.getSignificance());
  6. // Calculate error for the output layer.
  7. this.errorFunction.calculateError(
  8. flat.getActivationFunctions()[0], this.flat.getLayerSums(),this.flat.getLayerOutput(),
  9. pair.getIdeal().getData(), actual, this.layerDelta, 0,
  10. pair.getSignificance());
  11. // Apply regularization, if requested.
  12. if( this.l1> Encog.DEFAULT_DOUBLE_EQUAL
  13. || this.l2>Encog.DEFAULT_DOUBLE_EQUAL ) {
  14. double[] lp = new double[2];
  15. calculateRegularizationPenalty(lp);
  16. for(int i=0;i<actual.length;i++) {
  17. double p = (lp[0]*this.l1) + (lp[1]*this.l2);
  18. this.layerDelta[i]+=p;
  19. }
  20. }
  21. // Propagate backwards (chain rule from calculus).
  22. for (int i = this.flat.getBeginTraining(); i < this.flat
  23. .getEndTraining(); i++) {
  24. processLevel(i);
  25. }
  26. }

第8行的errorCalculation是这样初始化的。

  1. private ErrorFunction errorFunction = new CrossEntropyErrorFunction();

errorCalculation.updateError(...)调用的是CrossEntropyErrorFunction类的updateError方法,如下。其实就是计算了输出层节点输出和理想值之间的误差(乘了一个系数),写入了layerDelta数组总输出节点对应的位置。其他位置数值的含义我们结合下面循环计算梯度的过程来看。个人认为可以理解为梯度计算中的一些中间量。

  1. //CrossEntropyErrorFunction.java
  2. @Override
  3. public void calculateError(ActivationFunction af, double[] b, double[] a,
  4. double[] ideal, double[] actual, double[] error, double derivShift,
  5. double significance) {
  6. for(int i=0;i<actual.length;i++) {
  7. error[i] = (ideal[i] - actual[i]) *significance;
  8. }
  9. }

重点来了~接下来看processLevel中的循环计算部分

这个方法主要在计算currentLevel+1的节点xicurrentLevel的节点yi之间权值weights[wi]对应的偏导数,也就是梯度gradients[wi]

内层循环是currentLevel+1层中每个节点xi的遍历,外层循环是currentLevel的每个节点yi的遍历。(代码中的y只是计数用的,yi表示节点在网络节点数组中的下标)

  1. for (int y = 0; y < fromLayerSize; y++) {
  2. final double output = layerOutput[yi];
  3. double sum = 0;
  4. int wi = index + y;
  5. final int loopEnd = toLayerIndex+toLayerSize;
  6. for (int xi = toLayerIndex; xi < loopEnd; xi++, wi += fromLayerSize) {
  7. gradients[wi] += output * layerDelta[xi];
  8. sum += weights[wi] * layerDelta[xi];
  9. }
  10. layerDelta[yi] = sum
  11. * (activation.derivativeFunction(layerSums[yi], layerOutput[yi]));
  12. yi++;
  13. }

processLevel是从输出层到输入层被调用的,所以计算输出层梯度的时候layerDelta只有输出层节点赋了值(理想值和实际值的差×系数,简称误差)。layerDelta直接和output相乘得到第一层到输出层的所有权值的梯度。layerDelta对应下式第一项,output是currentLayer+1层节点的输出,对应下式第三项,第二项我也不知道去哪儿了。。。。。。

sum可以理解为下式中的求和部分,出了内层循环后计算sum乘以激活函数的导数,也就是计算出了下式的第一和第二部分的乘积,赋值给节点i(公式中的i在节点数组中的下标是yi)对应的layerDelta[yi],在调用processLevel(currentLayer+1)过程中 做准备的,而layerDelta[yi]是用到的。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注