PyTorch升级之旅——安装与基本知识

目录

一、安装

二、张量

创建tensor

 张量的操作

广播机制

三、自动求导

四、并行计算

(一)网络结构分布到不同的设备中(Network partitioning)

(二)同一层的任务分布到不同数据中(Layer-wise partitioning)

(三)不同的数据分布到不同的设备中,执行相同的任务(Data parallelism)


一、安装

pip install torch torchvision torchaudio

二、张量

张量(Tensor)是多维数组的数学对象,可以用于表示标量、向量、矩阵及更高维度的数据。在深度学习和机器学习中,张量是数据的基本单位,通常用于存储和处理输入数据、权重和激活值。张量的维度称为“阶”或“秩”,例如:

  • 标量:0阶张量
  • 向量:1阶张量
  • 矩阵:2阶张量
  • 3维张量:3阶张量(例如一系列图像)

在计算中,张量可以在多个维度上进行操作,例如加法、乘法、转置等。张量操作通常由深度学习框架(如TensorFlow或PyTorch)高效处理。

维度与数据类型的对应关系可能因具体任务和数据结构而异。以下是更详细的解释:

  1. 3维张量 = 时间序列

    • 时间序列数据通常是二维的(例如,时间步长×特征数),但在某些情况下,时间序列可以扩展为三维张量,以便包括批量处理(批量大小×时间步长×特征数)。
    • 示例:多个时间序列的批量数据。
  2. 4维张量 = 图像

    • 图像数据通常表示为三维张量(高度×宽度×通道),但当处理批量图像时,数据被表示为四维张量(批量大小×高度×宽度×通道)。
    • 示例:一批RGB图像数据。
  3. 5维张量 = 视频

    • 视频数据通常表示为五维张量,其中包含批量大小、时间维度(帧数)、高度、宽度和通道(批量大小×帧数×高度×宽度×通道)。
    • 示例:一批RGB视频数据。

这种维度与数据类型的对应关系是深度学习中处理数据的常见方式,帮助明确数据的结构和存储形式。

在深度学习中,图像通常以3D张量的形式表示,其中 (width, height, channel) 分别表示图像的宽度、高度和通道数(例如,RGB图像有三个通道)。当我们处理一组图像时,使用4D张量,其中 batch_size 代表批次大小,即同时处理的图像数量。因此,一个图像数据集通常表示为 (batch_size, width, height, channel) 的4D张量。

在PyTorch中,torch.Tensor 是一种用于存储和操作多维数组的强大工具,与NumPy的多维数组非常相似。但torch.Tensor 不仅限于CPU计算,它还支持GPU加速计算,这在深度学习中尤为重要。此外,PyTorch提供了自动求导功能,通过autograd机制来支持神经网络的梯度计算,这使得torch.Tensor在训练模型时特别有效。

具体来说,PyTorch中的Tensor在以下几个方面有独特优势:

  1. GPU加速Tensor可以在GPU上进行计算,大大加快处理速度,尤其是大规模数据和复杂模型。

  2. 自动求导:通过autograd机制,Tensor可以自动计算反向传播中的梯度,从而优化模型。

  3. 灵活性Tensor可以轻松转换为NumPy数组,且支持多种变换操作,如切片、拼接、维度变换等。

这些特性使得torch.Tensor成为深度学习中不可或缺的工具。

创建tensor

从数据创建Tensor

import torch# 从列表创建Tensor
data = [[1, 2], [3, 4]]
tensor_from_data = torch.tensor(data)
print(tensor_from_data)

通过torch函数创建特定类型的Tensor

# 创建一个全零的Tensor
zero_tensor = torch.zeros((3, 3))  # 3x3的零矩阵
print(zero_tensor)# 创建一个全一的Tensor
one_tensor = torch.ones((2, 4))  # 2x4的全一矩阵
print(one_tensor)# 创建一个随机初始化的Tensor
random_tensor = torch.rand((5, 2))  # 5x2的随机数矩阵
print(random_tensor)

创建特定类型和形状的Tensor

import torch# 从列表创建Tensor
data = [[1, 2,3], [3, 4,5]]
tensor_from_data = torch.tensor(data)
print(tensor_from_data)# 创建一个全零的Tensor,并指定数据类型为整数
zero_int_tensor = torch.zeros((2, 2), dtype=torch.int32)
print(zero_int_tensor)# 创建一个与已有Tensor形状相同的全零Tensor
same_shape_tensor = torch.zeros_like(tensor_from_data)
print(same_shape_tensor)

通过Numpy数组创建Tensor

import torch
import numpy as np# 从NumPy数组创建Tensor
np_array = np.array([[1, 2, 3], [4, 5, 6]])
tensor_from_np = torch.from_numpy(np_array)
print(tensor_from_np)

使用设备控制(CPU/GPU)

# 创建一个Tensor并指定设备为GPU(如果有GPU)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
gpu_tensor = torch.ones((2, 3), device=device)
print(gpu_tensor)

使用随机值初始化的Tensor

# 正态分布随机初始化
normal_tensor = torch.randn((3, 3))  # 3x3的正态分布随机矩阵
print(normal_tensor)

从另一个Tensor创建

