昇思MindSpore学习总结六——函数式自动微分

        神经网络的训练主要使用反向传播算法,模型预测值(logits)与正确标签(label)送入损失函数(loss function)获得loss,然后进行反向传播计算,求得梯度(gradients),最终更新至模型参数(parameters)。自动微分能够计算可导函数在某点处的导数值,是反向传播算法的一般化。自动微分主要解决的问题是将一个复杂的数学运算分解为一系列简单的基本运算,该功能对用户屏蔽了大量的求导细节和过程,大大降低了框架的使用门槛。

        MindSpore使用函数式自动微分的设计理念,提供更接近于数学语义的自动微分接口gradvalue_and_grad。下面我们使用一个简单的单层线性变换模型进行介绍。

import numpy as np
import mindspore
from mindspore import nn
from mindspore import ops
from mindspore import Tensor, Parameter

1、函数与计算图

        计算图是用图论语言表示数学函数的一种方式,也是深度学习框架表达神经网络模型的统一方法。我们将根据下面的计算图构造计算函数和神经网络。

 在这个模型中,𝑥为输入,𝑦为正确值,𝑤和𝑏是我们需要优化的参数。

x = ops.ones(5, mindspore.float32)  # input tensor
y = ops.zeros(3, mindspore.float32)  # expected output
w = Parameter(Tensor(np.random.randn(5, 3), mindspore.float32), name='w') # weight
b = Parameter(Tensor(np.random.randn(3,), mindspore.float32), name='b') # bias
print(x)
print(y)

2、构造损失函数

         我们根据计算图描述的计算过程,构造计算函数。 其中,binary_cross_entropy_with_logits 是一个损失函数,计算预测值和目标值之间的二值交叉熵损失。

mindspore.ops.binary_cross_entropy_with_logits(logitslabelweight=Nonepos_weight=Nonereduction='mean')

        输入经过sigmoid激活函数后作为预测值,binary_cross_entropy_with_logits 计算预测值和目标值之间的二值交叉熵损失。

将输入 logits 设置为 𝑋 ,输入 labels 设置为 𝑌 ,输入 weight 设置为 𝑊 ,输出设置为 𝐿 。则,

𝑖 表示 𝑖^𝑡ℎ 样例, 𝑗 表示类别。则,

ℓ 表示计算损失的方法。

        有三种方法:第一种方法是直接提供损失值,第二种方法是计算所有损失的平均值,第三种方法是计算所有损失的总和。

        该算子会将输出乘以相应的权重。 𝑤𝑒𝑖𝑔ℎ𝑡 表示一个batch中的每条数据分配不同的权重, 𝑝𝑜𝑠_𝑤𝑒𝑖𝑔ℎ𝑡 为每个类别的正例子添加相应的权重。

        此外,它可以通过向正例添加权重来权衡召回率和精度。 在多标签分类的情况下,损失可以描述为:

其中 c 是类别数目(c>1 表示多标签二元分类,c=1 表示单标签二元分类),n 是批次中样本的数量,𝑃𝑐 是 第c类正例的权重。 𝑃𝑐>1 增大召回率, 𝑃𝑐<1 增大精度。

【参数】

  • logits (Tensor) - 输入预测值。其数据类型为float16或float32。

  • label (Tensor) - 输入目标值,shape与 logits 相同。数据类型为float16或float32。

  • weight (Tensor,可选) - 指定每个批次二值交叉熵的权重。支持广播,使其shape与 logits 的shape保持一致。数据类型必须为float16或float32。默认值:None , weight 是值为 1 的Tensor。

  • pos_weight (Tensor,可选) - 指定正类的权重。是一个长度等于分类数的向量。支持广播,使其shape与 logits 的shape保持一致。数据类型必须为float16或float32。默认值:None , pos_weight 是值为 1 的Tensor。

  • reduction (str,可选) - 指定应用于输出结果的规约计算方式,可选 'none' 、 'mean' 、 'sum' ,默认值: 'mean' 。

    • 'none':不应用规约方法。

    • 'mean':计算输出元素的加权平均值。

    • 'sum':计算输出元素的总和。

def function(x, y, w, b):
    z = ops.matmul(x, w) + b
    #mindspore.ops.matmul(input, other)计算两个数组的乘积。
    #input (Tensor) - 输入Tensor,不支持Scalar, input 的最后一维度和 other 的倒数第二维度相
#等,且 input 和 other 彼此支持广播。
    #other (Tensor) - 输入Tensor,不支持Scalar, input 的最后一维度和 other 的倒数第二维度相
#等,且 input 和 other 彼此支持广播。
    loss = ops.binary_cross_entropy_with_logits(z, y, ops.ones_like(z), ops.ones_like(z))
    return loss
loss = function(x, y, w, b)
print(loss)

3、微分函数与梯度计算

        为了优化模型参数,需要求参数对loss的导数:∂loss/∂𝑤和∂loss/∂𝑏,此时我们调用mindspore.grad函数,来获得function的微分函数。

mindspore.grad(fngrad_position=0weights=Nonehas_aux=Falsereturn_ids=False)

        生成求导函数,用于计算给定函数的梯度。

函数求导包含以下三种场景:

  1. 对输入求导,此时 grad_position 非None,而 weights 是None;

  2. 对网络变量求导,此时 grad_position 是None,而 weights 非None;

  3. 同时对输入和网络变量求导,此时 grad_position 和 weights 都非None。

