PyTorch的核心是两个主要特征:
- 一个n维张量,类似于numpy,但可以在GPU上运行
- 搭建和训练神经网络时的自动微分/求导机制
本章节我们将使用全连接的ReLU网络作为运行示例。该网络将有一个单一的隐藏层,并将使用梯度下降训练,通过最小化网络输出和真正结果的欧几里得距离,来拟合随机生成的数据。 张量 在介绍PyTorch之前,本章节将首先使用numpy实现网络。 Numpy提供了一个n维数组对象,以及许多用于操作这些数组的 函数。Numpy是用于科学计算的通用框架;它对计算图、深度学习和梯度一无所知。然而,我们可以很容易地使用NumPy,手动实现网络的 前向和反向传播,来拟合随机数据:
import numpy as np
N,D_in,H,D_out=64,1000,100,10
x=np.random.randn(N,D_in)
y=np.random.randn(N,D_out)
w1=np.random.randn(D_in,H)
w2=np.random.randn(H,D_out)
learning_rate=1e-6
for t in range(500):
h=x.dot(w1)
h_relu=np.maximum(h,0)
y_pred=h_relu.dot(w2)
loss=np.square(y_pred-y).sum()
print(t,loss)
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)
w1 -= learning_rate * grad_w1
w2 -= learning_rate * grad_w2
PyTorch:张量 Numpy是一个很棒的框架,但它不能利用GPU来加速其数值计算。 对于现代深度神经网络,GPU通常提供50倍或更高的加速,所以,numpy不能满足当代深度学习的需求。在这里,先介绍最基本的PyTorch概念: 张量(Tensor):PyTorch的tensor在概念上与numpy的array相同: tensor是一个n维数组, PyTorch提供了许多函数用于操作这些张量。任何希望使用NumPy执行的计算也可以使用PyTorch 的tensor来完成,可以认为它们是科学计算的通用工具。 与Numpy不同,PyTorch可以利用GPU加速其数值计算。要在GPU上运行Tensor,在构造张量使用 device 参数把tensor建立在GPU上。 在这里,本章使用tensors将随机数据上训练一个两层的网络。和前面NumPy的例子类似,我们使 用PyTorch的tensor,手动在网络中实现前向传播和反向传播:
import torch
dtype=torch.float
device=torch.device("cpu")
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)
w2 = torch.randn(H, D_out, device=device, dtype=dtype)
learning_rate=1e-6
for t in range(500):
h=x.mm(w1)
h_relu=h.clamp(min=0)
y_pred=h_relu.mm(w2)
loss=(y_pred-y).pow(2).sum().item()
print(t,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)
w1 -= learning_rate * grad_w1
w2 -= learning_rate * grad_w2
自动求导 PyTorch:张量和自动求导 在上面的例子中,需要手动实现神经网络的前向和后向 传递。手动实现反向传递对于小型双层网 络来说并不是什么大问 题,但对于大型复杂网络来说很快就会变得非常繁琐。 但是可以使用自动微分来自动计算神经网络中的后向传递。 PyTorch中的 autograd 包提供了这个 功能。当使用autograd时,网络前向传播将定义一个计算图;图中的节点是tensor,边是函数,这些函数是输出tensor到输入tensor的映射。这张计算图使得在网络中反向传播时梯度的计算十分 简单。
这听起来很复杂,在实践中使用起来非常简单。 如果我们想计算某些的tensor的梯度,我们只需 要在建立这个tensor时加入这么一句: requires_grad=True 。这个tensor上的任何PyTorch的操作 都将构造一个计算图,从而允许我们稍后在图中执行反向传播。如果这个tensor x 的 requires_grad=True ,那么反向传播之后x.grad 将会是另一个张量,其为x关于某个标量值的梯 度。 有时可能希望防止PyTorch在requires_grad=True 的张量执行某些操作时构建计算图;例如,在 训练神经网络时,我们通常不希望通过权重更新步骤进行反向传播。在这种情况下,我们可以使 用torch.no_grad() 上下文管理器来防止构造计算图。 下面我们使用PyTorch的Tensors和autograd来实现我们的两层的神经网络;我们不再需要手动执 行网络的反向传播:
import torch
dtype = torch.float
device = torch.device("cpu")
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):
y_pred = x.mm(w1).clamp(min=0).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_()
定义新的自动求导函数 在底层,每一个原始的自动求导运算实际上是两个在Tensor上运行的函数。其中, forward 函数 计算从输入Tensors获得的输出Tensors。而backward 函数接收输出Tensors对于某个标量值的梯 度,并且计算输入Tensors相对于该相同标量值的梯度。 在PyTorch中,我们可以很容易地通过定义torch.autograd.Function 的子类并实现forward 和 backward函数,来定义自己的自动求导运算。之后我们就可以使用这个新的自动梯度运算符了。 然后,我们可以通过构造一个实例并像调用函数一样,传入包含输入数据的tensor调用它,这样来使用新的自动求导运算。这个例子中,我们自定义一个自动求导函数来展示ReLU的非线性。并用它实现我们的两层网络:
import torch
class MyReLU(torch.autograd.Function):
@staticmethod
def forward(ctx,x):
ctx.save_for_backward(x)
return x.clamp(min=0)
@staticmethod
def backward(ctx,grad_out_put):
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 = 64, 1000, 100, 10
x = torch.randn(N, D_in, device=device)
y = torch.randn(N, D_out, device=device)
w1 = torch.randn(D_in, H, device=device, requires_grad=True)
w2 = torch.randn(H, D_out, device=device, requires_grad=True)
learning_rate = 1e-6
for t in range(500):
y_pred = MyReLU.apply(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_()
PyTorch: nn 计算图和autograd是十分强大的工具,可以定义复杂的操作并自动求导;然而对于大规模的网 络,autograd太过于底层。 在构建神经网络时,我们经常考虑将计算安排成层,其中一些具有可 学习的参数,它们将在学习过程中进行优化。 TensorFlow里,有类似Keras,TensorFlow-Slim和TFLearn这种封装了底层计算图的高度抽象的接口,这使得构建网络十分方便。 在PyTorch中,包nn 完成了同样的功能。nn包中定义一组大致等价于层的模块。一个模块接受输 入的tesnor,计算输出的tensor,而且 还保存了一些内部状态比如需要学习的tensor的参数等。nn包中也定义了一组损失函数(loss functions),用来训练神经网络。 这个例子中,我们用nn包实现两层的网络:
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')
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 到目前为止,我们已经通过手动改变包含可学习参数的张量来更新模型的权重。对于随机梯度下 降(SGD/stochastic gradient descent)等简单的优化算法来说,这不是一个很大的负担,但在实践 中,我们经常使用AdaGrad、RMSProp、Adam等更复杂的优化器来训练神经网络。
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')
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 模块 有时候需要指定比现有模块序列更复杂的模型;对于这些情况,可以通过继承nn.Module 并定义 forward 函数,这个forward 函数可以 使用其他模块或者其他的自动求导运算来接收输入 tensor,产生输出tensor。在这个例子中,我们用自定义Module的子类构建两层网络:
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)
loss_fn = torch.nn.MSELoss(reduction='sum')
optimizer = torch.optim.SGD(model.parameters(), lr=1e-4)
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()
|