import torch# 先定义 tensor_from_data
data = [[1, 2], [3, 4]]
tensor_from_data = torch.tensor(data)# 然后再使用它
new_tensor = torch.tensor(tensor_from_data)
print(new_tensor)

常见的构造Tensor的方法:

 张量的操作

1. 张量加法

元素级加法

可以对两个形状相同的张量进行元素级加法:

import torch# 创建两个张量
tensor_a = torch.tensor([[1, 2], [3, 4]])
tensor_b = torch.tensor([[5, 6], [7, 8]])# 元素级加法
tensor_sum = tensor_a + tensor_b
# 或者使用 torch.add
tensor_sum_alt = torch.add(tensor_a, tensor_b)print(tensor_sum)

标量加法

将标量添加到张量的每个元素:

scalar_add = tensor_a + 10
print(scalar_add)

2. 张量索引

索引单个元素

可以使用常规的Python索引来访问张量的元素:

# 访问第一行第二列的元素
element = tensor_a[0, 1]
print(element)

切片操作

可以使用切片访问张量的部分数据:

# 访问第一行的所有列
row = tensor_a[0, :]
print(row)# 访问第二列的所有行
column = tensor_a[:, 1]
print(column)# 访问子张量
sub_tensor = tensor_a[0:2, 0:2]
print(sub_tensor)

3. 维度变换

张量维度的查看和调整

使用 .shape 查看张量的维度,使用 torch.viewtorch.reshape 调整张量的形状:

# 查看张量的维度
print(tensor_a.shape)  # 输出: torch.Size([2, 2])# 改变张量的维度,变成 1x4 的张量
reshaped_tensor = tensor_a.view(1, 4)
# 或者使用 reshape
reshaped_tensor_alt = torch.reshape(tensor_a, (1, 4))print(reshaped_tensor)

维度扩展或缩减

使用 torch.unsqueezetorch.squeeze 来增加或减少维度:

# 增加一个维度
tensor_c = torch.tensor([1, 2, 3, 4])
expanded_tensor = torch.unsqueeze(tensor_c, 0)  # 变为 1x4
print(expanded_tensor)# 减少一个维度
reduced_tensor = torch.squeeze(expanded_tensor)  # 还原为 1D
print(reduced_tensor)

转置操作

可以使用 torch.transpose.T 对张量进行转置:

# 创建一个 2x3 张量
tensor_d = torch.tensor([[1, 2, 3], [4, 5, 6]])# 转置张量
transposed_tensor = torch.transpose(tensor_d, 0, 1)  # 将行与列交换
# 或者使用简洁的 .T 方法
transposed_tensor_alt = tensor_d.Tprint(transposed_tensor)

广播机制

广播机制允许在不同形状的张量之间进行操作,PyTorch会自动扩展较小的张量以匹配较大的张量的形状。

# 创建一个形状为 (2, 1) 的张量
tensor_e = torch.tensor([[1], [2]])# 形状为 (1, 2) 的张量
tensor_f = torch.tensor([[3, 4]])# 通过广播机制进行加法,得到 (2, 2) 的张量
broadcast_sum = tensor_e + tensor_f
print(broadcast_sum)

三、自动求导

Autograd 是 PyTorch 中的一项核心功能,它使得自动计算张量的梯度成为可能。这对于实现反向传播(backpropagation)以及训练神经网络至关重要。以下是 Autograd 的基本概念和工作原理简介:

基本概念

  • Tensor:在 PyTorch 中,张量(Tensor)是主要的数据结构,与 NumPy 的多维数组类似。与 NumPy 不同的是,PyTorch 的 Tensor 具有 requires_grad 属性,表明是否需要跟踪该张量上的操作,以便随后计算梯度。

  • 计算图:PyTorch 的 Autograd 会记录张量的操作,构建一个有向无环图(DAG),其中节点是张量,边是产生这些张量的操作。这个图是动态的,在每次前向传播时都会构建新的计算图。

  • 梯度:如果一个 Tensor 的 requires_grad=True,那么对它的所有操作都会被 Autograd 记录下来。当调用 .backward() 时,Autograd 会自动计算这些操作的梯度,并将结果存储在对应张量的 .grad 属性中。

如何工作

Autograd 的工作流程可以简化为以下几个步骤:

  • 前向传播(Forward Pass)

    • 计算输入张量的函数值,并构建计算图。
    • 这个过程中,PyTorch 记录了所有操作的历史,以便稍后计算梯度。
  • 反向传播(Backward Pass)

    • 当调用 .backward() 方法时,Autograd 开始从输出向输入方向计算梯度(即反向传播)。
    • 梯度是通过链式法则(链式求导法)逐层计算的。
  • 更新权重

    • 通常,计算得到的梯度会用于优化算法(如随机梯度下降),以更新模型的参数。

梯度

在 PyTorch 中,backward() 方法用于计算梯度并进行反向传播。当你有一个标量(单个值)的输出时,调用 out.backward() 将自动计算该标量相对于每个输入张量的梯度。

为什么标量的 out.backward()out.backward(torch.tensor(1.)) 等价?

