MindSpore多元自动微分

技术背景

当前主流的深度学习框架,除了能够便捷高效的搭建机器学习的模型之外,其自动并行和自动微分等功能还为其他领域的科学计算带来了模式的变革。本文我们将探索如何用MindSpore去实现一个多维的自动微分,并且得到该多元函数的雅可比矩阵。

函数形式与雅可比矩阵形式

首先我们给定一个比较简单的z关于自变量x的函数形式(其中y和I是一些参数):

\[z_{i,j}(x)=y_ix_j \]

比如我们考虑一个3*3的z,我们最终需要计算的是这样一个雅可比矩阵:

\[J_z(x)= \left[ \begin{array}{l} \frac{\partial z_0}{\partial x_0} & \frac{\partial z_0}{\partial x_1} & \frac{\partial z_0}{\partial x_2}\\ \frac{\partial z_1}{\partial x_0} & \frac{\partial z_1}{\partial x_1} & \frac{\partial z_1}{\partial x_2}\\ \frac{\partial z_2}{\partial x_0} & \frac{\partial z_2}{\partial x_1} & \frac{\partial z_2}{\partial x_2} \end{array} \right] \]

假如我们给定一些简单的初始值:

\[x=[1,2,3]\\ y=[1,3,2] \]

那么理论上我们应该得到的结果是:

\[J_z(x)=\left[ \begin{array}{l} 1 & 0 & 0\\ 0 & 0 & 3\\ 0 & 2 & 0\\ \end{array} \right] \]

接下来我们看看如何在MindSpore的自动微分框架下实现这一功能。

初步尝试Grad自动微分

我们先按照上一章节中的公式的基本内容,直接写一个Net函数用于表示z,然后再用grad函数对其进行微分,代码内容如下所示:

from mindspore import nn, Tensor, ops
from mindspore.ops.functional import grad
import numpy as np
from mindspore import numpy as msnp

class Net(nn.Cell):
    def __init__(self, y, index):
        super(Net, self).__init__()
        self.y = y
        self.index = index
        self.norm = nn.Norm(-1)

    def construct(self, x):
        return self.y[self.index]*x

x = Tensor(np.array([1,2,3]).astype(np.float32))
y = Tensor(np.array([[1],[2],[3]]).astype(np.float32))
index = Tensor(np.array([0,2,1]).astype(np.int32))
shape = (y.shape[0], x.shape[0])

output = grad(Net(y,index))(x)
print(output)
# [6. 6. 6.]

在这个案例中,我们得到的结果,首先维度就不对,我们理想中的雅可比矩阵应该是3*3大小的,可见MindSpore中自动微分的逻辑是把其中的一个维度进行了加和,类似于这样的形式:

\[\left[ \frac{\partial z_0}{\partial x_0}+\frac{\partial z_1}{\partial x_0}+\frac{\partial z_2}{\partial x_0}, \frac{\partial z_0}{\partial x_1}+\frac{\partial z_1}{\partial x_1}+\frac{\partial z_2}{\partial x_1}, \frac{\partial z_0}{\partial x_2}+\frac{\partial z_1}{\partial x_2}+\frac{\partial z_2}{\partial x_2} \right] \]

所以为了得到我们的结果,需要对输入的x进行扩维。

尝试扩维输入的自动微分

在MindSpore中提供了BroadcastTo这样的接口,可以自动的在扩展维度填充待扩展张量的元素,我们需要把x的最外层维度扩展到与参数y一致,在这个案例中就是3*3的维度,具体代码实现如下所示:

from mindspore import nn, Tensor, ops
from mindspore.ops.functional import grad
import numpy as np
from mindspore import numpy as msnp

class Net(nn.Cell):
    def __init__(self, y, index):
        super(Net, self).__init__()
        self.y = y
        self.index = index
        self.norm = nn.Norm(-1)

    def construct(self, x):
        return self.y[self.index]*x

x = Tensor(np.array([1,2,3]).astype(np.float32))
y = Tensor(np.array([[1],[2],[3]]).astype(np.float32))
index = Tensor(np.array([0,2,1]).astype(np.int32))
shape = (y.shape[0], x.shape[0])

output = grad(Net(y,index))(ops.BroadcastTo(shape)(x))
print(output)
'''
[[1. 1. 1.]
 [3. 3. 3.]
 [2. 2. 2.]]
'''

从这个输出结果中我们发现,虽然维度上是被扩展成功了,但是那些本该为0的位置却出现了非0元素,这说明在自动微分计算的过程中,我们输入的参数y也被自动的Broadcast了,而实际上正确的计算过程中是不能使用Broadcast的。

为参数添加Mask

