【AI系统】动手实现自动微分

简介: 本章介绍如何实现自动微分,重点讲解前向自动微分的原理及Python实现方法。通过操作符重载,将程序分解为基础表达式组合,利用链式法则计算导数。示例代码展示了如何使用自定义类`ADTangent`实现加、减、乘、log、sin等操作的自动微分,验证了与PyTorch和MindSpore等框架的一致性。

在这章内容,会介绍是怎么实现自动微分的,因为代码量非常小,也许你也可以写一个玩玩。前面的文章当中,已经把自动微分的原理深入浅出的讲了一下,也引用了非常多的论文。有兴趣的可以顺着综述 A survey 这篇深扒一下。

前向自动微分原理

了解自动微分的不同实现方式非常有用。在这里呢,我们将介绍主要的前向自动微分,通过 Python 这个高级语言来实现操作符重载。在正反向模式中的这篇的文章中,我们介绍了前向自动微分的基本数学原理。

前向模式(Forward Automatic Differentiation,也叫做 tangent mode AD)或者前向累积梯度(前向模式)

前向自动微分中,从计算图的起点开始,沿着计算图边的方向依次向前计算,最终到达计算图的终点。它根据自变量的值计算出计算图中每个节点的值以及其导数值,并保留中间结果。一直得到整个函数的值和其导数值。整个过程对应于一元复合函数求导时从最内层逐步向外层求导。

image

简单确实简单,可以总结前向自动微分关键步骤为:

  • 分解程序为一系列已知微分规则的基础表达式的组合
  • 根据已知微分规则给出各基础表达式的微分结果
  • 根据基础表达式间的数据依赖关系,使用链式法则将微分结果组合完成程序的微分结果

而通过 Python 高级语言,进行操作符重载后的关键步骤其实也相类似:

  • 分解程序为一系列已知微分规则的基础表达式组合,并使用高级语言的重载操作
  • 在重载运算操作的过程中,根据已知微分规则给出各基础表达式的微分结果
  • 根据基础表达式间的数据依赖关系,使用链式法则将微分结果组合完成程序的微分结果

具体实现

首先呢,我们需要加载通用的 numpy 库,用于实际运算的,如果不用 numpy,在 python 中也可以使用 math 来代替。

import numpy as np

前向自动微分又叫做 tangent mode AD,所以我们准备一个叫做 ADTangent 的类,这类初始化的时候有两个参数,一个是 x,表示输入具体的数值;另外一个是 dx,表示经过对自变量 x 求导后的值。

需要注意的是,操作符重载自动微分不像源码转换可以给出求导的公式,一般而言并不会给出求导公式,而是直接给出最后的求导值,所以就会有 dx 的出现。

class ADTangent:

    # 自变量 x,对自变量进行求导得到的 dx
    def __init__(self, x, dx):
        self.x = x
        self.dx = dx

    # 重载 str 是为了方便打印的时候,看到输入的值和求导后的值
    def __str__(self):
        context = f'value:{self.x:.4f}, grad:{self.dx}'
        return context

下面是核心代码,也就是操作符重载的内容,在 ADTangent 类中通过 Python 私有函数重载加号,首先检查输入的变量 other 是否属于 ADTangent,如果是那么则把两者的自变量 x 进行相加。

其中值得注意的就是 dx 的计算,因为是正向自动微分,因此每一个前向的计算都会有对应的反向求导计算。求导的过程是这个程序的核心,不过不用担心的是这都是最基础的求导法则。最后返回自身的对象 ADTangent(x, dx)。

    def __add__(self, other):
        if isinstance(other, ADTangent):
            x = self.x + other.x
            dx = self.dx + other.dx
        elif isinstance(other, float):
            x = self.x + other
            dx = self.dx
        else:
            return NotImplementedError
        return ADTangent(x, dx)

下面则是对减号、乘法、log、sin 几个操作进行操作符重载,正向的重载的过程比较简单,基本都是按照上面的 add 的代码讨论来实现。

    def __sub__(self, other):
        if isinstance(other, ADTangent):
            x = self.x - other.x
            dx = self.dx - other.dx
        elif isinstance(other, float):
            x = self.x - other
            ex = self.dx
        else:
            return NotImplementedError
        return ADTangent(x, dx)

    def __mul__(self, other):
        if isinstance(other, ADTangent):
            x = self.x * other.x
            dx = self.x * other.dx + self.dx * other.x
        elif isinstance(other, float):
            x = self.x * other
            dx = self.dx * other
        else:
            return NotImplementedError
        return ADTangent(x, dx)

    def log(self):
        x = np.log(self.x)
        dx = 1 / self.x * self.dx
        return ADTangent(x, dx)

    def sin(self):
        x = np.sin(self.x)
        dx = self.dx * np.cos(self.x)
        return ADTangent(x, dx)

以公式为例:

$$ f(x_{1},x_{2})=ln(x_{1})+x_{1}*x_{2}−sin(x_{2}) \tag{1} \\ $$

因为是基于 ADTangent 类进行了操作符重载,因此在初始化自变量 x 和 y 的值需要使用 ADTangent 来初始化,然后通过代码 f = ADTangent.log(x) + x * y - ADTangent.sin(y) 来实现。

由于这里是求 f 关于自变量 x 的导数,因此初始化数据的时候呢,自变量 x 的 dx 设置为 1,而自变量 y 的 dx 设置为 0。

x = ADTangent(x=2., dx=1)
y = ADTangent(x=5., dx=0)
f = ADTangent.log(x) + x * y - ADTangent.sin(y)
print(f)
    value:11.6521, grad:5.5

从输出结果来看,正向计算的输出结果是跟上面图相同,而反向的导数求导结果也与上图相同。下面一个是 Pytroch 的实现结果对比,最后是 MindSpore 的实现结果对比。

可以看到呢,上面的简单实现的自动微分结果和 Pytroch 、MindSpore 是相同的。还是很有意思的。

Pytroch 对公式 1 的自动微分结果:

import torch
from torch.autograd import Variable

x = Variable(torch.Tensor([2.]), requires_grad=True)
y = Variable(torch.Tensor([5.]), requires_grad=True)
f = torch.log(x) + x * y - torch.sin(y)
f.backward()

print(f)
print(x.grad)
print(y.grad)

输出结果:

    tensor([11.6521], grad_fn=<SubBackward0>)
    tensor([5.5000])
    tensor([1.7163])

MindSpore 对公式 1 的自动微分结果:

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

class Fun(nn.Cell):
    def __init__(self):
        super(Fun, self).__init__()

    def construct(self, x, y):
        f = ops.log(x) + x * y - ops.sin(y)
        return f

x = Tensor(np.array([2.], np.float32))
y = Tensor(np.array([5.], np.float32))
f = Fun()(x, y)

grad_all = ops.GradOperation()
grad = grad_all(Fun())(x, y)

print(f)
print(grad[0])

输出结果:

    [11.65207]
    5.5

如果您想了解更多AI知识,与AI专业人士交流,请立即访问昇腾社区官方网站https://www.hiascend.com/或者深入研读《AI系统:原理与架构》一书,这里汇聚了海量的AI学习资源和实践课程,为您的AI技术成长提供强劲动力。不仅如此,您还有机会投身于全国昇腾AI创新大赛和昇腾AI开发者创享日等盛事,发现AI世界的无限奥秘~

