当前位置: 首页 > news >正文

使用AMD GPU进行图像分类的ResNet模型

ResNet for image classification using AMD GPUs — ROCm Blogs

2024年4月9日,作者:Logan Grado。

在这篇博客中,我们演示了如何使用ROCm在AMD GPU上训练一个简单的ResNet模型来进行CIFAR10数据集的图像分类。在AMD GPU上训练ResNet模型非常简单,仅需安装ROCm和适当的PyTorch库,无需额外的工作。

介绍

ResNet 模型最初由Kaiming He等人在 2015 年发表于Deep Residual Learning for Image Recognition 中,主要用于图像分类。该论文的关键贡献是引入了残差连接(residual connections),它允许训练比以往网络更深的网络(参见下图)。ResNet 模型被用于多种情境中,如图像分类、目标检测等。

png

残差连接示意图,来自原始论文。残差连接(右)绕过计算块。

先决条件

要跟随本博客的内容,您需要以下条件:

  •  硬件

    • AMD GPU - 参见兼容GPU列表

  • 操作系统

    • Linux - 参见受支持的Linux发行版

  • 软件

    • ROCm - 参见安装说明

运行本文代码

本文代码有两种运行方式。首先,您可以使用Docker(推荐),或者您可以构建自己的Python环境(参见附录中的在主机上运行)。

在 Docker 中运行

使用 Docker 是构建所需环境的最简单和最可靠的方法。

  • 确保你已经安装了 Docker。如果没有,请参阅安装说明

  • 确保您在主机上安装了 amdgpu-dkms(随 ROCm 一起提供),以便从 Docker 内部访问 GPU。请参阅ROCm Docker 说明。

  • 克隆仓库,并进入博客目录

    git clone git@github.com:ROCm/rocm-blogs.git
    cd rocm-blogs/blogs/artificial-intelligence/resnet
    
  • 构建并启动容器。有关构建过程的详细信息,请参阅 dockerfile。这将启动一个 jupyter lab 服务器。

    cd docker
    docker compose build
    docker compose up
    
  • 在浏览器中导航到 http://localhost:8888,以笔记本格式打开文件 resnet_blog.py(right click -> open with -> notebook)

    注意

    注意:此笔记本是一个JupyText 配对笔记本,采用 py-percent 格式

在 CIFAR10 数据集上训练 ResNet 18

下面,我们将逐步讲解训练代码。

导入

首先,导入所需的包

import random
import datetimeimport torch
import torchvision
from torchvision.transforms import v2 as transforms
from datasets import load_dataset
import matplotlib.pyplot as plt

数据集

任务中,我们将使用 CIFAR10 ,数据集,该数据集可以从 huggingface下载。CIFAR10数据集由60,000张32x32的图像组成,分为10个类别。

我们定义一个函数来获取训练和测试的dataloader。在这个函数中,我们将 (1) 下载数据集,(2) 设置数据格式为torch,(3) 构建训练和测试数据加载器。

def get_dataloaders(batch_size=256):"""返回cifar10数据集的测试/训练数据加载器"""# 下载数据集,并设置格式为torchdataset = load_dataset("cifar10")dataset.set_format("torch")# 构建训练/测试加载器train_loader = torch.utils.data.DataLoader(dataset["train"], shuffle=True, batch_size=batch_size)test_loader = torch.utils.data.DataLoader(dataset["test"], batch_size=batch_size)return train_loader, test_loader

数据变换

该数据集中的图像像素编码格式为 uint8,因此我们需要将其转换为 float32 并进行归一化。以下函数构造了一个组合变换,用于准备训练数据。

在下面的函数中,我们构造了一个组合的 torchvision transform,具体执行以下操作:

  • 重新排列通道维度,使得我们的一批图像是“通道优先”的格式,这是 pytorch 所要求的

  • 转换为 float32 类型

  • 将值缩放到 [0,1] 范围

  • 将值归一化到均值 0,标准差 1

