跳转至

Pytorch Tutorial

参考资料1:动手学习深度学习

参考资料2:深入浅出PytorchPytorch实用教程2

基础知识

张量的创建与计算

张量(Tensor)很好地支持加速计算和自动微分,而NumPy仅支持CPU计算。

# (1)得到张量 (指定、随机、全0或全1)
torch.tensor([[2, 1, 4, 3], [4, 3, 2, 1]]) # .from_numpy方法可以共享内存,但torch.tensor(ndarray)不会共享内存。
torch.tensor(inputs.to_numpy())
torch.rand(2,3)
torch.zeros_like(Y)
torch.zeros(2, 3, 4) 
torch.ones(2, 3, 4)

# (2)查看或改变张量形状
x.shape
y = torch.reshape(x, (3, 2)) # 新张量与原始张量共享数据存储
y = x.view(3, 2)

# (3)组合张量
torch.cat((X, Y), dim=0) # 按照行进行拼接
torch.cat((X, Y), dim=1) # 按照列进行拼接

# (4)转置、复制、范数
A.T
B = A.clone() 
torch.norm(A) # 默认是L2范数

模长是一个向量的长度或大小,通常是指向量的欧几里德范数,即 L2 范数。

  • L1 范数:也称为曼哈顿范数(Manhattan Norm),它是向量中所有元素的绝对值之和。
  • L2 范数:也称为欧几里德范数(Euclidean Norm),它是向量元素的平方和的平方根。

张量的自动求导

一个多元函数对其所有变量的偏导数,以得到该函数的梯度(gradient)向量。

# 开启自动求导
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
y = torch.tensor([2.0, 4.0, 6.0])
y.requires_grad_(True)

# 构建计算图
loss_function = nn.MSELoss()
optimizer = optim.SGD([x, y], lr=0.01) # [x, y] 表示要更新的参数列表
optimizer = optim.SGD(model.parameters(), lr=0.01)

# 前向传播 与 反向传播
predictions = x * 2
loss = loss_function(predictions, y)
loss.backward() # 每次调用时,梯度会累积到张量的 .grad 属性中

# 梯度更新 与 梯度清零
optimizer.step()
optimizer.zero_grad()

进阶知识

  • 多头注意力机制