目录
相关文章
|
3天前
|
人工智能 前端开发 小程序
2024年12月30日蜻蜓蜻蜓AI工具系统v1.0.0发布-优雅草科技本产品前端源代码已对外开源可免费商用-优雅草老八
2024年12月30日蜻蜓蜻蜓AI工具系统v1.0.0发布-优雅草科技本产品前端源代码已对外开源可免费商用-优雅草老八
2024年12月30日蜻蜓蜻蜓AI工具系统v1.0.0发布-优雅草科技本产品前端源代码已对外开源可免费商用-优雅草老八
|
8天前
|
人工智能 自然语言处理 并行计算
ASAL:Sakana AI 联合 OpenAI 推出自动探索人工生命的系统,通过计算机模拟生命进化的过程
ASAL 是由 Sakana AI 联合 OpenAI 等机构推出的自动化搜索人工生命系统,基于基础模型实现多种搜索机制,扩展了人工生命研究的边界。
59 1
ASAL:Sakana AI 联合 OpenAI 推出自动探索人工生命的系统,通过计算机模拟生命进化的过程
|
25天前
|
机器学习/深度学习 存储 人工智能
【AI系统】昇思MindSpore并行
本文介绍昇思MindSpore的并行训练技术,包括张量重排布、自动微分等,旨在简化并行策略搜索,提高大规模模型训练效率。文章探讨了大模型带来的挑战及现有框架的局限性,详细说明了MindSpore如何通过技术创新解决这些问题,实现高效的大模型训练。
73 20
【AI系统】昇思MindSpore并行
|
22天前
|
机器学习/深度学习 人工智能 自然语言处理
转载:【AI系统】AI的领域、场景与行业应用
本文概述了AI的历史、现状及发展趋势,探讨了AI在计算机视觉、自然语言处理、语音识别等领域的应用,以及在金融、医疗、教育、互联网等行业中的实践案例。随着技术进步,AI模型正从单一走向多样化,从小规模到大规模分布式训练,企业级AI系统设计面临更多挑战,同时也带来了新的研究与工程实践机遇。文中强调了AI基础设施的重要性,并鼓励读者深入了解AI系统的设计原则与研究方法,共同推动AI技术的发展。
转载:【AI系统】AI的领域、场景与行业应用
|
22天前
|
存储 人工智能 vr&ar
转载:【AI系统】CPU 基础
CPU,即中央处理器,是计算机的核心部件,负责执行指令和控制所有组件。本文从CPU的发展史入手,介绍了从ENIAC到现代CPU的演变,重点讲述了冯·诺依曼架构的形成及其对CPU设计的影响。文章还详细解析了CPU的基本构成,包括算术逻辑单元(ALU)、存储单元(MU)和控制单元(CU),以及它们如何协同工作完成指令的取指、解码、执行和写回过程。此外,文章探讨了CPU的局限性及并行处理架构的引入。
转载:【AI系统】CPU 基础
|
18天前
|
人工智能 安全 算法
CAMEL AI 上海黑客松重磅来袭!快来尝试搭建你的第一个多智能体系统吧!
掌握多智能体系统,🐫 CAMEL-AI Workshop & 黑客马拉松即将启航!
CAMEL AI 上海黑客松重磅来袭!快来尝试搭建你的第一个多智能体系统吧!
|
22天前
|
人工智能 缓存 并行计算
转载:【AI系统】CPU 计算本质
本文深入探讨了CPU计算性能,分析了算力敏感度及技术趋势对CPU性能的影响。文章通过具体数据和实例,讲解了CPU算力的计算方法、算力与数据加载之间的平衡,以及如何通过算力敏感度分析优化计算系统性能。同时,文章还考察了服务器、GPU和超级计算机等平台的性能发展,揭示了这些变化如何塑造我们对CPU性能的理解和期待。
转载:【AI系统】CPU 计算本质
|
22天前
|
机器学习/深度学习 存储 人工智能
转载:【AI系统】计算之比特位宽
本文详细介绍了深度学习中模型量化操作及其重要性,重点探讨了比特位宽的概念,包括整数和浮点数的表示方法。文章还分析了不同数据类型(如FP32、FP16、BF16、FP8等)在AI模型中的应用,特别是FP8数据类型在提升计算性能和降低内存占用方面的优势。最后,文章讨论了降低比特位宽对AI芯片性能的影响,强调了在不同应用场景中选择合适数据类型的重要性。
转载:【AI系统】计算之比特位宽
|
22天前
|
机器学习/深度学习 人工智能 算法
转载:【AI系统】关键设计指标
本文介绍了AI芯片的关键设计指标及其与AI计算模式的关系,涵盖计算单位(如OPS、MACs、FLOPs)、关键性能指标(精度、吞吐量、时延、能耗、成本、易用性)及优化策略,通过算术强度和Roofline模型评估AI模型在芯片上的执行性能,旨在帮助理解AI芯片设计的核心考量与性能优化方法。
转载:【AI系统】关键设计指标
|
22天前
|
机器学习/深度学习 人工智能 并行计算
转载:【AI系统】AI轻量化与并行策略
本文探讨了AI计算模式对AI芯片设计的重要性,重点分析了轻量化网络模型和大模型分布式并行两大主题。轻量化网络模型通过减少模型参数量和计算量,实现在资源受限设备上的高效部署;大模型分布式并行则通过数据并行和模型并行技术,解决大模型训练中的算力和内存瓶颈,推动AI技术的进一步发展。
转载:【AI系统】AI轻量化与并行策略