当前位置: 首页 > 科技观察

没有反向传播的深度学习:DeepMind的合成梯度

时间:2023-03-18 18:03:24 科技观察

在这篇博文中,我们将从DeepMind最近的一篇论文开始(从头开始)——使用合成梯度的解耦神经接口。1.合成梯度概述通常,神经网络将其预测与数据集进行比较,以决定如何更新其权重。然后使用反向传播来确定应如何移动每个权重以使预测更准确。然而,对于合成梯度,每一层都会对数据进行“最佳预测”,然后根据该预测更新权重。这种“最佳预测”称为合成梯度。这些数据仅用于帮助更新每一层的“预测器”或合成梯度生成器。这允许(在大多数情况下)各个层独立学习,从而提高训练速度。上图(在论文中)非常直观地解释了正在发生的事情(从左到右)。带圆角的正方形是图层,菱形对象是(我称之为)合成梯度生成器。我们来看看一个常见的神经网络层是如何更新的。2.使用合成梯度我们不关注合成梯度是如何创建的,而只关注如何使用它们。最左边的框显示了如何更新神经网络的第一层。第一层向前传播到合成梯度生成器(Mi+1),然后返回梯度。使用此梯度而不是实际梯度(这需要完整的前向和反向传播来计算)。然后,权重正常更新,这个合成梯度被认为是真正的梯度值。如果您需要知道如何使用梯度更新权重,请查看11行Python中的神经网络(http://iamtrask.github.io/2015/07/12/basic-python-network/)或遵循梯度ADescent的后续帖子。简而言之,SyntheticGradients就像正常的梯度一样使用,并且出于某种神奇的原因,它们似乎是准确的(没有使用数据)!喜欢魔法?让我们看看它们是否是如何构建的。3.生成合成梯度好吧,这部分真的很简洁,坦率地说,它的工作原理非常简洁。如何为神经网络生成合成梯度?当然使用另一个网络!合成梯度生成器只不过是一个训练有素的神经网络,它获取特定层的输出并预测该层可能出现的梯度。1.GeoffreyHinton相关工作其实这让我想起了GeoffreyHinton几年前做过的一些工作。虽然我找不到参考资料,但他确实做了一些工作,证明您可以通过随机生成的矩阵进行反向传播,并且仍然可以完成学习。此外,他表明它具有正则化效果。这确实是一些有趣的工作。好的,回到合成渐变。所以,现在我们知道合成梯度是由另一个神经网络训练的,它根据某一层的输出给出相应的梯度预测。该论文还表明,任何其他相关信息都可以用作合成梯度生成器网络的输入,但在该论文中,似乎只有该层的输出被用于普通的前馈网络。此外,该论文甚至指出可以使用单个线性层作为合成梯度生成器。太棒了,我们要试试看。2.我们如何学习产生合成梯度的网络?那么问题来了,我们如何学习一个产生合成梯度的神经网络呢?事实证明,当我们进行所有的前向和反向传播时,我们实际上得到了“正确”的梯度。我们可以将它与我们的“合成”梯度进行比较,就像我们将神经网络的输出与数据集进行比较一样。因此,我们可以通过假设“真实梯度”来自虚拟数据集来训练我们的合成神经网络。所以我们可以像往常一样训练他们。惊人的!3.如果我们的合成梯度网络需要反馈,那有什么意义呢?好问题!该技术的全部意义在于允许单个神经网络进行训练,而无需等待彼此完成前向和反向传播。如果我们的合成梯度网络需要等待完整的前向/后向传递,那么我们又回到原点并且仍然需要更多的计算(这更糟!)。为了找出答案,让我们回顾一下论文。请注意左边的第二部分。看看梯度(Mi+2)是如何通过(fi+1)反向传播到M(i+1)的?如您所见,每个合成梯度生成器实际上只使用合成梯度进行训练。因此,实际上只有最后一层是在数据上训练的。所有其他层,包括合成梯度生成器网络,都是在合成梯度上训练的。因此,网络可以通过简单地等待来自下一层的合成梯度来训练每一层。惊人的!四、Baseline神经网络编程时间!为了开始(因此我们有一个更简单的参考框架),我将使用经过反向传播训练的普通神经网络,其风格与11行Python中的神经网络类似。(所以,如果你不明白,就去读那篇文章再回来)。但是,我将添加一个额外的层,但这不应该妨碍理解。我只是在想,既然我们正在减少依赖性,那么更多的层可能会更好。对于我们的训练数据集,我将使用二进制加法来生成合成数据集。因此神经网络将取两个随机二进制数并预测它们的和(也是一个二进制数)。好消息是,这使我们可以根据需要灵活地增加任务的维度(难度)。下面是生成数据集的代码。这是在这个数据集上训练普通神经网络的代码现在,在这一点上,我觉得真的有必要做一些我在研究中几乎从未做过的事情,添加一些面向对象的结构。通常这会稍微模糊网络并使其更难阅读(在高层次上)正在发生的事情(与仅阅读python脚本相比)。然而,由于这篇文章是关于“解耦神经接口”及其优点的,实际上很难解释这些接口是否有理由进行解耦。因此,为了更容易学习,我首先将上述网络转换为完全相同的网络,但使用了稍后将转换为DNI的“Layer”类对象。让我们看一下这个Layer对象。classLayer(object):def__init__(self,input_dim,output_dim,nonlin,nonlin_deriv):self.weights=(np.random.randn(input_dim,output_dim)*0.2)-0.1self.nonlin=nonlinself.nonlin_deriv=nonlin_derivdefforward(self,输入):self.input=inputselfself.output=self.nonlin(self.input.dot(self.weights))returnself.outputdefbackward(self,output_delta):self.weight_output_delta=output_delta*self.nonlin_deriv(self.output)returnsself.weight_output_delta.dot(self.weights.T)defupdate(self,alpha=0.1):self.weights-=self.input.T.dot(self.weight_ou在这个层类中,我们有几个类变量。权重是我们用于从输入到输出的线性变换的矩阵(就像一个普通的线性层),如果需要,我们还可以包括一个非线性输出函数,它在我们的输出网络中放置非线性。如果我们不想要非线性,我们可以简单地将此值设置为lambdax:x。在我们的例子中,我们将传递给“sigmoid”函数。我们传递的第二个函数是nonlin_deriv,一个特殊的导数函数。该函数从非线性中获取输出并将其转换为导数。对于sigmoid,这就像(out*(1-out))一样简单,其中“out”是sigmoid的输出值。这一特定特征存在于几乎所有常见的神经网络非线性中。现在,让我们看看这个类中的各种方法。forward就像它的名字所暗示的那样。它通过层向前传播,首先通过线性变换,然后通过非线性函数。backward接受一个output_delta参数,它表示在反向传播过程中从下一层返回的“真实梯度”(不是合成梯度),然后我们用它来计算self.weight_output_delta,它是我们权重输出的导数(仅在非线性)。***,它将错误反向传播并将其向上传递到上一层,然后返回。更新可能是最简单的方法。它只是采用权重输出中的导数并使用它来进行权重更新。如果您对这些步骤有任何疑问,请再次查看11行Python中的神经网络并返回。如果你都明白了,让我们来看看我们在训练中的图层对象。layer_1=层(input_dim,layer_1_dim,sigmoid,sigmoid_out2deriv)layer_2=层(layer_1_dim,layer_2_dim,sigmoid,sigmoid_out2deriv)layer_3=层(layer_2_dim,output_dim,sigmoid,sigmoid_out2deriv)foriterinrange(迭代):error=0(forbatch(leniinrange))/batch_size)):batch_x=x[(batch_i*batch_size):(batch_i+1)*batch_size]batch_y=y[(batch_i*batch_size):(batch_i+1)*batch_size]layer_1layer_1_out=layer_1.forward(batch_x)layer_2layer_2_out=layer_2.forward(layer_1_out)layer_3layer_3_out=layer_3.forward(layer_2_out)layer_3_delta=layer_3_out-batch_ylayer_2_delta=layer_3.backward(layer_3_delta)layer_1_delta=layer_2.backward(layer_2_delta)layer_1.backward(layer_1_delta)layer_1.update()layer_2()layer_3.update()给定数据集x和y,这就是我们使用新层对象的方式。如果将它与之前的脚本进行比较,它几乎是相同的。我只是更改了调用的神经网络的版本。所以,我们所做的就是将之前神经网络的脚本代码拆分到类中的不同函数中。接下来,我们在实践中看看这一层。如果将之前的网络和这个网络都插入到Jupyter笔记本中,您会看到随机种子使这些网络具有完全相同的值。似乎Trinket.io可能没有完美地随机选择种子点来使这些网络具有几乎相同的值。但是,我向您保证,网络是一样的。如果您认为此网络没有意义,请不要进行下一步。在继续之前,请确保您熟悉这个抽象网络的工作原理,因为下面会变得更加复杂。5.整合合成渐变好的,现在我们要使用一个与上面非常相似的界面,唯一的区别是我们将我们学到的关于合成渐变的知识整合到一个Layer对象中(并将其重命名为DNI).首先,我们将向您展示这些课程,然后我将对其进行解释。一探究竟吧!classDNI(object):def__init__(self,input_dim,output_dim,nonlin,nonlin_deriv,alpha=0.1):#sameasbeforeself.weights=(np.random.randn(input_dim,output_dim)*0.2)-0.1self.nonlin=nonlinself.nonlin_deriv=nonlin_deriv#newstuffself.weights_synthetic_grads=(np.random.randn(output_dim,output_dim)*0.2)-0.1self.alpha=alpha#usedtobejust"forward",但现在我们更新了使用合成梯度的前向传递:)defforward_and_synthetic_update(self,input):#缓存输入self.input=input#forwardpropagateselfself.output=self.nonlin(self.input.dot(self.weights))#generatesyntheticgradientviasimplelineartransformationselfself.synthetic_gradient=self.output.dot(self.weights_synthetic_grads)#updateourregularweightsusingsyntheticgradientself.weight_synthetic_gradient=self.synthetic_gradient*self.nonlin_deriv(self.output)self.weights+=self.input.T.dot(self.weight_synthetic_gradient)*self.alpha#returnbackpropagatedsyntheticgradient(thisisliketheoutputof"backprop”来自层类的方法)#alsoreturnforwardpropagatedoutput(感觉很奇怪......)返回self.weight_synthetic_gradient.dot(self.weights.T),self.output#这就像之前的“更新”方法......除了操作合成权重defupdate_synthetic_weights(self,true_gradient):selfself.synthetic_gradient_delta=self。synthetic_gradient-true_gradientself.weights_synthetic_grads+=self.output。所以,第一个大的变化是我们有一些新的类变量。唯一真正重要的是self.weights_synthetic_grads变量,这是我们的合成生成器神经网络(只是一个线性层,实际上,,,只是一个矩阵)前向和合成更新:前向方法变成了forward_and_synthetic_update。还记得我们不需要网络的任何其他部分来更新我们的权重吗?那是神奇的地方。首先,前向传播发生在正常(第22行)。然后,我们通过非线性传递输出来生成合成梯度。这部分可能是更复杂的network,但我们决定保持简单,只使用一个简单的线性层来生成我们的合成渐变。当我们获得梯度时,我们继续并更新我们的正常权重(第28和29行)。最后,我们从权重的输出反向传播我们的合成梯度到它的输入,这样我们就可以把它传递给上一层。更新合成梯度:好的,所以我们在“前向”方法的最后返回了梯度。这是我们从下一层接受到update_synthetic_gradient方法的内容。所以,如果我们现在在第二层,第三层从它的forward_and_synthetic_update方法返回一个梯度,它被输入到第二层的update_synthetic_weights。然后,我们只需要像更新普通神经网络一样更新我们的合成梯度。我们将输入传递给合成梯度层(self.output),然后对输出增量执行平均外积运算(矩阵转置→矩阵乘法)。这与在普通神经网络中学习没有什么不同,我们只是在数据层中获得一些特殊的输入和输出。让我们在实践中看看。训练有点不同。更大的数据批大小和更小的alpha值似乎表现更好,但从积极的方面来看,训练中只完成了一半的迭代次数!(这可能很容易调整,但仍然..不错)。一些结论。培训似乎搞砸了(不跌倒)。我不知道引擎盖里发生了什么,但当它收敛时,它一定很快。原文:https://iamtrask.github.io/2017/03/21/synthetic-gradients/【本文为专栏机器心原创翻译,微信公众号“机器心(id:almosthuman2014)”】戳这里,阅读更多该作者的好文