上一个章节中说道,如果利用Tensor本身的自动Broadcast会导致输入参数被扩维,会得到一个错误的微分结果。因此这里我们手动对输入参数进行正确的扩维,这个过程是添加一个Mask矩阵,用于标记每一个参数所对应的位置。这里我们假设输入一个这样的Mask矩阵:

\[I=\left[ \begin{array}{l} 1 & 0 & 0\\ 0 & 0 & 1\\ 0 & 1 & 0 \end{array} \right] \]

这样理论上最终微分结果的非0元素应该跟这个矩阵是一致的,相关代码如下所示:

from mindspore import nn, Tensor, ops
from mindspore.ops.functional import grad
import numpy as np
from mindspore import numpy as msnp

class Net(nn.Cell):
    def __init__(self, y, index, size):
        super(Net, self).__init__()
        self.y = y
        self.index = index
        self.norm = nn.Norm(-1)
        self.mask = msnp.zeros((y.shape[0],size))
        self.mask[msnp.arange(self.index.shape[0]),self.index] = 1

    def construct(self, x):
        return self.mask*self.y[self.index]*x

x = Tensor(np.array([1,2,3]).astype(np.float32))
y = Tensor(np.array([[1],[2],[3]]).astype(np.float32))
index = Tensor(np.array([0,2,1]).astype(np.int32))
shape = (y.shape[0], x.shape[0])

output = grad(Net(y,index,x.shape[0]))(ops.BroadcastTo(shape)(x))
print(output)
'''
[[1. 0. 0.]
 [0. 0. 3.]
 [0. 2. 0.]]
'''

这里我们看到得到的结果就是正确的了。当然,需要说明的是,虽然这个案例只是非常简单的内容,但是这里给出的如何去计算多维函数的自动微分的方法,同样也适用于一些更加复杂的网络和函数。

总结概要

在本文中通过一个实际函数案例的多次尝试,给出了得到预期结果的一种解决方案。虽然MindSpore框架本身提供了Jvp和Vjp等功能,但是实际上和Grad没有太大的区别,只是用Tuple的形式增加了输入的一个维度。如果可以使用纯Tensor的输入,用这种Mask加上Grad或者GradOperation的方案会更加简单一些。同时我也尝试过使用HyperMap(类似于Jax中的vmap)来解决这个问题,只需要写好一条对z求导的函数形式,就可以自动对这个求导过程进行扩维,两者的结果是一致的。但是MindSpore的HyperMap在Graph模式下兼容效果不是很好,建议非必要不尝试。

版权声明

本文首发链接为:https://www.cnblogs.com/dechinphy/p/jvp.html

作者ID:DechinPhy

更多原著文章请参考:https://www.cnblogs.com/dechinphy/

打赏专用链接:https://www.cnblogs.com/dechinphy/gallery/image/379634.html

腾讯云专栏同步:https://cloud.tencent.com/developer/column/91958

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
今年的华为开发者大会 HDC 2020 上,除了**昇腾、鲲鹏等自研芯片硬件平台**之外,最令人期待的就是**深度学习框架 MindSpore 的开源**了。今天上午,华为 MindSpore **首席科学家陈雷**在活动中宣布这款产品正式开源,我们终于可以在开放平台上一睹它的真面目。 本文是根据机器之心报道的MindSpore 的开源介绍而整理的.md笔记 作为一款支持**端、边、云独立/协同的统一训练和推理框架,华为希望通过这款完整的软件堆栈,实现**一次性算子开发、一致的开发和调试体验**,以此帮助开发者实现**一次开发,应用在所有设备上平滑迁移**的能力。 三大创新能力:新编程范式,执行模式和协作方式 由**自动微分自动并行、数据处理**等功能构成 开发算法即代码、运行高效、部署态灵活**的**特点**, 三层核心:从下往上分别是**后端运行时、计算图引擎及前端表示层**。 最大特点:采用了业界最新的 **Source-to-Source 自动微分**,它能**利用编译器及编程语言的底层技术**,进一步**优化以支持更好的微分表达**。主流深度学习框架中主要有**三种自动微分技术,才用的不是静态计算图、动态计算图,而是基于**源码**转换:该技术源以**函数式编程框架**为基础,以**即时编译(JIT)**的方式**在中间表达(编译过程中程序的表达形式)上做自动微分变换**,支持**复杂控制流场景、高阶函数和闭包**。 MindSpore 主要概念就是张量、算子、单元和模型 其代码有两个比较突出的亮点:计算图的调整,动态图与静态图可以一行代码切换;自动并行特性,我们写的串行代码,只需要多加一行就能完成自动并行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值