python从入门到入土_自动微分从入门到入土(三):实现JVP

本文详细介绍了如何实现自动微分中的前向模式JVP求导。通过注册自完备的JVPDiffArray,存储求导所需信息,避免了无限循环的问题。在前向传播过程中,利用存储的jvp函数链进行计算,简化了高阶导数的获取。此外,还提供了计算JVP或雅可比矩阵的方法。
摘要由CSDN通过智能技术生成

上篇文章介绍了如何实现VJP求导,本文则介绍一下如何实现自动微分中的JVP求导。

1 注册梯度

VJP求导是从后向前求导,实现较为简单,而JVP求导则是从前向后求导,它存在的一个问题在于很难以一种简洁的方法实现高阶求导,因为导数计算时还会注册梯度,而JVP从前向后的计算机制会很容易让程序陷入无限循环。为了解决这一问题,我们需要让每个JVPDiffArray是自完备的,即它要携带所有涉及到它自己的求导所需的信息:

def register_diff(self, func, args, kwargs):

"""

Register the information required for forward.

"""

try:

if func is np.ufunc.__call__:

jvpmaker = primitive_jvps[args[0]]

else:

jvpmaker = primitive_jvps[func]

except KeyError:

raise NotImplementedError("JVP of func not defined")

jvp_args, parents = [], []

for arg in args:

if isinstance(arg, JVPDiffArray):

jvp_args.append(arg)

parents.append(arg)

elif not isinstance(arg, np.ufunc):

jvp_args.append(arg)

parent_argnums = tuple(range(len(parents)))

jvps = list(jvpmaker(parent_argnums, self, tuple(jvp_args), kwargs))

if self._jvp is None:

self._jvp = {}

for p, jvp in zip(parents, jvps):

if p not in self._jvp:

self._jvp[p] = [[jvp]]

else:

self._jvp[p] += [[jvp]]

if p._jvp:

for base in p._jvp:

if base not in self._jvp:

self._jvp[base] = []

self._jvp[base] += [flist + [jvp] for flist in p._jvp[base]]

2 前向传播

在计算梯度时,因为JVPDiffArray自己已包含所有所需信息,所以只要计算已经存储好的jvp函数链即可:

def _forward(self, x, grad_variables):

if self._jvp is None:

return grad_variables

result = []

for flist in self._jvp[x]:

cur_result = grad_variables

for f in flist:

cur_result = f(cur_result)

result.append(cur_result)

return reduce(lambda x, y: x + y, result)

这里我们不再需要写一个单独的_forward_jacobian,直接将v的不同位置赋1之后进行前向计算即可:

def to(self, x, grad_variables=None, jacobian=False):

"""

Calculate the JVP or Jacobian matrix of self to x.

"""

if self._jvp and x not in self._jvp:

raise ValueError("Please check if the base is correct.")

if jacobian:

if self._jacobian is None:

self._jacobian = {}

if x not in self._jacobian:

self._jacobian[x] = {}

for position in itertools.product(*[range(i) for i in np.shape(x)]):

grad_variables = np.zeros_like(x)

grad_variables.value[position] = 1

self._jacobian[x][position] = self._forward(x, grad_variables)

old_axes = tuple(range(np.ndim(self) + np.ndim(x)))

new_axes = old_axes[np.ndim(x) :] + old_axes[: np.ndim(x)]

self._jacobian[x] = np.transpose(

np.reshape(

np.stack(self._jacobian[x].values()), np.shape(x) + np.shape(self),

),

new_axes,

)

return self._jacobian[x]

else:

if self._diff is None:

self._diff = {}

if x not in self._diff:

if grad_variables is None:

grad_variables = np.ones_like(self)

self._diff[x] = self._forward(x, grad_variables)

return self._diff[x]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值