DL4J源码阅读(三):信号前传

    MultiLayerNetworkfit方法,使用DataSet next = iter.next()获取一个批次的数据。本例中batchSize = 50,所以一次取到50组数据,其中包括输入特征和标签(理想值)。setInput(next.getFeatureMatrix())setLabels(next.getLabels())语句分别设置输入特征和标签。调用solver.optimize()语句进行网络定型。在网络初始化阶段model.init()中调用MultiLayerNetworkpublic void init(INDArray parameters, boolean cloneParametersArray)方法时,新建了一个Solver对象,并赋给了solver变量。

    在Solver类中有一个方法public void initOptimizer(),是为Solver的核心变量optimizer赋值。这个方法很重要,要想正常使用Solver的对象,必须调用。如果将这个方法放到新建Solver的对象的方法中调用更好些。而不是交给应用自己调用。现在的情况是为了防止没有被调用过,反而到处调用。放到新建Solver的对象的方法中调用,每次新建都会自动调用,就不会有这个烦恼。

    本例中optimizer的具体类型是StochasticGradientDescent(随机梯度下降),当执行optimizer.optimize()时,会进入到StochasticGradientDescentoptimize()方法中。此方法开始是一个循环。

    代码读到这里可以发现有三处循环,分别对应数据处理时的三种粒度。StochasticGradientDescentoptimize()方法中的for (int i = 0; i < conf.getNumIterations(); i++)是一个批次的数据循环训练的次数。是MLPClassifierLinearmainiterations(1)方法设置的,本例中是1次。MultiLayerNetworkfit中的while (iter.hasNext())是循环每个批次,本例中有1000个输入特征,每批次50,则共循环处理20次。MLPClassifierLinearmain中的for (int n = 0; n < nEpochs; n++)是对整个输入特征的循环处理次数,本例中是nEpochs = 30。控制台共打印600次训练结果。程序设置监听器ScoreIterationListener是每迭代10次,打印一次结果。则本例中共打印60次结果。

    BaseOptimizerStochasticGradientDescent的父类)中的public Pair<Gradient, Double> gradientAndScore()方法返回梯度和得分。本例中的得分是误差,越小越好。此方法调用model.computeGradientAndScore()。由于model的类型是MultiLayerNetwork,所以流程又回到了MultiLayerNetwork代码中。虽然方法叫computeGradientAndScore,但不是在这个方法中计算的梯度和得分,各位先不要激动(我承认,当我看到这个方法后,以为就要揭开DL4J计算梯度的谜底了,激动了一小下)。此方法中的List<INDArray> activations = feedForwardToLayer(layers.length - 2, true)语句,顾名思义,是处理信号前传的。

 

1.1.1 信号前传

    computeGradientAndScore方法调用feedForwardToLayer方法时,传递参数layerNumlayers.length - 2。这个表示信号前传的阶段,不包括最后一层输出层。feedForwardToLayer方法中首先获取输入赋给currInput变量。feedForwardToLayer方法的注释已经说得很明白,本方法就是使用输入计算指定层的激活,放到输出列表中。输出列表包括原始输入。所以list.get(0)是原始输入。变量activations就是这个输出列表。

    调用activationFromPrevLayer(i, currInput, train)计算每层的输出。方法名中有PrevLayer,注释中也说from previous layer。其实指的是来自前一层的输入。使用layers[curr].activate(input, training)语句调用神经元层的激活方法。本例中神经元层的具体类型是DenseLayerDL4J有两个叫DenseLayer的类,这个DenseLayerorg.deeplearning4j.nn.layers.feedforward.dense包下的。

    public INDArray preOutput(boolean training)这个方法中,利用矩阵相乘,一次计算出50组输入与权重的乘积和。从这里可以看出,其实本例中的优化算法不是随机梯度下降,而是小批量梯度下降。不过网上一些资料说小批量梯度下降有时候也叫随机梯度下降。这应该暗示着纯正的随机梯度下降用的非常少。小批量梯度下降有个好处是可以利用矩阵相乘,一次计算出多个输出。就像DL4J所做的这样。取出偏移矩阵INDArray b = getParam(DefaultParamInitializer.BIAS_KEY),取出权重矩阵INDArray W = getParam(DefaultParamInitializer.WEIGHT_KEY)。输入数据矩阵乘以权重矩阵,再加上偏移矩阵INDArray ret = input.mmul(W).addiRowVector(b)

    input.mmul(W)是输入数据矩阵乘以权重矩阵。本例中输入数据是50*2的矩阵,权重矩阵是2*20的矩阵,相乘后得到50*20的矩阵。BaseNDArray类的public INDArray mmul(INDArray other)方法中,先构造了一个50*20的矩阵保存相乘的结果。又调用了一层mmul()方法,做了一些判断后,调用了Nd4j.getBlasWrapper().level3().gemm()方法进行矩阵相乘。

    DL4J中矩阵相关操作是使用openBlas实现的。BLASBasic Linear Algebra Subprograms (基本线性代数子程序)的首字母缩写,主要用来做基础的矩阵计算,或者是向量计算。它分为三级:1级,主要做向量与向量间的dot或乘加运算,对应元素的计算;2级,主要做矩阵和向量;3级,主要是矩阵和矩阵的计算,最典型的是A矩阵*B矩阵,得到一个C矩阵。本例中是矩阵乘矩阵,所以是level3()openblas类中的cblas_sgemm()最终完成矩阵相乘。这是个本地方法,由于矩阵相关操作都比较耗时,所以用本地方法效率高些。

    addiRowVector(b)是加上偏移。程序会走到BaseNDArray中的protected INDArray doRowWise(final INDArray rowVector, final char operation)方法中,新建一个ScalarAdd对象ScalarAdd op = new ScalarAdd(this, rowVector, this, this.length(), 0.0),然后交给Nd4j执行相加Nd4j.getExecutioner().exec(op)。最后会调到Nd4jCpuexecScalarFloat方法。这也是个本地方法。

    至此,preOutput方法走完,程序走到BaseLayerpublic INDArray activate(boolean training)方法第二句INDArray ret = layerConf().getActivationFn().getActivation(z, training)。这句是调用本层的激活函数对数据进行处理。本例中的激活函数是ActivationReLU。这个激活函数的处理逻辑是大于0的数保持不变,小于0的数变为0

    computeGradientAndScore方法调用feedForwardToLayer方法得到的activations是一个List,里面是每层的输出数据。但是我看computeGradientAndScore方法后面的逻辑,只使用了最后一层的输出数据:INDArray actSecondLastLayer = activations.get(activations.size() - 1),其它的没有使用。当然,本例中activations只有一层的输出数据。但是别的应用如果有多个隐藏层,也只是最后一个隐藏层的数据被使用。既然这样,activations里应该只保存最后一个隐藏层的输出数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值