当我们执行反向传播时,PyTorch 会自动计算输出张量对输入张量的梯度。如果输出张量 out 是一个标量(即一个零维张量),其梯度自然是 1(因为导数的基础定义是 df/dx,当 f 为标量时,df/dx 即为 1)。因此,out.backward() 本质上是在对输出张量执行链式求导,并将初始导数设为 1

调用 out.backward()out.backward(torch.tensor(1.)) 是等价的,因为在后者中你显式地告诉 PyTorch,输出张量 out 的梯度是 1

import torch# 创建张量,并允许其计算梯度
x = torch.tensor(2.0, requires_grad=True)
y = torch.tensor(3.0, requires_grad=True)# 定义一个简单的函数 out = x * y
out = x * y# 进行反向传播
out.backward()  # 等价于 out.backward(torch.tensor(1.))# 查看 x 和 y 的梯度
print(x.grad)  # 输出: 3.0,因为 dout/dx = y
print(y.grad)  # 输出: 2.0,因为 dout/dy = x

在这个例子中:

  • out = x * y 是一个标量。
  • 调用 out.backward() 后,x.grad 的值是 3.0,因为 outx 的导数是 y,即 3.0
  • 同样地,y.grad 的值是 2.0,因为 outy 的导数是 x,即 2.0

非标量情况

如果 out 不是标量,那么你必须为 backward() 提供一个与 out 形状相同的张量,作为反向传播的初始梯度。例如:

z = x**2 + y**2
# 假设 z 是一个向量,你需要提供初始梯度
z.backward(torch.tensor([1.0, 1.0]))

这种情况下,z.backward(torch.tensor([1.0, 1.0])) 指定了 z 中每个分量的初始梯度为 1.0

在标量的情况下,out.backward()out.backward(torch.tensor(1.0)) 是等价的,因为标量的导数默认是 1。这种等价性简化了反向传播的调用,使得用户无需显式提供初始梯度值。

基本的用法和示例

PyTorch通过torch.autograd模块提供了自动求导的功能,下面是一些基本的用法和示例:

创建带有梯度的张量

要使张量能够进行自动求导,首先需要在创建张量时将requires_grad参数设置为True。这样,PyTorch就会开始跟踪所有对这个张量的操作,以便稍后计算梯度。

import torch# 创建一个带有梯度的张量
x = torch.tensor([2.0, 3.0], requires_grad=True)
print(x)

执行张量操作并计算梯度

当对张量执行操作时,PyTorch会构建一个计算图,记录所有操作以便进行反向传播。以下是一个简单的例子:

# 定义一个函数 y = x^2 + 3x
y = x**2 + 3*x# 现在对 y 执行反向传播,计算 dy/dx
y.backward()# 查看 x 的梯度(dy/dx)
print(x.grad)  # 输出: tensor([7., 9.])

在这个例子中,我们定义了一个简单的二次函数,然后调用y.backward()来计算yx的梯度(dy/dx)。梯度值存储在x.grad中。

防止张量被跟踪

有时你不希望某些操作被自动求导跟踪,例如在模型评估或推理阶段。你可以通过以下方法禁用梯度计算:

使用torch.no_grad()

with torch.no_grad():z = x * 2
print(z)  # z不会有梯度信息

使用detach()方法

你还可以使用detach()方法从计算图中分离张量:

z = x.detach()
print(z)  # z没有梯度信息

计算图的动态性

PyTorch的计算图是动态的,意味着每次前向传播时计算图都会重新构建。这使得模型能够在每个迭代中使用不同的计算路径(例如,基于条件的模型)。

累积梯度和清除梯度

梯度是累积的,也就是说,如果你多次调用backward(),梯度会累加到之前的梯度中。因此,在每次反向传播之前,通常需要清除梯度:

# 清除梯度
x.grad.zero_()# 重新计算梯度
y = x**2 + 3*x
y.backward()
print(x.grad)

高阶求导

PyTorch也支持高阶导数,即对梯度的梯度进行计算:

# 重新计算 y
y = x**2 + 3*x# 计算一阶导数
y.backward(create_graph=True)# 计算高阶导数
x_grad = x.grad  # 保存一阶导数
x.grad.zero_()  # 清除当前梯度
x_grad.sum().backward()  # 计算二阶导数
print(x.grad)  # 输出的是二阶导数

高阶求导在需要计算复杂导数或应用某些优化算法时非常有用。

自动求导使得在PyTorch中实现深度学习模型的训练变得更加直观和简单,尤其是在复杂的模型和优化问题中,自动求导功能大大简化了计算过程。

四、并行计算

在深度学习和大规模数据处理任务中,并行计算能够显著提升性能和效率。PyTorch 提供了多种并行计算方式,使得我们能够充分利用多核 CPU 和 GPU 来加速模型训练和推理。下面介绍几种常见的并行计算方式及其在 PyTorch 中的实现。

1. 数据并行(Data Parallelism)

数据并行是最常用的并行计算方式之一。在数据并行中,数据集被拆分为多个子集,每个子集分配到一个独立的计算单元(如 GPU)进行计算,然后合并结果。PyTorch 提供了 torch.nn.DataParallel 来实现这一功能。

使用 DataParallel 进行数据并行

