卷积与自动微分引擎实战:NYU-DLSP20 深度学习课程第 5 周 05-3 精讲 示例工程【免费下载链接】NYU-DLSP20NYU Deep Learning Spring 2020项目地址https://gitcode.com/gh_mirrors/pyt/pytorch-Deep-Learning点击查看免费下载本文基于 NYU-DLSP20pytorch-Deep-Learning仓库中的韩文笔记 docs/ko/week05/05-3.md对应英文原版 docs/en/week05/05-3.md系统讲解一维/二维卷积的数学本质、PyTorch 中nn.Conv1d/nn.Conv2d的维度计算以及 PyTorch 自动微分autograd引擎的工作原理与自定义梯度函数的编写方法。读完本文你将掌握卷积核与输出特征图尺寸的推导公式、利用 IPython 问号快速查阅 API 文档的技巧、requires_grad/backward()/torch.no_grad()的正确用法以及通过继承torch.autograd.Function实现任意可微算子的完整套路。本篇笔记源自 Alfredo Canziani 在 2020 年 2 月 25 日的课堂讲解作者为 Leyi Zhu、Siqi Wang、Tao Wang、Anqi Zhang。整篇内容围绕两个核心主题展开合积卷积与自动微分引擎二者正是后续 CNN、RNN 等所有深度模型赖以工作的两块基石。1. 为什么用卷积稀疏性、平稳性与组合性课程首先回答了一个根本问题为什么卷积适合处理图像/信号这类数据原因是真实世界的数据通常具备三种结构性质稀疏性Sparsity每个输出只依赖局部的一小部分输入而非全部输入平稳性Stationarity同一套模式如边缘、纹理在数据的不同位置反复出现组合性Compositionality低层局部特征可以组合出高层的复杂概念。卷积恰好能同时利用这三种性质因此在同样的参数量下卷积网络往往比全连接网络表现更好。仓库中的卷积网络 notebook 06-convnet.ipynb 也用实验印证了这一点在参数量相同的前提下ConvNet 借助图像先验局部性与平稳性取得了更高精度并指出若这些假设不再成立如对像素做随机置换卷积的优势就会丧失。1.1 从矩阵乘法到滑动核回顾 第 4 周笔记 04-1 中讨论过的矩阵 $A$全连接层的输出是 $\boldsymbol{z} \boldsymbol{A}\boldsymbol{x}$矩阵的每一行 $\boldsymbol{a}^{(i)}$ 与整个输入向量做内积。而卷积的思路是不再让矩阵宽度等于输入长度 $n$而是改为一个远小于 $n$ 的核大小 $k$。这样一来矩阵的每一行就是一个核kernel / filter。把核在输入上堆叠stacking并平移shifting每滑动一次计算一次内积就可以得到高度为 $n-k1$ 的 $m$ 层输出$m$ 为核的个数即输出通道数。图 11D 卷积概念示意图核在输入上滑动得到多个通道的卷积结果于是卷积层的输出就是 $m$ 个厚度方向长度为 $n-k1$ 的向量图 21D 卷积结果示意输入长度为 $n$经 $m$ 个长度为 $k$ 的核卷积后得到 $m$ 个长度为 $n-k1$ 的输出1.2 输入信号的形式化描述单个输入向量可以看作一个单声道monophonic信号。把输入 $x$ 形式化为一个映射$$ x:\Omega\rightarrow\mathbb{R}^{c} $$其中 $\Omega \lbrace 1, 2, 3, \cdots \rbrace \subset \mathbb{N}^1$因为这是一维信号定义域是一维的。此时通道数 $c1$当 $c2$ 时输入就变成了立体声stereophonic信号——比如左右两个声道。对一维卷积而言我们只需要**逐个核计算标量积scalar product**即可核与输入中对应位置的窗口逐元素相乘再求和。这一操作逐层进行就得到了完整的输出特征图。2. PyTorch 中的卷积核维度与输出宽度2.1 快速查阅 APIIPython 的问号魔法动手写代码前先记住一个实用技巧在 IPython / Jupyter 中用?问号即可查看函数的文档与签名。例如在单元格中直接输入nn.Conv1d?即可看到nn.Conv1d的完整初始化签名Init signature: nn.Conv1d( in_channels, # 输入信号中的通道数 out_channels, # 卷积产生的通道数 kernel_size, # 卷积核的大小 stride1, # 卷积的步长 padding0, # 输入两侧添加的零填充 dilation1, # 核元素之间的间隔空洞卷积 groups1, # 输入到输出之间被阻断连接的分组数 biasTrue, # 若为 True则向输出添加可学习的偏置 padding_modezeros, # 接受 zeros 与 circular )各参数含义可整理成下表参数默认值含义in_channels必填输入数据的通道数如单声道信号为 1立体声为 2out_channels必填卷积输出的通道数即使用的核filter个数 $m$kernel_size必填卷积核尺寸 $k$stride1核每次滑动的步长padding0输入两侧补零的数量dilation1核元素之间的间距1 即为空洞卷积groups1分组卷积的分组数输入与输出通道被分成groups组biasTrue是否添加可学习的偏置项padding_modezeros填充方式支持zeros与circular循环填充2.2 1D 卷积实例立体声信号到 16 个特征通道课堂给出一个具体例子从 2 通道立体声卷积到 16 通道核大小 $k3$、步长 $1$。此时共有 16 个核每个核的厚度为 2与输入通道数一致、长度为 3。假设输入信号是 batch1、2 通道、64 个采样点那么输出就是 1 个信号、16 个通道、长度为 $64-3162$。由于每个核对应一个偏置偏置向量的尺寸也是 16。conv nn.Conv1d(2, 16, 3) # 2 通道立体声信号16 个尺寸为 3 的核 conv.weight.size() # 输出torch.Size([16, 2, 3]) conv.bias.size() # 输出torch.Size([16]) x torch.rand(1, 2, 64) # batch 为 12 通道64 个采样点 conv(x).size() # 输出torch.Size([1, 16, 62]) conv nn.Conv1d(2, 16, 5) # 2 通道16 个尺寸为 5 的核 conv(x).size() # 输出torch.Size([1, 16, 60])注意上面两组实验揭示的关键规律权重张量的形状为[out_channels, in_channels, kernel_size]即[16, 2, 3]而输出长度严格遵循公式 $n - k 1$。把核从 3 增大到 5 后输出长度从 62 变为 $64-5160$。2.3 2D 卷积实例高光谱图像把思路推广到二维。设输入为 1 个样本、20 个通道例如高光谱图像、高 64、宽 128。使用 16 个尺寸为 $3\times 5$ 的核进行 2D 卷积x torch.rand(1, 20, 64, 128) # 1 个样本20 通道高 64宽 128 conv nn.Conv2d(20, 16, (3, 5)) # 20 通道输入16 个核核尺寸 3 x 5 conv.weight.size() # 输出torch.Size([16, 20, 3, 5]) conv(x).size() # 输出torch.Size([1, 16, 62, 124])可以看到权重形状变为4 维[out_channels, in_channels, kernel_h, kernel_w]即[16, 20, 3, 5]。也就是说进行 2D 卷积时存储核集合需要 4 个维度输出高 $64 - 3 1 62$输出宽 $128 - 5 1 124$即二维情况下每个维度都独立应用 $n-k1$ 公式。2.4 用 padding 保持输入输出同尺寸如果希望输出与输入尺寸一致可以借助 padding。继续上面的代码给卷积添加stride1与padding(1, 2)其中1表示 $y$ 方向上下各补 12表示 $x$ 方向左右各补 2。# 20 通道输入16 个尺寸 3 x 5 的核stride1padding(1, 2) conv nn.Conv2d(20, 16, (3, 5), 1, (1, 2)) conv(x).size() # 输出torch.Size([1, 16, 64, 128])补齐后输出特征图的高宽64 × 128与输入完全一致。这一技巧在搭建深层 CNN如仓库中 06-convnet.ipynb 的CNN类其第一层即为nn.Conv2d(in_channels1, out_channelsn_feature, kernel_size5)时非常常用可避免特征图尺寸随网络加深而快速萎缩。3. 自动梯度引擎Autograd是如何工作的3.1 让 PyTorch 记录张量上的所有运算本部分要回答autograd 如何自动计算偏导数答案是让 PyTorch 在张量层面记录下所有计算过程即构建计算图之后只需调用一次backward()即可得到梯度。课堂给出了一个最小示例步骤如下创建一个具有梯度累积能力的 $2\times2$ 张量 $\boldsymbol{x}$将 $\boldsymbol{x}$ 的所有元素减去 2得到 $\boldsymbol{y}$继续运算$\boldsymbol{z} 3\boldsymbol{y}^2$计算 $\boldsymbol{z}$ 的均值。图 3自动梯度示例的流程图从输入出发依次经过减法、乘法、求均值等反向节点打印y.grad_fn会得到类似SubBackward0 object at 0x12904b290的输出它表明 $\boldsymbol{y}$ 是由减法运算$\boldsymbol{x}-2$生成的。还可以通过y.grad_fn.next_functions[0][0].variable追溯到原始的输入张量——这正是 PyTorch 计算图从结果回溯到输入的可视化路径。3.2 反向传播的手算验证反向传播backpropagation在这里等价于计算梯度 $\frac{d\boldsymbol{a}}{d\boldsymbol{x}}$。课堂先手动推导再用a.backward()验证二者得到的x.grad完全一致。推导过程如下。设 $\boldsymbol{a}$ 为 $\boldsymbol{z}$ 的均值$$ \begin{aligned} a \frac{1}{4} (z_1 z_2 z_3 z_4) \ z_i 3y_i^2 3(x_i-2)^2 \ \frac{da}{dx_i} \frac{1}{4}\times3\times2(x_i-2) \frac{3}{2}x_i-3 \end{aligned} $$取 $\boldsymbol{x} \begin{pmatrix} 12\34\end{pmatrix}$代入后得到$$ \left(\frac{da}{dx_i}\right)^\top \begin{pmatrix} 1.5-33-3\[2mm]4.5-36-3\end{pmatrix}\begin{pmatrix} -1.50\[2mm]1.53\end{pmatrix} $$一个值得注意的细节PyTorch 返回的梯度张量形状与原始数据相同但从数学上严格来说正确的雅可比Jacobian应当是上述结果的转置。PyTorch 内部以向量-雅可比积的形式处理这一转置关系对用户透明。3.3 从基础到进阶动态控制流中的梯度PyTorch 的 autograd 不要求计算图事先固定甚至支持带分支和循环的动态计算图。课堂用一个翻倍直到范数超过 1000的例子演示x torch.randn(3, requires_gradTrue) y x * 2 i 0 while y.data.norm() 1000: y y * 2 i 1由于 $\boldsymbol{x}$ 是随机初始化的循环次数无法预先得知。但借助梯度可以轻松推断当循环次数为 $i$ 时$\boldsymbol{y}$ 相对 $\boldsymbol{x}$ 的缩放系数为 $2^{i1}$因为y x * 2之后又翻了 $i$ 次。传入各不相同的初始梯度反向传播得到gradients torch.FloatTensor([0.1, 1.0, 0.0001]) y.backward(gradients) print(x.grad) tensor([1.0240e02, 1.0240e03, 1.0240e-01]) print(i) 9可以看到三个梯度值分别对应初始梯度乘以 $2^{10}1024$$0.1\times1024102.4$、$1.0\times10241024$、$0.0001\times10240.1024$与输出中的1.0240e02、1.0240e03、1.0240e-01完全吻合。这说明反向传播正确穿越了包含 9 次循环的动态计算图。3.4requires_grad与torch.no_grad()梯度的开关开梯度用requires_gradTrue标记希望追踪梯度累积的张量。下面的示例中$\boldsymbol{x}$ 与 $\boldsymbol{w}$ 都允许梯度累积# x 与 w 均允许梯度累积需先定义 n x torch.arange(1., n 1, requires_gradTrue) w torch.ones(n, requires_gradTrue) z w x z.backward() print(x.grad, w.grad, sep\n)如果 $\boldsymbol{x}$ 或 $\boldsymbol{w}$ 声明时遗漏了requires_gradTrue那么在 $\boldsymbol{z}$ 上调用backward()就会抛出运行时错误RuntimeError因为 $\boldsymbol{x}$ 或 $\boldsymbol{w}$ 上没有梯度累积。关梯度用with torch.no_grad():上下文管理器显式关闭梯度追踪常用于推理阶段或避免对无关张量建图x torch.arange(1., n 1) w torch.ones(n, requires_gradTrue) # 在 torch.no_grad() 上下文内所有张量都不再累积梯度 with torch.no_grad(): z w x try: z.backward() # 由于 z 没有梯度累积PyTorch 在这里会抛出错误 except RuntimeError as e: print(RuntimeError!!! :[) print(e)注意no_grad上下文中的z没有grad_fn因此调用z.backward()会触发RuntimeError上面的try/except正是用来验证这一行为。4. 进阶自定义梯度Custom Gradients4.1 继承torch.autograd.Function除了内置的数值运算我们完全可以定义自己的可微模块/函数并嵌入神经网络的计算图。做法是继承torch.autograd.Function并重写两个静态方法forward(ctx, *args)执行前向运算可通过ctx上下文保存反向阶段所需的张量如ctx.save_for_backward(...)backward(ctx, *grad_outputs)定义该算子的梯度公式接收输出侧传回的梯度返回与forward输入一一对应的梯度。只要知道输出对输入的偏导数就可以借助反向传播链式法则把这个自定义算子插入计算链的任何位置。仓库中的 extra/b-custom_grads.ipynb 完整演示了add、split、max三种自定义模块。4.2 自定义加法模块MyAdd加法算子的梯度规则很简单y x1 x2反向时梯度原样复制到两个输入。课堂笔记给出的实现在 notebook 中略有简化但核心一致# 自定义加法模块 class MyAdd(torch.autograd.Function): staticmethod def forward(ctx, x1, x2): # ctx 是上下文可在其中保存反向阶段需要的中间量 ctx.save_for_backward(x1, x2) return x1 x2 staticmethod def backward(ctx, grad_output): x1, x2 ctx.saved_tensors grad_x1 grad_output * torch.ones_like(x1) grad_x2 grad_output * torch.ones_like(x2) # 返回值必须与 forward 的输入顺序一致ctx 除外 return grad_x1, grad_x2使用方式为my_add MyAdd.apply之后就可以像普通函数一样调用并参与自动微分notebook 中还演示了包含求均值在内的AddAndAverage版本其反向梯度需要除以元素个数x.numel()。4.3 自定义拆分模块MySplitMySplit.forward将输入 $\boldsymbol{x}$ 克隆成两份输出反向传播时由于同一个来源被分到了两处梯度应当相加class MySplit(torch.autograd.Function): staticmethod def forward(ctx, x): ctx.save_for_backward(x) x1 x.clone() x2 x.clone() return x1, x2 staticmethod def backward(ctx, grad_x1, grad_x2): return grad_x1 grad_x2 # 分流处的梯度求和4.4 自定义最大值模块MyMaxMyMax演示了在自定义算子中混用非 torch 代码numpy前向用x.detach().numpy().max()求出最大值并记录最大值位置反向时梯度只流向最大值所在的元素置 1其余位置为 0——这正是argmax的梯度行为class MyMax(torch.autograd.Function): staticmethod def forward(ctx, x): maximum x.detach().numpy().max() # 用非 torch 代码求最大值 argmax x.detach().eq(maximum).float() # 最大值位置为 1其余为 0 ctx.save_for_backward(argmax) return torch.tensor(maximum) staticmethod def backward(ctx, grad_output): argmax ctx.saved_tensors[0] return grad_output * argmax # 梯度只回传到最大值元素总结要点不同自定义模块需要各自重写前向运算与其梯度规则——add梯度复制、split梯度求和、max梯度只流向最大值。这套前向定义 反向公式的机制正是 PyTorch 自动微分引擎可扩展性的根基也是实现自定义损失、特殊激活或不可微操作的平滑近似时最常用的底层接口。5. 小结本篇围绕两个主题展开卷积从数据稀疏性、平稳性、组合性出发理解核滑动 标量积的一维/二维卷积本质掌握 PyTorch 中nn.Conv1d/nn.Conv2d的参数含义、权重形状[out_channels, in_channels, *kernel]与输出尺寸公式 $n-k1$以及用 padding 保持尺寸的实操方法。自动微分理解requires_grad、grad_fn、backward()与torch.no_grad()的协作机制通过手算与代码双重验证梯度最后用torch.autograd.Function编写add/split/max三类自定义梯度模块把自定义算子无缝接入计算图。这些能力将在仓库后续内容如 06-convnet.ipynb 的卷积网络训练中直接复用卷积层负责特征提取autograd 负责自动求导两者共同构成现代深度学习模型训练的标准范式。赞分享示例工程【免费下载链接】NYU-DLSP20NYU Deep Learning Spring 2020项目地址https://gitcode.com/gh_mirrors/pyt/pytorch-Deep-Learning点击查看免费下载相关推荐PyTorch 卷积核维度计算与自动微分引擎NYU-DLSP20 第五周讲义 05-3 深度解读PyTorch 卷积核维度计算与自动微分引擎NYU DLSP20 第五周讲义 05 3 深度解读 本篇技术指南基于 NYU DLSP20PyTorch 深度示例工程深入理解一维卷积与 PyTorch 自动微分引擎NYU-DLSP20 第五周讲义精讲深入理解一维卷积与 PyTorch 自动微分引擎NYU DLSP20 第五周讲义精讲 本篇文章源于 NYU Deep Learning Spring 2020示例工程Jspreadsheet CE 下载与安装全指南NPM、CDN、ZIP 与本地构建Jspreadsheet CE 下载与安装全指南NPM、CDN、ZIP 与本地构建 Jspreadsheet CECommunity Edition是一个示例工程上一篇易班自动化工具智能解决高校日常管理的Python解决方案下一篇精读《JS with 语法》被遗忘的上下文注入关键字及其在沙盒与模板引擎中的实战前端精读周刊创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考