提交 5c403e9f 编写于 作者: 绝不原创的飞龙's avatar 绝不原创的飞龙

2024-02-04 16:33:02

上级 d8eec95d
......@@ -55,7 +55,7 @@ Gradient function for loss = <BinaryCrossEntropyWithLogitsBackward0 object at 0x
## 计算梯度
为了优化神经网络中参数的权重,我们需要计算损失函数相对于参数的导数,即我们需要在一些固定的`x``y`值下计算\(\frac{\partial loss}{\partial w}\)\(\frac{\partial loss}{\partial b}\)。要计算这些导数,我们调用`loss.backward()`,然后从`w.grad``b.grad`中检索值:
为了优化神经网络中参数的权重,我们需要计算损失函数相对于参数的导数,即我们需要在一些固定的`x``y`值下计算$\frac{\partial loss}{\partial w}$和$\frac{\partial loss}{\partial b}$。要计算这些导数,我们调用`loss.backward()`,然后从`w.grad``b.grad`中检索值:
```py
loss.backward()
......@@ -140,11 +140,11 @@ False
在许多情况下,我们有一个标量损失函数,需要计算相对于某些参数的梯度。然而,有些情况下输出函数是任意张量。在这种情况下,PyTorch 允许您计算所谓的**Jacobian product**,而不是实际梯度。
对于向量函数\(\vec{y}=f(\vec{x})\),其中\(\vec{x}=\langle x_1,\dots,x_n\rangle\)\(\vec{y}=\langle y_1,\dots,y_m\rangle\)\(\vec{y}\)相对于\(\vec{x}\)的梯度由**Jacobian 矩阵**给出:
对于向量函数$\vec{y}=f(\vec{x})$,其中$\vec{x}=\langle x_1,\dots,x_n\rangle$和$\vec{y}=\langle y_1,\dots,y_m\rangle$,$\vec{y}$相对于$\vec{x}$的梯度由**Jacobian 矩阵**给出:
\[J=\left(\begin{array}{ccc} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{1}}{\partial x_{n}}\\ \vdots & \ddots & \vdots\\ \frac{\partial y_{m}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{array}\right)\]
$$J=\left(\begin{array}{ccc} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{1}}{\partial x_{n}}\\ \vdots & \ddots & \vdots\\ \frac{\partial y_{m}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{array}\right)$$
PyTorch 允许您计算给定输入向量\(v=(v_1 \dots v_m)\)**Jacobian Product** \(v^T\cdot J\),而不是计算 Jacobian 矩阵本身。通过使用\(v\)作为参数调用`backward`来实现这一点。\(v\)的大小应该与原始张量的大小相同,我们希望计算乘积的大小:
PyTorch 允许您计算给定输入向量$v=(v_1 \dots v_m)$的**Jacobian Product** $v^T\cdot J$,而不是计算 Jacobian 矩阵本身。通过使用$v$作为参数调用`backward`来实现这一点。$v$的大小应该与原始张量的大小相同,我们希望计算乘积的大小:
```py
inp = torch.eye(4, 5, requires_grad=True)
......
......@@ -22,17 +22,17 @@ PyTorch 的*Autograd*功能是 PyTorch 灵活和快速构建机器学习项目
## 我们为什么需要自动微分?
机器学习模型是一个*函数*,具有输入和输出。在这里讨论中,我们将输入视为一个*i*维向量\(\vec{x}\),其中元素为\(x_{i}\)。然后我们可以将模型*M*表示为输入的矢量值函数:\(\vec{y} = \vec{M}(\vec{x})\)。(我们将 M 的输出值视为矢量,因为一般来说,模型可能具有任意数量的输出。)
机器学习模型是一个*函数*,具有输入和输出。在这里讨论中,我们将输入视为一个*i*维向量$\vec{x}$,其中元素为$x_{i}$。然后我们可以将模型*M*表示为输入的矢量值函数:$\vec{y} = \vec{M}(\vec{x})$。(我们将 M 的输出值视为矢量,因为一般来说,模型可能具有任意数量的输出。)
由于我们将主要讨论自动微分在训练的上下文中,我们感兴趣的输出将是模型的损失。*损失函数* L(\(\vec{y}\)) = L(\(\vec{M}\)(\(\vec{x}\)))是模型输出的单值标量函数。这个函数表达了我们的模型预测与特定输入的*理想*输出相差多远。*注意:在此之后,我们经常会省略向量符号,只要在上下文中清楚即可 - 例如,* \(y\) 而不是 \(\vec y\)
由于我们将主要讨论自动微分在训练的上下文中,我们感兴趣的输出将是模型的损失。*损失函数* L($\vec{y}$) = L($\vec{M}$($\vec{x}$))是模型输出的单值标量函数。这个函数表达了我们的模型预测与特定输入的*理想*输出相差多远。*注意:在此之后,我们经常会省略向量符号,只要在上下文中清楚即可 - 例如,* $y$ 而不是 $\vec y$
在训练模型时,我们希望最小化损失。在理想情况下,对于一个完美的模型,这意味着调整其学习权重 - 即函数的可调参数 - 使得所有输入的损失为零。在现实世界中,这意味着一个迭代的过程,微调学习权重,直到我们看到我们对各种输入获得了可接受的损失。
我们如何决定在多远和哪个方向微调权重?我们希望*最小化*损失,这意味着使其对输入的一阶导数等于 0:\(\frac{\partial L}{\partial x} = 0\)
我们如何决定在多远和哪个方向微调权重?我们希望*最小化*损失,这意味着使其对输入的一阶导数等于 0:$\frac{\partial L}{\partial x} = 0$
然而,请记住,损失并不是*直接*从输入导出的,而是模型输出的函数(这是输入的函数),\(\frac{\partial L}{\partial x}\) = \(\frac{\partial {L({\vec y})}}{\partial x}\)。根据微分计算的链式法则,我们有\(\frac{\partial {L({\vec y})}}{\partial x}\) = \(\frac{\partial L}{\partial y}\frac{\partial y}{\partial x}\) = \(\frac{\partial L}{\partial y}\frac{\partial M(x)}{\partial x}\)
然而,请记住,损失并不是*直接*从输入导出的,而是模型输出的函数(这是输入的函数),$\frac{\partial L}{\partial x}$ = $\frac{\partial {L({\vec y})}}{\partial x}$。根据微分计算的链式法则,我们有$\frac{\partial {L({\vec y})}}{\partial x}$ = $\frac{\partial L}{\partial y}\frac{\partial y}{\partial x}$ = $\frac{\partial L}{\partial y}\frac{\partial M(x)}{\partial x}$
\(\frac{\partial M(x)}{\partial x}\) 是复杂的地方。如果我们再次使用链式法则展开表达式,模型输出相对于输入的偏导数将涉及每个乘以学习权重、每个激活函数和模型中的每个其他数学变换的许多局部偏导数。每个这样的局部偏导数的完整表达式是通过计算图中以我们试图测量梯度的变量结尾的*每条可能路径*的局部梯度的乘积之和。
$\frac{\partial M(x)}{\partial x}$ 是复杂的地方。如果我们再次使用链式法则展开表达式,模型输出相对于输入的偏导数将涉及每个乘以学习权重、每个激活函数和模型中的每个其他数学变换的许多局部偏导数。每个这样的局部偏导数的完整表达式是通过计算图中以我们试图测量梯度的变量结尾的*每条可能路径*的局部梯度的乘积之和。
特别是,我们对学习权重上的梯度感兴趣 - 它们告诉我们*改变每个权重的方向*以使损失函数更接近零。
......@@ -54,7 +54,7 @@ import matplotlib.ticker as ticker
import math
```
接下来,我们将创建一个输入张量,其中包含区间\([0, 2{\pi}]\)上均匀间隔的值,并指定`requires_grad=True`。(像大多数创建张量的函数一样,`torch.linspace()`接受一个可选的`requires_grad`选项。)设置此标志意味着在接下来的每次计算中,autograd 将在该计算的输出张量中累积计算的历史。
接下来,我们将创建一个输入张量,其中包含区间$[0, 2{\pi}]$上均匀间隔的值,并指定`requires_grad=True`。(像大多数创建张量的函数一样,`torch.linspace()`接受一个可选的`requires_grad`选项。)设置此标志意味着在接下来的每次计算中,autograd 将在该计算的输出张量中累积计算的历史。
```py
a = torch.linspace(0., 2. * math.pi, steps=25, requires_grad=True)
......@@ -96,7 +96,7 @@ tensor([ 0.0000e+00, 2.5882e-01, 5.0000e-01, 7.0711e-01, 8.6603e-01,
grad_fn=<SinBackward0>)
```
这个`grad_fn`给了我们一个提示,即当我们执行反向传播步骤并计算梯度时,我们需要计算所有这个张量的输入的\(\sin(x)\)的导数。
这个`grad_fn`给了我们一个提示,即当我们执行反向传播步骤并计算梯度时,我们需要计算所有这个张量的输入的$\sin(x)$的导数。
让我们进行更多的计算:
......@@ -199,7 +199,7 @@ d = c + 1
out = d.sum()
```
添加一个常数,就像我们计算`d`时所做的那样,不会改变导数。这留下了\(c = 2 * b = 2 * \sin(a)\),其导数应该是\(2 * \cos(a)\)。从上面的图中可以看到,这正是我们看到的。
添加一个常数,就像我们计算`d`时所做的那样,不会改变导数。这留下了$c = 2 * b = 2 * \sin(a)$,其导数应该是$2 * \cos(a)$。从上面的图中可以看到,这正是我们看到的。
请注意,只有计算的*叶节点*的梯度被计算。例如,如果您尝试`print(c.grad)`,您会得到`None`。在这个简单的例子中,只有输入是叶节点,因此只有它的梯度被计算。
......@@ -477,19 +477,19 @@ Self CUDA time total: 32.191ms
## 高级主题:更多 Autograd 细节和高级 API
如果你有一个具有 n 维输入和 m 维输出的函数\(\vec{y}=f(\vec{x})\),完整的梯度是一个矩阵,表示每个输出对每个输入的导数,称为*雅可比矩阵*
如果你有一个具有 n 维输入和 m 维输出的函数$\vec{y}=f(\vec{x})$,完整的梯度是一个矩阵,表示每个输出对每个输入的导数,称为*雅可比矩阵*
\[J = \left(\begin{array}{ccc} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{1}}{\partial x_{n}}\\ \vdots & \ddots & \vdots\\ \frac{\partial y_{m}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{array}\right)\]
$$J = \left(\begin{array}{ccc} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{1}}{\partial x_{n}}\\ \vdots & \ddots & \vdots\\ \frac{\partial y_{m}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{array}\right)$$
如果你有一个第二个函数,\(l=g\left(\vec{y}\right)\),它接受 m 维输入(即与上面输出相同维度),并返回一个标量输出,你可以将其相对于\(\vec{y}\)的梯度表示为一个列向量,\(v=\left(\begin{array}{ccc}\frac{\partial l}{\partial y_{1}} & \cdots & \frac{\partial l}{\partial y_{m}}\end{array}\right)^{T}\) - 这实际上只是一个一列雅可比矩阵。
如果你有一个第二个函数,$l=g\left(\vec{y}\right)$,它接受 m 维输入(即与上面输出相同维度),并返回一个标量输出,你可以将其相对于$\vec{y}$的梯度表示为一个列向量,$v=\left(\begin{array}{ccc}\frac{\partial l}{\partial y_{1}} & \cdots & \frac{\partial l}{\partial y_{m}}\end{array}\right)^{T}$ - 这实际上只是一个一列雅可比矩阵。
更具体地,想象第一个函数是你的 PyTorch 模型(可能有许多输入和许多输出),第二个函数是一个损失函数(以模型的输出为输入,损失值为标量输出)。
如果我们将第一个函数的雅可比矩阵乘以第二个函数的梯度,并应用链式法则,我们得到:
\[J^{T}\cdot v=\left(\begin{array}{ccc} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{1}}\\ \vdots & \ddots & \vdots\\ \frac{\partial y_{1}}{\partial x_{n}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{array}\right)\left(\begin{array}{c} \frac{\partial l}{\partial y_{1}}\\ \vdots\\ \frac{\partial l}{\partial y_{m}} \end{array}\right)=\left(\begin{array}{c} \frac{\partial l}{\partial x_{1}}\\ \vdots\\ \frac{\partial l}{\partial x_{n}} \end{array}\right)\]
$$J^{T}\cdot v=\left(\begin{array}{ccc} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{1}}\\ \vdots & \ddots & \vdots\\ \frac{\partial y_{1}}{\partial x_{n}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{array}\right)\left(\begin{array}{c} \frac{\partial l}{\partial y_{1}}\\ \vdots\\ \frac{\partial l}{\partial y_{m}} \end{array}\right)=\left(\begin{array}{c} \frac{\partial l}{\partial x_{1}}\\ \vdots\\ \frac{\partial l}{\partial x_{n}} \end{array}\right)$$
注意:你也可以使用等效的操作\(v^{T}\cdot J\),并得到一个行向量。
注意:你也可以使用等效的操作$v^{T}\cdot J$,并得到一个行向量。
得到的列向量是第二个函数相对于第一个函数的输入的*梯度* - 或者在我们的模型和损失函数的情况下,是损失相对于模型输入的梯度。
......@@ -547,7 +547,7 @@ torch.autograd.functional.jacobian(exp_adder, inputs)
(tensor([[4.1137]]), tensor([[3.]]))
```
如果仔细观察,第一个输出应该等于\(2e^x\)(因为\(e^x\)的导数是\(e^x\)),第二个值应该是 3。
如果仔细观察,第一个输出应该等于$2e^x$(因为$e^x$的导数是$e^x$),第二个值应该是 3。
当然,您也可以使用高阶张量来做到这一点:
......
......@@ -20,7 +20,7 @@
+ 用于构建和训练神经网络的自动微分
我们将使用拟合\(y=\sin(x)\)的问题作为运行示例,使用三阶多项式。网络将有四个参数,并将通过梯度下降进行训练,通过最小化网络输出与真实输出之间的欧几里德距离来拟合随机数据。
我们将使用拟合$y=\sin(x)$的问题作为运行示例,使用三阶多项式。网络将有四个参数,并将通过梯度下降进行训练,通过最小化网络输出与真实输出之间的欧几里德距离来拟合随机数据。
注意
......@@ -242,7 +242,7 @@ print(f'Result: y = {a.item()} + {b.item()} x + {c.item()} x² + {d.item()} x³'
在 PyTorch 中,我们可以通过定义`torch.autograd.Function`的子类并实现`forward``backward`函数来轻松定义自己的自动求导运算符。然后,我们可以通过构建一个实例并像调用函数一样调用它来使用我们的新自动求导运算符,传递包含输入数据的张量。
在这个例子中,我们将我们的模型定义为\(y=a+b P_3(c+dx)\)而不是\(y=a+bx+cx²+dx³\),其中\(P_3(x)=\frac{1}{2}\left(5x³-3x\right)\)是三次[勒让德多项式](https://en.wikipedia.org/wiki/Legendre_polynomials)。我们编写自定义的自动求导函数来计算\(P_3\)的前向和反向,并使用它来实现我们的模型:
在这个例子中,我们将我们的模型定义为$y=a+b P_3(c+dx)$而不是$y=a+bx+cx²+dx³$,其中$P_3(x)=\frac{1}{2}\left(5x³-3x\right)$是三次[勒让德多项式](https://en.wikipedia.org/wiki/Legendre_polynomials)。我们编写自定义的自动求导函数来计算$P_3$的前向和反向,并使用它来实现我们的模型:
```py
# -*- coding: utf-8 -*-
......
......@@ -30,7 +30,7 @@
![fgsm_panda_image](img/d74012096c3134b776b5e9f70e8178f3.png)
从图中可以看出,\(\mathbf{x}\) 是原始输入图像,被正确分类为“熊猫”,\(y\)\(\mathbf{x}\)的地面真实标签,\(\mathbf{\theta}\) 代表模型参数,\(J(\mathbf{\theta}, \mathbf{x}, y)\) 是用于训练网络的损失。攻击将梯度反向传播回输入数据,计算\(\nabla_{x} J(\mathbf{\theta}, \mathbf{x}, y)\)。然后,它通过一个小步骤(即\(\epsilon\) 或图片中的 \(0.007\))调整输入数据的方向(即\(sign(\nabla_{x} J(\mathbf{\theta}, \mathbf{x}, y))\)),以最大化损失。得到的扰动图像\(x'\),然后被目标网络误分类为“长臂猿”,而实际上仍然是“熊猫”。
从图中可以看出,$\mathbf{x}$ 是原始输入图像,被正确分类为“熊猫”,$y$ 是$\mathbf{x}$的地面真实标签,$\mathbf{\theta}$ 代表模型参数,$J(\mathbf{\theta}, \mathbf{x}, y)$ 是用于训练网络的损失。攻击将梯度反向传播回输入数据,计算$\nabla_{x} J(\mathbf{\theta}, \mathbf{x}, y)$。然后,它通过一个小步骤(即$\epsilon$ 或图片中的 $0.007$)调整输入数据的方向(即$sign(\nabla_{x} J(\mathbf{\theta}, \mathbf{x}, y))$),以最大化损失。得到的扰动图像$x'$,然后被目标网络误分类为“长臂猿”,而实际上仍然是“熊猫”。
希望现在这个教程的动机已经清楚了,让我们开始实施吧。
......@@ -52,7 +52,7 @@ import matplotlib.pyplot as plt
本教程只有三个输入,并定义如下:
+ `epsilons` - 用于运行的 epsilon 值列表。在列表中保留 0 是重要的,因为它代表了模型在原始测试集上的性能。直观上,我们会期望 epsilon 越大,扰动越明显,但攻击在降低模型准确性方面更有效。由于数据范围在 \([0,1]\) 这里,没有 epsilon 值应超过 1。
+ `epsilons` - 用于运行的 epsilon 值列表。在列表中保留 0 是重要的,因为它代表了模型在原始测试集上的性能。直观上,我们会期望 epsilon 越大,扰动越明显,但攻击在降低模型准确性方面更有效。由于数据范围在 $[0,1]$ 这里,没有 epsilon 值应超过 1。
+ `pretrained_model` - 预训练的 MNIST 模型的路径,该模型是使用 [pytorch/examples/mnist](https://github.com/pytorch/examples/tree/master/mnist) 训练的。为简单起见,可以在[这里](https://drive.google.com/file/d/1HJV2nUHJqclXQ8flKvcWmjZ-OU5DGatl/view?usp=drive_link)下载预训练模型。
......@@ -166,11 +166,11 @@ Net(
### FGSM 攻击
现在,我们可以定义一个函数,通过扰动原始输入来创建对抗性示例。`fgsm_attack` 函数接受三个输入,*image* 是原始干净图像(\(x\)),*epsilon* 是像素级扰动量(\(\epsilon\)),*data_grad* 是损失相对于输入图像的梯度(\(\nabla_{x} J(\mathbf{\theta}, \mathbf{x}, y)\))。然后,函数创建扰动图像如下:
现在,我们可以定义一个函数,通过扰动原始输入来创建对抗性示例。`fgsm_attack` 函数接受三个输入,*image* 是原始干净图像($x$),*epsilon* 是像素级扰动量($\epsilon$),*data_grad* 是损失相对于输入图像的梯度($\nabla_{x} J(\mathbf{\theta}, \mathbf{x}, y)$)。然后,函数创建扰动图像如下:
\[perturbed\_image = image + epsilon*sign(data\_grad) = x + \epsilon * sign(\nabla_{x} J(\mathbf{\theta}, \mathbf{x}, y)) \]
$$perturbed\_image = image + epsilon*sign(data\_grad) = x + \epsilon * sign(\nabla_{x} J(\mathbf{\theta}, \mathbf{x}, y)) $$
最后,为了保持数据的原始范围,扰动图像被剪切到范围 \([0,1]\)
最后,为了保持数据的原始范围,扰动图像被剪切到范围 $[0,1]$
```py
# FGSM attack code
......@@ -207,7 +207,7 @@ def denorm(batch, mean=[0.1307], std=[0.3081]):
### 测试函数
最后,这个教程的核心结果来自 `test` 函数。每次调用此测试函数都会在 MNIST 测试集上执行完整的测试步骤,并报告最终准确性。但请注意,此函数还接受一个 *epsilon* 输入。这是因为 `test` 函数报告了受到强度为 \(\epsilon\) 的对手攻击的模型的准确性。更具体地说,对于测试集中的每个样本,该函数计算损失相对于输入数据的梯度(\(data\_grad\)),使用 `fgsm_attack` 创建扰动图像(\(perturbed\_data\)),然后检查扰动示例是否是对抗性的。除了测试模型的准确性外,该函数还保存并返回一些成功的对抗性示例,以便稍后进行可视化。
最后,这个教程的核心结果来自 `test` 函数。每次调用此测试函数都会在 MNIST 测试集上执行完整的测试步骤,并报告最终准确性。但请注意,此函数还接受一个 *epsilon* 输入。这是因为 `test` 函数报告了受到强度为 $\epsilon$ 的对手攻击的模型的准确性。更具体地说,对于测试集中的每个样本,该函数计算损失相对于输入数据的梯度($data\_grad$),使用 `fgsm_attack` 创建扰动图像($perturbed\_data$),然后检查扰动示例是否是对抗性的。除了测试模型的准确性外,该函数还保存并返回一些成功的对抗性示例,以便稍后进行可视化。
```py
def test( model, device, test_loader, epsilon ):
......@@ -281,7 +281,7 @@ def test( model, device, test_loader, epsilon ):
### 运行攻击
实现的最后一部分是实际运行攻击。在这里,我们对*epsilons*输入中的每个 epsilon 值运行完整的测试步骤。对于每个 epsilon 值,我们还保存最终的准确率和一些成功的对抗性示例,以便在接下来的部分中绘制。请注意,随着 epsilon 值的增加,打印出的准确率也在降低。另外,请注意\(\epsilon=0\)的情况代表原始的测试准确率,没有攻击。
实现的最后一部分是实际运行攻击。在这里,我们对*epsilons*输入中的每个 epsilon 值运行完整的测试步骤。对于每个 epsilon 值,我们还保存最终的准确率和一些成功的对抗性示例,以便在接下来的部分中绘制。请注意,随着 epsilon 值的增加,打印出的准确率也在降低。另外,请注意$\epsilon=0$的情况代表原始的测试准确率,没有攻击。
```py
accuracies = []
......@@ -308,7 +308,7 @@ Epsilon: 0.3 Test Accuracy = 1418 / 10000 = 0.1418
### 准确率 vs Epsilon
第一个结果是准确率与 epsilon 的图。正如前面提到的,随着 epsilon 的增加,我们预计测试准确率会降低。这是因为更大的 epsilon 意味着我们朝着最大化损失的方向迈出更大的一步。请注意,尽管 epsilon 值是线性间隔的,但曲线的趋势并不是线性的。例如,在\(\epsilon=0.05\)时的准确率仅比\(\epsilon=0\)时低约 4%,但在\(\epsilon=0.2\)时的准确率比\(\epsilon=0.15\)低 25%。另外,请注意,在\(\epsilon=0.25\)\(\epsilon=0.3\)之间,模型的准确率达到了一个随机准确率,这是一个 10 类分类器。
第一个结果是准确率与 epsilon 的图。正如前面提到的,随着 epsilon 的增加,我们预计测试准确率会降低。这是因为更大的 epsilon 意味着我们朝着最大化损失的方向迈出更大的一步。请注意,尽管 epsilon 值是线性间隔的,但曲线的趋势并不是线性的。例如,在$\epsilon=0.05$时的准确率仅比$\epsilon=0$时低约 4%,但在$\epsilon=0.2$时的准确率比$\epsilon=0.15$低 25%。另外,请注意,在$\epsilon=0.25$和$\epsilon=0.3$之间,模型的准确率达到了一个随机准确率,这是一个 10 类分类器。
```py
plt.figure(figsize=(5,5))
......@@ -325,7 +325,7 @@ plt.show()
### 示例对抗性示例
记住没有免费午餐的概念吗?在这种情况下,随着 epsilon 的增加,测试准确率降低**但**扰动变得更容易察觉。实际上,攻击者必须考虑准确率降低和可察觉性之间的权衡。在这里,我们展示了每个 epsilon 值下一些成功的对抗性示例的示例。图的每一行显示不同的 epsilon 值。第一行是\(\epsilon=0\)的示例,代表没有扰动的原始“干净”图像。每个图像的标题显示“原始分类 -> 对抗性分类”。请注意,在\(\epsilon=0.15\)时,扰动开始变得明显,在\(\epsilon=0.3\)时非常明显。然而,在所有情况下,人类仍然能够识别出正确的类别,尽管增加了噪音。
记住没有免费午餐的概念吗?在这种情况下,随着 epsilon 的增加,测试准确率降低**但**扰动变得更容易察觉。实际上,攻击者必须考虑准确率降低和可察觉性之间的权衡。在这里,我们展示了每个 epsilon 值下一些成功的对抗性示例的示例。图的每一行显示不同的 epsilon 值。第一行是$\epsilon=0$的示例,代表没有扰动的原始“干净”图像。每个图像的标题显示“原始分类 -> 对抗性分类”。请注意,在$\epsilon=0.15$时,扰动开始变得明显,在$\epsilon=0.3$时非常明显。然而,在所有情况下,人类仍然能够识别出正确的类别,尽管增加了噪音。
```py
# Plot several examples of adversarial samples at each epsilon
......
......@@ -22,19 +22,19 @@
GAN 是一个框架,用于教授深度学习模型捕获训练数据分布,以便我们可以从相同分布生成新数据。GAN 是由 Ian Goodfellow 于 2014 年发明的,并首次在论文[生成对抗网络](https://papers.nips.cc/paper/5423-generative-adversarial-nets.pdf)中描述。它们由两个不同的模型组成,一个*生成器*和一个*判别器*。生成器的任务是生成看起来像训练图像的“假”图像。判别器的任务是查看图像并输出它是来自真实训练图像还是来自生成器的假图像的概率。在训练过程中,生成器不断尝试欺骗判别器,生成越来越好的假图像,而判别器则努力成为更好的侦探,并正确分类真实和假图像。这个游戏的平衡是当生成器生成完美的假图像,看起来就像直接来自训练数据时,判别器总是以 50%的置信度猜测生成器的输出是真实的还是假的。
现在,让我们定义一些符号,这些符号将在整个教程中使用,从判别器开始。让\(x\)表示代表图像的数据。\(D(x)\)是判别器网络,它输出\(x\)来自训练数据而不是生成器的(标量)概率。在这里,由于我们处理的是图像,\(D(x)\)的输入是 CHW 大小为 3x64x64 的图像。直观地说,当\(x\)来自训练数据时,\(D(x)\)应该是高的,当\(x\)来自生成器时,\(D(x)\)应该是低的。\(D(x)\)也可以被视为传统的二元分类器。
现在,让我们定义一些符号,这些符号将在整个教程中使用,从判别器开始。让$x$表示代表图像的数据。$D(x)$是判别器网络,它输出$x$来自训练数据而不是生成器的(标量)概率。在这里,由于我们处理的是图像,$D(x)$的输入是 CHW 大小为 3x64x64 的图像。直观地说,当$x$来自训练数据时,$D(x)$应该是高的,当$x$来自生成器时,$D(x)$应该是低的。$D(x)$也可以被视为传统的二元分类器。
对于生成器的表示,让\(z\)是从标准正态分布中采样的潜在空间向量。\(G(z)\)表示生成器函数,它将潜在向量\(z\)映射到数据空间。生成器\(G\)的目标是估计训练数据来自的分布(\(p_{data}\)),以便可以从该估计分布(\(p_g\))生成假样本。
对于生成器的表示,让$z$是从标准正态分布中采样的潜在空间向量。$G(z)$表示生成器函数,它将潜在向量$z$映射到数据空间。生成器$G$的目标是估计训练数据来自的分布($p_{data}$),以便可以从该估计分布($p_g$)生成假样本。
因此,\(D(G(z))\)是生成器\(G\)的输出是真实图像的概率(标量)。如[Goodfellow 的论文](https://papers.nips.cc/paper/5423-generative-adversarial-nets.pdf)所述,\(D\)\(G\)在一个最小最大游戏中发挥作用,其中\(D\)试图最大化它正确分类真实和假图像的概率(\(logD(x)\)),而\(G\)试图最小化\(D\)预测其输出是假的概率(\(log(1-D(G(z)))\))。从论文中,GAN 的损失函数为:
因此,$D(G(z))$是生成器$G$的输出是真实图像的概率(标量)。如[Goodfellow 的论文](https://papers.nips.cc/paper/5423-generative-adversarial-nets.pdf)所述,$D$和$G$在一个最小最大游戏中发挥作用,其中$D$试图最大化它正确分类真实和假图像的概率($logD(x)$),而$G$试图最小化$D$预测其输出是假的概率($log(1-D(G(z)))$)。从论文中,GAN 的损失函数为:
\[ \underset{G}{\text{min}} \underset{D}{\text{max}}V(D,G) = \mathbb{E}_{x\sim p_{data}(x)}\big[logD(x)\big] + \mathbb{E}_{z\sim p_{z}(z)}\big[log(1-D(G(z)))\big] \]
$$ \underset{G}{\text{min}} \underset{D}{\text{max}}V(D,G) = \mathbb{E}_{x\sim p_{data}(x)}\big[logD(x)\big] + \mathbb{E}_{z\sim p_{z}(z)}\big[log(1-D(G(z)))\big] $$
理论上,这个极小极大博弈的解是当\(p_g = p_{data}\)时,如果输入是真实的还是伪造的,鉴别器会随机猜测。然而,GAN 的收敛理论仍在积极研究中,实际上模型并不总是训练到这一点。
理论上,这个极小极大博弈的解是当$p_g = p_{data}$时,如果输入是真实的还是伪造的,鉴别器会随机猜测。然而,GAN 的收敛理论仍在积极研究中,实际上模型并不总是训练到这一点。
### 什么是 DCGAN?
DCGAN 是上述 GAN 的直接扩展,除了明确在鉴别器和生成器中使用卷积和卷积转置层。它首次由 Radford 等人在论文[使用深度卷积生成对抗网络进行无监督表示学习](https://arxiv.org/pdf/1511.06434.pdf)中描述。鉴别器由步进的[卷积](https://pytorch.org/docs/stable/nn.html#torch.nn.Conv2d)层、[批量归一化](https://pytorch.org/docs/stable/nn.html#torch.nn.BatchNorm2d)层和[LeakyReLU](https://pytorch.org/docs/stable/nn.html#torch.nn.LeakyReLU)激活组成。输入是一个 3x64x64 的输入图像,输出是一个标量概率,表示输入来自真实数据分布。生成器由[卷积转置](https://pytorch.org/docs/stable/nn.html#torch.nn.ConvTranspose2d)层、批量归一化层和[ReLU](https://pytorch.org/docs/stable/nn.html#relu)激活组成。输入是一个从标准正态分布中抽取的潜在向量\(z\),输出是一个 3x64x64 的 RGB 图像。步进的卷积转置层允许将潜在向量转换为与图像形状相同的体积。在论文中,作者还提供了一些建议,关于如何设置优化器、如何计算损失函数以及如何初始化模型权重,所有这些将在接下来的章节中解释。
DCGAN 是上述 GAN 的直接扩展,除了明确在鉴别器和生成器中使用卷积和卷积转置层。它首次由 Radford 等人在论文[使用深度卷积生成对抗网络进行无监督表示学习](https://arxiv.org/pdf/1511.06434.pdf)中描述。鉴别器由步进的[卷积](https://pytorch.org/docs/stable/nn.html#torch.nn.Conv2d)层、[批量归一化](https://pytorch.org/docs/stable/nn.html#torch.nn.BatchNorm2d)层和[LeakyReLU](https://pytorch.org/docs/stable/nn.html#torch.nn.LeakyReLU)激活组成。输入是一个 3x64x64 的输入图像,输出是一个标量概率,表示输入来自真实数据分布。生成器由[卷积转置](https://pytorch.org/docs/stable/nn.html#torch.nn.ConvTranspose2d)层、批量归一化层和[ReLU](https://pytorch.org/docs/stable/nn.html#relu)激活组成。输入是一个从标准正态分布中抽取的潜在向量$z$,输出是一个 3x64x64 的 RGB 图像。步进的卷积转置层允许将潜在向量转换为与图像形状相同的体积。在论文中,作者还提供了一些建议,关于如何设置优化器、如何计算损失函数以及如何初始化模型权重,所有这些将在接下来的章节中解释。
```py
#%matplotlib inline
......@@ -199,7 +199,7 @@ def weights_init(m):
### 生成器
生成器\(G\)旨在将潜在空间向量(\(z\))映射到数据空间。由于我们的数据是图像,将\(z\)转换为数据空间最终意味着创建一个与训练图像相同大小的 RGB 图像(即 3x64x64)。在实践中,通过一系列步进的二维卷积转置层来实现这一点,每个层都与一个 2D 批量归一化层和一个 relu 激活函数配对。生成器的输出通过 tanh 函数传递,将其返回到输入数据范围\([-1,1]\)。值得注意的是,在卷积转置层之后存在批量归一化函数,这是 DCGAN 论文的一个重要贡献。这些层有助于训练过程中梯度的流动。下面是生成器的代码。
生成器$G$旨在将潜在空间向量($z$)映射到数据空间。由于我们的数据是图像,将$z$转换为数据空间最终意味着创建一个与训练图像相同大小的 RGB 图像(即 3x64x64)。在实践中,通过一系列步进的二维卷积转置层来实现这一点,每个层都与一个 2D 批量归一化层和一个 relu 激活函数配对。生成器的输出通过 tanh 函数传递,将其返回到输入数据范围$[-1,1]$。值得注意的是,在卷积转置层之后存在批量归一化函数,这是 DCGAN 论文的一个重要贡献。这些层有助于训练过程中梯度的流动。下面是生成器的代码。
![dcgan_generator](img/85974d98be6202902f21ce274418953f.png)
......@@ -280,7 +280,7 @@ Generator(
### 鉴别器
如前所述,鉴别器\(D\)是一个二元分类网络,接受图像作为输入,并输出一个标量概率,表示输入图像是真实的(而不是假的)。在这里,\(D\)接受一个 3x64x64 的输入图像,通过一系列的 Conv2d、BatchNorm2d 和 LeakyReLU 层处理,通过 Sigmoid 激活函数输出最终概率。如果需要,可以通过添加更多层来扩展这个架构,但是使用步进卷积、BatchNorm 和 LeakyReLU 具有重要意义。DCGAN 论文提到,使用步进卷积而不是池化进行下采样是一个好的做法,因为它让网络学习自己的池化函数。此外,批量归一化和 LeakyReLU 函数有助于促进健康的梯度流,这对于\(G\)\(D\)的学习过程至关重要。
如前所述,鉴别器$D$是一个二元分类网络,接受图像作为输入,并输出一个标量概率,表示输入图像是真实的(而不是假的)。在这里,$D$接受一个 3x64x64 的输入图像,通过一系列的 Conv2d、BatchNorm2d 和 LeakyReLU 层处理,通过 Sigmoid 激活函数输出最终概率。如果需要,可以通过添加更多层来扩展这个架构,但是使用步进卷积、BatchNorm 和 LeakyReLU 具有重要意义。DCGAN 论文提到,使用步进卷积而不是池化进行下采样是一个好的做法,因为它让网络学习自己的池化函数。此外,批量归一化和 LeakyReLU 函数有助于促进健康的梯度流,这对于$G$和$D$的学习过程至关重要。
鉴别器代码
......@@ -354,13 +354,13 @@ Discriminator(
### 损失函数和优化器
设置好\(D\)\(G\)后,我们可以通过损失函数和优化器指定它们的学习方式。我们将使用二元交叉熵损失([BCELoss](https://pytorch.org/docs/stable/generated/torch.nn.BCELoss.html#torch.nn.BCELoss))函数,PyTorch 中定义如下:
设置好$D$和$G$后,我们可以通过损失函数和优化器指定它们的学习方式。我们将使用二元交叉熵损失([BCELoss](https://pytorch.org/docs/stable/generated/torch.nn.BCELoss.html#torch.nn.BCELoss))函数,PyTorch 中定义如下:
\[\ell(x, y) = L = \{l_1,\dots,l_N\}^\top, \quad l_n = - \left[ y_n \cdot \log x_n + (1 - y_n) \cdot \log (1 - x_n) \right] \]
$$\ell(x, y) = L = \{l_1,\dots,l_N\}^\top, \quad l_n = - \left[ y_n \cdot \log x_n + (1 - y_n) \cdot \log (1 - x_n) \right] $$
请注意,此函数提供了目标函数中两个 log 组件的计算(即\(log(D(x))\)\(log(1-D(G(z)))\))。我们可以通过输入\(y\)来指定要使用 BCE 方程的哪一部分。这将在即将到来的训练循环中完成,但重要的是要理解我们如何通过改变\(y\)(即 GT 标签)来选择我们希望计算的组件。
请注意,此函数提供了目标函数中两个 log 组件的计算(即$log(D(x))$和$log(1-D(G(z)))$)。我们可以通过输入$y$来指定要使用 BCE 方程的哪一部分。这将在即将到来的训练循环中完成,但重要的是要理解我们如何通过改变$y$(即 GT 标签)来选择我们希望计算的组件。
接下来,我们将把真实标签定义为 1,将假标签定义为 0。在计算\(D\)\(G\)的损失时将使用这些标签,这也是原始 GAN 论文中使用的惯例。最后,我们设置了两个单独的优化器,一个用于\(D\),一个用于\(G\)。如 DCGAN 论文中所指定的,两者都是 Adam 优化器,学习率为 0.0002,Beta1 = 0.5。为了跟踪生成器的学习进展,我们将生成一批固定的潜在向量,这些向量是从高斯分布中抽取的(即 fixed_noise)。在训练循环中,我们将定期将这个 fixed_noise 输入到\(G\)中,随着迭代的进行,我们将看到图像从噪音中生成出来。
接下来,我们将把真实标签定义为 1,将假标签定义为 0。在计算$D$和$G$的损失时将使用这些标签,这也是原始 GAN 论文中使用的惯例。最后,我们设置了两个单独的优化器,一个用于$D$,一个用于$G$。如 DCGAN 论文中所指定的,两者都是 Adam 优化器,学习率为 0.0002,Beta1 = 0.5。为了跟踪生成器的学习进展,我们将生成一批固定的潜在向量,这些向量是从高斯分布中抽取的(即 fixed_noise)。在训练循环中,我们将定期将这个 fixed_noise 输入到$G$中,随着迭代的进行,我们将看到图像从噪音中生成出来。
```py
# Initialize the ``BCELoss`` function
......@@ -381,21 +381,21 @@ optimizerG = optim.Adam(netG.parameters(), lr=lr, betas=(beta1, 0.999))
### 训练
最后,现在我们已经定义了 GAN 框架的所有部分,我们可以开始训练。请注意,训练 GAN 有点像一种艺术形式,因为不正确的超参数设置会导致模式崩溃,而对出现问题的原因却没有太多解释。在这里,我们将紧密遵循[Goodfellow 的论文](https://papers.nips.cc/paper/5423-generative-adversarial-nets.pdf)中的算法 1,同时遵循[ganhacks](https://github.com/soumith/ganhacks)中显示的一些最佳实践。换句话说,我们将“为真实和伪造图像构建不同的小批量”,并调整 G 的目标函数以最大化\(log(D(G(z)))\)。训练分为两个主要部分。第一部分更新鉴别器,第二部分更新生成器。
最后,现在我们已经定义了 GAN 框架的所有部分,我们可以开始训练。请注意,训练 GAN 有点像一种艺术形式,因为不正确的超参数设置会导致模式崩溃,而对出现问题的原因却没有太多解释。在这里,我们将紧密遵循[Goodfellow 的论文](https://papers.nips.cc/paper/5423-generative-adversarial-nets.pdf)中的算法 1,同时遵循[ganhacks](https://github.com/soumith/ganhacks)中显示的一些最佳实践。换句话说,我们将“为真实和伪造图像构建不同的小批量”,并调整 G 的目标函数以最大化$log(D(G(z)))$。训练分为两个主要部分。第一部分更新鉴别器,第二部分更新生成器。
**第一部分 - 训练鉴别器**
回想一下,训练鉴别器的目标是最大化将给定输入正确分类为真实或伪造的概率。就 Goodfellow 而言,我们希望“通过升高其随机梯度来更新鉴别器”。实际上,我们希望最大化\(log(D(x)) + log(1-D(G(z)))\)。由于[ganhacks](https://github.com/soumith/ganhacks)中的单独小批量建议,我们将分两步计算这个过程。首先,我们将从训练集中构建一批真实样本,通过\(D\)进行前向传播,计算损失(\(log(D(x))\)),然后通过反向传播计算梯度。其次,我们将使用当前生成器构建一批伪造样本,将这批样本通过\(D\)进行前向传播,计算损失(\(log(1-D(G(z)))\)),并通过反向传播*累积*梯度。现在,通过从所有真实和所有伪造批次中累积的梯度,我们调用鉴别器的优化器步骤。
回想一下,训练鉴别器的目标是最大化将给定输入正确分类为真实或伪造的概率。就 Goodfellow 而言,我们希望“通过升高其随机梯度来更新鉴别器”。实际上,我们希望最大化$log(D(x)) + log(1-D(G(z)))$。由于[ganhacks](https://github.com/soumith/ganhacks)中的单独小批量建议,我们将分两步计算这个过程。首先,我们将从训练集中构建一批真实样本,通过$D$进行前向传播,计算损失($log(D(x))$),然后通过反向传播计算梯度。其次,我们将使用当前生成器构建一批伪造样本,将这批样本通过$D$进行前向传播,计算损失($log(1-D(G(z)))$),并通过反向传播*累积*梯度。现在,通过从所有真实和所有伪造批次中累积的梯度,我们调用鉴别器的优化器步骤。
**第二部分 - 训练生成器**
如原始论文所述,我们希望通过最小化\(log(1-D(G(z)))\)来训练生成器,以生成更好的伪造品。正如提到的,Goodfellow 指出,特别是在学习过程的早期,这并不能提供足够的梯度。为了解决这个问题,我们希望最大化\(log(D(G(z)))\)。在代码中,我们通过以下方式实现这一点:用鉴别器对第一部分的生成器输出进行分类,使用真实标签作为 GT 计算 G 的损失,通过反向传播计算 G 的梯度,最后使用优化器步骤更新 G 的参数。在损失函数中使用真实标签作为 GT 标签可能看起来有些反直觉,但这使我们可以使用`BCELoss`中的\(log(x)\)部分(而不是\(log(1-x)\)部分),这正是我们想要的。
如原始论文所述,我们希望通过最小化$log(1-D(G(z)))$来训练生成器,以生成更好的伪造品。正如提到的,Goodfellow 指出,特别是在学习过程的早期,这并不能提供足够的梯度。为了解决这个问题,我们希望最大化$log(D(G(z)))$。在代码中,我们通过以下方式实现这一点:用鉴别器对第一部分的生成器输出进行分类,使用真实标签作为 GT 计算 G 的损失,通过反向传播计算 G 的梯度,最后使用优化器步骤更新 G 的参数。在损失函数中使用真实标签作为 GT 标签可能看起来有些反直觉,但这使我们可以使用`BCELoss`中的$log(x)$部分(而不是$log(1-x)$部分),这正是我们想要的。
最后,我们将进行一些统计报告,并在每个时代结束时将我们的 fixed_noise 批次通过生成器,以直观地跟踪 G 的训练进度。报告的训练统计数据为:
+ **Loss_D** - 判别器损失,计算为所有真实批次和所有虚假批次的损失之和(\(log(D(x)) + log(1 - D(G(z)))\))。
+ **Loss_D** - 判别器损失,计算为所有真实批次和所有虚假批次的损失之和($log(D(x)) + log(1 - D(G(z)))$)。
+ **Loss_G** - 生成器损失,计算为\(log(D(G(z)))\)
+ **Loss_G** - 生成器损失,计算为$log(D(G(z)))$
+ **D(x)** - 判别器对所有真实批次的平均输出(跨批次)。这应该从接近 1 开始,然后在生成器变得更好时理论上收敛到 0.5。想一想为什么会这样。
......
......@@ -112,7 +112,7 @@ vocab 对象是基于训练数据集构建的,并用于将标记数值化为
给定一个一维顺序数据向量,`batchify()`将数据排列成`batch_size`列。如果数据不能完全分成`batch_size`列,则将数据修剪以适应。例如,对于字母表作为数据(总长度为 26)和`batch_size=4`,我们将字母表分成长度为 6 的序列,结果为 4 个这样的序列。
\[\begin{bmatrix} \text{A} & \text{B} & \text{C} & \ldots & \text{X} & \text{Y} & \text{Z} \end{bmatrix} \Rightarrow \begin{bmatrix} \begin{bmatrix}\text{A} \\ \text{B} \\ \text{C} \\ \text{D} \\ \text{E} \\ \text{F}\end{bmatrix} & \begin{bmatrix}\text{G} \\ \text{H} \\ \text{I} \\ \text{J} \\ \text{K} \\ \text{L}\end{bmatrix} & \begin{bmatrix}\text{M} \\ \text{N} \\ \text{O} \\ \text{P} \\ \text{Q} \\ \text{R}\end{bmatrix} & \begin{bmatrix}\text{S} \\ \text{T} \\ \text{U} \\ \text{V} \\ \text{W} \\ \text{X}\end{bmatrix} \end{bmatrix} \]
$$\begin{bmatrix} \text{A} & \text{B} & \text{C} & \ldots & \text{X} & \text{Y} & \text{Z} \end{bmatrix} \Rightarrow \begin{bmatrix} \begin{bmatrix}\text{A} \\ \text{B} \\ \text{C} \\ \text{D} \\ \text{E} \\ \text{F}\end{bmatrix} & \begin{bmatrix}\text{G} \\ \text{H} \\ \text{I} \\ \text{J} \\ \text{K} \\ \text{L}\end{bmatrix} & \begin{bmatrix}\text{M} \\ \text{N} \\ \text{O} \\ \text{P} \\ \text{Q} \\ \text{R}\end{bmatrix} & \begin{bmatrix}\text{S} \\ \text{T} \\ \text{U} \\ \text{V} \\ \text{W} \\ \text{X}\end{bmatrix} \end{bmatrix} $$
批处理使处理更具并行性。但是,批处理意味着模型独立处理每一列;例如,上面示例中`G``F`的依赖关系无法学习。
......
......@@ -108,27 +108,27 @@ class ReplayMemory(object):
我们的环境是确定性的,因此这里呈现的所有方程也是确定性的,为简单起见。在强化学习文献中,它们还会包含对环境中随机转换的期望。
我们的目标是训练一个策略,试图最大化折现的累积奖励\(R_{t_0} = \sum_{t=t_0}^{\infty} \gamma^{t - t_0} r_t\),其中\(R_{t_0}\)也被称为*回报*。折现率\(\gamma\)应该是一个在\(0\)\(1\)之间的常数,以确保总和收敛。较低的\(\gamma\)使得来自不确定的遥远未来的奖励对我们的代理不那么重要,而对于它可以相当自信的近期未来的奖励更为重要。它还鼓励代理收集比未来时间相对较远的等价奖励更接近的奖励。
我们的目标是训练一个策略,试图最大化折现的累积奖励$R_{t_0} = \sum_{t=t_0}^{\infty} \gamma^{t - t_0} r_t$,其中$R_{t_0}$也被称为*回报*。折现率$\gamma$应该是一个在$0$和$1$之间的常数,以确保总和收敛。较低的$\gamma$使得来自不确定的遥远未来的奖励对我们的代理不那么重要,而对于它可以相当自信的近期未来的奖励更为重要。它还鼓励代理收集比未来时间相对较远的等价奖励更接近的奖励。
训练循环
这个单元格实例化了我们的模型及其优化器,并定义了一些实用程序:
对于我们的训练更新规则,我们将使用一个事实,即某个策略的每个\(Q\)函数都遵守贝尔曼方程:
对于我们的训练更新规则,我们将使用一个事实,即某个策略的每个$Q$函数都遵守贝尔曼方程:
`plot_durations` - 用于绘制每一集的持续时间,以及最近 100 集的平均值(官方评估中使用的度量)。绘图将位于包含主训练循环的单元格下方,并将在每一集之后更新。
在这里,您可以找到一个`optimize_model`函数,执行优化的单个步骤。它首先对一批进行采样,将所有张量连接成一个张量,计算\(Q(s_t, a_t)\)\(V(s_{t+1}) = \max_a Q(s_{t+1}, a)\),并将它们组合成我们的损失。根据定义,如果\(s\)是一个终止状态,则我们设置\(V(s) = 0\)。我们还使用一个目标网络来计算\(V(s_{t+1})\)以增加稳定性。目标网络在每一步都会进行更新,使用由超参数`TAU`控制的[软更新](https://arxiv.org/pdf/1509.02971.pdf),这个超参数之前已经定义过。
在这里,您可以找到一个`optimize_model`函数,执行优化的单个步骤。它首先对一批进行采样,将所有张量连接成一个张量,计算$Q(s_t, a_t)$和$V(s_{t+1}) = \max_a Q(s_{t+1}, a)$,并将它们组合成我们的损失。根据定义,如果$s$是一个终止状态,则我们设置$V(s) = 0$。我们还使用一个目标网络来计算$V(s_{t+1})$以增加稳定性。目标网络在每一步都会进行更新,使用由超参数`TAU`控制的[软更新](https://arxiv.org/pdf/1509.02971.pdf),这个超参数之前已经定义过。
我们的模型将是一个前馈神经网络,它接收当前和前一个屏幕补丁之间的差异。它有两个输出,表示\(Q(s, \mathrm{left})\)\(Q(s, \mathrm{right})\)(其中\(s\)是网络的输入)。实际上,网络试图预测在给定当前输入时采取每个动作的*预期回报*
我们的模型将是一个前馈神经网络,它接收当前和前一个屏幕补丁之间的差异。它有两个输出,表示$Q(s, \mathrm{left})$和$Q(s, \mathrm{right})$(其中$s$是网络的输入)。实际上,网络试图预测在给定当前输入时采取每个动作的*预期回报*
最后,训练我们的模型的代码。
为了最小化这个误差,我们将使用[Huber 损失](https://en.wikipedia.org/wiki/Huber_loss)。当误差很小时,Huber 损失的作用类似于均方误差,但当误差很大时,它的作用类似于平均绝对误差 - 这使得在估计\(Q\)非常嘈杂时更加健壮。我们在从重放内存中采样的一批转换\(B\)上计算这个损失:
为了最小化这个误差,我们将使用[Huber 损失](https://en.wikipedia.org/wiki/Huber_loss)。当误差很小时,Huber 损失的作用类似于均方误差,但当误差很大时,它的作用类似于平均绝对误差 - 这使得在估计$Q$非常嘈杂时更加健壮。我们在从重放内存中采样的一批转换$B$上计算这个损失:
\[\mathcal{L} = \frac{1}{|B|}\sum_{(s, a, s', r) \ \in \ B} \mathcal{L}(\delta)\]
$$\mathcal{L} = \frac{1}{|B|}\sum_{(s, a, s', r) \ \in \ B} \mathcal{L}(\delta)$$
### \[\text{其中} \quad \mathcal{L}(\delta) = \begin{cases} \frac{1}{2}{\delta²} & \text{对于} |\delta| \le 1, \\ |\delta| - \frac{1}{2} & \text{否则。} \end{cases}\]
$$\text{其中} \quad \mathcal{L}(\delta) = \begin{cases} \frac{1}{2}{\delta²} & \text{对于} |\delta| \le 1, \\ |\delta| - \frac{1}{2} & \text{否则。} \end{cases}$$
`select_action` - 将根据ε贪婪策略选择一个动作。简单来说,我们有时会使用我们的模型来选择动作,有时我们只是均匀地随机采样一个。选择随机动作的概率将从`EPS_START`开始指数衰减到`EPS_END``EPS_DECAY`控制衰减的速率。
......@@ -149,15 +149,15 @@ class DQN(nn.Module):
return self.layer3(x)
```
## \[\pi^*(s) = \arg\!\max_a \ Q^*(s, a) \]
## $$\pi^*(s) = \arg\!\max_a \ Q^*(s, a) $$
### 然而,我们并不知道关于世界的一切,所以我们没有\(Q^*\)的访问权限。但是,由于神经网络是通用函数逼近器,我们可以简单地创建一个神经网络并训练它以类似于\(Q^*\)。超参数和实用程序
### 然而,我们并不知道关于世界的一切,所以我们没有$Q^*$的访问权限。但是,由于神经网络是通用函数逼近器,我们可以简单地创建一个神经网络并训练它以类似于$Q^*$。超参数和实用程序
\[\delta = Q(s, a) - (r + \gamma \max_a' Q(s', a)) \]
$$\delta = Q(s, a) - (r + \gamma \max_a' Q(s', a)) $$
+ 训练
+ Q 学习的主要思想是,如果我们有一个函数\(Q^*: State \times Action \rightarrow \mathbb{R}\),可以告诉我们,如果我们在给定状态下采取一个动作,我们的回报将是多少,那么我们可以轻松构建一个最大化奖励的策略:
+ Q 学习的主要思想是,如果我们有一个函数$Q^*: State \times Action \rightarrow \mathbb{R}$,可以告诉我们,如果我们在给定状态下采取一个动作,我们的回报将是多少,那么我们可以轻松构建一个最大化奖励的策略:
```py
# BATCH_SIZE is the number of transitions sampled from the replay buffer
......@@ -233,9 +233,9 @@ def plot_durations(show_result=False):
display.display(plt.gcf())
```
### 等式两边之间的差异被称为时间差分误差\(\delta\)
### 等式两边之间的差异被称为时间差分误差$\delta$
\[Q^{\pi}(s, a) = r + \gamma Q^{\pi}(s', \pi(s')) \]
$$Q^{\pi}(s, a) = r + \gamma Q^{\pi}(s', \pi(s')) $$
Q 网络
......
......@@ -60,7 +60,7 @@ PPO 通常被认为是一种快速高效的在线、基于策略的强化学习
为了完整起见,这里简要概述了损失的计算过程,尽管这已经由我们的[`ClipPPOLoss`](https://pytorch.org/rl/reference/generated/torchrl.objectives.ClipPPOLoss.html#torchrl.objectives.ClipPPOLoss "(in torchrl vmain (0.4.0 ))")模块处理——算法的工作方式如下:1. 通过在环境中执行策略一定数量的步骤来采样一批数据。2. 然后,我们将使用该批次的随机子样本执行一定数量的优化步骤,使用 REINFORCE 损失的剪切版本。3. 剪切将对我们的损失设置一个悲观的边界:较低的回报估计将优先于较高的回报估计。损失的精确公式如下:
\[L(s,a,\theta_k,\theta) = \min\left( \frac{\pi_{\theta}(a|s)}{\pi_{\theta_k}(a|s)} A^{\pi_{\theta_k}}(s,a), \;\; g(\epsilon, A^{\pi_{\theta_k}}(s,a)) \right),\]
$$L(s,a,\theta_k,\theta) = \min\left( \frac{\pi_{\theta}(a|s)}{\pi_{\theta_k}(a|s)} A^{\pi_{\theta_k}}(s,a), \;\; g(\epsilon, A^{\pi_{\theta_k}}(s,a)) \right),$$
在该损失中有两个组件:在最小运算符的第一部分中,我们简单地计算 REINFORCE 损失的重要性加权版本(例如,我们已经校正了当前策略配置滞后于用于数据收集的策略配置的事实)。最小运算符的第二部分是一个类似的损失,当比率超过或低于给定的一对阈值时,我们对比率进行了剪切。
......@@ -313,7 +313,7 @@ PPO 利用随机策略来处理探索。这意味着我们的神经网络将不
由于数据是连续的,我们使用 Tanh-Normal 分布来尊重动作空间的边界。TorchRL 提供了这样的分布,我们唯一需要关心的是构建一个神经网络,以输出策略所需的正确数量的参数(位置或均值,以及尺度):
\[f_{\theta}(\text{observation}) = \mu_{\theta}(\text{observation}), \sigma^{+}_{\theta}(\text{observation})\]
$$f_{\theta}(\text{observation}) = \mu_{\theta}(\text{observation}), \sigma^{+}_{\theta}(\text{observation})$$
这里唯一增加的困难是将我们的输出分成两个相等的部分,并将第二部分映射到严格正空间。
......
......@@ -54,13 +54,13 @@ from torchrl.data import TensorDictReplayBuffer, LazyMemmapStorage
**环境** 代理与之交互并学习的世界。
**动作** \(a\):代理对环境的响应。所有可能动作的集合称为*动作空间*
**动作** $a$:代理对环境的响应。所有可能动作的集合称为*动作空间*
**状态** \(s\):环境的当前特征。环境可能处于的所有可能状态的集合称为*状态空间*
**状态** $s$:环境的当前特征。环境可能处于的所有可能状态的集合称为*状态空间*
**奖励** \(r\):奖励是环境向代理提供的关键反馈。这是驱使代理学习并改变其未来行动的动力。在多个时间步骤上的奖励的聚合被称为**回报**
**奖励** $r$:奖励是环境向代理提供的关键反馈。这是驱使代理学习并改变其未来行动的动力。在多个时间步骤上的奖励的聚合被称为**回报**
**最优动作-值函数** \(Q^*(s,a)\):给出了如果你从状态 \(s\) 开始,采取任意动作 \(a\),然后在每个未来时间步骤中采取最大化回报的动作的预期回报。\(Q\) 可以说代表了状态中动作的“质量”。我们试图近似这个函数。
**最优动作-值函数** $Q^*(s,a)$:给出了如果你从状态 $s$ 开始,采取任意动作 $a$,然后在每个未来时间步骤中采取最大化回报的动作的预期回报。$Q$ 可以说代表了状态中动作的“质量”。我们试图近似这个函数。
## 环境
......@@ -328,9 +328,9 @@ class Mario(Mario): # subclassing for continuity
### 学习
马里奥在幕后使用[DDQN 算法](https://arxiv.org/pdf/1509.06461)。DDQN 使用两个 ConvNets - \(Q_{online}\)\(Q_{target}\) - 分别近似最优动作值函数。
马里奥在幕后使用[DDQN 算法](https://arxiv.org/pdf/1509.06461)。DDQN 使用两个 ConvNets - $Q_{online}$和$Q_{target}$ - 分别近似最优动作值函数。
在我们的实现中,我们在\(Q_{online}\)\(Q_{target}\)之间共享特征生成器`features`,但为每个分类器保持单独的 FC。 \(\theta_{target}\)\(Q_{target}\)的参数)被冻结以防止通过反向传播进行更新。相反,它会定期与\(\theta_{online}\)同步(稍后会详细介绍)。
在我们的实现中,我们在$Q_{online}$和$Q_{target}$之间共享特征生成器`features`,但为每个分类器保持单独的 FC。 $\theta_{target}$($Q_{target}$的参数)被冻结以防止通过反向传播进行更新。相反,它会定期与$\theta_{online}$同步(稍后会详细介绍)。
#### 神经网络
......@@ -383,17 +383,19 @@ class MarioNet(nn.Module):
学习涉及两个值:
**TD 估计** - 给定状态\(s\)的预测最优\(Q^*\)
**TD 估计** - 给定状态$s$的预测最优$Q^*$
\[{TD}_e = Q_{online}^*(s,a)\]
$${TD}_e = Q_{online}^*(s,a)$$
**TD 目标** - 当前奖励和下一个状态\(s'\)中估计的\(Q^*\)的聚合
**TD 目标** - 当前奖励和下一个状态$s'$中估计的$Q^*$的聚合
\[a' = argmax_{a} Q_{online}(s', a)\]\[{TD}_t = r + \gamma Q_{target}^*(s',a')\]
$$a' = argmax_{a} Q_{online}(s', a)$$
因为我们不知道下一个动作\(a'\)会是什么,所以我们使用在下一个状态\(s'\)中最大化\(Q_{online}\)的动作\(a'\)
$${TD}_t = r + \gamma Q_{target}^*(s',a')$$
请注意,我们在`td_target()`上使用[@torch.no_grad()](https://pytorch.org/docs/stable/generated/torch.no_grad.html#no-grad)装饰器来禁用梯度计算(因为我们不需要在\(\theta_{target}\)上进行反向传播)。
因为我们不知道下一个动作$a'$会是什么,所以我们使用在下一个状态$s'$中最大化$Q_{online}$的动作$a'$。
请注意,我们在`td_target()`上使用[@torch.no_grad()](https://pytorch.org/docs/stable/generated/torch.no_grad.html#no-grad)装饰器来禁用梯度计算(因为我们不需要在$\theta_{target}$上进行反向传播)。
```py
class Mario(Mario):
......@@ -419,13 +421,13 @@ class Mario(Mario):
#### 更新模型
当马里奥从他的重放缓冲区中采样输入时,我们计算\(TD_t\)\(TD_e\),并将这个损失反向传播到\(Q_{online}\)以更新其参数\(\theta_{online}\)\(\alpha\)是传递给`optimizer`的学习率`lr`
当马里奥从他的重放缓冲区中采样输入时,我们计算$TD_t$和$TD_e$,并将这个损失反向传播到$Q_{online}$以更新其参数$\theta_{online}$($\alpha$是传递给`optimizer`的学习率`lr`
\[\theta_{online} \leftarrow \theta_{online} + \alpha \nabla(TD_e - TD_t)\]
$$\theta_{online} \leftarrow \theta_{online} + \alpha \nabla(TD_e - TD_t)$$
\(\theta_{target}\)不通过反向传播进行更新。相反,我们定期将\(\theta_{online}\)复制到\(\theta_{target}\)
$\theta_{target}$不通过反向传播进行更新。相反,我们定期将$\theta_{online}$复制到$\theta_{target}$
\[\theta_{target} \leftarrow \theta_{online}\]
$$\theta_{target} \leftarrow \theta_{online}$$
```py
class Mario(Mario):
......
......@@ -92,15 +92,15 @@ DEFAULT_Y = 1.0
对于运动方程,我们将根据以下方式更新角速度:
\[\dot{\theta}_{t+1} = \dot{\theta}_t + (3 * g / (2 * L) * \sin(\theta_t) + 3 / (m * L²) * u) * dt\]
$$\dot{\theta}_{t+1} = \dot{\theta}_t + (3 * g / (2 * L) * \sin(\theta_t) + 3 / (m * L²) * u) * dt$$
其中\(\dot{\theta}\)是角速度(弧度/秒),\(g\)是重力,\(L\)是摆长,\(m\)是质量,\(\theta\)是角位置,\(u\)是扭矩。然后根据以下方式更新角位置
其中$\dot{\theta}$是角速度(弧度/秒),$g$是重力,$L$是摆长,$m$是质量,$\theta$是角位置,$u$是扭矩。然后根据以下方式更新角位置
\[\theta_{t+1} = \theta_{t} + \dot{\theta}_{t+1} dt\]
$$\theta_{t+1} = \theta_{t} + \dot{\theta}_{t+1} dt$$
我们将奖励定义为
\[r = -(\theta² + 0.1 * \dot{\theta}² + 0.001 * u²)\]
$$r = -(\theta² + 0.1 * \dot{\theta}² + 0.001 * u²)$$
当角度接近 0(摆锤向上位置)、角速度接近 0(无运动)且扭矩也为 0 时,将最大化奖励。
......
......@@ -41,7 +41,7 @@ bias = torch.randn(D)
x = torch.randn(D) # feature vector
```
让我们将 `predict` 视为一个将输入 `x`\(R^D \to R^D\) 的函数。PyTorch Autograd 计算向量-雅可比乘积。为了计算这个 \(R^D \to R^D\) 函数的完整雅可比矩阵,我们将不得不逐行计算,每次使用一个不同的单位向量。
让我们将 `predict` 视为一个将输入 `x`$R^D \to R^D$ 的函数。PyTorch Autograd 计算向量-雅可比乘积。为了计算这个 $R^D \to R^D$ 函数的完整雅可比矩阵,我们将不得不逐行计算,每次使用一个不同的单位向量。
```py
def compute_jac(xp):
......@@ -162,7 +162,7 @@ ft_jac_weight, ft_jac_bias = jacrev(predict, argnums=(0, 1))(weight, bias, x)
`jacfwd``jacrev` 可以互相替代,但它们具有不同的性能特征。
作为一个经验法则,如果你正在计算一个 \(R^N \to R^M\) 函数的雅可比矩阵,并且输出比输入要多得多(例如,\(M > N\)),那么首选 `jacfwd`,否则使用 `jacrev`。当然,这个规则也有例外,但以下是一个非严格的论证:
作为一个经验法则,如果你正在计算一个 $R^N \to R^M$ 函数的雅可比矩阵,并且输出比输入要多得多(例如,$M > N$),那么首选 `jacfwd`,否则使用 `jacrev`。当然,这个规则也有例外,但以下是一个非严格的论证:
在反向模式 AD 中,我们逐行计算雅可比矩阵,而在正向模式 AD(计算雅可比向量积)中,我们逐列计算。雅可比矩阵有 M 行和 N 列,因此如果它在某个方向上更高或更宽,我们可能更喜欢处理较少行或列的方法。
......@@ -291,7 +291,7 @@ True
## 批处理雅可比矩阵和批处理 Hessian
在上面的例子中,我们一直在操作单个特征向量。在某些情况下,您可能希望对一批输出相对于一批输入进行雅可比矩阵的计算。也就是说,给定形状为`(B, N)`的输入批次和一个从\(R^N \to R^M\)的函数,我们希望得到形状为`(B, M, N)`的雅可比矩阵。
在上面的例子中,我们一直在操作单个特征向量。在某些情况下,您可能希望对一批输出相对于一批输入进行雅可比矩阵的计算。也就是说,给定形状为`(B, N)`的输入批次和一个从$R^N \to R^M$的函数,我们希望得到形状为`(B, M, N)`的雅可比矩阵。
使用`vmap`是最简单的方法:
......@@ -325,7 +325,7 @@ batch_jacobian1 = jacrev(predict_with_output_summed, argnums=2)(weight, bias, x)
assert torch.allclose(batch_jacobian0, batch_jacobian1)
```
如果您的函数是从\(R^N \to R^M\),但输入是批处理的,您可以组合`vmap``jacrev`来计算批处理雅可比矩阵:
如果您的函数是从$R^N \to R^M$,但输入是批处理的,您可以组合`vmap``jacrev`来计算批处理雅可比矩阵:
最后,批次 Hessian 矩阵的计算方式类似。最容易的方法是使用`vmap`批处理 Hessian 计算,但在某些情况下,求和技巧也适用。
......
......@@ -103,7 +103,7 @@ torchviz.make_dot((grad_x, x, out), {"grad_x": grad_x, "x": x, "out": out})
更棘手的情况是当我们需要保存一个中间结果时。我们通过实现以下情况来演示这种情况:
\[sinh(x) := \frac{e^x - e^{-x}}{2} \]
$$sinh(x) := \frac{e^x - e^{-x}}{2} $$
由于 sinh 的导数是 cosh,因此在向后计算中重复使用 exp(x)和 exp(-x)这两个中间结果可能很有用。
......
......@@ -106,7 +106,7 @@ print(torch.__config__.parallel_info())
+ F1 分数的方程式是:
\[F1 = 2 * (\text{精确度} * \text{召回率}) / (\text{精确度} + \text{召回率}) \]
$$F1 = 2 * (\text{精确度} * \text{召回率}) / (\text{精确度} + \text{召回率}) $$
### 1.4 下载数据集
......
......@@ -178,7 +178,7 @@ def run(rank, size):
+ `dist.all_reduce(tensor, op, group)`: 与 reduce 相同,但结果存储在所有进程中。
+ `dist.scatter(tensor, scatter_list, src, group)`: 将第 \(i\) 个张量 `scatter_list[i]` 复制到第 \(i\) 个进程。
+ `dist.scatter(tensor, scatter_list, src, group)`: 将第 $i$ 个张量 `scatter_list[i]` 复制到第 $i$ 个进程。
+ `dist.gather(tensor, gather_list, dst, group)`: 将`tensor`从所有进程复制到`dst`
......
......@@ -102,7 +102,7 @@ class PositionalEncoding(nn.Module):
vocab 对象是基于训练数据集构建的,并用于将标记数值化为张量。从顺序数据开始,`batchify()`函数将数据集排列成列,将数据分成大小为`batch_size`的批次后,修剪掉任何剩余的标记。例如,以字母表作为序列(总长度为 26)和批次大小为 4,我们将字母表分成长度为 6 的 4 个序列:
\[\begin{bmatrix} \text{A} & \text{B} & \text{C} & \ldots & \text{X} & \text{Y} & \text{Z} \end{bmatrix} \Rightarrow \begin{bmatrix} \begin{bmatrix}\text{A} \\ \text{B} \\ \text{C} \\ \text{D} \\ \text{E} \\ \text{F}\end{bmatrix} & \begin{bmatrix}\text{G} \\ \text{H} \\ \text{I} \\ \text{J} \\ \text{K} \\ \text{L}\end{bmatrix} & \begin{bmatrix}\text{M} \\ \text{N} \\ \text{O} \\ \text{P} \\ \text{Q} \\ \text{R}\end{bmatrix} & \begin{bmatrix}\text{S} \\ \text{T} \\ \text{U} \\ \text{V} \\ \text{W} \\ \text{X}\end{bmatrix} \end{bmatrix}\]
$$\begin{bmatrix} \text{A} & \text{B} & \text{C} & \ldots & \text{X} & \text{Y} & \text{Z} \end{bmatrix} \Rightarrow \begin{bmatrix} \begin{bmatrix}\text{A} \\ \text{B} \\ \text{C} \\ \text{D} \\ \text{E} \\ \text{F}\end{bmatrix} & \begin{bmatrix}\text{G} \\ \text{H} \\ \text{I} \\ \text{J} \\ \text{K} \\ \text{L}\end{bmatrix} & \begin{bmatrix}\text{M} \\ \text{N} \\ \text{O} \\ \text{P} \\ \text{Q} \\ \text{R}\end{bmatrix} & \begin{bmatrix}\text{S} \\ \text{T} \\ \text{U} \\ \text{V} \\ \text{W} \\ \text{X}\end{bmatrix} \end{bmatrix}$$
模型将这些列视为独立的,这意味着无法学习`G``F`之间的依赖关系,但可以实现更高效的批处理。
......
......@@ -113,7 +113,7 @@ def run_worker(rank, world_size):
vocab 对象是基于训练数据集构建的,并用于将令牌数值化为张量。从顺序数据开始,`batchify()` 函数将数据集排列成列,将数据分成大小为 `batch_size` 的批次后,修剪掉任何剩余的令牌。例如,对于字母表作为序列(总长度为 26)和批次大小为 4,我们将字母表分成长度为 6 的 4 个序列:
\[ \begin{bmatrix} \text{A} & \text{B} & \text{C} & \ldots & \text{X} & \text{Y} & \text{Z} \end{bmatrix} \Rightarrow \begin{bmatrix} \begin{bmatrix}\text{A} \\ \text{B} \\ \text{C} \\ \text{D} \\ \text{E} \\ \text{F}\end{bmatrix} & \begin{bmatrix}\text{G} \\ \text{H} \\ \text{I} \\ \text{J} \\ \text{K} \\ \text{L}\end{bmatrix} & \begin{bmatrix}\text{M} \\ \text{N} \\ \text{O} \\ \text{P} \\ \text{Q} \\ \text{R}\end{bmatrix} & \begin{bmatrix}\text{S} \\ \text{T} \\ \text{U} \\ \text{V} \\ \text{W} \\ \text{X}\end{bmatrix} \end{bmatrix}\]
$$ \begin{bmatrix} \text{A} & \text{B} & \text{C} & \ldots & \text{X} & \text{Y} & \text{Z} \end{bmatrix} \Rightarrow \begin{bmatrix} \begin{bmatrix}\text{A} \\ \text{B} \\ \text{C} \\ \text{D} \\ \text{E} \\ \text{F}\end{bmatrix} & \begin{bmatrix}\text{G} \\ \text{H} \\ \text{I} \\ \text{J} \\ \text{K} \\ \text{L}\end{bmatrix} & \begin{bmatrix}\text{M} \\ \text{N} \\ \text{O} \\ \text{P} \\ \text{Q} \\ \text{R}\end{bmatrix} & \begin{bmatrix}\text{S} \\ \text{T} \\ \text{U} \\ \text{V} \\ \text{W} \\ \text{X}\end{bmatrix} \end{bmatrix}$$
这些列被模型视为独立的,这意味着`G``F`之间的依赖关系无法被学习,但可以实现更高效的批处理。
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册