100行Python代码,轻松搞定神经网络
作者:媒体转发 时间:2019-05-05 21:20
编译:周家乐、钱天培
用tensorflow,pytorch这类深度学习库来写一个神经网络早就不稀奇了。
可是,你知道怎么用python和numpy来优雅地搭一个神经网络嘛?
现如今,有多种深度学习框架可供选择,他们带有自动微分、基于图的优化计算和硬件加速等各种重要特性。对人们而言,似乎享受这些重要特性带来的便利已经是理所当然的事儿了。但其实,瞧一瞧隐藏在这些特性下的东西,能更好的帮助你理解这些网络究竟是如何工作的。
所以今天,文摘菌就来手把手教大家搭一个神经网络。原料就是简单的python和numpy代码!
文章中的所有代码可以都在这儿获取。
https://colab.research.google.com/github/eisenjulian/slides/blob/master/NN_from_scratch/notebook.ipynb
符号说明
在计算反向传播时, 我们可以选择使用函数符号、变量符号去记录求导过程。它们分别对应了计算图中的边和节点来表示它们。
给定R^n→R和x∈R^n, 那么梯度是由偏导∂f/∂j(x)组成的n维行向量
如果f:R^n→R^m 和x∈R^n,那么 Jacobian矩阵是下列函数组成的一个m×n的矩阵。

对于给定的函数f和向量a和b如果a=f(b)那么我们用∂a/∂b 表示Jacobian矩阵,当a是实数时则表示梯度
链式法则
给定三个分属于不同向量空间的向量a∈A及c∈C和两个可微函数f:A→B及g:B→C使得f(a)=b和g(b)=c,我们能得到复合函数的Jacobian矩阵是函数f和g的jacobian矩阵的乘积:

这就是大名鼎鼎的链式法则。提出于上世纪60、70年代的反向传播算法就是应用了链式法则来计算一个实函数相对于其不同参数的梯度的。
要知道我们的最终目标是通过沿着梯度的相反方向来逐步找到函数的最小值 (当然最好是全局最小值), 因为至少在局部来说, 这样做将使得函数值逐步下降。当我们有两个参数需要优化时, 整个过程如图所示:

反向模式求导
假设函数fi(ai)=ai+1由多于两个函数复合而成,我们可以反复应用公式求导并得到:

可以有很多种方式计算这个乘积,最常见的是从左向右或从右向左。
如果an是一个标量,那么在计算整个梯度的时候我们可以通过先计算∂an/∂an-1并逐步右乘所有的Jacobian矩阵∂ai/∂ai-1来得到。这个操作有时被称作VJP或向量-Jacobian乘积(Vector-Jacobian Product)。
又因为整个过程中我们是从计算∂an/∂an-1开始逐步计算∂an/∂an-2,∂an/∂an-3等梯度到最后,并保存中间值,所以这个过程被称为反向模式求导。最终,我们可以计算出an相对于所有其他变量的梯度。

相对而言,前向模式的过程正相反。它从计算Jacobian矩阵如∂a2/∂a1开始,并左乘∂a3/∂a2来计算∂a3/∂a1。如果我们继续乘上∂ai/∂ai-1并保存中间值,最终我们可以得到所有变量相对于∂a2/∂a1的梯度。当∂a2/∂a1是标量时,所有乘积都是列向量,这被称为Jacobian向量乘积(或者JVP,Jacobian-Vector Product )。

你大概已经猜到了,对于反向传播来说,我们更偏向应用反向模式——因为我们想要逐步得到损失函数对于每层参数的梯度。正向模式虽然也可以计算需要的梯度, 但因为重复计算太多而效率很低。
计算梯度的过程看起来像是有很多高维矩阵相乘, 但实际上,Jacobian矩阵常常是稀疏、块或者对角矩阵,又因为我们只关心将其右乘行向量的结果,所以就不需要耗费太多计算和存储资源。
在本文中, 我们的方法主要用于按顺序逐层搭建的神经网络, 但同样的方法也适用于计算梯度的其他算法或计算图。
关于反向和正向模式的详尽描述可以参考这里☟:
深度神经网络