def get_transform():"""构建并返回一个变换链,将加载的图像转换为正确的格式/数据类型,并进行归一化"""# CIFAR10 数据集的均值和标准差stats = ((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))transform = transforms.Compose([# 该数据集是通道最后格式 (B, H, W, C),需要将其重新排列为通道优先格式 (B, C, H, W) transforms.Lambda(lambda x: x.permute(0, 3, 1, 2)),# 转换为 float32 类型transforms.ToDtype(torch.float32),# 除以 255 将 uint8 转换为 [0,1] 范围transforms.Lambda(lambda x: x / 255),# 归一化transforms.Normalize(*stats, inplace=True),])return transform

构建模型、损失函数和优化器

接下来,我们需要构建在训练过程中使用的模型、损失函数以及优化器。

  • 模型: 我们将使用`torchvision`中的  ResNet18, 并将`num_classes`设置为10,以匹配CIFAR10数据集。`ResNet18`是较小规模的ResNet模型之一(由18个卷积层组成),适合进行较简单的任务,如CIFAR10分类。

  • 损失函数: 交叉熵损失, 标准的分类问题损失函数

  • 优化器: Adam 优化器

def build_model():"""构建模型、损失函数和优化器"""# ResNet18, 具有10个类别model = torchvision.models.resnet18(num_classes=10)# 标准的交叉熵损失loss_fn = torch.nn.CrossEntropyLoss()# Adam优化器optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=1e-4)return model, loss_fn, optimizer

训练循环 

最后,我们将使用 PyTorch 构建一个简单的训练循环。在这里,我们将训练模型经过预定数量的 epochs。在每个 epoch 中,我们对整个训练集进行一次完整的遍历,并计算训练损失。然后,我们对测试集进行一次完整的遍历,计算测试损失和准确率。

def train_model(model, loss_fn, optimizer, train_loader, test_loader, transform, num_epochs):"""根据预定的 epoch 数量进行模型训练"""# 声明训练设备print(f"Number of GPUs: {torch.cuda.device_count()}")print([torch.cuda.get_device_name(i) for i in range(torch.cuda.device_count())])device = torch.device("cuda" if torch.cuda.is_available() else "cpu")t0 = datetime.datetime.now()model.to(device)model.train()accuracy = []# 主训练循环for epoch in range(num_epochs):print(f"Epoch {epoch+1}/{num_epochs}")t0_epoch_train = datetime.datetime.now()# 迭代训练数据集train_losses, n_examples = [], 0for batch in train_loader:batch = {k: v.to(device) for k, v in batch.items()}optimizer.zero_grad()preds = model(transform(batch["img"]))loss = loss_fn(preds, batch["label"])loss.backward()optimizer.step()train_losses.append(loss)n_examples += batch["label"].shape[0]train_loss = torch.stack(train_losses).mean().item()t_epoch_train = datetime.datetime.now() - t0_epoch_train# 执行评估with torch.no_grad():t0_epoch_test = datetime.datetime.now()test_losses, n_test_examples, n_test_correct = [], 0, 0for batch in test_loader:batch = {k: v.to(device) for k, v in batch.items()}preds = model(transform(batch["img"]))loss = loss_fn(preds, batch["label"])test_losses.append(loss)n_test_examples += batch["img"].shape[0]n_test_correct += (batch["label"] == preds.argmax(axis=1)).sum()test_loss = torch.stack(test_losses).mean().item()test_accuracy = n_test_correct / n_test_examplest_epoch_test = datetime.datetime.now() - t0_epoch_testaccuracy.append(test_accuracy.cpu())# 打印指标print(f"  Epoch time: {t_epoch_train+t_epoch_test}")print(f"  Examples/second (train): {n_examples/t_epoch_train.total_seconds():0.4g}")print(f"  Examples/second (test): {n_test_examples/t_epoch_test.total_seconds():0.4g}")print(f"  Train loss: {train_loss:0.4g}")print(f"  Test loss: {test_loss:0.4g}")print(f"  Test accuracy: {test_accuracy*100:0.4g}%")total_time = datetime.datetime.now() - t0print(f"Total training time: {total_time}")return accuracy

训练模型Train the Model

最后,我们可以把所有组件组合起来放进主方法中。在这里,我们将:

  • 设置随机种子以确保可重复性

  • 构建所有组件(模型、数据加载器等)

  • 调用我们的训练方法