【参数】

  • fn (Union[Cell, Function]) - 待求导的函数或网络。

  • grad_position (Union[NoneType, int, tuple[int]]) - 指定求导输入位置的索引。若为int类型,表示对单个输入求导;若为tuple类型,表示对tuple内索引的位置求导,其中索引从0开始;若是None,表示不对输入求导,这种场景下, weights 非None。默认值: 0 。

  • weights (Union[ParameterTuple, Parameter, list[Parameter]]) - 训练网络中需要返回梯度的网络变量。一般可通过 weights = net.trainable_params() 获取。默认值: None 。

  • has_aux (bool) - 是否返回辅助参数的标志。若为 True , fn 输出数量必须超过一个,其中只有 fn 第一个输出参与求导,其他输出值将直接返回。默认值: False 。

  • return_ids (bool) - 是否返回由返回的梯度和指定求导输入位置的索引或网络变量组成的tuple。若为 True ,其输出中所有的梯度值将被替换为:由该梯度和其输入的位置索引,或者用于计算该梯度的网络变量组成的tuple。默认值: False 。

这里使用了grad函数的两个入参,分别为:

  • fn:待求导的函数。
  • grad_position:指定求导输入位置的索引。

由于我们对𝑤和𝑏求导,因此配置其在function入参对应的位置(2, 3)

使用grad获得微分函数是一种函数变换,即输入为函数,输出也为函数。

grad_fn = mindspore.grad(function, (2, 3))
# 执行微分函数,即可获得 𝑤、 𝑏对应的梯度。
grads = grad_fn(x, y, w, b)
print(grads)

3.1 Stop Gradient

        通常情况下,求导时会求loss对参数的导数,因此函数的输出只有loss一项。当我们希望函数输出多项时,微分函数会求所有输出项对参数的导数。此时如果想实现对某个输出项的梯度截断,或消除某个Tensor对梯度的影响,需要用到Stop Gradient操作。

        这里我们将function改为同时输出loss和z的function_with_logits,获得微分函数并执行。

def function_with_logits(x, y, w, b):
    z = ops.matmul(x, w) + b
    loss = ops.binary_cross_entropy_with_logits(z, y, ops.ones_like(z), ops.ones_like(z))
    return loss, z

grad_fn = mindspore.grad(function_with_logits, (2, 3))
grads = grad_fn(x, y, w, b)
print(grads)

        可以看到求得𝑤𝑤、𝑏𝑏对应的梯度值发生了变化。此时如果想要屏蔽掉z对梯度的影响,即仍只求参数对loss的导数,可以使用ops.stop_gradient接口,将梯度在此处截断。我们将function实现加入stop_gradient,并执行。

mindspore.ops.stop_gradient(value)

        用于消除某个值对梯度的影响,例如截断来自于函数输出的梯度传播。

【参数】

  • value (Any) - 需要被消除梯度影响的值。

def function_stop_gradient(x, y, w, b):
    z = ops.matmul(x, w) + b
    loss = ops.binary_cross_entropy_with_logits(z, y, ops.ones_like(z), ops.ones_like(z))
    return loss, ops.stop_gradient(z)
grad_fn = mindspore.grad(function_stop_gradient, (2, 3))
grads = grad_fn(x, y, w, b)
print(grads)

可以看到,求得𝑤𝑤、𝑏𝑏对应的梯度值与初始function求得的梯度值一致。 

3.2 Auxiliary data

        Auxiliary data意为辅助数据,是函数除第一个输出项外的其他输出。通常我们会将函数的loss设置为函数的第一个输出,其他的输出即为辅助数据。

  gradvalue_and_grad提供has_aux参数,当其设置为True时,可以自动实现前文手动添加stop_gradient的功能,满足返回辅助数据的同时不影响梯度计算的效果。

下面仍使用function_with_logits,配置has_aux=True,并执行。

grad_fn = mindspore.grad(function_with_logits, (2, 3), has_aux=True)
grads, (z,) = grad_fn(x, y, w, b)
print(grads, z)

        可以看到,求得𝑤𝑤、𝑏𝑏对应的梯度值与初始function求得的梯度值一致,同时z能够作为微分函数的输出返回。

4、神经网络梯度计算

        神经网络构造是继承自面向对象编程范式的nn.Cell。接下来我们通过Cell构造同样的神经网络,利用函数式自动微分来实现反向传播。

        首先我们继承nn.Cell构造单层线性变换神经网络。这里我们直接使用前文的𝑤、𝑏作为模型参数,使用mindspore.Parameter进行包装后,作为内部属性,并在construct内实现相同的Tensor操作。

# Define model
class Network(nn.Cell):
    def __init__(self):
        super().__init__()
        self.w = w
        self.b = b

    def construct(self, x):
        z = ops.matmul(x, self.w) + self.b
        return z

接下来我们实例化模型和损失函数。

# Instantiate model
model = Network()
# Instantiate loss function
loss_fn = nn.BCEWithLogitsLoss()

         完成后,由于需要使用函数式自动微分,需要将神经网络和损失函数的调用封装为一个前向计算函数。

# Define forward function
def forward_fn(x, y):
    z = model(x)
    loss = loss_fn(z, y)
    return loss

完成后,我们使用value_and_grad接口获得微分函数,用于计算梯度。

        由于使用Cell封装神经网络模型,模型参数为Cell的内部属性,此时我们不需要使用grad_position指定对函数输入求导,因此将其配置为None。对模型参数求导时,我们使用weights参数,使用model.trainable_params()方法从Cell中取出可以求导的参数。

grad_fn = mindspore.value_and_grad(forward_fn, None, weights=model.trainable_params())
loss, grads = grad_fn(x, y)
print(grads)

 执行微分函数,可以看到梯度值和前文function求得的梯度值一致。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值