pytorch_with_examples.md 27.9 KB
Newer Older
W
wizardforcel 已提交
1
# 用例子学习 PyTorch
W
wizardforcel 已提交
2

W
fmt  
wizardforcel 已提交
3 4 5
> 译者:[bat67](https://github.com/bat67)
> 
> 最新版会在[译者仓库](https://github.com/bat67/Deep-Learning-with-PyTorch-A-60-Minute-Blitz-cn)首先同步。
W
wizardforcel 已提交
6

W
fmt  
wizardforcel 已提交
7
**作者**[Justin Johnson](https://github.com/jcjohnson/pytorch-examples)
W
wizardforcel 已提交
8

9 10 11 12 13 14 15 16 17 18 19 20 21 22
这个教程通过自洽的示例介绍了PyTorch的基本概念。

PyTorch主要是提供了两个核心的功能特性:

* 一个类似于numpy的n维张量,但是可以在GPU上运行
* 搭建和训练神经网络时的自动微分/求导机制

我们将使用全连接的ReLU网络作为运行示例。该网络将有一个单一的隐藏层,并将使用梯度下降训练,通过最小化网络输出和真正结果的欧几里得距离,来拟合随机生成的数据。

## 目录

- [用例子学习 PyTorch](#用例子学习-pytorch)
  - [目录](#目录)
  - [张量](#张量)
片刻小哥哥's avatar
片刻小哥哥 已提交
23
    - [Warm-up:NumPy](#warm-up-numpy)
24 25 26 27 28 29 30 31
    - [PyTorch:张量](#pytorch张量)
  - [自动求导](#自动求导)
    - [PyTorch:张量和自动求导](#pytorch张量和自动求导)
    - [PyTorch:定义新的自动求导函数](#pytorch定义新的自动求导函数)
    - [TensorFlow:静态图](#tensorflow静态图)
  - [`nn`模块](#nn模块)
    - [PyTorch:`nn`](#pytorchnn)
    - [PyTorch:`optim`](#pytorchoptim)
飞龙 已提交
32 33
    - [PyTorch:自定义`nn`模块](#pytorch-custom-nn-modules)
    - [PyTorch:控制流和权重共享](#pytorch-control-flow-weight-sharing)
34 35 36 37
  - [Examples](#examples)
    - [Tensors](#tensors)
    - [Autograd](#autograd)
    - [`nn` module](#nn-module)
W
wizardforcel 已提交
38 39 40

## [张量](#tensors)

片刻小哥哥's avatar
片刻小哥哥 已提交
41
### [Warm-up:NumPy](#warm-up-numpy)
W
wizardforcel 已提交
42 43 44 45 46 47 48

在介绍PyTorch之前,我们将首先使用NumPy实现网络。

NumPy提供了一个n维数组对象和许多用于操作这些数组的函数。NumPy是用于科学计算的通用框架;它对计算图、深度学习和梯度一无所知。然而,我们可以很容易地使用NumPy,手动实现网络的前向和反向传播,来拟合随机数据:

```python
# 可运行代码见本文件夹中的 two_layer_net_numpy.py
W
wizardforcel 已提交
49 50
import numpy as np

W
wizardforcel 已提交
51 52
# N是批大小;D_in是输入维度
# H是隐藏层维度;D_out是输出维度  
W
wizardforcel 已提交
53 54
N, D_in, H, D_out = 64, 1000, 100, 10

W
wizardforcel 已提交
55
# 产生随机输入和输出数据
W
wizardforcel 已提交
56 57 58
x = np.random.randn(N, D_in)
y = np.random.randn(N, D_out)

W
wizardforcel 已提交
59
# 随机初始化权重
W
wizardforcel 已提交
60 61 62 63 64
w1 = np.random.randn(D_in, H)
w2 = np.random.randn(H, D_out)

learning_rate = 1e-6
for t in range(500):
W
wizardforcel 已提交
65
    # 前向传播:计算预测值y
W
wizardforcel 已提交
66 67 68 69
    h = x.dot(w1)
    h_relu = np.maximum(h, 0)
    y_pred = h_relu.dot(w2)

片刻小哥哥's avatar
片刻小哥哥 已提交
70
    # 计算并显示loss(损失)
W
wizardforcel 已提交
71 72 73
    loss = np.square(y_pred - y).sum()
    print(t, loss)

W
wizardforcel 已提交
74
    # 反向传播,计算w1、w2对loss的梯度
W
wizardforcel 已提交
75 76 77 78 79 80 81
    grad_y_pred = 2.0 * (y_pred - y)
    grad_w2 = h_relu.T.dot(grad_y_pred)
    grad_h_relu = grad_y_pred.dot(w2.T)
    grad_h = grad_h_relu.copy()
    grad_h[h < 0] = 0
    grad_w1 = x.T.dot(grad_h)

W
wizardforcel 已提交
82
    # 更新权重
W
wizardforcel 已提交
83 84 85 86
    w1 -= learning_rate * grad_w1
    w2 -= learning_rate * grad_w2
```

W
wizardforcel 已提交
87
### [PyTorch:张量](#pytorch-tensors)
W
wizardforcel 已提交
88

W
wizardforcel 已提交
89
NumPy是一个很棒的框架,但是它不支持GPU以加速运算。现代深度神经网络,GPU常常提供[50倍以上的加速]((https://github.com/jcjohnson/cnn-benchmarks)),所以NumPy不能满足当代深度学习的需求。 
W
wizardforcel 已提交
90

片刻小哥哥's avatar
片刻小哥哥 已提交
91
我们先介绍PyTorch最基础的概念:**张量(Tensor)**。逻辑上,PyTorch的tensor和NumPy array是一样的:tensor是一个n维数组,PyTorch提供了很多函数操作这些tensor。任何希望使用NumPy执行的计算也可以使用PyTorch的tensor来完成;可以认为它们是科学计算的通用工具。
W
wizardforcel 已提交
92

W
wizardforcel 已提交
93
和NumPy不同的是,PyTorch可以利用GPU加速。要在GPU上运行PyTorch张量,在构造张量使用`device`参数把tensor建立在GPU上。
W
wizardforcel 已提交
94

W
wizardforcel 已提交
95
这里我们利用PyTorch的tensor在随机数据上训练一个两层的网络。和前面NumPy的例子类似,我们使用PyTorch的tensor,手动在网络中实现前向传播和反向传播: 
W
wizardforcel 已提交
96 97


W
wizardforcel 已提交
98 99
```python
# 可运行代码见本文件夹中的 two_layer_net_tensor.py
W
wizardforcel 已提交
100 101
import torch

W
wizardforcel 已提交
102
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') 
W
wizardforcel 已提交
103

W
wizardforcel 已提交
104 105
# N是批大小; D_in 是输入维度;
# H 是隐藏层维度; D_out 是输出维度
W
wizardforcel 已提交
106 107
N, D_in, H, D_out = 64, 1000, 100, 10

W
wizardforcel 已提交
108 109 110
# 产生随机输入和输出数据
x = torch.randn(N, D_in, device=device)
y = torch.randn(N, D_out, device=device)
W
wizardforcel 已提交
111

W
wizardforcel 已提交
112 113 114
# 随机初始化权重
w1 = torch.randn(D_in, H, device=device)
w2 = torch.randn(H, D_out, device=device)
W
wizardforcel 已提交
115 116 117

learning_rate = 1e-6
for t in range(500):
W
wizardforcel 已提交
118
    # 前向传播:计算预测值y
W
wizardforcel 已提交
119 120 121 122
    h = x.mm(w1)
    h_relu = h.clamp(min=0)
    y_pred = h_relu.mm(w2)

片刻小哥哥's avatar
片刻小哥哥 已提交
123
    # 计算并输出loss;loss是存储在PyTorch的tensor中的标量,维度是()(零维标量);
W
wizardforcel 已提交
124 125 126
    # 我们使用loss.item()得到tensor中的纯python数值。
    loss = (y_pred - y).pow(2).sum()
    print(t, loss.item())
W
wizardforcel 已提交
127

W
wizardforcel 已提交
128
    # 反向传播,计算w1、w2对loss的梯度
W
wizardforcel 已提交
129 130 131 132 133 134 135
    grad_y_pred = 2.0 * (y_pred - y)
    grad_w2 = h_relu.t().mm(grad_y_pred)
    grad_h_relu = grad_y_pred.mm(w2.t())
    grad_h = grad_h_relu.clone()
    grad_h[h < 0] = 0
    grad_w1 = x.t().mm(grad_h)

W
wizardforcel 已提交
136
    # 使用梯度下降更新权重
W
wizardforcel 已提交
137 138 139 140
    w1 -= learning_rate * grad_w1
    w2 -= learning_rate * grad_w2
```

W
wizardforcel 已提交
141 142 143
## [自动求导](#autograd)

### [PyTorch:张量和自动求导](#pytorch-tensors-and-autograd)
W
wizardforcel 已提交
144

W
wizardforcel 已提交
145
在上面的例子里,需要我们手动实现神经网络的前向和后向传播。对于简单的两层网络,手动实现前向、后向传播不是什么难事,但是对于大型的复杂网络就比较麻烦了。 
W
wizardforcel 已提交
146

W
wizardforcel 已提交
147
庆幸的是,我们可以使用[自动微分](https://en.wikipedia.org/wiki/Automatic_differentiation)来自动完成神经网络中反向传播的计算。PyTorch中**autograd**包提供的正是这个功能。当使用autograd时,网络前向传播将定义一个**计算图**;图中的节点是tensor,边是函数,这些函数是输出tensor到输入tensor的映射。这张计算图使得在网络中反向传播时梯度的计算十分简单。 
W
wizardforcel 已提交
148

W
wizardforcel 已提交
149
这听起来复杂,但是实际操作很简单。如果我们想计算某些的tensor的梯度,我们只需要在建立这个tensor时加入这么一句:`requires_grad=True`。这个tensor上的任何PyTorch的操作都将构造一个计算图,从而允许我们稍后在图中执行反向传播。如果这个tensor`x``requires_grad=True`,那么反向传播之后`x.grad`将会是另一个张量,其为`x`关于某个标量值的梯度。
W
wizardforcel 已提交
150

W
wizardforcel 已提交
151
有时可能希望防止PyTorch在`requires_grad=True`的张量执行某些操作时构建计算图;例如,在训练神经网络时,我们通常不希望通过权重更新步骤进行反向传播。在这种情况下,我们可以使用`torch.no_grad()`上下文管理器来防止构造计算图。
W
wizardforcel 已提交
152

W
wizardforcel 已提交
153
下面我们使用PyTorch的Tensors和autograd来实现我们的两层的神经网络;我们不再需要手动执行网络的反向传播:
W
wizardforcel 已提交
154

W
wizardforcel 已提交
155 156
```python
# 可运行代码见本文件夹中的 two_layer_net_autograd.py
W
wizardforcel 已提交
157 158
import torch

W
wizardforcel 已提交
159
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') 
W
wizardforcel 已提交
160

W
wizardforcel 已提交
161 162
# N是批大小;D_in是输入维度;
# H是隐藏层维度;D_out是输出维度  
W
wizardforcel 已提交
163 164
N, D_in, H, D_out = 64, 1000, 100, 10

W
wizardforcel 已提交
165 166 167
# 产生随机输入和输出数据
x = torch.randn(N, D_in, device=device)
y = torch.randn(N, D_out, device=device)
W
wizardforcel 已提交
168

W
wizardforcel 已提交
169 170 171
# 产生随机权重tensor,将requires_grad设置为True意味着我们希望在反向传播时候计算这些值的梯度
w1 = torch.randn(D_in, H, device=device, requires_grad=True)
w2 = torch.randn(H, D_out, device=device, requires_grad=True)
W
wizardforcel 已提交
172 173 174

learning_rate = 1e-6
for t in range(500):
W
wizardforcel 已提交
175 176 177 178

    # 前向传播:使用tensor的操作计算预测值y。
    # 由于w1和w2有requires_grad=True,涉及这些张量的操作将让PyTorch构建计算图,
    # 从而允许自动计算梯度。由于我们不再手工实现反向传播,所以不需要保留中间值的引用。
W
wizardforcel 已提交
179 180
    y_pred = x.mm(w1).clamp(min=0).mm(w2)

W
wizardforcel 已提交
181
    # 计算并输出loss,loss是一个形状为()的张量,loss.item()是这个张量对应的python数值
W
wizardforcel 已提交
182 183
    loss = (y_pred - y).pow(2).sum()
    print(t, loss.item())
W
wizardforcel 已提交
184 185 186
    
    # 使用autograd计算反向传播。这个调用将计算loss对所有requires_grad=True的tensor的梯度。
    # 这次调用后,w1.grad和w2.grad将分别是loss对w1和w2的梯度张量。
W
wizardforcel 已提交
187 188
    loss.backward()

W
wizardforcel 已提交
189 190 191

    # 使用梯度下降更新权重。对于这一步,我们只想对w1和w2的值进行原地改变;不想为更新阶段构建计算图,
    # 所以我们使用torch.no_grad()上下文管理器防止PyTorch为更新构建计算图
W
wizardforcel 已提交
192 193 194 195
    with torch.no_grad():
        w1 -= learning_rate * w1.grad
        w2 -= learning_rate * w2.grad

W
wizardforcel 已提交
196
        # 反向传播之后手动置零梯度
W
wizardforcel 已提交
197 198 199 200
        w1.grad.zero_()
        w2.grad.zero_()
```

W
wizardforcel 已提交
201
### [PyTorch:定义新的自动求导函数](#pytorch-defining-new-autograd-functions)
W
wizardforcel 已提交
202

W
wizardforcel 已提交
203
在底层,每一个原始的自动求导运算实际上是两个在Tensor上运行的函数。其中,**forward**函数计算从输入Tensors获得的输出Tensors。而**backward**函数接收输出Tensors对于某个标量值的梯度,并且计算输入Tensors相对于该相同标量值的梯度。 
W
wizardforcel 已提交
204

W
wizardforcel 已提交
205
在PyTorch中,我们可以很容易地通过定义`torch.autograd.Function`的子类并实现`forward``backward`函数,来定义自己的自动求导运算。之后我们就可以使用这个新的自动梯度运算符了。然后,我们可以通过构造一个实例并像调用函数一样,传入包含输入数据的tensor调用它,这样来使用新的自动求导运算。
W
wizardforcel 已提交
206

W
wizardforcel 已提交
207
这个例子中,我们自定义一个自动求导函数来展示ReLU的非线性。并用它实现我们的两层网络:
W
wizardforcel 已提交
208

W
wizardforcel 已提交
209 210
```python
# 可运行代码见本文件夹中的 two_layer_net_custom_function.py
W
wizardforcel 已提交
211 212 213 214
import torch

class MyReLU(torch.autograd.Function):
    """
W
wizardforcel 已提交
215 216 217
    我们可以通过建立torch.autograd的子类来实现我们自定义的autograd函数,
    并完成张量的正向和反向传播。
    """
W
wizardforcel 已提交
218
    @staticmethod
W
wizardforcel 已提交
219
    def forward(ctx, x):
W
wizardforcel 已提交
220
        """
W
wizardforcel 已提交
221 222 223 224 225 226
        在正向传播中,我们接收到一个上下文对象和一个包含输入的张量;
        我们必须返回一个包含输出的张量,
        并且我们可以使用上下文对象来缓存对象,以便在反向传播中使用。
        """
        ctx.save_for_backward(x)
        return x.clamp(min=0)
W
wizardforcel 已提交
227 228 229 230

    @staticmethod
    def backward(ctx, grad_output):
        """
W
wizardforcel 已提交
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
        在反向传播中,我们接收到上下文对象和一个张量,
        其包含了相对于正向传播过程中产生的输出的损失的梯度。
        我们可以从上下文对象中检索缓存的数据,
        并且必须计算并返回与正向传播的输入相关的损失的梯度。
        """
        x, = ctx.saved_tensors
        grad_x = grad_output.clone()
        grad_x[x < 0] = 0
        return grad_x


device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

# N是批大小; D_in 是输入维度;
# H 是隐藏层维度; D_out 是输出维度
W
wizardforcel 已提交
246 247
N, D_in, H, D_out = 64, 1000, 100, 10

W
wizardforcel 已提交
248 249 250
# 产生输入和输出的随机张量
x = torch.randn(N, D_in, device=device)
y = torch.randn(N, D_out, device=device)
W
wizardforcel 已提交
251

W
wizardforcel 已提交
252 253 254
# 产生随机权重的张量
w1 = torch.randn(D_in, H, device=device, requires_grad=True)
w2 = torch.randn(H, D_out, device=device, requires_grad=True)
W
wizardforcel 已提交
255 256 257

learning_rate = 1e-6
for t in range(500):
W
wizardforcel 已提交
258 259 260
    # 正向传播:使用张量上的操作来计算输出值y;
    # 我们通过调用 MyReLU.apply 函数来使用自定义的ReLU
    y_pred = MyReLU.apply(x.mm(w1)).mm(w2)
W
wizardforcel 已提交
261

W
wizardforcel 已提交
262
    # 计算并输出loss
W
wizardforcel 已提交
263 264 265
    loss = (y_pred - y).pow(2).sum()
    print(t, loss.item())

W
wizardforcel 已提交
266
    # 使用autograd计算反向传播过程。
W
wizardforcel 已提交
267 268 269
    loss.backward()

    with torch.no_grad():
W
wizardforcel 已提交
270
        # 用梯度下降更新权重
W
wizardforcel 已提交
271 272 273
        w1 -= learning_rate * w1.grad
        w2 -= learning_rate * w2.grad

W
wizardforcel 已提交
274
        # 在反向传播之后手动清零梯度
W
wizardforcel 已提交
275 276 277 278 279
        w1.grad.zero_()
        w2.grad.zero_()

```

W
wizardforcel 已提交
280
### [TensorFlow:静态图](#tensorflow-static-graphs)
W
wizardforcel 已提交
281

W
wizardforcel 已提交
282
PyTorch自动求导看起来非常像TensorFlow:这两个框架中,我们都定义计算图,使用自动微分来计算梯度。两者最大的不同就是TensorFlow的计算图是**静态的**,而PyTorch使用**动态的**计算图。 
W
wizardforcel 已提交
283

W
wizardforcel 已提交
284
在TensorFlow中,我们定义计算图一次,然后重复执行这个相同的图,可能会提供不同的输入数据。而在PyTorch中,每一个前向通道定义一个新的计算图。 
W
wizardforcel 已提交
285

W
wizardforcel 已提交
286
静态图的好处在于你可以预先对图进行优化。例如,一个框架可能要融合一些图的运算来提升效率,或者产生一个策略来将图分布到多个GPU或机器上。如果重复使用相同的图,那么在重复运行同一个图时,,前期潜在的代价高昂的预先优化的消耗就会被分摊开。
W
wizardforcel 已提交
287

片刻小哥哥's avatar
片刻小哥哥 已提交
288
静态图和动态图的一个区别是控制流。对于一些模型,我们希望对每个数据点执行不同的计算。例如,一个递归神经网络可能对于每个数据点执行不同的时间步数,这个展开(unrolling)可以作为一个循环来实现。对于一个静态图,循环结构要作为图的一部分。因此,TensorFlow提供了运算符(例如`tf.scan`)来把循环嵌入到图当中。对于动态图来说,情况更加简单:既然我们为每个例子即时创建图,我们可以使用普通的命令式控制流来为每个输入执行不同的计算。 
W
wizardforcel 已提交
289

W
wizardforcel 已提交
290
为了与上面的PyTorch自动梯度实例做对比,我们使用TensorFlow来拟合一个简单的2层网络:
W
wizardforcel 已提交
291

W
wizardforcel 已提交
292 293
```python
# 可运行代码见本文件夹中的 tf_two_layer_net.py
W
wizardforcel 已提交
294 295 296
import tensorflow as tf
import numpy as np

片刻小哥哥's avatar
片刻小哥哥 已提交
297
# 首先我们建立计算图(computational graph)
W
wizardforcel 已提交
298

W
wizardforcel 已提交
299 300
# N是批大小;D是输入维度;
# H是隐藏层维度;D_out是输出维度。
W
wizardforcel 已提交
301 302
N, D_in, H, D_out = 64, 1000, 100, 10

W
wizardforcel 已提交
303 304
# 为输入和目标数据创建placeholder;
# 当执行计算图时,他们将会被真实的数据填充
W
wizardforcel 已提交
305 306 307
x = tf.placeholder(tf.float32, shape=(None, D_in))
y = tf.placeholder(tf.float32, shape=(None, D_out))

W
wizardforcel 已提交
308 309
# 为权重创建Variable并用随机数据初始化
# TensorFlow的Variable在执行计算图时不会改变
W
wizardforcel 已提交
310 311 312
w1 = tf.Variable(tf.random_normal((D_in, H)))
w2 = tf.Variable(tf.random_normal((H, D_out)))

W
wizardforcel 已提交
313 314 315
# 前向传播:使用TensorFlow的张量运算计算预测值y。
# 注意这段代码实际上不执行任何数值运算;
# 它只是建立了我们稍后将执行的计算图。
W
wizardforcel 已提交
316 317 318 319
h = tf.matmul(x, w1)
h_relu = tf.maximum(h, tf.zeros(1))
y_pred = tf.matmul(h_relu, w2)

片刻小哥哥's avatar
片刻小哥哥 已提交
320
# 使用TensorFlow的张量运算损失(loss)
W
wizardforcel 已提交
321 322
loss = tf.reduce_sum((y - y_pred) ** 2.0)

W
wizardforcel 已提交
323
# 计算loss对于w1和w2的导数
W
wizardforcel 已提交
324 325
grad_w1, grad_w2 = tf.gradients(loss, [w1, w2])

W
wizardforcel 已提交
326 327 328
# 使用梯度下降更新权重。为了实际更新权重,我们需要在执行计算图时计算new_w1和new_w2。
# 注意,在TensorFlow中,更新权重值的行为是计算图的一部分;
# 但在PyTorch中,这发生在计算图形之外。
W
wizardforcel 已提交
329 330 331 332
learning_rate = 1e-6
new_w1 = w1.assign(w1 - learning_rate * grad_w1)
new_w2 = w2.assign(w2 - learning_rate * grad_w2)

片刻小哥哥's avatar
片刻小哥哥 已提交
333
# 现在我们搭建好了计算图,所以我们开始一个TensorFlow的会话(session)来实际执行计算图。
W
wizardforcel 已提交
334
with tf.Session() as sess:
W
wizardforcel 已提交
335 336

    # 运行一次计算图来初始化Variable w1和w2
W
wizardforcel 已提交
337 338
    sess.run(tf.global_variables_initializer())

W
wizardforcel 已提交
339
    # 创建numpy数组来存储输入x和目标y的实际数据
W
wizardforcel 已提交
340 341
    x_value = np.random.randn(N, D_in)
    y_value = np.random.randn(N, D_out)
W
wizardforcel 已提交
342
    
W
wizardforcel 已提交
343
    for _ in range(500):
W
wizardforcel 已提交
344 345 346 347 348
        # 多次运行计算图。每次执行时,我们都用feed_dict参数,
        # 将x_value绑定到x,将y_value绑定到y,
        # 每次执行图形时我们都要计算损失、new_w1和new_w2;
        # 这些张量的值以numpy数组的形式返回。
        loss_value, _, _ = sess.run([loss, new_w1, new_w2], 
W
wizardforcel 已提交
349 350 351 352
                                    feed_dict={x: x_value, y: y_value})
        print(loss_value)
```

W
wizardforcel 已提交
353 354 355
## [`nn`模块](#nn-module)

### [PyTorch:`nn`](#pytorch-nn)
W
wizardforcel 已提交
356 357


W
wizardforcel 已提交
358
计算图和autograd是十分强大的工具,可以定义复杂的操作并自动求导;然而对于大规模的网络,autograd太过于底层。
W
wizardforcel 已提交
359

W
wizardforcel 已提交
360
在构建神经网络时,我们经常考虑将计算安排成**层**,其中一些具有**可学习的参数**,它们将在学习过程中进行优化。
W
wizardforcel 已提交
361

W
wizardforcel 已提交
362
TensorFlow里,有类似[Keras](https://github.com/fchollet/keras)[TensorFlow-Slim](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/slim)[TFLearn](http://tflearn.org/)这种封装了底层计算图的高度抽象的接口,这使得构建网络十分方便。 
W
wizardforcel 已提交
363

片刻小哥哥's avatar
片刻小哥哥 已提交
364
在PyTorch中,包`nn`完成了同样的功能。`nn`包中定义一组大致等价于层的**模块**。一个模块接受输入的tesnor,计算输出的tensor,而且还保存了一些内部状态比如需要学习的tensor的参数等。`nn`包中也定义了一组损失函数(loss functions),用来训练神经网络。 
W
wizardforcel 已提交
365

W
wizardforcel 已提交
366
这个例子中,我们用`nn`包实现两层的网络:
W
wizardforcel 已提交
367

W
wizardforcel 已提交
368 369 370

```python
# 可运行代码见本文件夹中的 two_layer_net_nn.py
W
wizardforcel 已提交
371 372
import torch

W
wizardforcel 已提交
373 374 375 376
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')

# N是批大小;D是输入维度
# H是隐藏层维度;D_out是输出维度
W
wizardforcel 已提交
377 378
N, D_in, H, D_out = 64, 1000, 100, 10

W
wizardforcel 已提交
379 380 381 382
# 产生输入和输出随机张量
x = torch.randn(N, D_in, device=device)
y = torch.randn(N, D_out, device=device)

W
wizardforcel 已提交
383

W
wizardforcel 已提交
384 385 386 387
# 使用nn包将我们的模型定义为一系列的层。
# nn.Sequential是包含其他模块的模块,并按顺序应用这些模块来产生其输出。
# 每个线性模块使用线性函数从输入计算输出,并保存其内部的权重和偏差张量。
# 在构造模型之后,我们使用.to()方法将其移动到所需的设备。
W
wizardforcel 已提交
388
model = torch.nn.Sequential(
W
wizardforcel 已提交
389 390 391 392 393
            torch.nn.Linear(D_in, H),
            torch.nn.ReLU(),
            torch.nn.Linear(H, D_out),
        ).to(device)

W
wizardforcel 已提交
394

W
wizardforcel 已提交
395 396 397 398 399
# nn包还包含常用的损失函数的定义;
# 在这种情况下,我们将使用平均平方误差(MSE)作为我们的损失函数。
# 设置reduction='sum',表示我们计算的是平方误差的“和”,而不是平均值;
# 这是为了与前面我们手工计算损失的例子保持一致,
# 但是在实践中,通过设置reduction='elementwise_mean'来使用均方误差作为损失更为常见。
W
wizardforcel 已提交
400 401 402 403 404
loss_fn = torch.nn.MSELoss(reduction='sum')

learning_rate = 1e-4
for t in range(500):

W
wizardforcel 已提交
405 406 407 408 409 410
    # 前向传播:通过向模型传入x计算预测的y。
    # 模块对象重载了__call__运算符,所以可以像函数那样调用它们。
    # 这么做相当于向模块传入了一个张量,然后它返回了一个输出张量。
    y_pred = model(x)
    
    # 计算并打印损失。我们传递包含y的预测值和真实值的张量,损失函数返回包含损失的张量。
W
wizardforcel 已提交
411 412
    loss = loss_fn(y_pred, y)
    print(t, loss.item())
W
wizardforcel 已提交
413 414
    
    # 反向传播之前清零梯度
W
wizardforcel 已提交
415 416
    model.zero_grad()

片刻小哥哥's avatar
片刻小哥哥 已提交
417
    # 反向传播:计算模型的损失对所有可学习参数的导数(梯度)。
W
wizardforcel 已提交
418 419
    # 在内部,每个模块的参数存储在requires_grad=True的张量中,
    # 因此这个调用将计算模型中所有可学习参数的梯度。
W
wizardforcel 已提交
420 421
    loss.backward()

W
wizardforcel 已提交
422 423
    # 使用梯度下降更新权重。
    # 每个参数都是张量,所以我们可以像我们以前那样可以得到它的数值和梯度
W
wizardforcel 已提交
424 425
    with torch.no_grad():
        for param in model.parameters():
W
wizardforcel 已提交
426
            param.data -= learning_rate * param.grad
W
wizardforcel 已提交
427 428
```

W
wizardforcel 已提交
429
### [PyTorch:`optim`](#pytorch-optim)
W
wizardforcel 已提交
430

W
wizardforcel 已提交
431
到目前为止,我们已经通过手动改变包含可学习参数的张量来更新模型的权重。对于随机梯度下降(SGD/stochastic gradient descent)等简单的优化算法来说,这不是一个很大的负担,但在实践中,我们经常使用AdaGrad、RMSProp、Adam等更复杂的优化器来训练神经网络。
W
wizardforcel 已提交
432 433


W
wizardforcel 已提交
434 435
```python
# 可运行代码见本文件夹中的 two_layer_net_optim.py
W
wizardforcel 已提交
436 437
import torch

W
wizardforcel 已提交
438 439
# N是批大小;D是输入维度
# H是隐藏层维度;D_out是输出维度
W
wizardforcel 已提交
440 441
N, D_in, H, D_out = 64, 1000, 100, 10

W
wizardforcel 已提交
442
# 产生随机输入和输出张量
W
wizardforcel 已提交
443 444 445
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)

W
wizardforcel 已提交
446
# 使用nn包定义模型和损失函数
W
wizardforcel 已提交
447
model = torch.nn.Sequential(
W
wizardforcel 已提交
448 449 450 451
          torch.nn.Linear(D_in, H),
          torch.nn.ReLU(),
          torch.nn.Linear(H, D_out),
        )
W
wizardforcel 已提交
452 453
loss_fn = torch.nn.MSELoss(reduction='sum')

片刻小哥哥's avatar
片刻小哥哥 已提交
454
# 使用optim包定义优化器(Optimizer)。Optimizer将会为我们更新模型的权重。
W
wizardforcel 已提交
455 456
# 这里我们使用Adam优化方法;optim包还包含了许多别的优化算法。
# Adam构造函数的第一个参数告诉优化器应该更新哪些张量。
W
wizardforcel 已提交
457 458
learning_rate = 1e-4
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
W
wizardforcel 已提交
459

W
wizardforcel 已提交
460
for t in range(500):
W
wizardforcel 已提交
461 462

    # 前向传播:通过像模型输入x计算预测的y
W
wizardforcel 已提交
463 464
    y_pred = model(x)

W
wizardforcel 已提交
465
    # 计算并打印loss
W
wizardforcel 已提交
466 467
    loss = loss_fn(y_pred, y)
    print(t, loss.item())
W
wizardforcel 已提交
468 469
    
    # 在反向传播之前,使用optimizer将它要更新的所有张量的梯度清零(这些张量是模型可学习的权重)
W
wizardforcel 已提交
470 471
    optimizer.zero_grad()

W
wizardforcel 已提交
472
    # 反向传播:根据模型的参数计算loss的梯度
W
wizardforcel 已提交
473 474
    loss.backward()

W
wizardforcel 已提交
475
    # 调用Optimizer的step函数使它所有参数更新
W
wizardforcel 已提交
476 477 478
    optimizer.step()
```

W
wizardforcel 已提交
479
### [PyTorch:自定义`nn`模块](#pytorch-custom-nn-modules)
W
wizardforcel 已提交
480

W
wizardforcel 已提交
481
有时候需要指定比现有模块序列更复杂的模型;对于这些情况,可以通过继承`nn.Module`并定义`forward`函数,这个`forward`函数可以使用其他模块或者其他的自动求导运算来接收输入tensor,产生输出tensor。 
W
wizardforcel 已提交
482

W
wizardforcel 已提交
483
在这个例子中,我们用自定义Module的子类构建两层网络:
W
wizardforcel 已提交
484

W
wizardforcel 已提交
485 486
```python
# 可运行代码见本文件夹中的 two_layer_net_module.py
W
wizardforcel 已提交
487 488 489 490 491
import torch

class TwoLayerNet(torch.nn.Module):
    def __init__(self, D_in, H, D_out):
        """
W
wizardforcel 已提交
492 493
        在构造函数中,我们实例化了两个nn.Linear模块,并将它们作为成员变量。
        """
W
wizardforcel 已提交
494 495 496 497 498 499
        super(TwoLayerNet, self).__init__()
        self.linear1 = torch.nn.Linear(D_in, H)
        self.linear2 = torch.nn.Linear(H, D_out)

    def forward(self, x):
        """
W
wizardforcel 已提交
500
        在前向传播的函数中,我们接收一个输入的张量,也必须返回一个输出张量。
片刻小哥哥's avatar
片刻小哥哥 已提交
501
        我们可以使用构造函数中定义的模块以及张量上的任意的(可微分的)操作。
W
wizardforcel 已提交
502
        """
W
wizardforcel 已提交
503 504 505 506
        h_relu = self.linear1(x).clamp(min=0)
        y_pred = self.linear2(h_relu)
        return y_pred

W
wizardforcel 已提交
507 508
# N是批大小; D_in 是输入维度;
# H 是隐藏层维度; D_out 是输出维度
W
wizardforcel 已提交
509 510
N, D_in, H, D_out = 64, 1000, 100, 10

W
wizardforcel 已提交
511
# 产生输入和输出的随机张量
W
wizardforcel 已提交
512 513 514
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)

W
wizardforcel 已提交
515
# 通过实例化上面定义的类来构建我们的模型。
W
wizardforcel 已提交
516 517
model = TwoLayerNet(D_in, H, D_out)

W
wizardforcel 已提交
518 519 520 521
# 构造损失函数和优化器。
# SGD构造函数中对model.parameters()的调用,
# 将包含模型的一部分,即两个nn.Linear模块的可学习参数。
loss_fn = torch.nn.MSELoss(reduction='sum')
W
wizardforcel 已提交
522 523
optimizer = torch.optim.SGD(model.parameters(), lr=1e-4)
for t in range(500):
W
wizardforcel 已提交
524
    # 前向传播:通过向模型传递x计算预测值y
W
wizardforcel 已提交
525 526
    y_pred = model(x)

W
wizardforcel 已提交
527 528
    #计算并输出loss
    loss = loss_fn(y_pred, y)
W
wizardforcel 已提交
529 530
    print(t, loss.item())

W
wizardforcel 已提交
531
    # 清零梯度,反向传播,更新权重
W
wizardforcel 已提交
532 533 534 535 536 537
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

```

W
wizardforcel 已提交
538
### [PyTorch:控制流和权重共享](#pytorch-control-flow-weight-sharing)
W
wizardforcel 已提交
539

W
wizardforcel 已提交
540
作为动态图和权重共享的一个例子,我们实现了一个非常奇怪的模型:一个全连接的ReLU网络,在每一次前向传播时,它的隐藏层的层数为随机1到4之间的数,这样可以多次重用相同的权重来计算。
W
wizardforcel 已提交
541

W
wizardforcel 已提交
542
因为这个模型可以使用普通的Python流控制来实现循环,并且我们可以通过在定义转发时多次重用同一个模块来实现最内层之间的权重共享。
W
wizardforcel 已提交
543

W
wizardforcel 已提交
544
我们利用Mudule的子类很容易实现这个模型:
W
wizardforcel 已提交
545

W
wizardforcel 已提交
546 547 548

```python
# 可运行代码见本文件夹中的 dynamic_net.py
W
wizardforcel 已提交
549 550 551 552 553 554
import random
import torch

class DynamicNet(torch.nn.Module):
    def __init__(self, D_in, H, D_out):
        """
W
wizardforcel 已提交
555 556
        在构造函数中,我们构造了三个nn.Linear实例,它们将在前向传播时被使用。
        """
W
wizardforcel 已提交
557 558 559 560 561 562 563
        super(DynamicNet, self).__init__()
        self.input_linear = torch.nn.Linear(D_in, H)
        self.middle_linear = torch.nn.Linear(H, H)
        self.output_linear = torch.nn.Linear(H, D_out)

    def forward(self, x):
        """
W
wizardforcel 已提交
564 565 566 567 568 569 570
        对于模型的前向传播,我们随机选择0、1、2、3,
        并重用了多次计算隐藏层的middle_linear模块。
        由于每个前向传播构建一个动态计算图,
        我们可以在定义模型的前向传播时使用常规Python控制流运算符,如循环或条件语句。
        在这里,我们还看到,在定义计算图形时多次重用同一个模块是完全安全的。
        这是Lua Torch的一大改进,因为Lua Torch中每个模块只能使用一次。
        """
W
wizardforcel 已提交
571 572 573 574 575 576
        h_relu = self.input_linear(x).clamp(min=0)
        for _ in range(random.randint(0, 3)):
            h_relu = self.middle_linear(h_relu).clamp(min=0)
        y_pred = self.output_linear(h_relu)
        return y_pred

W
wizardforcel 已提交
577 578 579

# N是批大小;D是输入维度
# H是隐藏层维度;D_out是输出维度
W
wizardforcel 已提交
580 581
N, D_in, H, D_out = 64, 1000, 100, 10

W
wizardforcel 已提交
582
# 产生输入和输出随机张量
W
wizardforcel 已提交
583 584 585
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)

W
wizardforcel 已提交
586
# 实例化上面定义的类来构造我们的模型
W
wizardforcel 已提交
587 588
model = DynamicNet(D_in, H, D_out)

片刻小哥哥's avatar
片刻小哥哥 已提交
589
# 构造我们的损失函数(loss function)和优化器(Optimizer)。
W
wizardforcel 已提交
590
# 用平凡的随机梯度下降训练这个奇怪的模型是困难的,所以我们使用了momentum方法。
W
wizardforcel 已提交
591 592 593
criterion = torch.nn.MSELoss(reduction='sum')
optimizer = torch.optim.SGD(model.parameters(), lr=1e-4, momentum=0.9)
for t in range(500):
W
wizardforcel 已提交
594 595
    
    # 前向传播:通过向模型传入x计算预测的y。
W
wizardforcel 已提交
596 597
    y_pred = model(x)

W
wizardforcel 已提交
598
    # 计算并打印损失
W
wizardforcel 已提交
599 600 601
    loss = criterion(y_pred, y)
    print(t, loss.item())

W
wizardforcel 已提交
602
    # 清零梯度,反向传播,更新权重 
W
wizardforcel 已提交
603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
```

## [Examples](#id25)

You can browse the above examples here.

### [Tensors](#id26)

![https://pytorch.org/tutorials/_images/sphx_glr_two_layer_net_numpy_thumb.png](img/0e81253e59a21c3493d728554e793e80.jpg)

[Warm-up: numpy](examples_tensor/two_layer_net_numpy.html#sphx-glr-beginner-examples-tensor-two-layer-net-numpy-py)

![https://pytorch.org/tutorials/_images/sphx_glr_two_layer_net_tensor_thumb.png](img/3ea753e2a569c2d9ad05c6050094ad92.jpg)

[PyTorch: Tensors](examples_tensor/two_layer_net_tensor.html#sphx-glr-beginner-examples-tensor-two-layer-net-tensor-py)

### [Autograd](#id27)

![https://pytorch.org/tutorials/_images/sphx_glr_two_layer_net_autograd_thumb.png](img/d594436be094667a78e5df2a33068666.jpg)

[PyTorch: Tensors and autograd](examples_autograd/two_layer_net_autograd.html#sphx-glr-beginner-examples-autograd-two-layer-net-autograd-py)

![https://pytorch.org/tutorials/_images/sphx_glr_two_layer_net_custom_function_thumb.png](img/91670ac48d775e53a93477397af73921.jpg)

[PyTorch: Defining New autograd Functions](examples_autograd/two_layer_net_custom_function.html#sphx-glr-beginner-examples-autograd-two-layer-net-custom-function-py)

![https://pytorch.org/tutorials/_images/sphx_glr_tf_two_layer_net_thumb.png](img/7e7963967b1dfe2b5920d096cd75502b.jpg)

[TensorFlow: Static Graphs](examples_autograd/tf_two_layer_net.html#sphx-glr-beginner-examples-autograd-tf-two-layer-net-py)

W
wizardforcel 已提交
636
### [`nn` module](#id28)
W
wizardforcel 已提交
637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652

![https://pytorch.org/tutorials/_images/sphx_glr_two_layer_net_nn_thumb.png](img/2d060e233e6a979b9526a129949c945b.jpg)

[PyTorch: nn](examples_nn/two_layer_net_nn.html#sphx-glr-beginner-examples-nn-two-layer-net-nn-py)

![https://pytorch.org/tutorials/_images/sphx_glr_two_layer_net_optim_thumb.png](img/5db16619c1e6eaf064d6315abd3d0565.jpg)

[PyTorch: optim](examples_nn/two_layer_net_optim.html#sphx-glr-beginner-examples-nn-two-layer-net-optim-py)

![https://pytorch.org/tutorials/_images/sphx_glr_two_layer_net_module_thumb.png](img/51365bd7bda45f0bb24d1e9b875c3533.jpg)

[PyTorch: Custom nn Modules](examples_nn/two_layer_net_module.html#sphx-glr-beginner-examples-nn-two-layer-net-module-py)

![https://pytorch.org/tutorials/_images/sphx_glr_dynamic_net_thumb.png](img/bf0b252ce2d39ba6da26c16bee984d39.jpg)

[PyTorch: Control Flow + Weight Sharing](examples_nn/dynamic_net.html#sphx-glr-beginner-examples-nn-dynamic-net-py)