深度学习(3) - 计算框架PyTorch

PyTorch

张量

张量是一种专门的数据结构,与数组和矩阵非常相似。张量类似于 NumPy 的 ndarrays,但张量可以在 GPU 或其他硬件加速器上运行,张量也针对自动微分进行了优化。

默认情况下,张量是在 CPU 上创建的。我们需要使用 .to 方法将张量显式地移动到 GPU(在检查 GPU 是否可用后)。请记住,跨设备复制大型张量在时间和内存方面可能代价高昂!

将结果存储到操作数中的操作称为就地操作。它们用 后缀表示。例如:x.copy(y)、x.t_() 将会改变 x。

数据加载

PyTorch 提供了两个数据基本类型:torch.utils.data.DataLoader 和 torch.utils.data.Dataset,它们使您可以使用预加载的数据集以及您自己的数据。

神经网络

torch.nn 命名空间提供了构建您自己的神经网络所需的所有构建块。PyTorch 中的每个模块都是 nn.Module 的子类。

自动微分

在训练神经网络时,最常用的算法是 反向传播。在此算法中,参数(模型权重)会根据损失函数相对于给定参数的 梯度 进行调整。

为了计算这些梯度,PyTorch 有一个内置的微分引擎,称为 torch.autograd。它支持任何计算图的梯度自动计算。当我们需要计算损失函数相对于这些变量的梯度,我们设置了这些张量的requiresgrad 属性。可以在创建张量时设置requires_grad的值,或者之后使用x.requires_grad(True)方法。要计算这些导数,我们调用loss.backward(),然后从w.grad和b.grad中检索值。

要阻止一个张量被跟踪历史,可以调用.detach()方法将其与计算历史分离,并阻止它未来的计算记录被跟踪。为了防止跟踪历史记录(和使用内存),可以将代码块包装在 with torch.no_grad(): 中。

优化

在训练循环中,优化分三个步骤进行:
调用optimizer.zero_grad()重置模型参数的梯度。默认情况下,梯度会累加;为了防止重复计数,我们在每次迭代中显式地将其归零。

使用loss.backward()反向传播预测损失。PyTorch 将损失相对于每个参数的梯度存储起来。

在获得梯度后,我们调用optimizer.step()根据反向传播中收集的梯度调整参数。

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
def train_loop(dataloader, model, loss_fn, optimizer):
size = len(dataloader.dataset)
# Set the model to training mode - important for batch normalization and dropout layers
# Unnecessary in this situation but added for best practices
model.train()
for batch, (X, y) in enumerate(dataloader):
# Compute prediction and loss
pred = model(X)
loss = loss_fn(pred, y)

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

if batch % 100 == 0:
loss, current = loss.item(), batch * batch_size + len(X)
print(f"loss: {loss:>7f} [{current:>5d}/{size:>5d}]")


def test_loop(dataloader, model, loss_fn):
# Set the model to evaluation mode - important for batch normalization and dropout layers
# Unnecessary in this situation but added for best practices
model.eval()
size = len(dataloader.dataset)
num_batches = len(dataloader)
test_loss, correct = 0, 0

# Evaluating the model with torch.no_grad() ensures that no gradients are computed during test mode
# also serves to reduce unnecessary gradient computations and memory usage for tensors with requires_grad=True
with torch.no_grad():
for X, y in dataloader:
pred = model(X)
test_loss += loss_fn(pred, y).item()
correct += (pred.argmax(1) == y).type(torch.float).sum().item()