seed = 0
random.seed(seed)
torch.manual_seed(seed)model, loss, optimizer = build_model()
train_loader, test_loader = get_dataloaders()
transform = get_transform()test_accuracy = train_model(model, loss, optimizer, train_loader, test_loader, transform, num_epochs=8)
    Epoch 1/8Epoch time: 0:00:15.129099Examples/second (train): 3639Examples/second (test): 7204Train loss: 1.796Test loss: 1.409Test accuracy: 48.88%...Epoch 8/8Epoch time: 0:00:07.136725Examples/second (train): 8182Examples/second (test): 9748Train loss: 0.6939Test loss: 0.7904Test accuracy: 72.87%Total training time: 0:00:57.931011

接下来,我们画出训练过程中准确率的变化情况。

fig,ax = plt.subplots()
ax.plot(test_accuracy)
ax.set_xlabel("epoch")
ax.set_ylabel("accuracy")
plt.show()

最后,我们可以绘制一些预测图像来查看我们的结果。

label_dict = {0: 'airplane',1: 'automobile',2: 'bird',3: 'cat',4: 'deer',5: 'dog',6: 'frog',7: 'horse',8: 'ship',9: 'truck'}# 绘制前5张图片
N = 5
device='cuda'
for batch in test_loader:batch = {k: v.to(device) for k, v in batch.items()}preds = model(transform(batch["img"])).argmax(axis=1)labels = batch['label'].cpu()fig,ax = plt.subplots(1,N,tight_layout=True)for i in range(N):ax[i].imshow(batch['img'][i].cpu())ax[i].set_xticks([])ax[i].set_yticks([])ax[i].set_xlabel(f"Label: {label_dict[labels[i].item()]}\nPred: {label_dict[preds[i].item()]}")break

png

总结

在这篇博客中,我们展示了如何使用AMD GPU在CIFAR10数据集上训练ResNet图像分类器,并在不到一分钟的时间内实现了73%的准确率!所有这些都可以在搭载ROCm的AMD GPU上无缝运行。我们可以通过采用一些技术进一步提高性能,例如学习率调度器、数据增强和更多的训练轮数,这些技术将留给读者自己完成。

参考文献

  • 图像识别的深度残差学习

  • huggingface cifar10数据集

  • CIFAR10CIFAR10 - 从微小图像中学习多层特征,Alex Krizhevsky,2009

附录

在主机上运行

如果您不想使用 Docker,也可以直接在您的机器上运行这篇博客 - 虽然这需要多一点工作。

  •  先决条件:

    • 安装ROCm 5.7.x

    • 确保您已经安装了 Python 3.10

    • 安装 PDM - 在这里用于创建可重复的 Python 环境

  • 在博客的根目录下创建 Python 虚拟环境:

    pdm sync
    
  • 启动笔记本

    pdm run jupyter-lab
    

导航到 https://localhost:8888 并运行博客 


http://www.mrgr.cn/news/67338.html

相关文章:

  • win10 cmake源码编译安装opencv(解决ffmpeg下载失败导致opencv无法处理视频)
  • 数据转换 | Matlab基于SP符号递归图(Symbolic recurrence plots)一维数据转二维图像方法
  • 【5.8】指针算法-双指针验证回文串
  • 蓝桥杯真题——三角回文数(C语言)
  • qt QWizard详解
  • Linux系统编程——线程概述、线程控制和线程私有数据
  • ArcGIS006:ArcMap常用操作151-200例动图演示
  • 龙芯交叉编译openssl
  • Scala的包及其导入
  • Renesas R7FA8D1BH (Cortex®-M85) Flash的功能介绍
  • 【LeetCode】【算法】155. 最小栈
  • 11.6日志
  • RTMP推流H264和AAC
  • 计算机网络综合题
  • 【c++语言程序设计】字符串与浅层复制(深拷贝与浅拷贝)
  • jenkins流水线pipeline
  • 使用Rust实现http/https正向代理
  • UE5.4 PCG 创建圆形植被聚落
  • GORM优化器和索引提示
  • C语言 | Leetcode C语言题解之第542题01矩阵
  • 速盾:高防cdn遭受攻击会瘫痪吗?
  • Java Agent使用
  • 网站架构知识之Ansible(day020)
  • 映像?什么是映像
  • 使用 Javascript 停用外部集成的 Javascript 文件
  • C语言常用的宏定义