import torch
import torch.nn as nn
import torch.optim as optim# 假设我们有一个简单的模型
class SimpleModel(nn.Module):def __init__(self):super(SimpleModel, self).__init__()self.fc = nn.Linear(10, 10)def forward(self, x):return self.fc(x)# 实例化模型
model = SimpleModel()# 将模型包装为 DataParallel
model = nn.DataParallel(model)# 将模型转移到 GPU
model = model.cuda()# 定义损失函数和优化器
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.001)# 假设我们有一些输入数据
inputs = torch.randn(32, 10).cuda()
targets = torch.randn(32, 10).cuda()# 前向传播,计算损失
outputs = model(inputs)
loss = criterion(outputs, targets)# 反向传播
loss.backward()# 优化步骤
optimizer.step()

在这个例子中,DataParallel 会自动将输入张量 inputs 拆分,并将每个子集分配给可用的 GPU,计算结果后再将它们合并。

2. 分布式数据并行(Distributed Data Parallelism)

对于多节点、多 GPU 的训练任务,torch.nn.parallel.DistributedDataParallel(DDP)提供了更高效的并行计算方式。与 DataParallel 不同,DDP 会在每个进程中独立地管理模型副本,减少了进程间的通信开销。

使用 DistributedDataParallel

分布式训练通常需要使用 torch.distributed 包来初始化进程组,然后使用 DistributedDataParallel 进行并行计算。