def forward(self, x):
    # 拆分多头
    B, N, C = x.shape
    qkv = self.qkv(x).reshape(B, N, 3, self.num_heads, C // self.num_heads).permute(2, 0, 3, 1, 4) # 一般8个头
    q, k, v = qkv[0], qkv[1], qkv[2]  

    # 注意力计算
    attn = (q @ k.transpose(-2, -1)) * self.scale
    attn = attn.softmax(dim=-1)
    x = (attn @ v).transpose(1, 2).reshape(B, N, C)

    # ! 整合多头信息:线性层可以将不同头的输出组合在一起,学习到更高级的特征。
    x = self.proj(x) 
    x = self.proj_drop(x)
    return x
  • Grad-CAM 与 Hook

Grad-CAM(Gradient-weighted Class Activation Mapping)是一种用于理解和解释卷积神经网络决策过程的可视化技术。Grad-CAM 利用梯度信息来生成输入图像的类激活映射,显示模型在进行特定预测时关注的区域。这对于解释深度学习模型的行为非常有用,特别是在计算机视觉任务中,如图像分类和物体检测。

参考:6.4 CAM可视化与hook函数使用/Grad CAM 手动实现

并行计算

注意,单卡和多卡的保存和加载也是不一样的,这里不过多介绍。

参考资料:单卡/多卡保存和加载

快速开始

import os
os.environ['CUDA_VISIBLE_DEVICES'] = '0' # 如果是多卡改成类似0,1,2
model = model.cuda()  # 单卡
model = torch.nn.DataParallel(model).cuda()  # 多卡

详细介绍

  • step1:指定可用GPU
import os
os.environ["CUDA_VISIBLE_DEVICE"] = "2"

model.cuda()
x.cuda()
y.cuda()
CUDA_VISBLE_DEVICE=0,1 python train.py
  • step2:使用 DataParallel (单机多卡DP)

实现比较简单,不怎么需要修改代码,但是性能不如DDP。具体来说,DP进行分布式多卡训练的方式容易造成负载不均衡,第一块GPU显存占用更多,因为输出默认都会被gather到第一块GPU上,也就是后续的loss计算只会在cuda:0上进行,没法并行。

# 单机多卡
model.cuda() # 模型显示转移到CUDA上
if torch.cuda.device_count() > 1: # 含有多张GPU的卡
    model = nn.DataParallel(model, device_ids=[0,1]) 
  • step2:使用 DistributedDataParallel (多机多卡DDP)

略,好复杂,不如用Lightning和huggingface自带的。

精读与性能

在PyTorch中使用autocast配置半精度训练,同时需要在下面三处加以设置:

# 导入模块:import autocast
from torch.cuda.amp import autocast

# 模型设置:用autocast装饰模型中的forward函数
@autocast()   
def forward(self, x):
    ...
    return x

# 训练过程:只需在将数据输入模型及其之后的部分放入“with autocast():“即可
for x in train_loader:
    x = x.cuda()
    with autocast():
        output = model(x)
        ...

可视化

模型可视化

# pip install torchinfo 

import torchvision.models as models
from torchinfo import summary

resnet18 = models.resnet18() 
summary(resnet18, (1, 3, 224, 224)) # 1:batch_size 3:图片的通道数 224: 图片的高宽

训练可视化

# 方法一:pip install tensorboardX
from tensorboardX import SummaryWriter
writer = SummaryWriter('./runs')

# 方法二:使用自带的tensorboard
from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter('./runs')

# 启动tensorboard
# tensorboard --logdir=/path/to/logs/ --port=xxxx
#  ssh -L 16006:127.0.0.1:6006 username@remote_server_ip(远程访问)

# 训练完成之后要记得释放一下资源
writer.close()
  • 举例如下:
# 模型可视化
model = Net()
writer.add_graph(model, input_to_model = torch.rand(1, 3, 224, 224))

# 训练可视化
for epoch in range(num_epochs):
    model.train()
    for i, (data, target) in enumerate(train_loader):
        optimizer.zero_grad()
        output = model(data)
        loss = loss_function(output, target)
        loss.backward()
        optimizer.step()

        # 记录损失值
        writer.add_scalar('Loss/train', loss.item(), epoch * len(train_loader) + i)

Pytorch一般框架

  • 定义神经网络模型
class Net(nn.Module):
    def __init__(self): # 模型初始化,可以传入其他模型
        super(Net, self).__init__() # 调用父类 nn.Module 的构造函数
        self.fc1 = nn.Linear(10, 20)
        self.fc2 = nn.Linear(20, 1)

    def forward(self, x): # 模型前向传播过程
        x = torch.relu(self.fc1(x))
        x = self.fc2(x)
        return x
  • 定义数据集和数据加载器
class MyDataset(Dataset):
    def __init__(self, data, labels):
        self.data = data
        self.labels = labels

    def __len__(self):
        return len(self.data)

    def __getitem__(self, idx):
        # 当然这里还可以自定义一些数据处理操作
        return self.data[idx], self.labels[idx]

dataset = MyDataset(data, labels)
loader = DataLoader(dataset, batch_size=32, shuffle=True)
  • 定义损失函数和优化器
# 损失函数,可以理解为损失和梯度的计算器
criterion = nn.MSELoss() 
# 优化器,可以理解为梯度的处理器
optimizer = optim.SGD(model.parameters(), lr=0.01) 
  • 训练模型
for epoch in range(num_epochs):
    for inputs, labels in loader:
        optimizer.zero_grad() # 清空之前计算的梯度,以避免梯度累积
        outputs = model(inputs)
        loss = criterion(outputs, labels)
        loss.backward() # 自动计算所有可学习参数的梯度(反向传播)
        optimizer.step() # 根据梯度更新模型参数
  • 评估模型
model.eval()
with torch.no_grad():
    for inputs, labels in test_loader:
        outputs = model(inputs)
        # 计算评估指标
  • 保存和加载模型
# 保存模型
torch.save(model.state_dict(), 'model.pth')
# 加载模型
model = Net()
model.load_state_dict(torch.load('model.pth'))
  • 冻结部分参数

请在模型训练前冻结参数

for param in model.parameters():
    if XXXX:
        param.requires_grad = False

部署

通常人们会将模型部署在手机端、开发板,嵌入式设备上,但是这些设备上由于框架的规模,环境依赖,算力的限制,我们无法直接使用训练好的权重进行推理,因此我们需要将得到的权重进行变换才能使我们的模型可以成功部署在上述设备上。

将PyTorch训练好的模型转换为ONNX 格式,然后使用ONNX Runtime运行它进行推理。

参考资料:使用ONNX进行部署并推理