<转>如何用C++实现自动微分
转摘链接:https://www.zhihu.com/question/48356514/answer/123290631
来源:知乎
著作权归作者所有。
实现 AD 有两种方式,函数重载与代码生成。两种方式的原理都一样,链式法则。
不难想象,任何计算都可以由第1步到第k步的序列形式,其中第 i 步计算的输入,在之前的 i-1 步中已经计算(例如编译器生成的汇编指令序列)。因此,任何计算都可以看作形式如下图左侧的复合函数。微积分中的链式法则告诉我们,符合函数的导数可写作下图右侧的形式(假设每一步都可导)。请注意偏导数和全导数的区别。
图一
&amp;amp;lt;img src="https://pic4.zhimg.com/7152be19b866948c071caeabb32aa69b_b.png" data-rawwidth="817" data-rawheight="424" class="origin_image zh-lightbox-thumb" width="817" data-original="https://pic4.zhimg.com/7152be19b866948c071caeabb32aa69b_r.png"&amp;amp;gt;
自动微分的第一个难点就在这,微积分中的链式法则。大家在课堂上学的链式法则的示例通常只有两到三个函数,而自动微分面对的计算,有无数个函数。许多人不习惯在这样大的规模上应用链式法则。不过一旦习惯,就会发现自动微分的原理十分简单。
如果上述内容过于抽象,请参看下面这个例子以后再看一遍。
图二
&amp;amp;lt;img src="https://pic4.zhimg.com/67efe239a248c8ca69ba71995d74c123_b.png" data-rawwidth="720" data-rawheight="506" class="origin_image zh-lightbox-thumb" width="720" data-original="https://pic4.zhimg.com/67efe239a248c8ca69ba71995d74c123_r.png"&amp;amp;gt;
在上图中,顶部方程是二维旋转。输入 有三个变量,旋转角度及二维坐标。输出
有两个变量,即旋转后的二维坐标。上图列表第二列是该计算的序列形式,第一列是每一步对应的表达式,第三列是对应的链式法则(请对比图一)。
太繁琐了?看不出个所以然?不用担心。
如果把计算的序列形式及其导数计算每个步骤的依赖关系表示成图
图三
&amp;amp;lt;img src="https://pic3.zhimg.com/1d4a6263a53516e6daa84823e90f48f6_b.png" data-rawwidth="819" data-rawheight="436" class="origin_image zh-lightbox-thumb" width="819" data-original="https://pic3.zhimg.com/1d4a6263a53516e6daa84823e90f48f6_r.png"&amp;amp;gt;不难发现,两张图是等价的。也就是说,计算序列形式的每一步都与其导数计算的步骤有一一对应的关系。源程序怎么算,其导数就可以怎么算(从顺序上来说)。
不难发现,两张图是等价的。也就是说,计算序列形式的每一步都与其导数计算的步骤有一一对应的关系。源程序怎么算,其导数就可以怎么算(从顺序上来说)。
以上便是自动微分的基本原理。下面我们来谈实现。
如图二,图三,我们有两种方式来考虑自动微分的实现。
- 用户提供图二第二列序列形式的源代码,按顺序生成第三列的微分计算。此种方法的特点是,读一行源代码,生成一行微分计算,因此可以动态生成。
若源代码这一行在做乘法,那么就依据乘法法则生成该步的微分计算。若源代码这一行是三角函数 cos(x),那么它对应的微分计算就是 -sin(x),以此类推。每一步计算的偏导数都根据链式法则组合,得出该步骤的全导数。
该种方法的常见手段是函数重载。优点是简单直接,缺点是动态生成成本较高。 - 用户提供源代码,在编译时生成图三左侧的程序结构图,并生成图三右侧对应的微分程序。
该种方法的常见手段是编译时的代码生成(比如用 flex-bison 做词法、语法分析)。优点是静态生成效率高,一次生成,多次使用。缺点是编译原理有门槛,非计算机专业望而却步。
力求简单,下面给出以函数重载实现的简单示例 SAD - Simple Automatic Differentiation。请对照图二中的列表阅读。
main 函数中对应图二表中第二列,2D 旋转的计算。ADV 里重载的 +, -, *, sin, cos 不仅完成本来的计算,还负责图二表中第三列导数的计算。
main 函数中 x, y, z 的序号都与图二中对应。
#include <cmath>
#include <vector>
#include <iostream>
namespace SAD // Simple Automatic Differentiation
{
class ADV
{
public:
ADV(double v = , double d = ); // overloaded unary and binary operators
ADV operator + (const ADV &x) const;
ADV operator - (const ADV &x) const;
ADV operator * (const ADV &x) const;
friend ADV sin(const ADV &x);
friend ADV cos(const ADV &x); double val; // value of the variable
double dval; // derivative of the variable
}; ADV::ADV(double v, double d) : val(v), dval(d) {} ADV ADV::operator+(const ADV &x) const
{
ADV y;
y.val = val + x.val;
y.dval = dval + x.dval;
return y;
} ADV ADV::operator-(const ADV &x) const
{
ADV y;
y.val = val - x.val;
y.dval = dval - x.dval; // sum rule
return y;
} ADV ADV::operator*(const ADV &x) const
{
ADV y;
y.val = val*x.val;
y.dval = x.val*dval + val*x.dval; // product rule
return y;
} ADV sin(const ADV &x)
{
ADV y;
y.val = std::sin(x.val);
y.dval = std::cos(x.val)*x.dval; // chain rule
return y;
} ADV cos(const ADV &x)
{
ADV y;
y.val = std::cos(x.val);
y.dval = -std::sin(x.val)*x.dval; // chain rule
return y;
}
} int main()
{
using namespace SAD;
using namespace std; static const double PI = 3.1415926;
vector<ADV> x; x.emplace_back(PI, ); // x = [PI, 2, 1]
x.emplace_back(, );
x.emplace_back(, ); ADV y1 = cos(x[]);
ADV y2 = sin(x[]);
ADV y3 = x[] * y1;
ADV y4 = x[] * y2;
ADV y5 = x[] * y2;
ADV y6 = x[] * y1; ADV z1 = y3 + y4;
ADV z2 = y6 - y5; cout << "x = [" << x[].val << ", " << x[].val << ", " << x[].val << "]" << endl;
cout << "z = [" << z1.val << ", " << z2.val << "]" << endl;
cout << "[dz1/dx0, dz2/dx0] = [" << z1.dval << "," << z2.dval << "]" << endl;
}
运行结果:
&amp;amp;lt;img src="https://pic3.zhimg.com/b25e13c3f8b3204d184ebae299df35b2_b.png" data-rawwidth="256" data-rawheight="59" class="content_image" width="256"&amp;amp;gt;
矢量 [2,1] 被旋转 180° , 变为 [-2,-1]。关于角度的导数为 [-1,2]。
自动微分的经典教材是该题目的奠基人 Griewank 著的 Evaluating Derivatives
(Society for Industrial and Applied Mathematics)
该书囊括了自动微分的所有方面,比如本文未介绍的 reverse mode, sparse Jacobian, Hessian 等。
如果不求全面,一本更通俗更面向代码实现的书是 The Art of Differentiating Computer Programs
(Society for Industrial and Applied Mathematics)
最后,自动微分是算导数的最优方法,比符号计算、有限微分更快更精确。
自动微分已经广泛应用在优化领域,包括人工神经网络的训练算法 back-propagation。
要解连续优化或非线性方程,自动微分是不二的选择。
几何福利(自动微分的张量推导)
刚才是科普,下面给出自动微分的张量公式,及其对应的,便于编程实现的矩阵公式。
图一的计算序列可以记作如下形式
其中是自变量
是中间变量
是因变量 他们可以是任意维度
微分流形上的矢量有两种:切空间 (Tangent space)的矢量,对偶空间 (Dual space)的矢量。
其中切空间的基 是关于坐标系的偏导算符
对偶空间的基是关于坐标系的微分
从切矢量出发 我们可以得到自动微分的正序模式 (forward mode)
从对偶矢量出发 我们可以得到自动微分的逆序模式 (reverse mode)
任意切矢量 的定义是其对应的方向导数算符
将它依次应用在计算序列的左边 便可获得下图左侧的张量公式
对张量缩并可得上图右侧的矩阵公式 其中 便是一阶导 Jacobian 矩阵
这种计算导数的方式与计算序列同序 故名正序模式
每一个方向导数的计算复杂度与计算序列相同 空间复杂度也相同
注意 若计算序列的自变量有 n 维 则获得 Jacobian 矩阵需要计算 n 个方向上的方向导数
任意对偶矢量 的定义是其对应方向的微分
对该矢量做关于坐标基 的坐标变换 便可获得下图左侧的张量公式
对涨量缩并可得上图右侧的矩阵公式 其中 D += TP 表示叠加 D = D + TP 便是关于
的一阶导
这种计算导数的方式与计算序列逆序 故名逆序模式
展开每一个基底 的计算复杂度与计算序列相同 但由于内存访问的顺序与计算序列相逆 所有中间结果都需要保存下来 因而空间复杂度与计算复杂度相当
若计算序列的因变量有 m 维 则获得 Jacobian 矩阵需要展开 m 个基底
<转>如何用C++实现自动微分的更多相关文章
- MindSpore多元自动微分
技术背景 当前主流的深度学习框架,除了能够便捷高效的搭建机器学习的模型之外,其自动并行和自动微分等功能还为其他领域的科学计算带来了模式的变革.本文我们将探索如何用MindSpore去实现一个多维的自动 ...
- 附录D——自动微分(Autodiff)
本文介绍了五种微分方式,最后两种才是自动微分. 前两种方法求出了原函数对应的导函数,后三种方法只是求出了某一点的导数. 假设原函数是$f(x,y) = x^2y + y +2$,需要求其偏导数$\fr ...
- pytorch学习-AUTOGRAD: AUTOMATIC DIFFERENTIATION自动微分
参考:https://pytorch.org/tutorials/beginner/blitz/autograd_tutorial.html#sphx-glr-beginner-blitz-autog ...
- 自动微分(AD)学习笔记
1.自动微分(AD) 作者:李济深链接:https://www.zhihu.com/question/48356514/answer/125175491来源:知乎著作权归作者所有.商业转载请联系作者获 ...
- (转)自动微分(Automatic Differentiation)简介——tensorflow核心原理
现代深度学习系统中(比如MXNet, TensorFlow等)都用到了一种技术——自动微分.在此之前,机器学习社区中很少发挥这个利器,一般都是用Backpropagation进行梯度求解,然后进行SG ...
- PyTorch自动微分基本原理
序言:在训练一个神经网络时,梯度的计算是一个关键的步骤,它为神经网络的优化提供了关键数据.但是在面临复杂神经网络的时候导数的计算就成为一个难题,要求人们解出复杂.高维的方程是不现实的.这就是自动微分出 ...
- 【tensorflow2.0】自动微分机制
神经网络通常依赖反向传播求梯度来更新网络参数,求梯度过程通常是一件非常复杂而容易出错的事情. 而深度学习框架可以帮助我们自动地完成这种求梯度运算. Tensorflow一般使用梯度磁带tf.Gradi ...
- PyTorch 自动微分示例
PyTorch 自动微分示例 autograd 包是 PyTorch 中所有神经网络的核心.首先简要地介绍,然后训练第一个神经网络.autograd 软件包为 Tensors 上的所有算子提供自动微分 ...
- PyTorch 自动微分
PyTorch 自动微分 autograd 包是 PyTorch 中所有神经网络的核心.首先简要地介绍,然后将会去训练的第一个神经网络.该 autograd 软件包为 Tensors 上的所有操作提供 ...
随机推荐
- 使用 Kafka 在生产环境构建大规模机器学习
智能实时应用为所有行业带来了革命性变化.机器学习及其分支深度学习正蓬勃发展,因为机器学习让计算机能够在无人指引的情况下挖掘深藏的洞见.这种能力正是多种领域所需要的,如非结构化数据分析.图像识别.语音识 ...
- Vivado HLS初识---阅读《vivado design suite tutorial-high-level synthesis》(3)
Vivado HLS初识---阅读<vivado design suite tutorial-high-level synthesis>(3) 优化lab1 1.创建工程,开启HLS 运行 ...
- Excel技巧--空白处补零
当我们有一表格,而表格的空白单元格要补零时,如下图: 那么手动添加零比较麻烦,特别是行数很多时.可以用如下方法: 1.使用“替换”功能: 查找内容为空,替换处填写0,然后点击全部替换即可. 2.使用定 ...
- HTTP协议之chunk介绍
http chunked 当客户端向服务器请求一个静态页面或者一张图片时,服务器可以很清楚的知道内容大小,然后通过Content-Length消息首部字段告诉客户端需要接收多少数据.但是如果是动态页面 ...
- 使用Html Agility Pack快速解析Html内容
Html Agility Pack 是一个开源的.NET 方案HTML解析器. 开源地址:https://github.com/zzzprojects/html-agility-pack 用法:vs上 ...
- 轻松快速实现MySql数据向SQLServer数据转移
转移数据的方式其实园子里各位亲友已经写过不少了,这里挑一种常用的ODBC数据转移,主要是把每个步骤尽可能完善讲到,下次直接按文章从头到尾看一遍,可以在最短时间完成数据转移. 这里用到的工具有MYSQL ...
- vue之v-bind
接触Vue已经有很长一段时间了,后来因为工作的原因,已经有差不多一年的时间没有碰过它了,害怕时间久,自己就完全忘记了,所以还是想抽出一点时间将以前的知识整理一下. 刚接触vue的时候,觉着最神奇的地方 ...
- 解决wordpress文章归档和分类目录小工具标题重复问题
最近更新了wordpress,发现更新后小工具中的文章归档和分类目录出现了标题重复,经检查,是部分主题下,主题的代码已经输出了标题,而wordpress的代码又再次输出了一次.于是我们需要删除word ...
- OpenStack镜像服务基本操作
查询Glance服务状态 #glance-control all status [root@controller ~]# glance-control all status glance-api (p ...
- android scrollview listview显示不全
原来处理方法是重写ListView import android.content.Context; import android.util.AttributeSet; import android.v ...