- 示例工程
【免费下载链接】NYU-DLSP20
NYU Deep Learning Spring 2020
本文基于 NYU-DLSP20(NYU Deep Learning Spring 2020)课程第二周第一节讲义(意大利语版,主讲人 Yann LeCun)整理撰写。文章以参数化模型、损失函数、梯度下降、反向传播的数学推导为主线,并结合本仓库的 PyTorch 实践代码与优化器实现,帮助读者建立从「线性层 + 非线性激活」到「自动求导」的完整认知,最终能够在 PyTorch 中独立搭建可训练的多层神经网络并理解其梯度更新过程。
目录
- 参数化模型(Parametrised Models)
- 计算图的框图记法(Block Diagram Notations)
- 损失函数(Loss Functions)
- 梯度下降(Gradient Descent)
- 随机梯度下降的优势(Advantages of SGD)
- 传统神经网络的结构
- 通过非线性函数的反向传播
- 通过加权求和的反向传播
- PyTorch 实现神经网络
- 广义反向传播:通过功能模块与雅可比矩阵
- 多级图(Multi-Stage Graph)中的反向传播
参数化模型(Parametrised Models)
课程首先引入参数化模型的概念。所谓参数化模型,本质上就是一组依赖于**输入(input)与可训练参数(trainable parameters)**的函数:
$$ \bar{y} = G(x,w) $$
其中 $x$ 是输入,$w$ 是参数向量,$\bar{y}$ 是模型输出(预测值)。
从课程原义看,输入与参数之间并没有本质区别——两者都只是函数的自变量。唯一的差异在于参数的共享方式:可训练参数在全部训练样本之间共享,而输入则随样本不同而变化。
在大多数深度学习框架(如 PyTorch)中,参数是**隐式(implicit)**的:调用模型函数时并不显式传入参数,参数被「保存」在函数对象内部——这正是面向对象(object-oriented)建模方式的体现。
在监督学习范式下,模型输出 $\bar{y}$ 会进入损失函数(代价函数)$C(y, \bar{y})$,与真实输出 $y$ 进行比较。参数化模型的计算图如下图所示:
图 1:参数化模型的计算图表示(来源:docs/it/week02/02-1.md)
参数化函数的经典例子
- 线性模型(Linear Model):输入向量各分量的加权求和:
$$ \bar{y} = \sum_i w_i x_i, \quad C(y,\bar{y}) = \Vert y - \bar{y}\Vert^2 $$
- 最近邻(Nearest Neighbour):给定输入向量 $\mathbf{x}$ 与权重矩阵 $\mathbf{W}$(矩阵的每一行由 $k$ 索引),输出是距离输入 $\mathbf{x}$ 最近的矩阵行所对应的 $k$ 值:
$$ \bar{y} = \underset{k}{\arg\min} \Vert x - w_{k,.} \Vert^2 $$
值得注意的是,参数化模型也可以包含非常复杂的函数——线性模型只是最简单的一种特例。
从源码看参数化模型的对象化封装
在 PyTorch 中,参数被封装在nn.Module子类的对象内部,而非每次调用时显式传入。本仓库的螺旋分类实践 04-spiral_classification.ipynb 正是这一思想的直接体现:
# nn package to create our linear model # each Linear module has a weight and bias model = nn.Sequential( nn.Linear(D, H), nn.Linear(H, C) )nn.Linear内部持有权重矩阵与偏置向量,调用model(X)时并不需要把参数作为函数参数传入——参数是「保存」在模块内部的。
计算图的框图记法
课程给出了一套用于绘制计算图(computation graph)的框图符号,这些符号贯穿后续所有章节:
变量(Variables)
- 输入变量(observed input):
表示系统观测到的输入,例如训练数据 $x$;
- 计算变量(computed variable):
表示由某个确定性函数计算得到的变量。
确定性函数(Deterministic Functions)
- 接受多个输入,可产生多个输出;
- 含有隐式参数变量 $w$;
- 圆角一侧表示「容易计算」的方向:在图中,由 $x$ 计算 $\bar{y}$ 比反向更容易(这正是前向传播的方向)。
标量值函数(Scalar-Valued Function)
- 用于表示代价(成本)函数;
- 隐含标量输出;
- 接受多个输入,输出单个值(通常为输入之间的距离度量)。
损失函数
损失函数(loss function)是训练过程中需要最小化的目标函数。课程将其分为两类:
1. 单样本损失(Per Sample Loss)
$$ L(x,y,w) = C(y, G(x,w)) $$
即对单个样本 $(x, y)$,损失等于代价函数在模型输出上的取值。
2. 平均损失(Average Loss)
给定一组样本集合:
$$S = \lbrace (x[p],y[p]) \mid p \in \lbrace 0, \cdots, P-1 \rbrace \rbrace$$
集合 $S$ 上的平均损失为:
$$ L(S,w) = \frac{1}{P} \sum_{(x,y) \in S} L(x,y,w) $$
图 2:带平均损失模型的计算图(来源:docs/it/week02/02-1.md)
在标准监督学习范式下,单样本损失就是代价函数的输出。机器学习本质上是在做函数优化(通常是求最小值);它也可能涉及在两个函数之间寻找纳什均衡(Nash Equilibrium),例如 GAN(生成对抗网络)。这些优化通常通过基于梯度的方法完成,但不一定是梯度下降本身。
梯度下降
基于梯度的方法(Gradient-Based Method)是一类假设可以方便计算函数梯度、从而寻找函数极小值的方法。其前提是目标函数连续且几乎处处可微(并不要求处处可微)。
直觉描述
课程给出一个非常直观的比喻:想象你身处浓雾深夜的山中,想要下山回到村庄,但视线受限——你只能在紧邻的周围寻找最陡的下降方向,然后朝那个方向迈出一步。梯度下降正是「每一步都沿着当前点的负梯度方向前进」的迭代过程。
两种更新规则
- 全量(batch)梯度下降更新规则:
$$ w \leftarrow w - \eta \frac{\partial L(S,w)}{\partial w} $$
- 随机梯度下降(SGD, Stochastic Gradient Descent):先随机挑选一个样本 $p \in \lbrace 0, \cdots, P-1 \rbrace$,再更新:
$$ w \leftarrow w - \eta \frac{\partial L(x[p], y[p],w)}{\partial w} $$
其中 $w$ 表示待优化的参数,$\eta$ 是学习率(步长)。
学习率的深层含义
课程特别指出:这里的 $\eta$ 是常数,但在更复杂的算法中,它可以是矩阵:
- 若 $\eta$ 是半正定矩阵,我们仍然会「向下走」,但不一定沿着最陡下降方向;
- 事实上,最陡下降方向未必是我们想要移动的方向——这是二阶优化方法(如牛顿法及其变体)的核心动机之一。
不可微函数与零阶方法
如果函数不可微——例如存在「洞」、呈阶梯状或平坦区域,梯度不提供任何信息——则需要求助于其他方法,称为零阶方法(0-th Order Methods)或无梯度方法(Gradient-Free Methods)。深度学习几乎完全依赖基于梯度的方法。
强化学习中的梯度估计
强化学习(RL)则涉及梯度估计(Gradient Estimation),因为目标函数通常没有显式的梯度形式。课程以「机器人学骑自行车」为例:机器人会不时摔倒,目标函数衡量自行车保持不倒的时间——但这个目标函数没有梯度。机器人只能不断尝试不同的行为。
关键区别在于:
- 监督学习中,代价函数 $C$ 通常是可微的,且网络基于梯度训练;
- 强化学习中,代价函数 $C$通常不可微,甚至是完全未知的——它像黑盒一样,输入进去、返回一个输出。这使得 RL 效率很低,尤其在参数向量维数很高时(解空间巨大,难以找到移动方向)。
课程进一步介绍 RL 中非常流行的Actor-Critic(演员-评论家)方法:评论家(critic)本质上是一个已知且可训练的模块 $C$,我们可以训练这个可微的 $C$ 模块去近似代价/奖励函数(奖励是负代价,更像惩罚)。这就把代价函数变得可微(至少可用可微函数近似),从而可以对它做反向传播。
仓库中的优化器实现佐证
本仓库 extra/utils/optim.py 定义了一个基于torch.optim.Optimizer的基类Optim,其step()方法遍历所有参数组:
for group in self.param_groups: for p in group['params']: if p.grad is None: continue self.my_step(p, self.state[p], group)子类只需实现my_step()即可定义自己的更新规则。例如 extra/optimization.ipynb 中手写的 SGD:
class SGDOptimizer(Optim): def __init__(self, params, lr): defaults = dict(lr=lr) super(SGDOptimizer, self).__init__(params, defaults) def my_step(self, p, state, group): lr = group['lr'] d_p = p.grad.data p.data.add_(-group['lr'], d_p)这里的p.data.add_(-lr, d_p)正是课程公式 $w \leftarrow w - \eta \frac{\partial L}{\partial w}$ 的直接翻译。
随机梯度下降的优势
实际训练中,我们用随机梯度来计算目标函数关于参数的梯度:不再计算所有样本的平均全量梯度,而是只取一个样本,计算其损失 $L$ 与梯度,然后沿负梯度方向前进一步:
$$ w \leftarrow w - \eta \frac{\partial L(x[p], y[p],w)}{\partial w} $$
即:$w$ 更新为 $w$ 减去「步长 × 该样本损失关于参数的梯度」。
噪声轨迹与平均行为
如果对单个样本做更新,会得到非常嘈杂的轨迹,如下图所示:损失并不是直接「下山」,而是随机的——每个样本都会把损失拉向不同方向,只是平均值最终把我们带向平均损失的极小值。
图 3:单样本更新下的随机梯度下降轨迹(来源:docs/it/week02/02-1.md)
课程强调:虽然看起来低效,但在机器学习语境下,当样本之间存在冗余(redundancy)时,SGD 比全量批量梯度下降快得多。
实际中的批处理(Batching)
在实践中,我们并不会真的在单个样本上做 SGD,而是使用小批量(mini-batch):对一批随机样本的梯度求平均,然后沿负梯度方向前进一步。这样做的唯一理由是更高效地利用现有硬件(GPU、多核 CPU)——批量计算更容易并行化,批处理是最简单的并行化方式。
仓库中的完整训练循环
04-spiral_classification.ipynb 给出了一个标准训练循环,完整体现了「前向 → 计算损失 → 清零梯度 → 反向传播 → 更新参数」的流程:
learning_rate = 1e-3 lambda_l2 = 1e-5 model = nn.Sequential( nn.Linear(D, H), nn.Linear(H, C) ) model.to(device) criterion = torch.nn.CrossEntropyLoss() # we use the optim package to apply # stochastic gradient descent for our parameter updates optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate, weight_decay=lambda_l2) # built-in L2 # Training for t in range(1000): # Feed forward to get the logits y_pred = model(X) # Compute the loss and accuracy loss = criterion(y_pred, y) score, predicted = torch.max(y_pred, 1) acc = (y == predicted).sum().float() / len(y) # zero the gradients before running the backward pass optimizer.zero_grad() # Backward pass to compute the gradient of loss w.r.t our learnable params loss.backward() # Update params optimizer.step()这里可以看到 PyTorch 的批处理更新是隐式的:torch.optim.SGD默认按参数组整体更新(等价于在全量/批量梯度上更新),weight_decay提供了内置的 L2 正则化。而loss.backward()正是课程所讲的自动反向传播——用户无需手写任何梯度公式。
传统神经网络的结构
传统神经网络本质上是线性运算层与逐点非线性运算层交替堆叠:
- 线性运算:概念上就是矩阵-向量乘法——输入向量乘以权重矩阵;
- 非线性运算:将加权和向量的每个分量通过一个简单的非线性函数(如 $\texttt{ReLU}(\cdot)$、$\tanh(\cdot)$ 等)。
图 4:传统神经网络结构(来源:docs/it/week02/02-1.md)
关于「几层网络」的约定
图 4 所示是一个2 层网络,因为计数的是「线性 + 非线性」的成对组合(pairs)。有些人称之为 3 层网络,是因为他们在数变量(层间向量)。
课程特别强调一个关键观察:如果中间没有非线性,那么整个网络可以合并为单层(使用不同的权重)——因为两个线性函数的复合仍然是线性函数。这解释了为什么现代神经网络必须依赖非线性激活函数。
线性块与非线性块的内部结构
下图展示了网络的线性与非线性功能块如何堆叠:
图 5:线性与非线性块的内部结构(来源:docs/it/week02/02-1.md)
在图中,$s[i]$ 是第 $i$ 个单元的加权和:
$$ s[i]=\sum_{j \in UP(i)}w[i,j]\cdot z[j] $$
其中 $UP(i)$ 表示 $i$ 的前驱节点集合,$z[j]$ 是上一层第 $j$ 个输出。而单元输出:
$$ z[i]=f(s[i]) $$
其中 $f$ 是非线性函数。
通过非线性函数的反向传播
反向传播的第一种方式是穿过一个非线性函数。我们把网络中的某个特定非线性函数 $h$ 单独拿出来,其余部分视为「黑盒」。
图 6:通过非线性函数进行反向传播(来源:docs/it/week02/02-1.md)
链式法则(Chain Rule)
使用复合函数求导的链式法则计算梯度:
$$ g(h(s))' = g'(h(s))\cdot h'(s) $$
其中 $h'(s)$ 是 $z$ 关于 $s$ 的导数,即 $\frac{\mathrm{d}z}{\mathrm{d}s}$。把导数之间的连接写清楚,上式可改写为:
$$ \frac{\mathrm{d}C}{\mathrm{d}s} = \frac{\mathrm{d}C}{\mathrm{d}z}\cdot \frac{\mathrm{d}z}{\mathrm{d}s} = \frac{\mathrm{d}C}{\mathrm{d}z}\cdot h'(s) $$
因此,如果网络中存在一连串这样的函数,我们就可以逐个相乘所有 $h$ 函数的导数,一路链式乘法回传到最底层——这就是反向传播的朴素形态。
扰动视角的理解
用「扰动」来思考更直观:
- 将 $s$ 扰动 $\mathrm{d}s$,$z$ 将被扰动:
$$\mathrm{d}z = \mathrm{d}s \cdot h'(s)$$
- 这进而扰动 $C$:
$$ \mathrm{d}C = \mathrm{d}z\cdot\frac{\mathrm{d}C}{\mathrm{d}z} = \mathrm{d}s\cdot h'(s)\cdot\frac{\mathrm{d}C}{\mathrm{d}z} $$
再次得到与链式法则相同的公式。扰动语言把抽象的求导变成了「误差如何逐层传播」的直觉。
通过加权求和的反向传播
对于线性模块,反向传播发生在加权求和上。这里我们把整个网络视为黑盒,只关注从变量 $z$ 连向一组变量 $s$ 的 3 条连接:
图 7:通过加权求和进行反向传播(来源:docs/it/week02/02-1.md)
这次扰动是加权和:$Z$ 影响多个变量。将 $z$ 扰动 $\mathrm{d}z$,$s[0]$、$s[1]$、$s[2]$ 分别被扰动:
$$ \mathrm{d}s[0]=w[0]\cdot \mathrm{d}z $$
$$ \mathrm{d}s[1]=w[1]\cdot \mathrm{d}z $$
$$ \mathrm{d}s[2]=w[2]\cdot\mathrm{d}z $$
这会通过如下方式扰动 $C$:
$$ \mathrm{d}C = \mathrm{d}s[0]\cdot \frac{\mathrm{d}C}{\mathrm{d}s[0]}+\mathrm{d}s[1]\cdot \frac{\mathrm{d}C}{\mathrm{d}s[1]}+\mathrm{d}s[2]\cdot\frac{\mathrm{d}C}{\mathrm{d}s[2]} $$
于是 $C$ 的变化是这 3 个变化的总和:
$$ \frac{\mathrm{d}C}{\mathrm{d}z} = \frac{\mathrm{d}C}{\mathrm{d}s[0]}\cdot w[0]+\frac{\mathrm{d}C}{\mathrm{d}s[1]}\cdot w[1]+\frac{\mathrm{d}C}{\mathrm{d}s[2]}\cdot w[2] $$
这就是线性层反向传播的本质:上游梯度乘以对应权重后求和,即「梯度沿边的权重回传」。它对应了 PyTorch 中nn.Linear在反向传播时,将梯度按权重矩阵转置回传的操作。
PyTorch 实现神经网络
传统神经网络的框图
- 线性块:$s_{k+1}=w_k z_k$
- 非线性块:$z_k=h(s_k)$