test_loss /= num_batches
correct /= size
print(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")

保存和加载模型

保存和加载模型权重

PyTorch 模型将学习到的参数存储在内部状态字典中,称为 state_dict。这些可以通过 torch.save 方法持久化。

1
2
model = models.vgg16(weights='IMAGENET1K_V1')
torch.save(model.state_dict(), 'model_weights.pth')

要加载模型权重,您需要首先创建一个相同模型的实例,然后使用 load_state_dict() 方法加载参数。

1
2
3
model = models.vgg16() # we do not specify ``weights``, i.e. create untrained model
model.load_state_dict(torch.load('model_weights.pth', weights_only=True))
model.eval()

注意请务必在推理之前调用 model.eval() 方法,以将 dropout 和批归一化层设置为评估模式。如果不这样做,将产生不一致的推理结果。

保存和加载具有形状的模型

1
2
torch.save(model, 'model.pth')
model = torch.load('model.pth', weights_only=False),

参考文献

https://pytorch.ac.cn/tutorials/beginner/basics/tensor_tutorial.html

深度学习(2)-计算性能

命令式编程和符号式编程

命令式编程(Imperative Programming)和符号式编程(Symbolic Programming)是两种不同的编程范式。命令式编程描述了计算机应该执行的具体步骤来达到预期的结果。这种编程方式侧重于描述“怎么做”,而不是“做什么”。符号式编程侧重于表达计算的本质,而不是具体的执行步骤。程序表达的是问题的解决方案,而不是具体的操作细节,代码通常只在完全定义了过程之后才执行计算。

异步计算

异步计算(Asynchronous Computing)是一种编程和计算模式,它允许程序在等待某些操作完成的同时继续执行其他任务。与同步计算不同,异步计算可以提高程序的响应性和性能,尤其是在处理 I/O 操作、网络请求、长时间运行的任务等情况时。

GPU,TPU,NPU

GPU即Graphics Processing Unit,中文名为图形处理单元。需要注意的是,GPU没有独立工作的能力,必须由CPU进行控制调用才能工作,且GPU的功耗一般比较高。TPU即Tensor Processing Unit,中文名为张量处理器,是谷歌在2015年6月的IO开发者大会上推出了为优化自身的TensorFlow框架而设计打造的一款计算神经网络专用芯片。NPU即Neural-network Processing Unit,中文名为神经网络处理器,它采用“数据驱动并行计算”的架构,特别擅长处理视频、图像类的海量多媒体数据。

并行计算

CUDA是NVIDIA提供的一种GPU并行计算框架。对于GPU本身的编程,使用的是CUDA语言来实现的。在编写程序中,当我们使用了 .cuda() 时,其功能是让我们的模型或者数据从CPU迁移到GPU上(默认是0号GPU)当中,通过GPU开始计算。

当我们的服务器上有多个GPU,我们应该指明我们使用的GPU是哪一块,如果我们不设置的话,tensor.cuda()方法会默认将tensor保存到第一块GPU上,等价于tensor.cuda(0)。

PyTorch提供了两种多卡训练的方式,分别为DataParallel和DistributedDataParallel(以下我们分别简称为DP和DDP)。这两种方法中官方更推荐我们使用DDP,因为它的性能更好。DP只能在单机上使用,且DP是单进程多线程的实现方式,比DDP多进程多线程的方式会效率低一些。

首先我们来看单机多卡DP,通常使用一种叫做数据并行 (Data parallelism) 的策略,即将计算任务划分成多个子任务并在多个GPU卡上同时执行这些子任务。主要使用到了nn.DataParallel函数。

1
2
3
4
5
model = Net()
model.cuda() # 模型显示转移到CUDA上

if torch.cuda.device_count() > 1: # 含有多张GPU的卡
model = nn.DataParallel(model, device_ids=[0,1]) # 单机多卡DP训练

不过通过DP进行分布式多卡训练的方式容易造成负载不均衡,有可能第一块GPU显存占用更多,因为输出默认都会被gather到第一块GPU上。为此Pytorch也提供了torch.nn.parallel.DistributedDataParallel(DDP)方法来解决这个问题。

针对每个GPU,启动一个进程,然后这些进程在最开始的时候会保持一致(模型的初始化参数也一致,每个进程拥有自己的优化器),同时在更新模型的时候,梯度传播也是完全一致的,这样就可以保证任何一个GPU上面的模型参数就是完全一致的,所以这样就不会出现DataParallel那样显存不均衡的问题。

参考文献

https://zh.d2l.ai/chapter_computational-performance/index.html

https://datawhalechina.github.io/thorough-pytorch/%E7%AC%AC%E4%BA%8C%E7%AB%A0/2.3%20%E5%B9%B6%E8%A1%8C%E8%AE%A1%E7%AE%97%E7%AE%80%E4%BB%8B.html

https://datawhalechina.github.io/thorough-pytorch/%E7%AC%AC%E4%BA%8C%E7%AB%A0/2.4%20AI%E7%A1%AC%E4%BB%B6%E5%8A%A0%E9%80%9F%E8%AE%BE%E5%A4%87.html