Pytorch学习笔记(二)

2018-12-14

本文涉及通过一些具体实例来进一步学习Pytorch。

本质上,pythorch提供两个主要功能:

  • n维Tensor,类似一个运行在GPU上的numpy
  • 对神经网络自动构建和差分

我们将使用一个全连接的ReLU网络作为例子。这个网络有一个隐含层,使用梯度下降算法学习,欧式距离作为损失函数。

Some Hyper Parameters

(1)batchsize:每批数据量的大小。DL通常用SGD的优化算法进行训练,也就是一次(1 个iteration)一起训练batchsize个样本,计算它们的平均损失函数值,来更新参数。

(2)iteration:1个iteration即迭代一次,也就是用batchsize个样本训练一次。

(3)epoch:1个epoch指用训练集中的全部样本训练一次,此时相当于batchsize 等于训练集的样本数。

(4)将训练集分成多个mini_batch(即常说的batch),一次迭代训练一个minibatch(即batchsize个样本),根据该batch数据的loss更新权值。这相比于全数据集训练,相当于是在寻找最优时人为增加了一些随机噪声,来修正由局部数据得到的梯度,尽量避免因batchsize过大陷入局部最优。

(5)整体优化效果与batchsize有关:
batchsize越小,一个batch中的随机性越大,越不易收敛。然而batchsize越小,速度越快,权值更新越频繁;且具有随机性,对于非凸损失函数来讲,更便于寻找全局最优。从这个角度看,收敛更快,更容易达到全局最优。batchsize越大,越能够表征全体数据的特征,其确定的梯度下降方向越准确,(因此收敛越快),且迭代次数少,总体速度更快。然而大的batchsize相对来讲缺乏随机性,容易使梯度始终向单一方向下降,陷入局部最优;而且当batchsize增大到一定程度,再增大batchsize,一次batch产生的权值更新(即梯度下降方向)基本不变。因此理论上存在一个最合适的batchsize值,使得训练能够收敛最快或者收敛效果最好(全局最优点)。

根据现有的调参经验,加入正则化项BN后,在内存容量允许的情况下,一般来说设置一个较大的batchsize值更好,通常从128开始调整。

Matrix Calculus

由于本科的时候没有学习过矩阵求导的相关知识,所以从网上找了个教程机器学习-矩阵求导。我个人认为其中的要点就是以下的四个公式的套用。

Tensors

Warm-up: numpy

在介绍Pytorch之前,我们先利用numpy构建一个网络。Numpy提供n维数组对象和很多操作数组的函数。numpy是科学计算的一个通用框架,不支持计算图、深度学习或梯度。但是我们利用numpy提供的操作可以很容易实现一个两层网络的前向/后向逻辑,并训练这个网络学习一个随机生成的数据的分布。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import numpy as np

# N is batch size; D_in is input dimension;
# H is hidden dimension; D_out is output dimension.
N, D_in, H, D_out = 64, 1000, 100, 10

# Create random input and output data
x = np.random.randn(N, D_in)
y = np.random.randn(N, D_out)

# Randomly initialize weights
w1 = np.random.randn(D_in, H)
w2 = np.random.randn(H, D_out)

learning_rate = 1e-6
for t in range(500):
# Forward pass: compute predicted y
h = x.dot(w1)
h_relu = np.maximum(h, 0)
y_pred = h_relu.dot(w2)

# Compute and print loss
loss = np.square(y_pred - y).sum()
print(t, loss)

# Backprop to compute gradients of w1 and w2 with respect to loss
# m的梯度 就是 loss 对 m 求偏导
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)

# Update weights
w1 -= learning_rate * grad_w1
w2 -= learning_rate * grad_w2

Pytorch: Tensors

Numpy 是一个伟大的框架,但是他不能利用 GPUs 来加快数值计算。对于现代深度神经网络, GPU 通常提供50倍或更高的加速,所以不幸的是 numpy 不足以用于现代深度学习。

这里,我介绍 PyTorch 中最基础的概念: 张量(Tensor)。Tensor 在概念上与 numpy 数组相同,它是一个 N 维的数组,并提供很多操作它的函数。和 numpy 类似,PyTorch 中的 Tensors 没有深度学习、计算图和梯度这些内容,他们是科学计算的通用工具。

然而,PyTorch的Tensor 和 numpy 不同,它可以利用 GPUs 来加快数值计算。为了让 Tensors 在 GPU上运行,你只需把它转型维一个新的类型即可。

这里我们使用 PyTorch 来训练一个两层的网络让它拟合随机数据。 和上面 numpy 的例子类似,我们需要手动实现网络的前向和反向传播:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import torch