import torch
import torch.distributed as dist
import torch.nn as nn
import torch.optim as optim
from torch.nn.parallel import DistributedDataParallel as DDPdef setup(rank, world_size):# 初始化进程组dist.init_process_group("nccl", rank=rank, world_size=world_size)def cleanup():dist.destroy_process_group()def demo_ddp(rank, world_size):setup(rank, world_size)# 创建模型并移动到当前 GPU 设备model = nn.Linear(10, 10).to(rank)ddp_model = DDP(model, device_ids=[rank])# 定义损失函数和优化器criterion = nn.MSELoss()optimizer = optim.SGD(ddp_model.parameters(), lr=0.001)# 假设我们有一些输入数据inputs = torch.randn(32, 10).to(rank)targets = torch.randn(32, 10).to(rank)# 前向传播,计算损失outputs = ddp_model(inputs)loss = criterion(outputs, targets)# 反向传播loss.backward()# 优化步骤optimizer.step()cleanup()# 通常通过启动多个进程来实现多GPU训练
world_size = 4  # 假设有4个GPU
torch.multiprocessing.spawn(demo_ddp

在这个例子中,DistributedDataParallel 提供了更高效的并行机制,适用于大规模分布式训练。

3. 模型并行(Model Parallelism)

在模型并行中,模型的不同部分分布在不同的设备上进行计算。这在模型非常大、单个 GPU 无法容纳整个模型时非常有用。

示例:简单的模型并行

import torch
import torch.nn as nn# 定义一个简单的模型,其中一部分在第一个 GPU 上,另一部分在第二个 GPU 上
class ModelParallelNet(nn.Module):def __init__(self):super(ModelParallelNet, self).__init__()self.fc1 = nn.Linear(10, 10).to('cuda:0')self.fc2 = nn.Linear(10, 10).to('cuda:1')def forward(self, x):x = self.fc1(x)x = x.to('cuda:1')x = self.fc2(x)return x# 创建模型
model = ModelParallelNet()# 输入数据
inputs = torch.randn(32, 10).to('cuda:0')# 前向传播
outputs = model(inputs)

在这个例子中,模型的不同层被分布在不同的 GPU 上进行计算。虽然这种方法减少了单个 GPU 的内存压力,但在跨设备传输数据时会有额外的开销。

4. 并行数据加载

PyTorch 提供了 torch.utils.data.DataLoader,通过设置 num_workers 参数,可以并行加载数据。num_workers 控制加载数据时的并行子进程数量。

import torch
from torch.utils.data import DataLoader, TensorDataset# 创建一些假数据
data = torch.randn(1000, 10)
targets = torch.randn(1000, 1)# 创建数据集和数据加载器
dataset = TensorDataset(data, targets)
data_loader = DataLoader(dataset, batch_size=32, num_workers=4)# 迭代数据加载器
for batch_data, batch_targets in data_loader:print(batch_data.size(), batch_targets.size())

通过增加 num_workers,可以利用多核 CPU 并行加载数据,提高数据加载效率。

5. 多线程并行(Multi-threading Parallelism)

在某些计算密集型操作中,您可以通过多线程加速。PyTorch 提供了 torch.jit.forktorch.jit.wait,允许并行执行计算密集型任务。

import torch
from torch import jit# 定义一个计算密集型函数
def compute(x):return x * x# 使用 fork 并行执行多个计算
fut1 = jit.fork(compute, torch.tensor(10))
fut2 = jit.fork(compute, torch.tensor(20))# 等待并获取结果
result1 = jit.wait(fut1)
result2 = jit.wait(fut2)print(result1, result2)

并行计算在 PyTorch 中应用广泛,从数据并行、模型并行到分布式训练,不同的并行策略可以有效利用计算资源,提升模型训练和推理的效率。选择合适的并行方式取决于具体的任务需求和硬件环境。

(一)网络结构分布到不同的设备中(Network partitioning)

在深度学习中,网络结构分布到不同设备中的技术称为网络分区(Network Partitioning)模型并行(Model Parallelism)。这种方法在处理非常大的模型时特别有用,尤其当单个 GPU 无法容纳整个模型时。模型的不同部分可以放在不同的 GPU 或其他设备上运行,这样能够减小单个设备的内存负担,并充分利用多设备的计算能力。

这里遇到的问题就是,不同模型组件在不同的GPU上时,GPU之间的传输就很重要,对于GPU之间的通信是一个考验。但是GPU的通信在这种密集任务中很难办到,所以这个方式慢慢淡出了视野。

import torch
import torch.nn as nn# 定义一个简单的模型,分布在两个 GPU 上
class ModelParallelNet(nn.Module):def __init__(self):super(ModelParallelNet, self).__init__()# 第一部分的层放在 cuda:0 上self.fc1 = nn.Linear(10, 10).to('cuda:0')# 第二部分的层放在 cuda:1 上self.fc2 = nn.Linear(10, 10).to('cuda:1')def forward(self, x):# 将输入移动到 cuda:0x = x.to('cuda:0')# 通过第一部分的网络x = self.fc1(x)# 将数据移动到 cuda:1x = x.to('cuda:1')# 通过第二部分的网络x = self.fc2(x)return x# 创建模型
model = ModelParallelNet()# 输入数据
inputs = torch.randn(32, 10).to('cuda:0')# 前向传播
outputs = model(inputs)
print(outputs)

(二)同一层的任务分布到不同数据中(Layer-wise partitioning)

网络结构分布到不同设备中的技术被称为模型并行(Model Parallelism),也叫网络分区(Network Partitioning)。当一个神经网络模型太大,以至于单个 GPU 无法容纳整个模型时,可以通过将网络结构分布到多个设备(例如多个 GPU)上来处理。

在模型并行中,不同的网络层或者部分网络会被分配到不同的设备上。例如,网络的前半部分可以放在 GPU 0 上,后半部分可以放在 GPU 1 上。通过这种方式,模型的每个部分在不同的设备上进行计算,然后将结果传输给下一个设备,直到整个前向和后向传播过程完成。

假设我们有一个简单的两层神经网络,每层都有大量的参数,使得无法在单个 GPU 上处理。我们可以将第一个线性层放在 GPU 0 上,第二个线性层放在 GPU 1 上。

import torch
import torch.nn as nn# 定义一个简单的模型,其中一部分在第一个 GPU 上,另一部分在第二个 GPU 上
class ModelParallelNet(nn.Module):def __init__(self):super(ModelParallelNet, self).__init__()# 第一层在线性层在第一个 GPU 上self.fc1 = nn.Linear(5000, 10000).to('cuda:0')# 第二层在线性层在第二个 GPU 上self.fc2 = nn.Linear(10000, 5000).to('cuda:1')def forward(self, x):# 前向传播:首先在 GPU 0 上计算x = self.fc1(x)# 将数据移动到 GPU 1x = x.to('cuda:1')# 在 GPU 1 上继续计算x = self.fc2(x)return x# 创建模型
model = ModelParallelNet()# 输入数据,放在 GPU 0 上
inputs = torch.randn(32, 5000).to('cuda:0')# 前向传播
outputs = model(inputs)print(outputs.device)  # 输出: cuda:1 表明最终输出在 GPU 1 上

这样可以保证在不同组件之间传输的问题,但是在我们需要大量的训练,同步任务加重的情况下,会出现和第一种方式一样的问题。

对于更复杂的模型,比如带有多个分支或递归结构的模型,模型并行可以变得更加复杂。以下是一些常见的策略:

  • 层级并行:将整个模型按层级分割,比如前几层在一个设备上,后几层在另一个设备上。

  • 分支并行:在具有分支的网络中(如 ResNet 中的残差块),不同的分支可以放置在不同的设备上。

  • 递归神经网络(RNN)并行:在长序列的 RNN 中,可以将序列的不同部分分割到不同的设备上,以平衡计算负载。

(三)不同的数据分布到不同的设备中,执行相同的任务(Data parallelism)

数据并行(Data Parallelism)是一种常见的并行计算技术,适用于在多个设备上执行相同的任务。它的基本思想是将数据集划分为多个子集,并将每个子集分配给不同的计算设备(如多个 GPU),然后在每个设备上执行相同的模型,并行计算这些子集的结果,最后汇总这些结果。

1. 数据并行的基本原理

  • 数据拆分:将数据集划分为多个子集,每个设备处理其中的一部分数据。
  • 模型复制:在每个设备上复制模型,所有设备执行相同的计算任务。
  • 梯度汇总:在每个设备上独立计算梯度,然后汇总(通常通过平均)这些梯度,并更新模型参数。

2. PyTorch中的数据并行

PyTorch 提供了 torch.nn.DataParalleltorch.nn.parallel.DistributedDataParallel(DDP)来实现数据并行。这两者都是为了利用多个 GPU 加速训练过程,区别在于 DataParallel 主要用于单机多 GPU,而 DistributedDataParallel 适用于多机多 GPU 场景,并具有更好的性能和扩展性。

使用 DataParallel 进行数据并行

DataParallel 是最简单的并行方式,适用于在单个计算节点上利用多块 GPU。

import torch
import torch.nn as nn
import torch.optim as optim# 定义一个简单的模型
class SimpleModel(nn.Module):def __init__(self):super(SimpleModel, self).__init__()self.fc = nn.Linear(10, 10)def forward(self, x):return self.fc(x)# 实例化模型
model = SimpleModel()# 使用 DataParallel 将模型并行化
model = nn.DataParallel(model)# 将模型转移到 GPU
model = model.cuda()# 定义损失函数和优化器
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.001)# 生成一些随机数据作为输入
inputs = torch.randn(32, 10).cuda()
targets = torch.randn(32, 10).cuda()# 前向传播计算损失
outputs = model(inputs)
loss = criterion(outputs, targets)# 反向传播
loss.backward()# 更新参数
optimizer.step()

