computational graph:
若能用计算图表示一个函数,那便可以使用反向传播技术,递归的调用链式法则来计算图中的每个变量的梯度(反向传播是链式法则的递归调用)
在前向传播时,每个节点都会计算local gradient
在反向传播时,每到达一个节点,都会得到从上游返回的梯度(这个梯度时对此节点的输出的求导)
如下图:在反向传播时,当我们到达这个节点,就已经从上游得到了L对z的梯度。现在的想法是去求对此节点的输入的梯度(即对x,y的梯度)
计算L对此节点输入的梯度(L对x,L对y的梯度):运用链式法则去计算。(已知L对Z(节点的输出)的梯度*local gradient(z对x的梯度))(上游传回的梯度值乘以本地梯度值从而得到关于此节点输入的梯度)
当计算出这些结果(关于对此节点输入的梯度),然后反向传播给前面的节点但或者直接相连的节点。
有个小例子:f=max(z,w)
在进行反向传播时,其中一个变量会得到传递回来的完整梯度值,而另一个变量的梯度会为0
max gate:梯度路由器(将获取的梯度路由到它其中一个分支)
add gate:梯度分配器(将相同的梯度回传给进来的分支)
mul gate:梯度转换器
max gate理解如下:查看前向传播,只有最大值能被传递到计算图的其余部分,即只有最大值真正影响到最后的函数计算,因此当反向传播时,想通过计算分支仅仅调整它
若有多个梯度值一同返回到一个节点,根据多元链式法则,返回的上游梯度值会累加
变量是高维的情况:
雅可比矩阵(Jacobian matrix):每一行都是偏导数,矩阵里的每个元素是输出向量的每个元素对输入向量的每个元素分别求偏导的结果
如以下的例子:
此时 f 表示此节点的输出,x表示此节点的输入,L对f的偏导是从上游传回的梯度值;f对x的偏导是local gradient,若x是向量时,local gradient则变成了雅可比矩阵。
这里的雅可比矩阵的维度是4096*4096
in practice,we process an entire minibatch(eg:1000) of examples at noe time:则雅可比矩阵的维度为409600 * 409600;矩阵维度太大,无法计算。
在多数情况下,不需要计算这么大的雅可比矩阵。这是因为对每个元素分别运算,比如输入里的第一元素仅仅影响输出的第一个元素,所以雅可比矩阵是对角矩阵。因此无需写出整个矩阵,仅仅需要知道输出中的元素对和它相关的输出元素的偏导,然后填入此矩阵中。
向量的梯度总是和原向量保持相同大小,每个梯度的元素代表着这个特定元素对对最终函数影响的大小
模块化的实现:前向和反向API
class ComputationalGraph(object):
def forward(inputs):
# 1. pass inputs to input gates...
# 2. forward the computational graph:
for gate in self.graph.nodes_topologically_sorted():
gate.forward()
return loss # the final gate in the graph outputs the loss
def backward():
for gate in reversed(self.graph.nodes_topologically_sorted()):
gate.backward()
return inputs_gradients
一个例子如下:
class MultiplyGate(object):
def forward(x,y):
z = x*y
#缓存下x,y,反向传播时会多次拥到
self.x = x
self.y = y
return z
def backward(dz):
dx = self.y *dz #链式法则
dy = self.x *dz
return [dx, dy]
作业思路:
第一步先计算图,然后再前向计算,再反向传播