dtype = torch.float
device = torch.device("cpu")
# device = torch.device("cuda:0") # Uncomment this to run on GPU

# N is batch size; D_in is input dimension;
# H is hidden dimension; D_out is output dimension.
N, D_in, H, D_out = 64, 1000, 100, 10

# Create random input and output data
x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)

# Randomly initialize weights
w1 = torch.randn(D_in, H, device=device, dtype=dtype)
w2 = torch.randn(H, D_out, device=device, dtype=dtype)

learning_rate = 1e-6
for t in range(500):
# Forward pass: compute predicted y
h = x.mm(w1)
# clamp(min,max)把范围限制在[min,max]区域内
h_relu = h.clamp(min=0)
y_pred = h_relu.mm(w2)

# Compute and print loss
# item()方法把字典中每对key和value组成一个元组,并把这些元组放在列表中返回。
loss = (y_pred - y).pow(2).sum().item()
print(t, loss)

# Backprop to compute gradients of w1 and w2 with respect to loss
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)

# Update weights using gradient descent
w1 -= learning_rate * grad_w1
w2 -= learning_rate * grad_w2

自动求导

PyTorch: Variables 和 autograd

在上面的例子中,我们自己实现了神经网络的前向和反向传播。手动实现反向传播对于一个小型的两层网络来说并不是什么难题,但是对于大型复杂啊网络,很快就会变得棘手。

值得庆幸的是,我们可以使用自动微分来自动计算神经网络的反向传播。PyTorch 中的 autograd 包提供了这一功能。当我们使用自动求导时,你的神经网络的前向传播讲定义一个计算图;图中的节点将是 Tensors,边表示从输入张量产生输出张量的函数。通过图反向传播可以让你轻松的计算梯度。

每个张量表示计算图中的一个节点。如果x是一个具有x.requires_grad=True的属性的Tensor,那么x.grad则是另一个用来保存 x 关于某个标量值的梯度的Tensor。
这里我们使用 PyTorch Tensors 和自动求导来实现我们的两层网络;现在我们不再需要手动地实现反向传播。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import torch

dtype = torch.float
device = torch.device("cpu")
# device = torch.device("cuda:0") # Uncomment this to run on GPU

N, D_in, H, D_out = 64, 1000, 100, 10


x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)

# Setting requires_grad=True indicates that we want to compute gradients with respect to these Tensors during the backward pass.
w1 = torch.randn(D_in, H, device=device, dtype=dtype, requires_grad=True)
w2 = torch.randn(H, D_out, device=device, dtype=dtype, requires_grad=True)

learning_rate = 1e-6
for t in range(500):
y_pred = x.mm(w1).clamp(min=0).mm(w2)

# Now loss is a Tensor of shape (1,)
# loss.item() gets the a scalar value held in the loss.
loss = (y_pred - y).pow(2).sum()
print(t, loss.item())

# 计算梯度
loss.backward()

# Manually update weights using gradient descent. Wrap in torch.no_grad() because weights have requires_grad=True, but we don't need to track this in autograd.
# An alternative way is to operate on weight.data and weight.grad.data. Recall that tensor.data gives a tensor that shares the storage with tensor, but doesn't track history. You can also use torch.optim.SGD to achieve this.
with torch.no_grad():
w1 -= learning_rate * w1.grad
w2 -= learning_rate * w2.grad

w1.grad.zero_()
w2.grad.zero_()

PyTorch:定义新的 autograd 函数

在底层,每个原始 autograd 操作符实际上是在 Tensors 上操作的两个函数。forward 函数从输入张量来计算输出张量。backward函数接收输出向量关于某个标量的梯度,然后计算输入张量关于关于同一个标量的梯度。

在 PyTorch 中, 我们可以通过定义一个 torch.autograd.Function 的子类并实现 forwardbackward两个函数容易地实现我们自己的 autograd 操作符。我们可以通过定义一个该操作符的实例,并像函数一样给它传递一个包含输入数据的 Variable 来调用它,这样就使用我们新定义的 autograd 操作符。

在这个例子中我们自己定义了一个 autograd 函数来执行 ReLU 非线性映射,并使用它实现了一个两层的网络:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import torch

class MyReLU(torch.autograd.Function):

@staticmethod
def forward(ctx, input):
"""
In the forward pass we receive a Tensor containing the input and return
a Tensor containing the output. ctx is a context object that can be used
to stash information for backward computation. You can cache arbitrary
objects for use in the backward pass using the ctx.save_for_backward method.
"""
ctx.save_for_backward(input)
return input.clamp(min=0)

