资讯详情 深入理解一维卷积与 PyTorch 自动微分引擎:NYU-DLSP20 第五周讲义精讲
📅 2026/10/10 11:31:41
示例工程【免费下载链接】NYU-DLSP20NYU Deep Learning Spring 2020项目地址https://gitcode.com/gh_mirrors/pyt/pytorch-Deep-Learning点击查看免费下载本篇文章源于 NYU Deep Learning Spring 2020NYU-DLSP20仓库目录pytorch-Deep-Learning第五周第三节讲义对应 docs/ja/week05/05-3.md 及英文原版 docs/en/week05/05-3.md由 Alfredo Canziani 讲授。文章从数据的三重结构性质稀疏性、平稳性、构成性出发系统地讲解一维卷积的数学表示与 PyTorch 中的维度计算随后深入剖析自动微分autograd引擎的反向传播机制最后介绍如何通过继承torch.autograd.Function自定义可微函数。读完本文你将能够独立推导卷积层输出尺寸、手算并验证任意计算图的梯度并能在自己的网络中以插件形式接入自定义前向/反向传播模块。1. 从线性代数到卷积用核替换全连接矩阵在第四周讲义中全连接层被表示为输入向量 $\boldsymbol{x}\in\mathbb{R}^n$ 与权重矩阵 $\boldsymbol{A}\in\mathbb{R}^{m\times n}$ 的乘法 $\boldsymbol{z}\boldsymbol{A}\boldsymbol{x}$。该表示对短序列可行但对于真实音频例如 3.2 秒、采样率 22.05kHz即 7 万多个样本而言权重矩阵会变得异常肥胖参数数量爆炸难以训练。卷积的出发点就是利用自然信号的三条结构性质来约束并简化这一矩阵稀疏性sparsity局部性 locality远离当前位置的数据点对当前输出几乎没有影响因此矩阵中大部分位置可以填 0只保留与核相关的非零块平稳性stationarity自然界的数据信号中某些模式/动机motif会反复出现因此同一个核可以被反复复用即权重共享weight sharing构成性compositionality信号是多个局部特征的组合多层卷积通过叠加不同核来逐层构建更复杂的表示。讲义给出的具体做法是不再使用第 $k$ 列全连接矩阵 $\boldsymbol{A}$而是将矩阵的宽度改为核大小 $k$矩阵的每一行就是一个核。通过对核进行堆叠stacking与平移shifting可以得到高度为 $n-k1$ 的若干层。下图直观展示了核组 $m\times k$ 如何在长度为 $n$ 的输入上滑动该结构正是上一周讲义中提到的Toeplitz 矩阵的推广每条从左到右的下降对角线上的值恒定且整体为稀疏矩阵。其输出是 $m$厚度个尺寸为 $n-k1$ 的向量2. 信号的形式化建模单声道与立体声讲义将单个输入向量视作单声道monophonic信号。形式上输入 $x$ 是一个映射$$ x:\Omega\rightarrow\mathbb{R}^{c} $$其中 $\Omega \lbrace 1, 2, 3, \cdots \rbrace \subset \mathbb{N}^1$因为这是 1 维信号定义域是 1 维的。通道数 $c1$ 时为单声道当 $c2$ 时即为立体声stereophonic信号。这一建模与第四周讲义不同数据集的维度一节完全一致音频数据的定义域是一维离散时间索引通道数可以是 1单声道、2立体声、51杜比 5.1等。对于一维卷积计算方式非常朴素逐核计算标量积内积即把每个核与输入中对应位置的局部窗口做内积然后沿时间轴滑动这种运行中的标量积running scalar product正是卷积的实质——仓库中的 07-listening_to_kernels.ipynb 用一个真实音频片段Windows 系统关机音演示了用多个音高核卷积原信号即可提取出旋律中的各个音符是理解本节的绝佳动手实验。3. PyTorch 中核的维度与输出宽度3.1 用 IPython 问号查看文档讲义给出一个实用技巧在 IPython/Jupyter 中使用?问号可以直接调出函数的完整签名文档。例如nn.Conv1d?3.2 nn.Conv1d 完整签名Init signature: nn.Conv1d( in_channels, # number of channels in the input image out_channels, # number of channels produced by the convolution kernel_size, # size of the convolving kernel stride1, # stride of the convolution padding0, # zero-padding added to both sides of the input dilation1, # spacing between kernel elements groups1, # nb of blocked connections from input to output biasTrue, # if True, adds a learnable bias to the output padding_modezeros, # accepted values zeros and circular )各参数含义如下参数默认值说明in_channels必填输入通道数如立体声信号为 2out_channels必填卷积产生的通道数即核的数量kernel_size必填卷积核大小1D 时为单个整数stride1卷积滑动的步长padding0在输入两侧补零的宽度dilation1核元素之间的间距空洞卷积groups1输入到输出的分组连接数分组卷积biasTrue是否在输出上添加可学习的偏置padding_modezeros填充模式接受zeros与circular对应地1D 卷积输出长度遵循公式stride1、padding0 时即 $n-k1$$$ L_{\text{out}} \left\lfloor \frac{L_{\text{in}} 2\times\text{padding} - \text{dilation}\times(k-1) - 1}{\text{stride}} 1 \right\rfloor $$3.3 一维卷积实例立体声 → 16 通道考虑一个核大小为 $3$、步长为 $1$ 的一维卷积将 $2$ 通道立体声映射为 $16$ 通道即使用 $16$ 个核每个核的厚度为 $2$匹配输入通道数、长度为 $3$。设输入批大小为 $1$、通道数 $2$、样本数 $64$则输出层为 $1$ 个信号、$16$ 个通道、长度 $62 64 - 3 1$。由于每个输出通道对应一个偏置偏置尺寸为 $16$。conv nn.Conv1d(2, 16, 3) # 2 channels (stereo signal), 16 kernels of size 3 conv.weight.size() # output: torch.Size([16, 2, 3]) conv.bias.size() # output: torch.Size([16]) x torch.rand(1, 2, 64) # batch of size 1, 2 channels, 64 samples conv(x).size() # output: torch.Size([1, 16, 62]) conv nn.Conv1d(2, 16, 5) # 2 channels, 16 kernels of size 5 conv(x).size() # output: torch.Size([1, 16, 60])注意权重张量的形状规律[out_channels, in_channels, kernel_size]即[16, 2, 3]——16 个核每个核覆盖 2 个输入通道、长度为 3。当核长度从 3 增至 5 时输出长度相应地从 62 变为 60$64-51$与公式吻合。3.4 二维卷积实例高光谱图像将思路扩展到 2D。设输入为 $1$ 个样本、$20$ 个通道例如高光谱图像、高 $64$、宽 $128$。用 $20$ 个输入通道到 $16$ 个核、核大小为 $3\times 5$ 的二维卷积输出为 $1$ 样本、$16$ 通道、高 $6264-31$、宽 $124128-51$x torch.rand(1, 20, 64, 128) # 1 sample, 20 channels, height 64, and width 128 conv nn.Conv2d(20, 16, (3, 5)) # 20 channels, 16 kernels, kernel size is 3 x 5 conv.weight.size() # output: torch.Size([16, 20, 3, 5]) conv(x).size() # output: torch.Size([1, 16, 62, 124])2D 卷积中核的集合需要用4 个维度存储[out_channels, in_channels, kernel_h, kernel_w]对应上面的[16, 20, 3, 5]。3.5 用 padding 保持空间维度若希望输出与输入尺寸一致可以引入填充。在上例基础上添加stride1与padding(1, 2)即在 $y$ 方向各补 1上 1、下 1$x$ 方向各补 2从而抵消核的收缩效应输出恢复到 $64\times128$# 20 channels, 16 kernels of size 3 x 5, stride is 1, padding of 1 and 2 conv nn.Conv2d(20, 16, (3, 5), 1, (1, 2)) conv(x).size() # output: torch.Size([1, 16, 64, 128])这与仓库中的 06-convnet.ipynb 所演示的卷积神经网络实践相呼应——网络正是依靠卷积 padding 池化的组合在控制特征图尺寸的同时逐层提取特征。4. 自动微分引擎从手算到 autograd本节要求 PyTorch 追踪张量上的全部运算从而自动完成偏导数的计算。仓库根目录下的 03-autograd_tutorial.ipynb 完整复现了本节的全部实验是配套的最佳练习。4.1 构建计算图逐步构造一个标量输出 $a$创建具有梯度累积能力的 $2\times2$ 张量 $\boldsymbol{x}$所有元素减去 $2$ 得到 $\boldsymbol{y}$继续运算$\boldsymbol{z}3\boldsymbol{y}^2$对 $\boldsymbol{z}$ 取均值得到 $a$。import torch # 创建 2x2 且可累积梯度的张量 x torch.tensor([[1, 2], [3, 4]], requires_gradTrue, dtypetorch.float32) y x - 2 # y.grad_fn 为 SubBackward0 ...表示 y 由减法 x-2 产生 z y * y * 3 a z.mean() # 均值标量关键观察点打印y.grad_fn会得到形如SubBackward0 object at 0x...的结果说明y是由减法模块生成的中间量同时可以通过y.grad_fn.next_functions[0][0].variable追溯回原始张量 $\boldsymbol{x}$。这正体现了 autograd 的核心机制——define-by-run动态定义计算图由代码的实际执行顺序动态构建每一次迭代都可以不同。4.2 手算反向传播验证反向传播即计算梯度 $\frac{d a}{d \boldsymbol{x}}$。以手算作为验证$$ \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 \ x \begin{pmatrix} 12\34\end{pmatrix} \ \left(\frac{da}{dx_i}\right)^\top \begin{pmatrix} 1.5-33-3\4.5-36-3\end{pmatrix}\begin{pmatrix} -1.50\1.53\end{pmatrix} \end{aligned} $$执行a.backward()后x.grad将精确等于上述手算结果从而验证了引擎的正确性。讲义同时提醒一个易错点PyTorch 中偏导数的形状与原始数据一致而数学上正确的雅可比Jacobian应是其转置。4.3 动态图与疯狂的梯度autograd 的强大之处在于它完全适应动态控制流。考虑 $1\times3$ 向量 $x$令 $y2x$并不断将 $y$ 翻倍直到其范数不小于 1000x torch.randn(3, requires_gradTrue) y x * 2 i 0 while y.data.norm() 1000: y y * 2 i 1由于 $x$ 的随机性迭代次数 $i$ 无法预先得知但可以借助梯度轻松反推。因为 $y$ 不是标量backward()需要显式传入与输出同形的梯度种子grad_outputgradients 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这里x.grad的值与 $i9$$2^{10}$ 倍放大后除以 norm 判据相互印证——每个元素恰好是初始梯度乘以 $2^{10}1024$对应1.0240e02 0.1×1024、1.0240e03 1.0×1024、1.0240e-01 0.0001×1024。如讲义所述灵活性越大责任越大——动态图正是 PyTorch 区别于静态图框架的核心特性。4.4 requires_grad 与 torch.no_grad()梯度追踪的开关是requires_gradTrue。若在 $x$ 或 $w$ 声明时省略该标记又在 $z$ 上调用backward()由于 $x$、$w$ 上没有梯度累积将触发运行时错误# 两个都允许梯度累积 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)与之相对with torch.no_grad():上下文会临时关闭梯度累积常用于推理阶段或参数冻结场景x torch.arange(1., n 1) w torch.ones(n, requires_gradTrue) # 该上下文内所有张量都不会累积梯度 with torch.no_grad(): z w x try: z.backward() # PyTorch 在此抛出错误因为 z 没有梯度累积 except RuntimeError as e: print(RuntimeError!!! :[) print(e)5. 自定义梯度扩展 torch.autograd.Function除了内置运算我们还可以自定义可微函数/模块并插入到神经网络的计算图中。仓库中的 extra/b-custom_grads.ipynb 提供了完整的示例代码含add、split、max三个自定义模块是本节的标准配套材料。实现方法继承torch.autograd.Function并重写静态方法forward()与backward()。训练网络时只要已知输出关于输入的偏导数就可以利用反向传播的链式法则把该模块插入到运算链的任意位置。forward()的签名约定是第一个参数为上下文ctx用于在反向传播时保存中间计算随后是与输入对应的若干张量backward()则接收与forward输出数量相同的梯度参数并返回与forward输入数量相同的梯度顺序一一对应、且不含ctx。5.1 自定义加法模块梯度向两侧复制加法运算的梯度规则是反向传播时梯度被原样复制到两个输入上# Custom addition module class MyAdd(torch.autograd.Function): staticmethod def forward(ctx, x1, x2): # ctx is a context where we can save # computations for backward. 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) # need to return grads in order # of inputs to forward (excluding ctx) return grad_x1, grad_x2使用自定义函数时通过.apply调用而不是直接实例化my_add MyAdd.apply y my_add(x1, x2) # x1, x2 均 requires_gradTrue z y.mean() z.backward() # x1.grad、x2.grad 被正确填充仓库 notebook 中还给出了一个更精细的变体AddAndAverage它在前向中同时完成求和 均值因此反向时必须除以元素个数x1.numel()这正是不同自定义模块需按其自身前向逻辑推导反向规则的生动示例。5.2 自定义 split 模块梯度在汇聚处求和从同一输入分叉出两个输出的split模块其反向规则是把两条分支传回的梯度相加因为 $x$ 同时影响 $x_1$ 与 $x_2$属于多路径链式法则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): x ctx.saved_tensors[0] print(fgrad_x1: {grad_x1}) print(fgrad_x2: {grad_x2}) return grad_x1 grad_x25.3 自定义 max 模块独热式梯度max模块使用非 PyTorch 代码numpy计算最大值其反向规则是独热one-hot式的只有取得最大值的那个索引处梯度为 1其余位置为 0。实现上通过argmax掩码与上游梯度相乘class MyMax(torch.autograd.Function): staticmethod def forward(ctx, x): # example where we explicitly use non-torch code maximum x.detach().numpy().max() argmax x.detach().eq(maximum).float() ctx.save_for_backward(argmax) return torch.tensor(maximum) staticmethod def backward(ctx, grad_output): argmax ctx.saved_tensors[0] return grad_output * argmax注意forward中使用了x.detach()断开梯度追踪后再做numpy()转换这是在自定义函数中混用非张量计算时的标准做法而backward返回的grad_output * argmax恰好实现了最高索引处为 1、其余为 0的梯度传播。6. 小结与延伸阅读本文沿着数据性质 → 卷积表示 → 维度计算 → 自动微分 → 自定义梯度的主线完整还原了 NYU-DLSP20 第五周第三节讲义的精华。要点回顾一维卷积通过核堆叠 滑动 标量积将全连接矩阵压缩为稀疏、共享权重的 Toeplitz 结构利用了数据的稀疏性、平稳性与构成性在 PyTorch 中卷积层的输出维度由in_channels、out_channels、kernel_size、stride、padding、dilation共同决定权重张量的形状遵循[out_channels, in_channels, *kernel_size]规律autograd 是 define-by-run 的动态计算图引擎backward()沿图反向传播链式法则标量输出可无参调用非标量输出需传入同形梯度种子requires_gradTrue开启梯度追踪torch.no_grad()上下文用于推理阶段关闭追踪继承torch.autograd.Function并重写forward/backward即可将任意甚至含非 PyTorch 代码的运算接入计算图。进一步学习建议动手运行仓库根目录下的 03-autograd_tutorial.ipynb 与 extra/b-custom_grads.ipynb并结合 06-convnet.ipynb 与 07-listening_to_kernels.ipynb 观察卷积在真实网络与音频信号上的应用效果前一讲 docs/en/week04/04-1.md 提供了卷积与线性代数之间过渡的完整推导。赞分享示例工程【免费下载链接】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 深度示例工程Android Debug Database 项目推荐Android Debug Database 项目推荐 1. 项目基础介绍和主要编程语言 Android Debug Database 是一个用于调试 Andr示例工程PyTorch 卷积维度计算与自动微分引擎实战NYU DLSP20 第五周实践课解析PyTorch 卷积维度计算与自动微分引擎实战NYU DLSP20 第五周实践课解析 本篇文章基于 NYU Deep Learning Spring 2020示例工程上一篇5分钟零代码Pinpoint监控指标无缝对接Grafana Cloud的终极指南下一篇如何自定义python-vimrc10个进阶配置技巧打造专属开发环境创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考