Autograd 自动微分实战指南:对任意 NumPy 代码直接求梯度
机器学习【免费下载链接】autogradEfficiently computes derivatives of NumPy code.项目地址https://gitcode.com/gh_mirrors/au/autograd点击查看免费下载Autograd 是一个高效的自动微分Automatic Differentiation, AD库核心能力是直接对普通 Python 与 NumPy 代码求导你只需用autograd.numpy写出目标函数grad就会返回一个计算其梯度的新函数。本文以仓库 docs/tutorial.md 为主线结合 autograd/core.py、autograd/tracer.py、autograd/differential_operators.py 等源码系统讲解 Autograd 的使用方法、反向模式自动微分原理、支持范围、与 Xarray 等数组容器的互操作以及如何用primitive和defvjp扩展自定义梯度。读完本文你将掌握用梯度下降训练逻辑回归、对含while/if/递归的函数求导、为任意外部函数注册向量-雅可比积VJP的完整实战方案。为什么需要 Autograd第三种求梯度方式训练机器学习模型通常意味着两件事设计一个损失函数来度量模型与数据的拟合程度然后对模型参数优化该损失。当参数很多神经网络可能有数百万个参数时就必须用到梯度。传统上有两条路自己手推并实现梯度容易出错且模型一改就要重新推导把模型塞进 Theano、TensorFlow 这类符号图框架的语法和语义约束中需要把控制流表达成框架专用的迷你语言例如tf.while、tf.cond学习成本和迁移成本都不小。Autograd 提供第三种方式直接用标准数值库NumPy把损失函数写出来Autograd 返回它的梯度。由于梯度计算完全自动化修改模型结构、快速迭代假设变得非常容易——这正是教程中强调的“用 Python 语法描述概率模型的最大收益”。快速上手grad 函数与第一个例子autograd.grad接收一个函数返回一个计算该函数导数的函数。使用约束只有一个被求导函数的输出必须是标量float这恰好覆盖了“用梯度做优化”的常见场景。Autograd 可以处理包含while循环、if语句、闭包等全部常规 Python 控制结构的普通代码。下面是用一个开放式循环计算 sine 函数泰勒展开并求导的经典例子import autograd.numpy as np # 对 NumPy 的薄封装版本 from autograd import grad def taylor_sine(x): # sine 函数的泰勒近似 ans currterm x i 0 while np.abs(currterm) 0.001: currterm -currterm * x**2 / ((2 * i 3) * (2 * i 2)) ans ans currterm i 1 return ans grad_sine grad(taylor_sine) print(Gradient of sin(pi) is, grad_sine(np.pi))这里taylor_sine内部有一个不确定迭代次数的while循环循环终止条件取决于运行时的数值大小np.abs(currterm) 0.001Autograd 依然能正确求导。根据微积分sin 在 π 处的导数为 cos(π) -1因此程序打印的梯度应接近 -1与实际导数值一致。几个实用细节grad的第二个可选参数argnum指定对第几个位置参数求导默认0源码中unary_to_nary包装后的grad会按argnum抽取对应输入见 autograd/differential_operators.pygrad返回的函数与原始函数接收相同参数但返回梯度且梯度类型与对应参数类型一致如果输出不是标量grad会抛出TypeError提示改用jacobian、elementwise_grad或holomorphic_grad见 differential_operators.py。完整实战用梯度下降训练逻辑回归自动微分最典型的应用是训练概率模型。教程给出一个麻雀虽小五脏俱全的例子为二分类任务指定并训练逻辑回归模型完整代码可在 examples/logistic_regression.py 中找到该文件还额外调用了check_grads做数值校验import autograd.numpy as np from autograd import grad def sigmoid(x): return 0.5 * (np.tanh(x / 2.) 1) def logistic_predictions(weights, inputs): # 输出标签为真的概率逻辑模型。 return sigmoid(np.dot(inputs, weights)) def training_loss(weights): # 训练损失是训练标签的负对数似然。 preds logistic_predictions(weights, inputs) label_probabilities preds * targets (1 - preds) * (1 - targets) return -np.sum(np.log(label_probabilities)) # 构造一个玩具数据集。 inputs np.array([[0.52, 1.12, 0.77], [0.88, -1.08, 0.15], [0.52, 0.06, -1.30], [0.74, -2.49, 1.39]]) targets np.array([True, True, False, True]) # 用 Autograd 定义返回训练损失梯度的函数。 training_gradient_fun grad(training_loss) # 用梯度下降优化权重。 weights np.array([0.0, 0.0, 0.0]) print(Initial loss:, training_loss(weights)) for i in range(100): weights - training_gradient_fun(weights) * 0.01 print(Trained loss:, training_loss(weights))要点拆解模型logistic_predictions用np.dot把输入与权重线性组合再过 sigmoid 输出概率sigmoid用np.tanh的数值稳定写法实现损失training_loss是标准的负对数似然NLL对每个样本累加preds * targets (1 - preds) * (1 - targets)的对数取负求和训练grad(training_loss)一次性得到损失对weights的梯度函数随后在 100 轮迭代里执行weights - grad * 0.01的梯度下降损失随迭代持续下降验证仓库示例用check_grads(training_loss, modes[rev])(weights)把 Autograd 的梯度与数值差分对比校验实现见 autograd/test_util.py这是排查自定义梯度错误的标准手段。底层原理primitive、Box 与计算图教程的 “Whats going on under the hood?” 一节揭示了 Autograd 的核心机制先记录再求导。用primitive包装函数Autograd 用primitive包装被追踪的函数使它们被调用时能把自己加入已执行操作的记录列表。tracer.py中的primitive会检查参数中是否含 Boxfind_top_boxed_args若有则拆箱取出真实值执行底层运算然后构造一个记录节点Node并返回新的 Box见 autograd/tracer.pyVJP 注册表core.py维护一张把包装后的 primitive 映射到其梯度函数更准确地说是向量-雅可比积 VJP 函数的表primitive_vjpsdefvjp就是向这张表注册 VJP 的入口见 autograd/core.py用 Box 标记求导变量要对哪个输入求导就把该输入用Box类包装。日常使用中你不需要接触Box但打印调试信息时可能会看到它Box.__str__会显示Autograd ArrayBox with value ...见 tracer.py。函数求值结束后Autograd 手里有了一张记录所有施加于输入之上的运算的计算图。求导就是对图中每个节点应用微分法则backward_pass从末端节点出发做拓扑排序逐个弹出节点、调用其 VJP 把梯度向父节点传播见 autograd/core.py。反向模式微分Reverse Mode给定多个函数嵌套例如 L(x) F(G(H(x)))链式法则给出 dL/dx dF/dG · dG/dH · dH/dx从右往左计算 (dF/dG · (dG/dH · dH/dx))与计算本身同序称为前向模式forward-mode从左往右计算 ((dF/dG · dG/dH) · dH/dx)与计算顺序相反称为反向模式reverse-mode。对“输入是大向量、输出是单个标量”这类函数反向模式远比有限差分或前向模式实用因此成为机器学习中人人皆知的backpropagation反向传播。它的优雅之处在于不需要显式实例化中间雅可比矩阵只需逐层应用免矩阵化的 VJP 函数序列。因为 Autograd 支持高阶导数Hessian-vector productHVP二阶导的一种同样可用且高效。源码层面grad的实现是make_vjp加“用全 1 种子向量做反向传播”differential_operators.pyhessian直接由jacobian(jacobian(fun))组合而来differential_operators.pymake_hvp/hessian_vector_product/make_ggnvp等算子提供 HVP、广义 Gauss-Newton-向量积等更高级的运算全部构建在make_vjp之上differential_operators.py。为什么支持 if、while 和递归有些自动微分框架如 TensorFlow要求你先声明计算图把if/for等控制流也画进去再整体变换成梯度图——好处是可以做编译期优化代价是你必须把控制流写成框架认识的迷你语言。Autograd 则完全不需要知道求值过程中经过了哪些if、分支、循环或递归求某个输入的梯度只需要知道实际施加在该输入上的连续变换而不需要知道“还可能施加了哪些别的变换”。由于 Autograd 对每次函数调用分别记录相关运算Python 控制流对它不可见根本不是问题——反而大大简化了实现。这正是本仓库 docs/tutorial.md 中“How can you support ifs, while loops and recursion?”一节的结论。可以微分什么输入与输出约束主要约束任何作用于 Box 的函数都必须被标记为primitive并实现其梯度。NumPy 库中的绝大多数函数已经替你处理好了这一步你也可以轻松编写自己的梯度见下文“扩展 Autograd”。输入类型标量、复数、向量、元组、向量的元组、元组的元组……都可作为求导输入。输出类型grad输出必须是标量elementwise_grad单趟计算雅可比各列之和雅可比为对角阵时即对角线differential_operators.pyjacobian不要求标量输出输入输出可为任意形状数组雅可比形状为输出形状 输入形状differential_operators.py。autograd包对外导出的微分算子相当完整见 autograd/init.pygrad、value_and_grad同时返回函数值与梯度适合scipy.optimize、grad_and_aux、hessian、jacobian、elementwise_grad、deriv、holomorphic_grad、checkpoint用重计算换显存、make_jvp/make_vjp/make_hvp/make_ggnvp等。与 Xarray 等数组容器互操作Autograd 与实现了 NumPy__array_ufunc__协议的数组容器如xarray.DataArray可以直接互操作无需任何特殊处理当 NumPy ufunc 作用于持有或被乘以可微值的此类容器时运算会穿过容器一路传播到 Autograd 包装的 primitive 上。这意味着你可以直接写np.sin(data_array)而不必绕道xr.apply_ufunc(np.sin, data_array)。Autograd 对 Xarray或任何此类库零依赖完全依赖标准协议因此任何实现该协议的容器都能用同一套方式工作。教程给出了一个使用命名坐标轴的完整示例要点grad需要标量输出所以最终归约前要用.data把底层纯数组取出来import autograd.numpy as np from autograd import grad import xarray as xr # 命名轴数据集三个特征四个时间步。 measurements xr.DataArray( np.array([[0.5, 1.2, -0.3], [0.1, 0.4, 0.9], [-0.7, 0.2, 1.1], [0.3, -0.5, 0.6]]), dims[time, feature], coords{feature: [a, b, c]}, ) def loss(weights): # weights 是我们对其求导的对象。广播遵循命名的 feature 轴 # box 化的值跟随在 DataArray 内部。 scores np.tanh(measurements * weights) # 取出纯数组做 grad 所需的标量归约 return np.sum(scores.data ** 2) weights np.array([0.5, -1.0, 2.0]) print(loss:, loss(weights)) print(grad:, grad(loss)(weights))源码佐证了这一机制的实现路径ArrayBox.__array_ufunc__会把 ufunc 调用转发给 Autograd 的包装版本autograd/numpy/numpy_boxes.pytracer.primitive在检测到“Box 参数 其他__array_ufunc__覆写者”时会先把处理权交给 ufunc 分发机制autograd/tracer.py保证 xarray 这类容器有机会先处理运算并把 Box 安全保存在容器内。NumPy/SciPy 支持范围NumPy 功能庞大Autograd 已为其中大部分实现了梯度当前已支持大部分数学运算大部分数组与矩阵操作例程部分线性代数函数大部分快速傅里叶变换例程复数全支持N 维卷积部分 SciPy 例程包括scipy.stats.norm。numpy_vjps.py中还可看到两类明确处理被register_notrace注册为不可微的nograd_functions如floor、round、sign、argsort等以及用defvjp注册梯度常量的函数如nan_to_num见 autograd/numpy/numpy_vjps.py。仍有一些功能未实现需要特别注意支持索引x A[i, j, :]不支持赋值A[i,j] x对正在求导的数组。赋值难以支持因为它要求保存被覆盖数据的副本即使代码看起来在就地赋值系统也要在背后做拷贝往往违背就地操作的初衷不支持A.dot(B)语法请用等价的np.dot(A, B)。原因是子类化ndarray会引发一连串问题由于不子类化ndarray某些子类检查会失效例如isinstance(x, np.ndarray)可能返回False。此时应改用 Autograd 提供的版本from autograd.builtins import isinstancebuiltins.py中isinstance被包装为notrace_primitive见 autograd/builtins.py对不参与求导的数组做就地修改如A[i] x或A B不会报错但要小心Autograd 会在反向传播需要时保存前向传播用到的变量引用就地修改很容易在你不知情时改掉这些值而显式拷贝又太慢列表与字典可以自由使用——和 Python 控制流一样Autograd 通常甚至不需要感知它们。唯一的例外是把 list 传给 primitive 函数如autograd.numpy.sum因为需要检查列表内容里是否藏着 Box。目前autograd.numpy.array与autograd.numpy.concatenate支持直接接收列表其他情况下建议先用autograd.numpy.array显式构造数组再传给 primitive。备选方案是使用autograd.builtins中的list、dict、tuple它们与 Python 内置同名类型行为一致同时保证 Box 不会被藏进容器。注意这些问题只在你把 list/tuple 传给 primitive 函数时才出现在你自己非 primitive的函数内部把 Box 放进 list、tuple、dict 完全没问题。教程最后给出简明清单✅ 放心用绝大部分 numpy 函数绝大部分 numpy.ndarray 方法sum、mean、reshape、transpose等已批量绑定到 ArrayBox见 numpy_boxes.py部分 scipy 函数数组索引与切片x A[3, :, 2:4]从列表显式构造数组A np.array([x, y])。❌ 不要用对数组赋值A[0,0] x隐式把列表强转成数组A np.sum([x, y])应写A np.sum(np.array([x, y]))A.dot(B)记号改用np.dot(A, B)就地操作如a b改用a a b未先from autograd.builtins import isinstance, tuple就做的部分 isinstance 检查如isinstance(x, np.ndarray)或isinstance(x, tuple)。如果担心哪里算错了数值梯度校验很容易仓库的autograd.test_util.check_grads提供标准校验工具逻辑回归与自定义梯度示例都通过它验证examples/logistic_regression.py、examples/define_gradient.py。扩展 Autograd自定义 primitive 与 VJP如果 Autograd 不支持你需要的函数怎么办这通常发生在你的代码依赖外部库调用或 C 代码时有时为了速度或数值稳定性主动给某个纯 Python 函数提供梯度也是个好主意。教程以“数值稳定的log(sum(exp(x)))”为例scipy.special中已有且受支持这里自制一个。第一步用标准 Python 定义函数并加上primitive装饰器import autograd.numpy as np from autograd.extend import primitive, defvjp primitive def logsumexp(x): 数值稳定的 log(sum(exp(x))) max_x np.max(x) return max_x np.log(np.sum(np.exp(x - max_x)))primitive告诉 Autograd不要窥探函数内部而是把它当作梯度稍后可指定的黑盒。带此装饰器的函数可以包含 Python 能执行的任何代码包括调用其他语言。第二步写一个指定logsumexp梯度的函数。它返回一个向量-雅可比积VJP算子——一个把参数g右乘 logsumexp 雅可比矩阵的函数不显式构造矩阵元素def logsumexp_vjp(ans, x): x_shape x.shape return lambda g: np.full(x_shape, g) * np.exp(x - np.full(x_shape, ans))g是最终目标函数对anslogsumexp 的输出的梯度梯度计算可以同时依赖原函数的输入x和输出ans若想支持高阶导数VJP 函数内部的代码必须本身可被 Autograd 微分——通常意味着用其他已有 VJP 的 primitive如 NumPy 函数来写。第三步把 VJP 注册给 Autograddefvjp(logsumexp, logsumexp_vjp)注册的本质是把logsumexp_vjp填入core.py的primitive_vjps表defvjp会按argnums把 VJP maker 翻译进查表结构反向传播时VJPNode.__init__通过primitive_vjps[fun]取用autograd/core.py。现在logsumexp可以出现在任何待求导的大函数里from autograd import grad def example_func(y): z y**2 lse logsumexp(z) return np.sum(lse) grad_of_example grad(example_func) print(Gradient: , grad_of_example(np.array([1.5, 6.7, 1e-10])))完整可运行脚本见 examples/define_gradient.py其中还示范了用check_grads(example_func, modes[rev])做数值校验。顺带说明autograd.extend模块autograd/extend.py正是官方暴露的扩展 API 入口除primitive/defvjp外还提供defjvp、defvjp_argnum、vspace、notrace_primitive等工具。复数支持约定Autograd 支持复数数组与标量约定如下。考虑一个复到复函数f用实部/虚部组件u、v表示def f(z): x, y real(z), imag(z) return u(x, y) v(x, y) * 1jf的grad定义为def grad_f(z): x, y real(z), imag(z) return grad(u, 0)(x, y) - i * grad(u, 1)(x, y)grad的第二个参数指定对哪个参数求导。注意vf的虚部被整个丢弃。该约定覆盖三个重要场景若f是全纯函数得到通常的复导数因为此时grad(u, 0) grad(v, 1)且grad(u, 1) -grad(v, 0)若f是复参数x的实值损失函数得到的结果可直接用于基于梯度的优化器——沿grad(f)(x)的共轭方向取步长若f是实到实函数只是内部恰好用了复数 primitive比如用 FFT 实现卷积其中某些 primitive 必然非全纯则得到的结果与纯实数实现完全一致。该约定不覆盖的情形f是非全纯函数且你需要 du/dx、du/dy、dv/dx、dv/dy 全部四个偏导——那样答案要包含四个实数值无法用一个复数表达。复数 primitive 的 VJP 定义如下def f_vjp(g, z): z_x, z_y real(z), imag(z) g_x, g_y real(g), imag(g) return ( g_x * grad(u, 0)(x, y) - i * g_x * grad(u, 1)(x, y) - g_y * grad(v, 0)(x, y) i * g_y * grad(v, 1)(x, y))对全纯 primitive这就是普通复导数乘以g所以大多数简单数学 primitive 无需改动实数实现对非全纯 primitive它保留全部四个实偏导仿佛把复数当作实数二元组处理只是带上了几个负号。源码层面holomorphic_grad直接对np.real(fun(x))求梯度并对非复数输入给出警告autograd/differential_operators.py。教程中提到更细节的 primitive VJP 定义可参考作者 Dougal 的博士论文第 4 章。更多学习资源仓库 examples/ 目录包含大量复杂示例可直接运行研究简单神经网络、卷积神经网络、循环神经网络、长短期记忆网络LSTM、反向传播穿过流体模拟Matt 在 2017 蒙特利尔深度学习暑期学校的讲座系统讲解了自动微分、Autograd 的实现与高级自动微分技术具体链接见原文档 docs/tutorial.mdAutograd 由 Dougal Maclaurin、David Duvenaud 与 Matthew Johnson 编写并持续开发遇到 bug 或有功能诉求可向维护者提交反馈。小结围绕 docs/tutorial.md本文覆盖了 Autograd 的完整使用闭环从grad求标量梯度、while/if控制流下的泰勒级数求导到逻辑回归的端到端训练从primitive Box VJP 表 反向传播的计算图原理到__array_ufunc__协议下的 Xarray 互操作从 NumPy/SciPy 支持边界与“能/不能用”速查清单到用primitive/defvjp注册自定义 VJP 与复数约定。核心心智模型只有一句话写出普通 NumPy 代码Autograd 负责把梯度算好——需要深入时autograd/core.py 与 autograd/tracer.py 就是最好的说明书。赞分享机器学习【免费下载链接】autogradEfficiently computes derivatives of NumPy code.项目地址https://gitcode.com/gh_mirrors/au/autograd点击查看免费下载相关推荐Oblique渐变角度完全教程8种GradientAngle方向轻松打造视觉渐变图片Oblique渐变角度完全教程8种GradientAngle方向轻松打造视觉渐变图片 想要让 App 里的图片告别呆板的矩形边框呈现出充满设计感的斜角与视觉机器学习MXNet autograd 自动微分实战指南从 NDArray 梯度计算到动态控制流MXNet autograd 自动微分实战指南从 NDArray 梯度计算到动态控制流 autograd 是 MXNet 的自动微分引擎它让求损失函数对权深度学习机器学习人工智能MXNet Gluon 自动微分autograd完全指南从梯度原理到动态图实战MXNet Gluon 自动微分autograd完全指南从梯度原理到动态图实战 本指南以 Apache MXNet 官方教程 autograd 文档 ht深度学习人工智能机器学习分布式训练上一篇Electron.NET本地数据存储终极指南LiteDB与Entity Framework Core集成详解下一篇uosc字幕管理终极指南从搜索下载到外部加载全流程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →