代码支持混合精度训练(FP16)和不同的训练参数设置,如批量大小、学习率、训练轮数等:

from __future__ import print_function
import argparse
import torch
import spconv.pytorch as spconv
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
from torch.optim.lr_scheduler import StepLR
import contextlib
import torch.cuda.amp
import time 

@contextlib.contextmanager
def identity_ctx():
    yield


class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.net = spconv.SparseSequential(
            nn.BatchNorm1d(3),
            spconv.SubMConv2d(3, 32, 3, 1),
            nn.ReLU(),
            spconv.SubMConv2d(32, 64, 3, 2),
            nn.ReLU(),
            spconv.SubMConv2d(64, 64, 3, 1),
            nn.ReLU(),
            spconv.SparseMaxPool2d(2, 2),
            spconv.SubMConv2d(64, 64, 3, 1),
            nn.ReLU(),
            spconv.SparseMaxPool2d(2, 2),
            spconv.SubMConv2d(64, 64, 3, 1),
            nn.ReLU(),
            spconv.SparseMaxPool2d(2, 2),

            spconv.ToDense(),
        )
        self.fc1 = nn.Linear(9*9 * 64, 128)
        self.fc2 = nn.Linear(128, 10)
        self.dropout1 = nn.Dropout2d(0.25)
        self.dropout2 = nn.Dropout2d(0.5)

    def forward(self, x: torch.Tensor):
        # x: [N, 28, 28, 1], must be NHWC tensor
        # print(x.shape)
        x_sp = spconv.SparseConvTensor.from_dense(x.reshape(-1, 72, 72, 3))
        # create SparseConvTensor manually: see SparseConvTensor.from_dense
        x = self.net(x_sp)
        x = torch.flatten(x, 1)
        x = self.dropout1(x)
        # print(x.shape)
        x = self.fc1(x)
        x = F.relu(x)
        x = self.dropout2(x)
        x = self.fc2(x)
        output = F.log_softmax(x, dim=1)
        return output


def train(args, model, device, train_loader, optimizer, epoch):
    model.train()
    scaler = torch.cuda.amp.grad_scaler.GradScaler()
    amp_ctx = contextlib.nullcontext()
    if args.fp16:
        amp_ctx = torch.cuda.amp.autocast()
    for batch_idx, (data, target) in enumerate(train_loader):
        data, target = data.to(device), target.to(device)
        print(data.shape)
        optimizer.zero_grad()
        with amp_ctx:
            output = model(data)
            loss = F.nll_loss(output, target)
            scale = 1.0
            if args.fp16:
                assert loss.dtype is torch.float32
                scaler.scale(loss).backward()
                # scaler.step() first unscales the gradients of the optimizer's assigned params.
                # If these gradients do not contain infs or NaNs, optimizer.step() is then called,
                # otherwise, optimizer.step() is skipped.
                # scaler.unscale_(optim)

                # Since the gradients of optimizer's assigned params are now unscaled, clips as usual.
                # You may use the same value for max_norm here as you would without gradient scaling.
                # torch.nn.utils.clip_grad_norm_(models[0].net.parameters(), max_norm=0.1)

                scaler.step(optimizer)
                # Updates the scale for next iteration.
                scaler.update()
                scale = scaler.get_scale()
            else:
                loss.backward()
                optimizer.step()

        if batch_idx % args.log_interval == 0:
            print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
                epoch, batch_idx * len(data), len(train_loader.dataset),
                100. * batch_idx / len(train_loader), loss.item()))


def test(args, model, device, test_loader):
    model.eval()
    test_loss = 0
    correct = 0
    amp_ctx = contextlib.nullcontext()
    if args.fp16:
        amp_ctx = torch.cuda.amp.autocast()

    with torch.no_grad():
        for data, target in test_loader:

            data, target = data.to(device), target.to(device)
            with amp_ctx:
                
                output = model(data)
            test_loss += F.nll_loss(
                output, target, reduction='sum').item()  # sum up batch loss
            pred = output.argmax(
                dim=1,
                keepdim=True)  # get the index of the max log-probability
            correct += pred.eq(target.view_as(pred)).sum().item()

    test_loss /= len(test_loader.dataset)

    print(
        '\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
            test_loss, correct, len(test_loader.dataset),
            100. * correct / len(test_loader.dataset)))