@staticmethod
def backward(ctx, grad_output):
"""
In the backward pass we receive a Tensor containing the gradient of the loss
with respect to the output, and we need to compute the gradient of the loss
with respect to the input.
"""
input, = ctx.saved_tensors
grad_input = grad_output.clone()
grad_input[input < 0] = 0
return grad_input

dtype = torch.float
device = torch.device("cpu")
# device = torch.device("cuda:0") # Uncomment this to run on GPU

N, D_in, H, D_out = 64, 1000, 100, 10

x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)

w1 = torch.randn(D_in, H, device=device, dtype=dtype, requires_grad=True)
w2 = torch.randn(H, D_out, device=device, dtype=dtype, requires_grad=True)

learning_rate = 1e-6
for t in range(500):
# To apply our Function, we use Function.apply method. We alias this as 'relu'.
relu = MyReLU.apply

y_pred = relu(x.mm(w1)).mm(w2)

loss = (y_pred - y).pow(2).sum()
print(t, loss.item())

loss.backward()

with torch.no_grad():
w1 -= learning_rate * w1.grad
w2 -= learning_rate * w2.grad

w1.grad.zero_()
w2.grad.zero_()

Tensorflow: 静态图

PyTorch 的 autograd 看起来很像 Tensorflow: 在两个框架中,我们都定义一个计算图,然后使用自动微分来计算梯度。最大的区别是: Tensorflow 中的计算图是静态的,而 PyTorch 使用的是动态计算图。

在 Tensorflow 中,我们只定义一次计算图,然后重复执行同一个图,可能将不同的输入数据提供给图。在 PyTorch 中,每次前向传播都会定义一个新的计算图。

静态图很好,因为您可以预先优化图; 例如,一个框架可能决定为了效率而融合某些图操作,或者想出一个在许多GPU或许多机器上的分布运行计算图的策略。 如果您一遍又一遍地重复使用同一个图,那么这个潜在的昂贵的前期优化可以在同一个图重复运行的情况下分摊。

静态图和动态图不同的一个方面是控制流。对于某些模型,我们可能希望对数据点执行不同的计算;例如,对于每个数据点,循环网络可以展开不同数量的时间步长,这个展开可以作为一个循环来实现。

使用静态图,循环结构需要成为图形的一部分;出于这个原因,Tensorflow 提供了像 tf.scan 这样的操作付来将循环嵌入到计算图中。使用动态图,这种情形就变得更简单了:由于我们为每个示例动态地都建图,我们可以使用普通的命令式流控制来执行每个输入的不同计算。
(由于tensorflow我还没有学,所以这里就先这样,待补)

nn Module

PyTorch:nn

计算图和 autograd 是定义复杂运算符和自动求导的的一个非常强大的范例。然而对于大规模的神经网络, 原始的 autograd 可能有点太低级了。

当构建神经网络时,我们经常想到把计算组织维层级结构,其中一些具有可学习的参数,这些参数将在学习期间被优化。

在 Tensorflow 中,像 KerasTensorFlow-SlimTFLearn 这样的软件包提供了对原始图的更高级的抽象,这对于构建神经网络很有用。

在 PyTorch 中, nn 包提供了同样的功能。 nn 包提供了一组Modules,他们大致相当于神经网络层。一个模块接收一个输入tensor并计算输出tensor,也可以保存内部状态,如包含可学习的参数。 nn 包还定义了一组训练神经网络时有用的损失函数。

在这个例子中我们使用 nn 包来实现我们的两层网络:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import torch

N, D_in, H, D_out = 64, 1000, 100, 10

x = torch.randn(N, D_in)
y = torch.randn(N, D_out)

# Use the nn package to define our model as a sequence of layers. nn.Sequential
# is a Module which contains other Modules, and applies them in sequence to
# produce its output. Each Linear Module computes output from input using a
# linear function, and holds internal Tensors for its weight and bias.
model = torch.nn.Sequential(
torch.nn.Linear(D_in, H),
torch.nn.ReLU(),
torch.nn.Linear(H, D_out),
)

# The nn package also contains definitions of popular loss functions; in this
# case we will use Mean Squared Error (MSE) as our loss function.
loss_fn = torch.nn.MSELoss(reduction='sum')

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

y_pred = model(x)

loss = loss_fn(y_pred, y)
print(t, loss.item())

model.zero_grad()

loss.backward()

with torch.no_grad():
for param in model.parameters():
param -= learning_rate * param.grad

PyTorch: optim