在这个例子中,DataParallel 会自动将输入数据 inputs 拆分,并将每个子集分配到可用的 GPU 上。每个 GPU 会计算相同的模型,然后将结果合并,最终返回到主 GPU。

使用 DistributedDataParallel 进行数据并行

DistributedDataParallel 是更推荐的方式,特别是当你需要在多个节点上进行并行训练时。它相较于 DataParallel 更高效,减少了进程间的通信开销。

import torch
import torch.distributed as dist
import torch.nn as nn
import torch.optim as optim
from torch.nn.parallel import DistributedDataParallel as DDPdef setup(rank, world_size):# 初始化进程组dist.init_process_group("nccl", rank=rank, world_size=world_size)def cleanup():dist.destroy_process_group()def demo_ddp(rank, world_size):setup(rank, world_size)# 创建模型并转移到当前 GPUmodel = nn.Linear(10, 10).to(rank)ddp_model = DDP(model, device_ids=[rank])# 定义损失函数和优化器criterion = nn.MSELoss()optimizer = optim.SGD(ddp_model.parameters(), lr=0.001)# 生成一些随机数据作为输入inputs = torch.randn(32, 10).to(rank)targets = torch.randn(32, 10).to(rank)# 前向传播,计算损失outputs = ddp_model(inputs)loss = criterion(outputs, targets)# 反向传播loss.backward()# 优化参数optimizer.step()cleanup()# 通常使用 torch.multiprocessing.spawn 来启动多个进程
world_size = 4  # 假设我们有4个 GPU
torch.multiprocessing.spawn(demo_ddp, args=(world_size,), nprocs=world_size, join=True)

在这个例子中,DistributedDataParallel 可以在多个 GPU 上运行,每个 GPU 有自己独立的进程。DistributedDataParallel 的优势在于它减少了 GPU 之间的通信开销,使得多 GPU 训练更加高效。

3. 常见问题与优化

  • 同步开销:在数据并行中,所有 GPU 都需要同步,以便汇总梯度和更新参数。DistributedDataParallel 使用更高效的通信方式,能减少同步开销。

  • 批量大小:在数据并行中,批量大小(Batch Size)通常会被等比例分配到每个设备上。因此,适当增加全局批量大小可以有效利用多 GPU 的计算能力。

  • 梯度累积:如果使用较小的批量大小,可能会导致模型更新频繁,计算不稳定。梯度累积(Gradient Accumulation)是一种技术,它通过在多次小批量计算后再进行一次权重更新,从而模拟大批量训练。

  • 数据加载:当使用多 GPU 时,数据加载的效率也至关重要。可以通过 DataLoadernum_workers 参数增加数据加载的并行性,避免数据加载成为瓶颈。

4. 应用场景

数据并行适用于以下场景:

  • 大规模训练任务:当数据集非常大时,通过数据并行可以有效缩短训练时间。
  • 多节点训练:在分布式计算环境中,数据并行是最常用的方式之一。
  • 模型参数相对较小,但数据量大:当模型参数占用内存较小,且数据量庞大时,数据并行可以最大限度地利用硬件资源。

使用CUDA加速训练

单卡训练

在PyTorch框架下,CUDA的使用变得非常简单,我们只需要显式的将数据和模型通过.cuda()方法转移到GPU上就可加速我们的训练。如下:

model = Net()
model.cuda() # 模型显示转移到CUDA上for image,label in dataloader:# 图像和标签显示转移到CUDA上image = image.cuda() label = label.cuda()

多卡训练

PyTorch提供了两种多卡训练的方式,分别为DataParallelDistributedDataParallel(以下我们分别简称为DP和DDP)。这两种方法中官方更推荐我们使用DDP,因为它的性能更好。但是DDP的使用比较复杂,而DP经需要改变几行代码既可以实现,所以我们这里先介绍DP,再介绍DDP

单机多卡DP

多机多卡DDP

DP 与 DDP 的优缺点

Data Parallel (DP)Distributed Data Parallel (DDP) 是在深度学习训练中常用的两种数据并行技术。它们都用于加速多 GPU 环境下的模型训练,但在实现方式、性能和适用场景上存在不同的优缺点。

1. Data Parallel (DP)