def main():
    # Training settings
    parser = argparse.ArgumentParser(description='PyTorch MNIST Example')
    parser.add_argument('--batch-size',
                        type=int,
                        default=64,
                        metavar='N',
                        help='input batch size for training (default: 64)')
    parser.add_argument('--test-batch-size',
                        type=int,
                        default=1000,
                        metavar='N',
                        help='input batch size for testing (default: 1000)')
    parser.add_argument('--epochs',
                        type=int,
                        default=14,
                        metavar='N',
                        help='number of epochs to train (default: 14)')
    parser.add_argument('--lr',
                        type=float,
                        default=1.0,
                        metavar='LR',
                        help='learning rate (default: 1.0)')
    parser.add_argument('--gamma',
                        type=float,
                        default=0.7,
                        metavar='M',
                        help='Learning rate step gamma (default: 0.7)')
    parser.add_argument('--no-cuda',
                        action='store_true',
                        default=False,
                        help='disables CUDA training')
    parser.add_argument('--seed',
                        type=int,
                        default=1,
                        metavar='S',
                        help='random seed (default: 1)')
    parser.add_argument(
        '--log-interval',
        type=int,
        default=10,
        metavar='N',
        help='how many batches to wait before logging training status')

    parser.add_argument('--save-model',
                        action='store_true',
                        default=False,
                        help='For Saving the current Model')
    parser.add_argument('--fp16',
                        action='store_true',
                        default=False,
                        help='For mixed precision training')

    args = parser.parse_args()
    use_cuda = not args.no_cuda and torch.cuda.is_available()

    torch.manual_seed(args.seed)

    device = torch.device("cuda" if use_cuda else "cpu")

    kwargs = {'num_workers': 1, 'pin_memory': True} if use_cuda else {}

    
    train_loader = torch.utils.data.DataLoader(
        datasets.ImageFolder(
            './data',
            transform=transforms.Compose([
                transforms.ToTensor(),
                # here we remove norm to get sparse tensor with lots of zeros
                # transforms.Normalize((0.1307,), (0.3081,))
            ])),
        batch_size=args.batch_size,
        shuffle=True,
        **kwargs)
    test_loader = torch.utils.data.DataLoader(
        datasets.ImageFolder(
            './data',
            transform=transforms.Compose([
                transforms.ToTensor(),
                # here we remove norm to get sparse tensor with lots of zeros
                # transforms.Normalize((0.1307,), (0.3081,))
            ])),
        batch_size=args.test_batch_size,
        shuffle=True,
        **kwargs)

    model = Net().to(device)
    optimizer = optim.Adadelta(model.parameters(), lr=args.lr)

    scheduler = StepLR(optimizer, step_size=1, gamma=args.gamma)
    for epoch in range(1, args.epochs + 1):
        train(args, model, device, train_loader, optimizer, epoch)
        test(args, model, device, test_loader)
        scheduler.step()

    if args.save_model:
        torch.save(model.state_dict(), "mnist_cnn.pt")

    test_loader = torch.utils.data.DataLoader(
        datasets.ImageFolder(
            './data',
            transform=transforms.Compose([
                transforms.ToTensor(),
                # here we remove norm to get sparse tensor with lots of zeros
                # transforms.Normalize((0.1307,), (0.3081,))
            ])),
        batch_size=1,
        shuffle=True,
        **kwargs)

    s  = time.time()
    times= 0 
    with torch.no_grad():
        
        for  idx , (data, target) in  enumerate(test_loader):

            data, target = data.to(device), target.to(device)
            s = time.time()
            output = model(data)
            # pred = output.argmax(
            #     dim=1,
            #     keepdim=True)  # get the index of the max log-probability
            e = time.time()
            

            speed = (e-s) #/(idx+1)
            times += speed
            print(idx,speed,times/(idx+1))
if __name__ == '__main__':
    main()


#Test set: Average loss: 0.2780, Accuracy: 584/648 (90%)

# Train Epoch: 14 [0/648 (0%)]    Loss: 0.347989
# Train Epoch: 14 [80/648 (91%)]  Loss: 0.133278

# Test set: Average loss: 0.2761, Accuracy: 586/648 (90%)
# 稀疏卷积
# test dataset  batchsize =1 mean   time  有数据加载  0.019453911501684306        
#  只模型推理-> 0.008924334642507562

代码的分析:

一、整体功能概述

