Task 1-PyTorch的基本概念
1. 什么是PyTorch?为什么选择PyTorch?
- PyTorch是一个深度学习框架,同时它也是一个科学计算库。**这是PyTorch的核心团队对PyTorch的描述。PyTorch的科学计算方面主要是PyTorch的张量库和相关张量运算的结果。PyTorch的Tensor(张量)可以在GPU上运行。PyTorch的最初版本是在2017年1月发布,其前身是机器学习框架Torch。Soumith Chintala因为引导了PyTorch项目而广受赞誉,而他创建PyTorch的原因也很简单:Lua版本的Torch正在老化,因此需要用Python编写更新版本。因此,PyTorch诞生了。我们可能听说过PyTorch是由Facebook创造和维护的,这是因为PyTorch被创建时Soumith Chintala正在Facebook的AI研究中心工作。
- 对于深度学习和神经网络的初学者来说,学习PyTorch的最主要的原因就是它是一个简洁且高效快速的框架。使用PyTorch进行编程之后,我们将对神经网络和深度学习有一个更深的理解。PyTorch的顶级哲学之一就是不干涉,这使得我们可以更加专注于神经网络而不是实际框架。当我们在使用PyTorch写代码时,我们就只是在扩展标准Python类,当我们需要调试时,也是使用标准的Python调试器。PyTorch的设计是现代化的,其源码对于Python开发者来说非常容易阅读,因为其主要是由Python实现的,只对一些有性能瓶颈的操作使用了C++和CUDA的代码。总而言之,PyTorch是一个伟大的工具,它将加深我们对深度学习和神经网络的理解。
2. PyTorch的安装
首先介绍一下Anaconda,它是一个开源的包、环境管理器,可以用于在同一个机器上安装不同版本的软件包及其依赖,并能够在不同的环境之间切换。
安装分为以下几步:
-
为特定环境指定适当的配置选项,例:
- OS: Windows
- Package Manager:conda
- Language:Python3.7
- CUDA:10.0
-
然后运行生成的命令:
> conda install pytorch torchvision cudatoolkit=10.0 -c pytorch
验证PyTorch的安装:
- To use PyTorch we
import torch
. - To check the vision, we use
torch.__version__
验证GPU的功能:
> torch.cuda.is_available()
True
> torch.version.cuda
'10.0'
3. PyTorch的基础概念
分点陈述:
- Tensor和Numpy的ndarrays类似,但PyTorch的tensor支持GPU加速。Tensor和numpy的数组间的互相操作非常容易且快速(他们共享内存)。就算numpy中有些操作在tensor中无法实现,也可以通过先转为numpy数组处理,之后再转回tensor。
- PyTorch是一个灵活的深度学习框架,它允许通过动态神经网络(即if条件语句和while循环语句那样利用动态控制流的网络)自动分化。它支持GPU加速、分布式训练、多种优化以及更多的、更简洁的特性。
4. 通过代码实现流程(实现一个深度学习的代码流程)
在这里,我们使用PyTorch的Tensors将双层神经网络与随机数据进行匹配,我们需要手动实现网络中的前向和后向传播。
import torch
dtype = torch.float
device = touch.device("cpu")
# device = touch.device("cuda:0") #当运行在GPU时,要加上这行代码
# N是batch size;D_in是输入的维度
# H是隐藏层的维度;D_out是输出的维度
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)
# 学习率
learing_rate = 1e-6
for t in range(500):
# 前向传播:计算出预测的y
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)
# 后向传播,计算出w1和w2相对于损耗的梯度
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可以借用Autograd轻松解决这个问题。当使用autograd时,我们将定义一个计算图,图中的结点是tensors,图中的边是产生从输入张量到输出张量的函数。通过此图进行反向传播,我们可以轻松计算梯度。如果x是一个张量,并且x.requires_grad=True,那么x.grad就是另一个张量( x.grad
is another Tensor holding the gradient of x
with respect to some scalar value )。
import torch
dtype = torch.float
device = torch.device("cpu")
# device = touch.device("cuda:0") #当运行在GPU时,要加上这行代码
# N是batch size;D_in是输入的维度
# H是隐藏层的维度;D_out是输出的维度
N, D_in, H, D_out = 64, 1000, 100, 10
# 构造随机生成的输入和输出数据
# 设置requires_grad=False,表明在后向传播时,我们不需要计算关于这些张量的梯度
x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(H, D_out, device=device, dtype=dtype)
# 网络权重随机初始化
# 设置requires_grad=True,表明在后向传播时,我们需要计算关于这些张量的梯度
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)
# 学习率
learing_rate = 1e-6
for t in range(500):
# 前向传播:这些与我们使用tensors计算正向传播的操作完全相同,但是我们不需要
# 保留对中间值的引用,因为我们没有手动实现反向传播。
y_pred = x.mm(w1).clamp(min=0).mm(w2)
# 使用tensor操作计算并打印损失
# 现在loss是一个shape(1,)类型的张量
# loss.item()可以获取loss中保存的标量值
loss = (y_pred - y).pow(2).sum()
print(t, loss.item())
# 使用autograd来计算后向传播;这个调用将计算相对于所有requires_grad=True的
# 张量的损失的梯度。
# After this call w1.grad and w2.grad will be Tensors holding the gradient
# of the loss with respect to w1 and w2 respectively.
loss.backward()
# 使用梯度下降手动更新权重。之所以封装于torch.no_grad(),是因为权重的
# requires_grad=True,而我们在自动微分中没有必要跟踪这些;
# 另一种可选择的操作是operate on weight.data and weight.grad.data.
# You can also use torch.optim.SGD to achieve this.
with torch.no_grad():
w1 -= learning_rate * w1.grad
w2 -= learning_rate * w2.grad
# 更新权重之后,手动将梯度设置为0
w1.grad.zero_()
w2.grad.zero_()
PyTorch: 定义新的autograd函数
Under the hood, each primitive autograd operator is really two functions that operate on Tensors. The forward function computes output Tensors from input Tensors. The backward function receives the gradient of the output Tensors with respect to some scalar value, and computes the gradient of the input Tensors with respect to that same scalar value.
In PyTorch we can easily define our own autograd operator by defining a subclass of torch.autograd.Function
and implementing the forward
and backward
functions. We can then use our new autograd operator by constructing an instance and calling it like a function, passing Tensors containing input data.
In this example we define our own custom autograd function for performing the ReLU nonlinearity, and use it to implement our two-layer network:
import torch
class MyReLU(torch.autograd.Function):
"""
我们可以实现自定义(定制的)autograd函数,通过继承torch.autograd.Function并实现运行 Tensors上的前向和后向传播。
"""
@staticmethod
def forward(ctx, input):
"""
前向传播里,我们接收到一个包含输入的张量并输出一个包含输出的张量。ctx是一个上下文 对象,可用于存储信息以进行反向计算。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):
""""
后向传播里,我们接收到一个包含关于输出的损失的梯度的张量,并且我们需要计算关于输入 的损失的梯度
""""
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 = touch.device("cuda:0") #当运行在GPU时,要加上这行代码
# N是batch size;D_in是输入的维度
# H是隐藏层的维度;D_out是输出的维度
N, D_in, H, D_out = 64, 1000, 100, 10
# 构造随机生成的输入和输出数据
# 设置requires_grad=False,表明在后向传播时,我们不需要计算关于这些张量的梯度
x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(H, D_out, device=device, dtype=dtype)
# 网络权重随机初始化
# 设置requires_grad=True,表明在后向传播时,我们需要计算关于这些张量的梯度
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)
# 学习率
learing_rate = 1e-6
for t in range(500):
# 为了使用我们定义的函数,我们使用Function.apply函数。
relu = MyReLU.apply
# 前向传播:compute predicted y using operations; we compute
# ReLU using our custom autograd operation.
y_pred = relu(x.mm(w1)).mm(w2)
# 计算并打印损失
loss = (y_pred - y).pow(2).sum()
print(t, loss.item())
# 使用autograd计算后向传播
loss.backward()
# 使用梯度下降更新权值
with torch.no_grad():
w1 -= learning_rate * w1.grad
w2 -= learning_rate * w2.grad
# 更新权重之后,手动将梯度设置为0
w1.grad.zero_()
w2.grad.zero_()