DP 是一种简单的并行方式,它在单个计算节点(如一台服务器)上利用多块 GPU 来进行模型训练。PyTorch 提供了 torch.nn.DataParallel 来实现这种并行。

优点:

  • 简单易用DataParallel 的 API 使用简单,只需要在模型上包裹 nn.DataParallel,然后将模型转移到 GPU 上即可。代码修改量少,易于集成到现有的单 GPU 代码中。
  • 单节点多 GPU:特别适合单台机器上有多块 GPU 的情况,可以在单节点上最大化利用 GPU 资源。
  • 适合小规模实验:在进行小规模实验或开发阶段,DataParallel 非常方便,能快速验证想法。

缺点:

  • 性能瓶颈DataParallel 的主要瓶颈在于模型参数的同步。模型参数会被复制到所有 GPU 上,计算完成后,各个 GPU 的梯度需要同步到主 GPU 上进行更新。这种同步开销较大,尤其是当 GPU 数量增加时,性能瓶颈更加明显。
  • 不支持多节点DataParallel 只能在单个节点上运行,不适用于需要跨节点的分布式训练场景。
  • 低效率的数据拷贝:每次前向和反向传播都需要在主 GPU 上聚合数据,造成数据传输的开销大。

2. Distributed Data Parallel (DDP)

DDP 是一种更为高效的数据并行方式,特别适用于多节点、多 GPU 的分布式训练场景。PyTorch 提供了 torch.nn.parallel.DistributedDataParallel 来实现这一功能。

优点:

  • 高效的梯度同步:DDP 通过基于 NCCLGloo 的通信后端,实现了更高效的梯度同步。每个 GPU 都有自己独立的进程,减少了 GPU 间通信的开销,性能更高。
  • 支持多节点训练:DDP 设计为分布式训练,可以在多台机器上运行,每台机器上的多块 GPU 也能被充分利用,非常适合大规模分布式训练任务。
  • 不依赖主 GPU:DDP 中没有单一的主 GPU,各个 GPU 的进程是独立的,减少了单一 GPU 的负载问题,提高了整体训练速度和效率。
  • 可扩展性好:DDP 更适合扩展到大规模训练任务,尤其是在多机多 GPU 环境中表现尤为出色。

缺点:

  • 实现复杂:与 DataParallel 相比,DDP 的使用更复杂。需要配置进程组、通信后端等,开发和调试也更为繁琐。
  • 开发开销大:DDP 的代码较为复杂,尤其是多节点、多 GPU 环境下的调试与监控。需要更细致的管理和运维。
  • GPU 分配需要手动处理:在 DataParallel 中,GPU 分配是自动的,而在 DDP 中,开发者需要手动管理 GPU 分配和进程创建。

3. 适用场景

  • Data Parallel (DP) 适合:

    • 小规模实验:代码修改少,适合快速迭代模型开发和小规模实验。
    • 单节点多 GPU 环境:如果你仅在一台机器上训练,且 GPU 数量不多,DataParallel 是一个方便的选择。
  • Distributed Data Parallel (DDP) 适合:

    • 大规模分布式训练:需要跨多台机器进行分布式训练时,DDP 是必选方案,能够充分利用所有的计算资源。
    • 高效训练:当需要在多个 GPU 上高效训练时,DDP 提供了更好的性能和扩展性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/407805.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

GoModule