其中 $w_k$ 是矩阵,$z_k$ 是向量,$h$ 是对每个分量应用标量函数 $h$。这是一个3 层网络(按线性-非线性成对计数为 3 对),不过大多数现代神经网络并不具有如此清晰的线性/非线性分界,结构要复杂得多。
面向对象的 PyTorch 实现
课程给出了用 PyTorch 以面向对象方式实现神经网络的完整代码:
import torch from torch import nn image = torch.randn(3, 10, 20) d0 = image.nelement() class mynet(nn.Module): def __init__(self, d0, d1, d2, d3): super().__init__() self.m0 = nn.Linear(d0, d1) self.m1 = nn.Linear(d1, d2) self.m2 = nn.Linear(d2, d3) def forward(self, x): z0 = x.view(-1) # appiattimento del vettore di input (flatten input tensor) s1 = self.m0(z0) z1 = torch.relu(s1) s2 = self.m1(z1) z2 = torch.relu(s2) s3 = self.m2(z2) return s3 model = mynet(d0, 60, 40, 10) out = model(image)代码要点解析
- 类定义与参数持有:先为网络定义类,并在构造函数中使用预定义的
nn.Linear类初始化线性层。线性层必须是独立对象,因为每个层都包含自己的参数向量;nn.Linear还会隐式添加偏置向量(bias)。 - 前向函数:定义
forward函数描述如何计算输出,使用torch.relu作为非线性激活。不需要为 relu 单独初始化对象,因为它没有参数。 - 自动求导:我们不需要自己计算梯度——只要给出
forward函数,PyTorch 就知道如何反向传播并计算梯度(这正是 03-autograd_tutorial.ipynb 中详细介绍的 autograd 机制)。
维度流转
以本代码为例:输入image形状为(3, 10, 20),nelement()得到 $d0 = 3\times10\times20 = 600$。forward中先x.view(-1)展平为 600 维向量,随后依次经过m0(600→60)、ReLU、m1(60→40)、ReLU、m2(40→10),最终输出 10 维的 logits(对应 10 个类别)。
广义反向传播:通过功能模块与雅可比矩阵
前面两节是反向传播的特例,课程进一步给出更一般化的形式——通过任意「功能模块(functional module)」的反向传播:
图 8:通过功能模块进行反向传播(来源:docs/it/week02/02-1.md)
向量函数的链式法则
设模块输入为 $z_f$(维度 $[d_f\times 1]$),输出为 $z_g$(维度 $[d_g\times 1]$):
$$ \frac{\partial c}{\partial z_f}=\frac{\partial c}{\partial z_g}\cdot \frac{\partial z_g}{\partial z_f} $$
对应维度关系:
$$ [1\times d_f] = [1\times d_g]\times[d_g\times d_f] $$
这是用链式法则计算 $\frac{\partial c}{\partial z_f}$ 的基本公式。课程特别提醒一个约定:标量函数关于向量的梯度,是与被求导向量同维的向量;为保持记号一致,这里将其写作行向量而非列向量。
雅可比矩阵(Jacobian Matrix)
矩阵 $\frac{\partial z_g}{\partial z_f}$ 的每个元素为:
$$ \left(\frac{\partial z_g}{\partial z_f}\right)_{ij}=\frac{\partial (z_g)_i}{\partial (z_f)_j} $$
也就是说,第 $ij$ 个元素等于输出向量第 $i$ 个分量对输入向量第 $j$ 个分量的偏导。给定损失关于 $z_g$ 的梯度,我们需要这个雅可比矩阵来计算损失关于 $z_f$ 的梯度。
级联(cascade)的关键结论:如果有一串模块,我们只需逐个相乘所有模块的雅可比矩阵,就能得到损失关于所有内部变量的梯度。
从源码看雅可比思想的落地
PyTorch 的 autograd 引擎正是以「反向图」的方式逐节点累积梯度(链式相乘雅可比),03-autograd_tutorial.ipynb 对此有专门讲解;而 extra/b-custom_grads.ipynb 则演示了如何自定义梯度函数——即用户手动提供模块的「雅可比乘积」来参与反向传播。
多级图中的反向传播
考虑神经网络中堆叠的多个模块(多级图,multi-stage graph):
图 9:多级图中的反向传播(来源:docs/it/week02/02-1.md)
两类梯度
反向传播算法需要两组梯度:
- 关于**状态(states)**的梯度——网络中每个模块;
- 关于**权重(weights)**的梯度——特定模块内的所有参数。
因此每个模块关联两个雅可比矩阵,都可以用链式法则进行反向传播。
向量函数的链式法则
对第 $k$ 个模块 $z_{k+1} = f_k(z_k, w_k)$:
关于状态的梯度:
$$ \frac{\partial c}{\partial z_k}=\frac{\partial c}{\partial z_{k+1}}\cdot \frac{\partial z_{k+1}}{\partial z_k}=\frac{\partial c}{\partial z_{k+1}}\cdot \frac{\partial f_k(z_k,w_k)}{\partial z_k} $$
关于权重的梯度:
$$ \frac{\partial c}{\partial w_k}=\frac{\partial c}{\partial z_{k+1}}\cdot \frac{\partial z_{k+1}}{\partial w_k}=\frac{\partial c}{\partial z_{k+1}}\cdot \frac{\partial f_k(z_k,w_k)}{\partial w_k} $$
每个模块的两个雅可比矩阵:
- 一个关于 $z[k]$(状态);
- 一个关于 $w[k]$(权重)。
与训练循环的对应
这条公式链与 PyTorch 的训练流程一一对应:
loss.backward()执行从损失开始、沿多级图逐模块回传的链式求导,计算出每个nn.Linear参数(权重与偏置)的.grad;optimizer.step()则用这些梯度执行更新规则。
以本仓库 extra/optimization.ipynb 中的带动量 SGD 为例,可以看到「梯度累积状态 + 更新参数」的实现:
class SGDMomentumOptimizer(Optim): def __init__(self, params, lr, momentum): defaults = dict(lr=lr, momentum=momentum) super(SGDMomentumOptimizer, self).__init__(params, defaults) def my_step(self, p, state, group): lr = group['lr'] momentum = group['momentum'] d_p = p.grad.data if 'v' not in state: state['v'] = torch.clone(d_p).detach() else: state['v'] = d_p + momentum * state['v'] d_p = state['v'] p.data.add_(-group['lr'] * d_p)其中速度项v的更新v = d_p + momentum * v是带动量 SGD 的标准形式(动量参数momentum通常取 0.9 左右),而p.data.add_(-lr * d_p)仍是课程更新公式的落地。
总结与延伸阅读
- 核心公式链:参数化模型 $\bar{y}=G(x,w)$ → 损失 $L(S,w)=\frac{1}{P}\sum L(x,y,w)$ → 更新规则 $w \leftarrow w - \eta \frac{\partial L}{\partial w}$(SGD 取单个样本或小批量)。
- 反向传播本质:沿计算图反向应用链式法则——非线性块回传乘 $h'(s)$,线性块(加权和)回传按权重加权求和,一般模块则用雅可比矩阵连乘。
- 工程落地:PyTorch 中只需定义
nn.Module子类与forward,autograd 自动完成全部梯度计算;训练循环 =zero_grad()→backward()→step()。
与本主题直接相关的仓库资源:
- docs/it/week02/02-1.md:本文依据的意大利语课程笔记(英文原版);
- 04-spiral_classification.ipynb:含 SGD 优化器与完整训练循环的螺旋分类实践;
- extra/utils/optim.py:
Optim优化器基类; - extra/optimization.ipynb:手写 SGD / 带动量 SGD / RMSProp / Adam 优化器对比;
- 03-autograd_tutorial.ipynb:PyTorch autograd 自动求导教程;
- extra/b-custom_grads.ipynb:自定义梯度(雅可比乘积)的实现示例。
- 示例工程
【免费下载链接】NYU-DLSP20
NYU Deep Learning Spring 2020
相关推荐
梯度下降与反向传播算法精解:NYU-DLSP20 第二周课程笔记(PyTorch 实现篇)
梯度下降与反向传播算法精解:NYU DLSP20 第二周课程笔记(PyTorch 实现篇) 本文整理自 NYU Deep Learning Spring 202
示例工程NYU-DLSP20 第二周深度学习课程:梯度下降、反向传播算法与 PyTorch 神经网络实战
NYU DLSP20 第二周深度学习课程:梯度下降、反向传播算法与 PyTorch 神经网络实战 本文基于 NYU Deep Learning Spring 2
示例工程NYU-DLSP20 第 2 周:梯度下降与反向传播算法导论(PyTorch 实现)
NYU DLSP20 第 2 周:梯度下降与反向传播算法导论(PyTorch 实现) 导读 本文整理自 NYU Deep Learning Spring 202
示例工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考