python代码实现了一个使用稀疏卷积神经网络(Sparse Convolutional Neural Network)进行图像分类的训练和测试程序
·
代码支持混合精度训练(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)和不同的训练参数设置,如批量大小、学习率、训练轮数等。
二、代码结构分析
-
导入模块
from __future__ import print_function:确保代码在不同版本的 Python 中具有一致的打印行为。import argparse:用于命令行参数解析。import torch、import spconv.pytorch as spconv等:导入 PyTorch 和稀疏卷积相关的模块。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定义了一个名为identity_ctx的上下文管理器,它不执行任何操作,只是简单地返回。class Net(nn.Module)定义了一个神经网络模型类,包含稀疏卷积层、全连接层、Dropout 层等。模型的前向传播过程包括将输入转换为稀疏卷积张量,经过一系列稀疏卷积和池化操作,然后转换为密集张量进行全连接层的处理,最后输出分类结果。
-
定义训练和测试函数
train(args, model, device, train_loader, optimizer, epoch):进行模型的训练。在训练过程中,根据是否使用混合精度训练选择不同的上下文管理器,并使用优化器进行梯度更新。test(args, model, device, test_loader):进行模型的测试。在测试过程中,同样根据是否使用混合精度训练选择不同的上下文管理器,计算测试损失和准确率。
-
主函数
main():主函数首先解析命令行参数,然后设置随机种子和设备(CPU 或 GPU)。接着创建数据加载器、模型、优化器和学习率调度器。在训练循环中,调用train和test函数进行训练和测试,并根据设置保存模型。最后,在测试数据集中进行单样本的推理,并测量时间。
三、关键部分详解
-
稀疏卷积神经网络构建
- 在
Net类的初始化方法中,使用spconv.SparseSequential构建稀疏卷积神经网络的主体结构,包括多个稀疏卷积层、激活函数和池化层。最后通过spconv.ToDense将稀疏张量转换为密集张量,以便后续的全连接层处理。
- 在
-
混合精度训练
- 通过命令行参数
--fp16可以开启混合精度训练。在训练函数中,根据是否使用混合精度训练选择不同的上下文管理器。如果使用混合精度训练,使用torch.cuda.amp.autocast进行自动混合精度转换,并使用torch.cuda.amp.grad_scaler.GradScaler进行梯度缩放和更新。
- 通过命令行参数
-
数据加载和预处理
- 使用
torch.utils.data.DataLoader加载数据集,通过datasets.ImageFolder加载图像文件夹中的数据,并使用transforms.Compose进行数据预处理,包括将图像转换为张量,但去除了归一化操作以获取带有大量零值的稀疏张量。
- 使用
-
模型训练和测试循环
- 在主函数的训练循环中,通过多次调用
train和test函数进行模型的训练和测试。在每个训练轮次结束后,更新学习率。在测试阶段,计算测试损失和准确率,以评估模型的性能。
- 在主函数的训练循环中,通过多次调用
四、应用场景和可能的改进方向
-
应用场景
- 这段代码可以应用于图像分类任务,特别是对于具有稀疏特征的图像数据。例如,可以用于处理医学图像、遥感图像等领域的数据。
-
可能的改进方向
- 模型结构优化:可以尝试不同的稀疏卷积神经网络结构,或者结合其他类型的神经网络层,以提高模型的性能。
- 数据增强:可以添加数据增强技术,如随机旋转、翻转、裁剪等,以增加数据的多样性,提高模型的泛化能力。
- 超参数调整:可以尝试不同的学习率、批量大小、训练轮数等超参数,以找到最佳的训练配置。
- 混合精度训练优化:可以进一步调整混合精度训练的参数,如梯度缩放因子、更新策略等,以提高训练效率和稳定性。
- 模型解释性:可以使用模型解释性技术,如可视化卷积核、特征图等,以更好地理解模型的决策过程。
更多推荐
所有评论(0)