GOPATH 最早的就是GOPATH构建模式, go get下载的包都在path中的src目录下 src目录是源代码存放目录。 package mainimport ("net/http""github.com/gorilla/mux" )func main() {r : mux.NewRouter()r.HandleFunc("/hello", func(w h…

解决使用matplotlib不显示中文的问题

某季度某城市某天11点到12点气温变化图 import random x range(60) y_BeiJing [random.uniform(15,18) for i in x] plt.figure(figsize(20,8),dpi80) plt.plot(x,y_BeiJing) x_label ["11点{}分".format(i) for i in x] plt.xticks(x[::5],x_label[::5]) plt.yt…

【精选】基于微信小程序的地铁站点查询系统(全网独一无二,阿龙原创设计)

博主介绍: ✌我是阿龙,一名专注于Java技术领域的程序员,全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师,我在计算机毕业设计开发方面积累了丰富的经验。同时,我也是掘金、华为云、阿里云、InfoQ等平台…

C# x Unity面向对象补全计划 设计模式 之 实现一个简单的有限状态机

一个简单的有限状态机可以有如下内容 1.状态基类(定义基本状态的方法,如进入(Enter)、执行(Execute)和退出(Exit),同时可以在此声明需要被管理的对象) 2.具体…

【精选】基于python的影片数据爬取与数据分析

博主介绍: ✌我是阿龙,一名专注于Java技术领域的程序员,全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师,我在计算机毕业设计开发方面积累了丰富的经验。同时,我也是掘金、华为云、阿里云、InfoQ等平台…

软件设计师教程(第5版)第5章 软件工程基础知识(更新中)

5.1 软件工程概述 【软件工程】是指应用计算机科学、数学及管理科学等原理,以工程化的原则和方法来解决软件问题的工程,其目的是提高软件生产率、提高软件质量、降低软件成本。P239 5.1.1 计算机软件 计算机软件是指计算机系统中的【程序】及其【文档】。P240 【…

一文解决---IDEA汉化问题(含中英文切换)

一、英文->中文: ①.下载汉化包插件: 操作顺序:File->Settings->Plugins 在搜索框输入Chinese,然后找到 Chinese (Simplified) Language (汉化插件),等待下载完→Install (安装)&…

OpenCV几何图像变换(9)仿射变换函数warpAffine()的使用

操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 函数是应用一个仿射变换到图像上。 warpAffine 函数使用指定的矩阵对源图像进行仿射变换: dst ( x , y ) src ( M 11 x M 12 y M…

《机器学习》 决策树剪枝、树模型参数及案例演示

目录 一、决策树剪枝 1、什么是决策树剪枝? 2、如何剪枝 3、剪枝剪哪个位置的叶子结点 二、树模型参数及用法 1、参数种类 2、参数解释 1)criterion:gini or entropy 2)splitter:best or random 3&#xff0…

从心理学的角度,探究一下人类为什么爱玩游戏。(缓解压力、社交需求、 获得成就感)

文章目录 引言I 游戏中的美学和文化元素,是影响玩家心理状态的关键因素。音乐美工文化背景II 成年人对游戏的心理需求获得成就感社交需求缓解压力III 心流理论(Flow Theory)解释玩家虽受虐,但也其乐无穷的现象知识扩展: 心流知识扩展: 心流活动知识扩展:得性乐观(Learne…

新版本 | GreatSQL 8.0.32-26全新发布 增强“四高”诸多新特性

近日,GreatSQL开源数据库社区正式发布 GreatSQL 8.0.32-26新版本,在高可用、高性能、高兼容、高安全等诸多方面进行了特性增强,修复多个缺陷,并详细说明了多个典型应用场景下,升级/降级到GreatSQL 8.0.32-26的操作策略…

Linux自旋锁和读写锁

在前面的文章中我们已经介绍了有关互斥锁的概念与使用,本篇将开始介绍在 Linux 中的自旋锁和读写锁。这三种锁分别用于在不同的应用场景之中,其中互斥锁最为常用,但是我们需要了解一下其他的锁。 对于自旋锁和读写锁都介绍了其原理以及接口使…

游戏如何对抗 IL2cppDumper逆向分析

众所周知,Unity引擎中有两种脚本编译器,分别是 Mono 和 IL2CPP 。相较于Mono,IL2CPP 具备执行效率高、跨平台支持等优势,已被大多数游戏采用。 IL2CPP 模式下,可以将游戏 C# 代码转换为 C 代码,然后编译为…

GPT-4o System Card is released

GPT-4o System Card is released, including red teaming, frontier risk evaluations, and other key practices for industrial-strength Large Language Models. https://openai.com/index/gpt-4o-system-card/ 报告链接 企业级生成式人工智能LLM大模型技术、算法及案例实战…

UE5用蓝图实现物体A始终朝向物体B |Find Look at Rotation|

非常常用的蓝图节点 |Find Look at Rotation|:获取 物体A 到 物体B 的Rotator。 Tick中将算出的Rotator设置给物体A,即可实现永远朝向物体B

C++STL之map的使用详解

简介&#xff1a;map底层实现为红黑树&#xff0c;增删查的时间复杂度&#xff1a;O(logn), key是有序的&#xff0c;默认升序 一、初始化 #include<iostream> #include<map> #include<string> using namespace std; int main() {std::map<int, std::st…

楼顶气膜羽毛球馆:城市健身新空间—轻空间

随着城市化进程的加快&#xff0c;城市土地资源愈发紧张&#xff0c;如何高效利用有限的空间成为一大挑战。楼顶气膜羽毛球馆作为一种创新的体育场馆建设方式&#xff0c;凭借其独特的优势&#xff0c;逐渐成为城市健身的新宠。它不仅有效利用了楼顶闲置空间&#xff0c;还为市…

鸿蒙Harmony编程开发:服务端证书锁定防范中间人攻击示例

1. TLS通讯中间人攻击及防范简介 TLS安全通讯的基础是基于对操作系统或者浏览器根证书的信任&#xff0c;如果CA证书签发机构被入侵&#xff0c;或者设备内置证书被篡改&#xff0c;都会导致TLS握手环节面临中间人攻击的风险。其实&#xff0c;这种风险被善意利用的情况还是很…

【25届秋招】饿了么0817算法岗笔试

目录 1. 第一题2. 第二题3. 第三题 ⏰ 时间&#xff1a;2024/08/17 &#x1f504; 输入输出&#xff1a;ACM格式 ⏳ 时长&#xff1a;100min 本试卷还有单选和多选部分&#xff0c;但这部分比较简单就不再展示。 最近终于有时间继续整理之前的笔试题了&#xff0c;因为时间仓促…

数学建模之数据分析【九】:数据清理概述

文章目录 一、什么是数据清理二、为什么数据清理很重要三、执行数据清洁的步骤四、如何执行数据清理五、数据清理的Python库实现5.1 数据检查与探索5.2 使用df.info()检查数据信息5.3 检查分类和数字列5.4 检查分类列中唯一值的总数5.5 执行数据清理的步骤5.5.1 删除所有上述不…