backprop()得到全网梯度设置gradient和epsilon。流程回到computeGradientAndScore()方法中。下面是计算得分:score = ((IOutputLayer) getOutputLayer()).computeScore(calcL1(true), calcL2(true), true)。BaseOutputLayer中的computeScore()方法调用了preOutput2d()。这个方法在计算梯度时调用过了。如果当时保存了这个结果,就不用再计算了。调用损失函数computeScore()计算分数。算出来的分数除以批次50就是真正的分数。
computeGradientAndScore()方法结束,流程回到BaseOptimizergradientAndScore()方法中。这里有个地方可以优化一下,不需要下面条语句Pair<Gradient, Double> pair = model.gradientAndScore(),在model.computeGradientAndScore()语句就返回Pair<Gradient, Double> pair更好些。下面的语句updateGradientAccordingToParams(pair.getFirst(), model, model.batchSize())是对梯度进行整理。
BaseMultiLayerUpdater中的public void update(Gradient gradient, int iteration, int batchSize)方法首先将梯度按层、W、b的结构保存到layerGradients中。如果一开始就用这种结构更好些。结构层次清晰,而且也不用再整理。调用ub.update(iteration)更新梯度。本例中使用的是NESTEROVS,会调用NesterovsUpdater中的applyUpdater方法更新梯度。得到的梯度再除以批次50,getFlattenedGradientsView().divi(batchSize)。由于梯度都很小,除以50后,非常小,几乎等于0了,但其实不是0。
流程一路返回到StochasticGradientDescent.optimize()方法中,Pair<Gradient, Double> pair保存的是全网梯度和得分。stepFunction.step(params, gradient.gradient())语句更新全网参数。更新时比较简单,全网参数行向量减去全网梯度行向量。这就是扁平化会提高效率的所在。model.setParams(params)语句将更新过的参数设置到网络中。然后评估是否可以终止和迭代数加1。流程回到MultiLayerNetwork.fit()方法。因为在循环中,会再获取下50行数据,继续训练网络。
网络测试
MLPClassifierLinear中,从Evaluation eval = new Evaluation(numOutputs)语句开始往后,都是网络测试部分。加载测试数据时,没有使用异步加载。INDArray predicted = model.output(features, false)语句是执行测试数据,并返回测试结果。MultiLayerNetwork的output()方法中调用INDArray ret = silentOutput(input, train).detach()语句进行测试,流程会调到feedForwardToLayer(layers.length - 1, train)方法。这里可以看到和训练时调用feedForwardToLayer(layers.length - 2, true)语句时,方法的第一个参数是不同的。训练时没有包括输出层,测试时包括了。当然第二个参数也不同。
梯度、误差计算过程
输出层激活函数Softmax,损失函数是NegativeLogLikelihood(MCXENT)梯度、误差计算过程:
1、误差矩阵=激活函数Softmax的输出矩阵减去标签矩阵:delta = output.subi(labels)
2、梯度矩阵=本层的输入矩阵转置乘以误差矩阵:Nd4j.gemm(input, delta, weightGradView, true, false, 1.0, 0.0);
3、上层误差矩阵=本层权重矩阵乘以delta矩阵的转置的结果再转置:INDArray epsilonNext = params.get(DefaultParamInitializer.WEIGHT_KEY).mmul(delta.transpose()).transpose();
隐藏层激活函数ReLU梯度、误差计算过程:
1、误差矩阵=激活函数ReLU的输出矩阵求导,然后乘以误差矩阵:
INDArray delta = Nd4j.getExecutioner().execAndReturn(new RectifedLinear(in).derivative());
delta.muli(epsilon);
2、梯度矩阵=本层的输入矩阵转置乘以误差矩阵:Nd4j.gemm(input, delta, weightGrad, true, false, 1.0, 0.0);
3、上层误差矩阵=本层权重矩阵乘以delta矩阵的转置的结果再转置:INDArray epsilonNext = params.get(DefaultParamInitializer.WEIGHT_KEY).mmul(delta.transpose()).transpose();
输出层和隐藏层计算过程,误差矩阵的计算方式不同,其它两步类似。