此前更新模型权重都是通过手动修改可学习的tensor(利用torch.no_grad()或.data()来阻止autograd跟踪变化历史)。这对于像随机梯度下降这种简单的优化算法来说不是很困难,但是在实际训练神经网络时,我们常常使用更复杂的优化算法,如 AdaGrad,RMSProp, Adam等。

PyTorch 的 optim 包抽象了优化算法的思想,并提供了常用算法的实现。

在这个例子中,我们讲使用 nn 包来重新定义我们之前的模型,但是我们会使用 optim 包提供的 Adam 算法来优化我们的模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import torch

N, D_in, H, D_out = 64, 1000, 100, 10

x = torch.randn(N, D_in)
y = torch.randn(N, D_out)

model = torch.nn.Sequential(
torch.nn.Linear(D_in, H),
torch.nn.ReLU(),
torch.nn.Linear(H, D_out),
)
loss_fn = torch.nn.MSELoss(reduction='sum')

# Use the optim package to define an Optimizer that will update the weights of
# the model for us. Here we will use Adam; the optim package contains many other
# optimization algoriths. The first argument to the Adam constructor tells the
# optimizer which Tensors it should update.

learning_rate = 1e-4
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
for t in range(500):

y_pred = model(x)

loss = loss_fn(y_pred, y)
print(t, loss.item())

optimizer.zero_grad()

loss.backward()

optimizer.step()

PyTorch: 自定义模块

有时你想要指定比现有模块序列更复杂的模型;对于这种情况,你可以通过继承 nn.Module 来定义自己的模块,并实现 forward 函数,他接收一个输入变量,并使用其他模块或 autograd 操作符来生成输出变量。

在这个例子中,我们实现了一个两层网络来作为自定义模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import torch

class TwoLayerNet(torch.nn.Module):
def __init__(self, D_in, H, D_out):
super(TwoLayerNet, self).__init__()
self.linear1 = torch.nn.Linear(D_in, H)
self.linear2 = torch.nn.Linear(H, D_out)

def forward(self, x):
h_relu = self.linear1(x).clamp(min=0)
y_pred = self.linear2(h_relu)
return y_pred

N, D_in, H, D_out = 64, 1000, 100, 10

x = torch.randn(N, D_in)
y = torch.randn(N, D_out)

model = TwoLayerNet(D_in, H, D_out)

# Construct our loss function and an Optimizer. The call to model.parameters()
# in the SGD constructor will contain the learnable parameters of the two
# nn.Linear modules which are members of the model.

criterion = torch.nn.MSELoss(reduction='sum')
optimizer = torch.optim.SGD(model.parameters(), lr=1e-4)
for t in range(500):

y_pred = model(x)

loss = criterion(y_pred, y)
print(t, loss.item())

optimizer.zero_grad()
loss.backward()
optimizer.step()

PyTorch: 控制流 + 共享权重

作为动态图和权值共享的例子,我们实现了一个不同的模型: 一个全连接的 ReLU 网络,每次前向传播时, 从1到4随机选择一个数来作为隐藏层的层数。多次重复使用相同的权重来计算最内层的隐藏层。

对于这个模型,我们可以使用普通的 Python 控制流来实现循环,在定义前向传播时,通过简单的重复使用同一个模块,我们可以实现最内层之间的权重共享。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import random
import torch

class DynamicNet(torch.nn.Module):
def __init__(self, D_in, H, D_out):

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):
"""
For the forward pass of the model, we randomly choose either 0, 1, 2, or 3
and reuse the middle_linear Module that many times to compute hidden layer
representations.

Since each forward pass builds a dynamic computation graph, we can use normal
Python control-flow operators like loops or conditional statements when
defining the forward pass of the model.
"""

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

N, D_in, H, D_out = 64, 1000, 100, 10

x = torch.randn(N, D_in)
y = torch.randn(N, D_out)

model = DynamicNet(D_in, H, D_out)

criterion = torch.nn.MSELoss(reduction='sum')
optimizer = torch.optim.SGD(model.parameters(), lr=1e-4, momentum=0.9)
for t in range(500):
y_pred = model(x)

loss = criterion(y_pred, y)
print(t, loss.item())

optimizer.zero_grad()
loss.backward()
optimizer.step()

本文来源:「想飞的小菜鸡」的个人网站 vodkazy.cn

版权声明:本文为「想飞的小菜鸡」的原创文章,采用 BY-NC-SA 许可协议,转载请附上原文出处链接及本声明。

原文链接:https://vodkazy.cn/2018/12/14/Pytorch学习笔记(二)

支付宝打赏 微信打赏

如果文章对你有帮助,欢迎点击上方按钮打赏作者,更多文章请访问想飞的小菜鸡