ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PyTorch nn.Linear深度解析:从线性变换数学本质到工程实践

PyTorch nn.Linear深度解析:从线性变换数学本质到工程实践 1. 线性变换的本质先从矩阵和向量的关系说起很多读者第一次接触nn.Linear时只是把它当作一个全连接层输入一个向量输出一个向量中间经过了一次矩阵乘法。这个理解没错但如果你只停留在会用的层面那很容易在调试网络、分析梯度、设计自定义层的时候栽跟头。我自己刚用 PyTorch 时也踩过不少坑后来发现根源都是对nn.Linear的数学本质不够清楚。从数学上看nn.Linear做的事情就是一个仿射变换Affine Transformation它是线性映射的一个推广。我们回忆一下线性代数的定义给定两个向量空间 (V) 和 (W)如果映射 (T: V \to W) 满足两个条件——可加性和齐次性——那么 (T) 就是一个线性映射。写成公式就是可加性(T(u v) T(u) T(v))齐次性(T(c \cdot u) c \cdot T(u))如果你把一个 1 维向量 (x) 输入到nn.Linear中它输出的 (y) 满足 (y Wx b)。当偏置 (b 0) 时这就是一个严格的线性映射当 (b \neq 0) 时它是一个仿射变换。很多教材里会把这两者统称为线性层但实际数学上仿射变换并不满足齐次性——因为 (T(0) b \neq 0)。不过 PyTorch 的文档和社区习惯都直接叫它 Linear我们不用在术语上较真但要清楚这一点。为什么要理解这个本质因为你的网络每一次前向传播都是在做一系列这样的变换和激活函数的交替。nn.Linear本身不包含任何非线性能力它的作用是改变数据的维度空间把输入张量投影到另一个线性空间中。这也是为什么在深度学习中非线性必须由激活函数如 ReLU、Sigmoid来提供。如果整个网络只有nn.Linear那无论堆多少层最终都可以等价为一个线性变换这是线性代数的基本结论。有个最直观的例子两个线性变换的复合(T_2(T_1(x)) W_2(W_1 x b_1) b_2 (W_2 W_1) x (W_2 b_1 b_2))这本质上还是一个线性变换。所以没有激活函数深层网络就失去了深度的意义。从几何角度理解这个变换把输入空间中的一组基向量映射到输出空间。比如输入是 (R^d)输出是 (R^m)权重矩阵 (W) 的每一列都可以看作输出空间中的一个基向量的原像。训练过程就是在不断调整这些基的方向和大小使得输入数据经过变换后在输出空间能被更好地分割或拟合。PyTorch 里nn.Linear的核心参数就是in_features和out_features。从数学上看in_features决定了输入向量的维度 (d)out_features决定了输出向量的维度 (m)。权重矩阵的形状是 ((m, d))偏置的形状是 ((m,))。这里有个很关键的细节为什么权重矩阵的形状不是 ((d, m))如果你手写前向传播时会发现如果你的输入是形状为 ((batch_size, d)) 的张量那么要得到 ((batch_size, m)) 的输出就需要用 (x \cdot W^T) 或者 (W \cdot x^T) 的方式。PyTorch 内部选择的是 (W) 的形状为 ((out_features, in_features))这样在前向计算时直接做 (x \cdot W^T) 即可。这种设计是为了在反向传播时让梯度的维度计算更直观。我遇到过不少刚接触的读者会混淆矩阵乘法和逐元素乘法的区别。nn.Linear中绝对没有任何逐元素乘法它全是矩阵乘法。如果你误用了逐元素乘法那输出的每个维度就会变成对应输入维度的某种加权完全破坏了线性映射的语义。2. nn.Linear 的前向传播公式与张量维度推导前面提到nn.Linear的数学表达式是 (y xW^T b)。但实际在 PyTorch 中输入往往不是一个 2D 张量而是带有批量维度的甚至可能是任意高维张量。这里我们必须把它和张量形状的规则一起搞清楚。假设你有一个输入张量x形状为(batch_size, *, in_features)其中*表示任意数量的中间维度。nn.Linear会对除了最后一个维度之外的所有维度进行广播broadcast也就是说它把这个张量看作多个向量的组合每个向量都有in_features个元素然后对每个向量独立做一个线性变换。用 PyTorch 的话来说它只对输入的最后一个维度应用仿射变换其他维度保持不动。举个例子import torch import torch.nn as nn layer nn.Linear(in_features4, out_features3) x torch.randn(2, 5, 4) # 输入形状 (batch_size2, 中间维度5, in_features4) y layer(x) # 输出形状 (2, 5, 3)在这个例子中layer内部有一个形状为(3, 4)的权重矩阵W和一个形状为(3,)的偏置b。前向计算时x会被 reshape 成形状(2*5, 4)也就是一共10个向量每个向量都乘以 (W^T) 并加上 (b)得到形状(2*5, 3)最后再重新 reshape 回(2, 5, 3)。这与把x原样进行x W.T b的结果完全等价。这种设计有一个数学上的优雅之处它保证了对每个样本、每个时间步如果是序列数据、每个空间位置如果是图像特征图都使用同一个权重矩阵进行变换。权重共享让模型参数量大大减少同时让模型具备平移不变性——至少在特征维度上是如此。卷积神经网络的权值共享思想也可以在这里看到影子只不过nn.Linear是全局共享而卷积是局部共享。矩阵乘法的具体过程可以拆解成行视角与列视角两种理解方式。行视角输出的第 (i) 个元素是权重矩阵第 (i) 行与输入向量的点积加上偏置。也就是 (y_i \sum_{j1}^{d} W_{i,j} x_j b_i)。这个公式揭示了每个输出维度都依赖于全部输入维度这也是全连接名称的由来。列视角权重矩阵的第 (j) 列表示输入的第 (j) 个特征对输出的整体贡献。如果某一列的值很大说明对应输入特征对输出有很大影响。这种视角对解释特征重要性很有用。在我自己调试网络时经常遇到维度不匹配的错误比如mat1 and mat2 shapes cannot be multiplied (4x3 and 4x5)。这种错误的根源就是忘记在nn.Linear中权重矩阵形状是(out_features, in_features)而输入是(batch, in_features)所以必须把输入放到矩阵乘法的左边权重放到右边。数学上这是线性映射 (T(x) W x) 的标准写法但由于我们在代码里通常用行向量表示一个样本所以需要转置权重矩阵。这是初学者最容易混淆的地方我甚至见到过有人自己实现nn.Linear时手忙脚乱地把维度转错。还有一个细节是关于偏置的广播机制。偏置虽然形状是(out_features,)但在批处理时它会自动广播到(batch_size, out_features)给每个样本都加上同样的偏置。在数学上这相当于在每个样本的仿射变换中使用同一个平移向量保证了同一次前向传播对相同的输入维度施加一致的偏移。3. 权重和偏置的梯度推导反向传播中的链式法则理解nn.Linear的反向传播是真正从会用到懂原理的分水岭。你不需要像数学专业那样背公式但至少要能推导出权重 (W)、偏置 (b) 和输入 (x) 分别对应的梯度这样在面对梯度消失、梯度爆炸或者自定义复杂网络时才有章可循。我们设输入是一个向量 (x)当前层输出 (y W x b)假设从损失函数 (L) 到 (y) 的梯度是 (\frac{\partial L}{\partial y})这个梯度完全由后续的层决定。那么根据链式法则我们需要求三个偏导(1) (\frac{\partial L}{\partial W})(2) (\frac{\partial L}{\partial b})(3) (\frac{\partial L}{\partial x})。先看 (W) 的梯度。因为 (y_i \sum_{j} W_{i,j} x_j b_i)所以 (\frac{\partial y_i}{\partial W_{i,j}} x_j)。由此可得 [ \frac{\partial L}{\partial W_{i,j}} \frac{\partial L}{\partial y_i} \cdot \frac{\partial y_i}{\partial W_{i,j}} \frac{\partial L}{\partial y_i} \cdot x_j ] 用矩阵形式表示(\frac{\partial L}{\partial W} (\frac{\partial L}{\partial y})^T \cdot x)。注意这里的结果形状与 (W) 相同也是(out_features, in_features)这是保持梯度形状与参数形状一致的约定。再看 (b) 的梯度。因为 (y_i \sum_j W_{i,j} x_j b_i)所以 (\frac{\partial y_i}{\partial b_i} 1)。于是 [ \frac{\partial L}{\partial b_i} \frac{\partial L}{\partial y_i} ] 也就是说偏置的梯度就是损失函数对输出梯度的直接拷贝。在批量训练时需要对所有样本的梯度取平均所以实际计算通常是 (\frac{1}{N} \sum_{n1}^N \frac{\partial L}{\partial y_i^{(n)}})。最关键的是输入 (x) 的梯度因为它要传给上一层的传播。[ \frac{\partial y_i}{\partial x_j} W_{i,j} ] 所以 [ \frac{\partial L}{\partial x_j} \sum_i \frac{\partial L}{\partial y_i} \cdot \frac{\partial y_i}{\partial x_j} \sum_i \frac{\partial L}{\partial y_i} \cdot W_{i,j} ] 用矩阵形式(\frac{\partial L}{\partial x} \frac{\partial L}{\partial y} \cdot W)。这里不需要转置在批量维度上就是grad_output weight。如果你用 PyTorch 的自动求导机制torch.autograd会自动帮你完成这些运算但理解原始推导会让你在设置requires_grad、检查梯度、调整学习率时心里更有数。我自己就有过一次惨痛经历自定义了一个没有偏置的线性层并在反向传播中手动实现了grad_weight grad_output.T input但由于忘记对 batch 维度取平均导致训练时 loss 直接飞到了 NaN。后来我才意识到PyTorch 的nn.Linear在默认情况下会对 batch 内部所有样本的梯度做求和不是平均而优化器更新时则通过学习率来控制步长。这个细微差别如果不懂数学推导很难定位到问题。另外关于梯度的稳定性有一个值得注意的点当输入 (x) 的维度很大时权重矩阵每一行的梯度计算就是所有输入特征的加权求和。如果输入特征数值范围波动很大梯度也会随之产生大的波动可能导致梯度爆炸。这就是为什么我们总是强调要对输入做标准化如 BatchNorm或者在初始化时对权重做方差缩放——本质上是在控制梯度的尺度。4. 参数初始化的数学逻辑为什么不能全置为零初始化是影响nn.Linear训练效果的一个极为隐秘的因素。很多人刚开始写代码时会把权重全部初始化为 0这时问题就来了如果所有权重都是 0那么前向传播时所有神经元的输出都是 0 加上偏置反向传播时所有输入梯度都相同这会导致同一层的所有神经元都变得一模一样称为对称性问题。在数学上这意味着损失函数关于权重矩阵的方向导数处处相等模型无法打破对称更新后的权重依然保持一致这会让整个网络的表达能力退化成一个线性模型和只用一个神经元没有区别。PyTorch 的nn.Linear默认采用一种均匀分布初始化根据源码其权重在 (U(-\sqrt{k}, \sqrt{k})) 中采样其中 (k \frac{1}{in_features})。这个选择不是拍脑袋决定的。从数学上分析如果初始权重方差过大输入信号经过线性变换后方差会被放大很多倍导致输出极易落在激活函数的饱和区如 Sigmoid 的两端使得梯度很小训练停滞。如果权重初始化方差过小信号则会逐渐消失深度网络中梯度更新量级不足也难收敛。在我刚使用 PyTorch 时我经常懒得管初始化直接用默认值。但有一个任务需要训练一个深度只有 50 层的 MLP默认初始化一直难以收敛loss 下降得非常缓慢。后来我改成了 Kaiming 初始化kaiming_uniform_loss 才开始正常下降。这里的数学解释在于ReLU 激活函数的输出有一半为 0相当于信号衰减一半如果初始权重不补偿这个衰减前向传播和反向传播的方差都会逐层递减导致梯度消失。Kaiming 初始化正是为了在每层之间维持一个恒定的方差它只需要知道输入的神经元数量即in_features就可以设定权重标准差为 (\sqrt{2 / in_features})。还有一种常见的初始化是 Xavier 初始化它假设激活函数接近线性如 Tanh、Sigmoid 的未饱和区域所以权重标准差设定为 (\sqrt{2 / (in_features out_features)})。两种初始化的区别不仅仅在公式上更在于它们对应的激活函数行为不同、网络深度的假设不同。你如果从头实现一个自定义的线性层强烈建议至少要调用init.kaiming_uniform_(self.weight, amath.sqrt(5))而不是随机初始化torch.randn后乘一个固定倍数。我之前犯过这样的错误手动初始化标准差为 0.01结果训练 100 轮后 acc 还是 50%。换成合适的初始化策略后10 轮就超过了 90%。训练神经网络的很多 玄学 问题追根溯源往往都能归结为初始化方差没有匹配网络结构。偏置初始化也有讲究。在默认情况下PyTorchnn.Linear的偏置使用 (U(-\sqrt{k}, \sqrt{k})) 均匀分布其中 (k 1 / in_features)。偏置主要作用是平移决策边界初始化时通常不希望它过大地影响早期前向输出。如果你想要更好的收敛尤其是最后一层同时接分类头很多实践会把偏置初始化为 0只保留权重初始化。这不是绝对正确但在大多数分类任务中都能稳定工作。5. 从数学角度看 nn.Linear 的参数量与计算量nn.Linear的参数量公式极其简单一个线性层的参数量 in_features * out_features out_features权重加偏置。这个数字在网络设计时非常重要因为它决定了模型的整体大小、内存占用和过拟合风险。假设你有一个输入维度为 1024、输出维度为 1024 的线性层参数量就是 (1024 \times 1024 1024 1, 049, 600)约 100 万。如果这个层数很多模型瞬间就会变得巨大。计算量方面单次前向传播对一个形状为(batch_size, in_features)的输入需要多少次乘加操作输出是(batch_size, out_features)每个输出元素需要对in_features个输入做乘法和加法所以总的乘加次数是batch_size * out_features * in_features。对于批量大小 32、输入 512、输出 512 的网络就是 (32 \times 512 \times 512 8, 388, 608) 次乘加约 800 万次。在训练时你还需要乘上反向传播的两倍计算量对于权重梯度和输入梯度实际计算负担会更重。这些数学指标能帮你判断一个网络的复杂度和瓶颈。比如你在nn.Linear之后接一个nn.LayerNorm前者的计算量是线性的后者则需要计算每个特征维度的均值和方差也涉及 O(batch × features) 的开销。如果两者并用整体计算量会叠加。在我的一个 NLP 项目中模型的核心就是多层nn.Linear堆叠参数量超过了 5000 万在没有 GPU 的环境下训练一轮需要几十分钟。后来我通过把其中两个线性层的维度从 2048 降到 1024参数量直接减少了四分之三训练速度提升了接近三倍而任务性能只下降了 0.2%。这就是利用参数量的直接计算来指导模型设计的一个实际例子。另一个让人容易忽略的点是bias项是否启用。从数学上说偏置向量的参数量虽然小但对表达能力的贡献却很重要。去掉偏置后决策边界只能通过原点这会显著降低模型的拟合能力。在有些情况下比如你已经在输入上做了中心化处理或者网络后面跟着某个归一化层那么去掉偏置也许没什么影响。但我自己的经验是除非有特别的理由如权重共享约束否则保留偏置通常是更方便、更稳妥的选择。6. 多维输入与批量矩阵乘法实现层面的数学细节前面提到nn.Linear支持任意高维输入但内部到底是怎么利用矩阵乘法实现的对于形状为(batch_size, sequence_length, in_features)的输入比如一个 transformer 中的序列数据你会想用nn.Linear对每个时间步的特征做线性变换。PyTorch 会直接在最后一个维度上做矩阵乘法效果等同于把(batch_size * sequence_length, in_features)作为输入矩阵然后乘以(in_features, out_features)的权重转置。我们可以用批量矩阵乘法的视角理解这个过程。对于批量维度B原始输入形状(B, N, d)nn.Linear的实现在底层可能把它看作一个(B * N, d)的矩阵乘上(d, m)的权重转置。但在一些特殊场景下比如你在做时间序列预测你需要明确每个时间步都会共享同一个权重矩阵这就像一个循环神经网络中的线性变换一样。这里有一个很容易踩坑的实际问题如果你想同时对一个 batch 中的不同样本使用不同的线性权重矩阵nn.Linear是不支持的——它只有一组共享的权重和偏置。如果你真的需要每个样本有自己的线性变换你需要用torch.einsum或者torch.bmm来实现。比如你想对形状为(B, N, d)的输入每个 batch 样本使用一个不同的W矩阵形状为(B, N, m)你就可以用torch.bmm(x, W.transpose(1, 2))来实现。这在数学上与nn.Linear并不一致但它展示了线性变换的底层逻辑。用代码来对比更直观# 标准 nn.Linear layer nn.Linear(4, 3) x torch.randn(2, 5, 4) y1 layer(x) # shape: (2, 5, 3) # 手动实现批量矩阵乘 W layer.weight # shape: (3, 4) b layer.bias # shape: (3,) # 需要先将 W 扩展到 batch 维度 W_expanded W.unsqueeze(0).expand(2, 3, 4) y2 torch.bmm(x, W_expanded.transpose(1, 2)) b # y1 和 y2 完全一致这个例子说明了nn.Linear的本质操作就是一次标准的矩阵乘法加上广播偏置。当你理解了这一点你在面对自定义层时就可以自如地做扩展比如实现线性层共享权重、施加不同的 mask 等。7. 实际训练中的数值稳定性问题梯度消失与梯度爆炸从数学角度分析nn.Linear还有一个绕不开的话题数值稳定性。假设网络有 100 个nn.Linear层每层输出和输入的方差关系是 (\text{Var}(y) \text{Var}(W) \cdot \text{Var}(x) \cdot in_features)。如果初始化方差没有做好缩放方差可能会以指数级递增。举个例子输入方差为 1权重标准差 0.1那么每经过一层输出的方差就变为 (0.1^2 \times in_features \times 1)。如果in_features是 512那么输出方差直接放大到 5.12 倍。100 层之后信号的尺度会变得极大或极小最终要么梯度爆炸要么梯度消失。为什么说这是数学决定的因为矩阵连乘的谱半径决定了信号衰减或放大的速度。权重矩阵的谱半径最大奇异值如果大于 1信号就会逐层放大反之则会逐层缩小。初始化的核心就是在所有层之间保持谱半径尽量接近 1。这在实际操作中意味着当你堆叠大量nn.Linear层时你最好使用残差连接Residual Connection来缓解退化问题。虽然残差连接最初是为了解决深度网络的退化问题但从数学上看它把信号传播路径变成了恒等映射 非线性变换相当于为信号提供了多条穿透路径因此梯度和前向传播都能更顺畅。在我的实践中当网络超过 20 层线性层时我会考虑加 LayerNorm 或残差连接而不是单纯去调整初始化。有一次我尝试了一个 50 层的纯 MLP无论怎么调学习率和初始化训练 loss 都会在某个点突然变成 NaN。后来我用torch.autograd.set_detect_anomaly(True)定位到某个线性层的梯度出现了数值溢出根源是前面若干层的权重矩阵特征值过大。我换成了 Kaiming 初始化并添加了 LayerNorm 后问题立刻消失了。这个排查过程让我深刻体会到理解nn.Linear的数学稳定性比记住某个 API 的调用方式更有价值。8. 从源码验证PyTorch 官方的实现思路可能有人会觉得理解了数学原理就不用看源码了但我要说看源码能帮你把抽象的数学符号和具体的实现一一对应起来。nn.Linear的forward函数可以类比如下伪代码def forward(self, input): return F.linear(input, self.weight, self.bias)F.linear的底层调用是torch.addmm(bias, input, weight.t())也就是计算bias input weight.T。如果你在 GPU 上跑过大批量数据你会发现这个操作可能走 cuBLAS 或 cuDNN 的优化路径。PyTorch 的源码里addmm是一个非常常见的底层算子它把矩阵乘法与加法融合在一起减少了内存访问次数。理解这一点对性能调优很有帮助如果你需要在一个大矩阵上做多次线性变换考虑合并权重矩阵一次性计算往往比循环调用nn.Linear高效得多。另外PyTorch 的nn.Linear在构造函数中有biasTrue参数。设置biasFalse时偏置项就不再参与计算参数数量也随之减少。源代码中还会根据bias的标志决定是否注册bias参数。在自定义网络时如果你要让一个线性层的权重与另一个层共享可以直接把两个模块的weight指向同一个Parameter对象。但要注意共享权重的两个层的梯度会叠加这在数学上等价于对多个输出位置施加同一个线性映射类似于多任务学习中的硬参数共享。还有一个源码中的细节nn.Linear的权重和偏置默认都参与优化器的更新因为它们都被标记为requires_gradTrue。如果你想让某个层冻结不更新需要手动设置requires_gradFalse这经常用于迁移学习中的特征提取器。冻结层在前向传播时仍然会参与计算只是梯度不再回传优化器也不会更新其参数。从数学上说这相当于把一个线性变换视为固定不变的常量你只更新模型的其他部分。9. 常见误用与改进替代方案的数学依据nn.Linear在绝大多数场景下够用但有时你需要在数学上做一些调整否则模型的表达力会受限。最常见的一个调整是去掉偏置项。为什么有时要去偏置因为你可能在一个归一化层之后使用线性层而归一化层已经让数据变成零均值、单位方差了此时偏置的存在与否对表达能力的影响较小。另一个常见场景是在做矩阵低秩分解时把一个大线性层分解为两个较小的线性层数学上这近似于对权重矩阵做低秩近似。假设原来的权重形状是(m, d)可以分解成一个(m, r)和一个(r, d)的线性层其中 (r \min(m, d))。这种分解最早在 LoRA 类工作中被广泛使用能够大幅减少可训练参数量同时保持几乎相同的效果。我在做一个推荐系统模型时就采用过这种思路。原始的 embeddings 拼接之后通过一个nn.Linear(512, 256)输出预测分数规模较大的权重矩阵训练代价不小。后来我把这层替换成了两个线性层先nn.Linear(512, 64)再nn.Linear(64, 256)。参数量从 (512 \times 256 256) 约 13 万降低到 (512 \times 64 64 64 \times 256 256) 约 5 万减少了约 60%而最终的离线指标几乎没有变化。这说明基于低秩近似的改进在数学上是可靠的因为很多真实任务中的权重矩阵本身就是低秩的。另外一个常见的替代改进是把线性层换成卷积层的特殊情况。严格地说nn.Conv1d在kernel_size1时等价于一个对最后一维的线性变换其参数共享和局部连接方式都是一致的。如果输入形状是(batch, channels, seq_len)你既可以用nn.Conv1d(chan_in, chan_out, kernel_size1)也可以用nn.Linear(chan_in, chan_out)并置换维度。两种方式在数学上等价但计算效率和内存布局不同。我的建议是如果你要对序列数据做特征映射优先考虑nn.Linear因为它更清晰如果你已经在处理图像特征图或卷积网络则用 1x1 卷积会更自然。最后还有一点值得从数学上明确nn.Linear不会改变输入的 batch 大小也不会改变除了最后一维以外的其它维度。它满足维度守恒规则形状(..., in_features)变为(..., out_features)。这保证了你可以自由地在序列、时间步、特征之间组合模块这为构建复杂的神经网络架构提供了极大的灵活性。理解这条规则后你在拼接网络时才不会出现维度对不上的疑惑。10. 实战心法如何调试线性层的输出与梯度仅掌握公式还不够真正到了调试网络的时候你会用到一些非常实用的技巧。第一个技巧是在前向传播后检查输出张量的统计量均值和标准差是否在合理范围。如果某个线性层的输出方差突然变得特别大比如超过 (10^4)你就要怀疑前一层的权重初始化或者数据分布有问题。反向传播时你也可以通过 register_hook 来查看每一层的梯度范数。梯度范数可以用torch.norm(grad)得到如果某一层的梯度范数是 NaN 或者为 0说明网络在前向或反向传播中已经数值崩溃。第二个实用技巧是使用torch.autograd.gradcheck验证自定义层的反向传播是否正确实现。它会根据数值差分来校验你提供的梯度公式是否与解析梯度一致。这个工具对理解nn.Linear也十分有帮助你可以写一个自定义的线性函数然后用gradcheck来确认你的反向传播推导是否正确。在我自己写网络组件时这几乎是必做的步骤。第三个技巧是控制变量法。当你的模型训练效果不佳不要急着调整优化器或学习率先检查线性层本身的输出是否符合预期。比如在一个简单任务上把模型缩减为一个只有一两个nn.Linear层的网络如果最小网络都学不好那问题很可能不在模型结构而在数据或训练设置。如果把nn.Linear换成torch.matmul手动实现同样计算结果完全一致也能帮助排除代码层面的 bug。我在开源的模型代码中见过不少把in_features和out_features搞反的情况。这种错误的典型表现就是维度报错但如果你用torch.matmul手动实现就不会出错因为你会自然地把维度理顺。所以我的建议是在初次接触nn.Linear时不妨手动实现一遍前向传播和反向传播哪怕只是在纸上推导一遍也比只看文档的理解要深得多。数学是定义但真正的理解来自于你亲手写出那个正确维度的矩阵乘法并观察到它在反向传播中产生对应的梯度的过程。以上这些是我在使用nn.Linear这个模块时从数学角度积累下来的核心经验。希望它能帮你从调 API升级到懂原理在面对更复杂的网络结构时能够有一个清晰的数学直觉作为支撑。
返回列表