前言
**文章性质:**学习笔记 📖
**学习资料:**吴茂贵《 Python 深度学习基于 PyTorch ( 第 2 版 ) 》【ISBN】978-7-111-71880-2
**主要内容:**根据学习资料撰写的学习笔记,该篇主要介绍了单 GPU 加速和多 GPU 加速,以及使用 GPU 的注意事项。
预:关于 GPU 加速
深度学习涉及很多向量或多矩阵运算,如矩阵相乘、矩阵相加、矩阵-向量乘法等。深层模型的算法,如 BP 、自编码器、CNN 等,都可以写成矩阵运算的形式,而无须写成循环运算。然而,在单核 CPU 上执行时,矩阵运算会被展开成循环的形式,本质上还是串行执行。GPU(Graphic Process Unit,图形处理器)的众核体系结构包含几千个流处理器,**可将矩阵运算并行化执行,**大幅缩短计算时间。随着 NVIDIA 、AMD 等公司不断推进其 GPU 的大规模并行架构,面向通用计算的 GPU 已成为加速可并行应用程序的重要手段。得益于 GPU 众核(Many-Core)体系结构,程序在 GPU 系统上的运行速度相较于单核 CPU 往往提升几十倍乃至上千倍。
目前,GPU 已经发展到了较为成熟的阶段。利用 GPU 来训练深度神经网络,可以充分发挥其计算核心的能力,使得在使用海量训练数据的场景下所耗费的时间大幅缩短,占用的服务器也更少。如果对深度神经网络进行合理优化,一块 GPU 卡相当于数十甚至上百台 CPU 服务器的计算能力,因此 GPU 已经成为业界在深度学习模型训练方面的首选解决方案。
如何使用 GPU ? 现在很多深度学习工具都支持 GPU 运算,使用时只需要简单配置即可。PyTorch 支持 GPU,可以通过 to(device) 函数来将数据从内存中转移到 GPU 显存,如果有多个 GPU ,还可以定位到哪个或哪些 GPU ?PyTorch 一般把 GPU 作用于张量或模型(包括 torch.nn 下面的一些网络模型以及自己创建的模型)等数据结构上。
一、单 GPU 加速
使用 GPU 之前,需要确保 GPU 是可用的,可以通过 torch.cuda.is_available() 的返回值来进行判断。返回 True 则表示具有能够使用的 GPU 。 通过 torch.cuda.device_count() 可以获得可用的 GPU 的数量。
如何查看平台 GPU 的配置信息? 在命令行输入命令 nvidia-smi 即可(适合于 Linux 或 Windows 环境),如图 5-28 所示。
把数据从内存转移到 GPU ,通常针对 张量 (我们需要的数据)和 模型 。
- 对于类型为 FloatTensor 或 LongTensor 等的张量,我们直接使用方法 .to(device) 或 .cuda() 即可。
python
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
# 或 device = torch.device("cuda:0")
device1 = torch.device("cuda:1")
for batch_idx, (img, label) in enumerate(train_loader):
img = img.to(device)
label = label.to(device)
- 对于模型来说,也使用 .to(device) 或 .cuda() 方法来将网络放到 GPU 显存中。
python
# 实例化网络
model = Net()
model.to(device) # 使用序号为 0 的 GPU
# 或 model.to(device1) # 使用序号为 1 的 GPU
二、多 GPU 加速
这里我们介绍单主机多 GPU 的情况,单主机多 GPU 主要采用的是 DataParallel 函数,而不是 DistributedParallel,后者一般用于多主机多 GPU ,当然也可用于单主机多 GPU 。使用多 GPU 训练的方式有很多,前提是我们的设备中存在两个及以上 GPU 。使用时直接用模型传入 torch.nn.DataParallel 函数即可,代码如下:
python
# 对于模型
net = torch.nn.DataParallel(model)
这时,默认所有存在的显卡都会被使用。如果你的电脑有很多显卡,但只想利用其中的部分,例如,只使用编号为 0 、1 、3 、4 的四个 GPU ,那么可以采用以下方式:
python
# 假设有 4 个 GPU ,其 id 设置如下
device_ids = [0, 1, 2, 3]
# 对于数据
input_data = input_data.to(device=device_ids[0])
# 对于模型
net = torch.nn.DataParallel(model)
net.to(device)
或者:
python
os.environ["CUDA_VISIBLE_DEVICES"] = ','.join(map(str, [0, 1, 2, 3]))
net = torch.nn.DataParallel(model)
说明:其中的 CUDA_VISIBLE_DEVICES 表示当前可以被 PyTorch 程序检测到的 GPU 。
下面为单机多 GPU 的实现代码:
1)背景说明。以波士顿房价数据为例,共 506 个样本,13 个特征。数据划分成训练集和测试集,然后用 data.DataLoader 将数据转换为可批加载的方式。采用 nn.DataParallel 并发机制,环境有 2 个 GPU 。当然,数据量很小,按理不宜用 nn.DataParallel 。
2)加载数据。
python
boston = load_boston()
X, y = (boston.data, boston.target)
dim = X.shape[1]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)
# 组合训练数据及标签
myset = list(zip(X_train, y_train))
3)把数据转换为批处理加载方式。批次大小为 128 ,打乱数据。
python
from torch.utils import data
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
dtype = torch.FloatTensor
train_loader = data.DataLoader(myset, batch_size=128, shuffle=True)
4)定义网络。
python
class Net1(nn.Module):
"""
使用 Sequential() 函数构建网络,Sequential()函数的功能是将网络的层组合到一起
"""
def __init__(self, in_dim, n_hidden_1, n_hidden_2, out_dim):
super(Net1, self).__init__()
self.layer1 = torch.nn.Sequential(nn.Linear(in_dim, n_hidden_1))
self.layer2 = torch.nn.Sequential(nn.Linear(n_hidden_1, n_hidden_2))
self.layer3 = torch.nn.Sequential(nn.Linear(n_hidden_2, out_dim))
def forward(self, x):
x1 = F.relu(self.layer1(x))
x1 = F.relu(self.layer2(x1))
x2 = self.layer3(x1)
# 显示每个 GPU 分配的数据大小
print("\tIn Model: input size", x.size(), "output size", x2.size())
return x2
5)把模型转换为多 GPU 并发处理格式。
python
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
# 实例化网络
model = Net1(13, 16, 32, 1)
if torch.cuda.device_count() > 1:
print("Let's use", torch.cuda.device_count(), "GPUs")
# dim = 0 [64, xxx] -> [32, ...], [32, ...] on 2GPUs
model = nn.DataParallel(model)
model.to(device)
运行的结果如下:
6)选择优化器及损失函数。
python
optimizer_orig = torch.optim.Adam(model.parameters(), lr=0.01)
loss_func = torch.nn.MSELoss()
7)模型训练,并可视化损失值。
python
from torch.utils.tensorboard import SummaryWriter
# from tensorboardX import SummaryWriter
writer = SummaryWriter(log_dir='logs')
for epoch in range(100):
model.train()
for data, label in train_loader:
input = data.type(dtype).to(device)
label = label.type(dtype).to(device)
output = model(input)
loss = loss_func(output, label)
# 反向传播
optimizer_orig.zero_grad()
loss.backward()
optimizer_orig.step()
print("Outside: input size", input.size(), "output_size", output.size())
writer.add_scalar('train_loss_paral', loss, epoch)
运行的部分结果如下:
从运行结果可以看出,一个批次数据( batch-size=128 )拆分成两份,每份大小为 64 ,分别放在不同的 GPU 上。此时用 GPU 监控也可以发现两个 GPU 同时在使用,如图 5-29 所示。
8)通过 Web 页面查看损失值的变化情况,如图 5-30 所示。
图形中出现较大振幅是由于采用批次处理,而且数据没有做任何预处理,因此对数据进行规范化应该更平滑一些。
单主机多 GPU 也可使用 DistributedParallel 函数,虽然配置比使用 nn.DataParallel 函数稍微麻烦一点,但是训练速度和效果更好。
具体配置为:
python
# 初始化使用 nccl 后端
torch.distributed.init_process_group(backend="nccl")
# 模型并行化,使用多进程,可单机或分布式训练
model = torch.nn.parallel.DistributedDataParallel(model)
单主机运行时,使用下列方法启动:
python
python -m torch.distributed.launch main.py
参考代码:feiguyunai/Python-DL-PyTorch2/pytorch-05/pytorch-05-05.ipynb at main · Wumg3000/feiguyunai · GitHub
三、使用 GPU 的注意事项
使用 GPU 可以提升训练的速度,但如果使用不当,可能影响使用效率,具体注意事项如下:
• GPU 的数量尽量为偶数,奇数个 GPU 可能会出现异常中断的情况;
• GPU 训练速度很快,但数据量较小时,效果可能没有单 GPU 好,甚至还不如 CPU ;
• 如果内存不够大,使用多 GPU 训练的时候可通过设置 pin_memory 为 False,当然有时使用精度稍低的数据类型的效果也还行。
第五章の小结
本章从机器学习的概念出发,首先说明其基本任务、一般流程等,然后说明在机器学习中解决过拟合、欠拟合的一些常用技巧或方法。同时介绍了各种激活函数、损失函数、优化器等机器学习、深度学习的核心内容。最后说明在程序中如何设置 GPU 设备、如何用 GPU 加速训练模型等内容。这章是深度学习的基础。