这段 Python 代码实现了一个使用稀疏卷积神经网络(Sparse Convolutional Neural Network)进行图像分类的训练和测试程序。它使用 PyTorch 和 spconv 库构建模型,并在 MNIST 风格的数据集上进行训练和测试。代码支持混合精度训练(FP16)和不同的训练参数设置,如批量大小、学习率、训练轮数等。

二、代码结构分析

  1. 导入模块

    • from __future__ import print_function:确保代码在不同版本的 Python 中具有一致的打印行为。
    • import argparse:用于命令行参数解析。
    • import torchimport spconv.pytorch as spconv等:导入 PyTorch 和稀疏卷积相关的模块。
    • import torch.nn as nnimport torch.nn.functional as F等:用于构建神经网络和定义激活函数等。
    • import torch.optim as optim:用于优化器的选择。
    • from torchvision import datasets, transforms:用于加载数据集和进行数据预处理。
    • from torch.optim.lr_scheduler import StepLR:用于学习率调度。
    • import contextlibimport torch.cuda.amp:用于混合精度训练的上下文管理。
    • import time:用于时间测量。
  2. 定义上下文管理器和模型类

    • @contextlib.contextmanager定义了一个名为identity_ctx的上下文管理器,它不执行任何操作,只是简单地返回。
    • class Net(nn.Module)定义了一个神经网络模型类,包含稀疏卷积层、全连接层、Dropout 层等。模型的前向传播过程包括将输入转换为稀疏卷积张量,经过一系列稀疏卷积和池化操作,然后转换为密集张量进行全连接层的处理,最后输出分类结果。
  3. 定义训练和测试函数

    • train(args, model, device, train_loader, optimizer, epoch):进行模型的训练。在训练过程中,根据是否使用混合精度训练选择不同的上下文管理器,并使用优化器进行梯度更新。
    • test(args, model, device, test_loader):进行模型的测试。在测试过程中,同样根据是否使用混合精度训练选择不同的上下文管理器,计算测试损失和准确率。
  4. 主函数

    • main():主函数首先解析命令行参数,然后设置随机种子和设备(CPU 或 GPU)。接着创建数据加载器、模型、优化器和学习率调度器。在训练循环中,调用traintest函数进行训练和测试,并根据设置保存模型。最后,在测试数据集中进行单样本的推理,并测量时间。

三、关键部分详解

  1. 稀疏卷积神经网络构建

    • Net类的初始化方法中,使用spconv.SparseSequential构建稀疏卷积神经网络的主体结构,包括多个稀疏卷积层、激活函数和池化层。最后通过spconv.ToDense将稀疏张量转换为密集张量,以便后续的全连接层处理。
  2. 混合精度训练

    • 通过命令行参数--fp16可以开启混合精度训练。在训练函数中,根据是否使用混合精度训练选择不同的上下文管理器。如果使用混合精度训练,使用torch.cuda.amp.autocast进行自动混合精度转换,并使用torch.cuda.amp.grad_scaler.GradScaler进行梯度缩放和更新。
  3. 数据加载和预处理

    • 使用torch.utils.data.DataLoader加载数据集,通过datasets.ImageFolder加载图像文件夹中的数据,并使用transforms.Compose进行数据预处理,包括将图像转换为张量,但去除了归一化操作以获取带有大量零值的稀疏张量。
  4. 模型训练和测试循环

    • 在主函数的训练循环中,通过多次调用traintest函数进行模型的训练和测试。在每个训练轮次结束后,更新学习率。在测试阶段,计算测试损失和准确率,以评估模型的性能。

四、应用场景和可能的改进方向

  1. 应用场景

    • 这段代码可以应用于图像分类任务,特别是对于具有稀疏特征的图像数据。例如,可以用于处理医学图像、遥感图像等领域的数据。
  2. 可能的改进方向

    • 模型结构优化:可以尝试不同的稀疏卷积神经网络结构,或者结合其他类型的神经网络层,以提高模型的性能。
    • 数据增强:可以添加数据增强技术,如随机旋转、翻转、裁剪等,以增加数据的多样性,提高模型的泛化能力。
    • 超参数调整:可以尝试不同的学习率、批量大小、训练轮数等超参数,以找到最佳的训练配置。
    • 混合精度训练优化:可以进一步调整混合精度训练的参数,如梯度缩放因子、更新策略等,以提高训练效率和稳定性。
    • 模型解释性:可以使用模型解释性技术,如可视化卷积核、特征图等,以更好地理解模型的决策过程。
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