数据探索
将从Kaggle 的Boat数据集开始,以了解多类图像分类问题。该数据集包含约1,500种不同类型的船的图片:浮标,游轮,渡船,货船,吊船,充气船,皮划艇,纸船和帆船。目标是创建一个模型,以查看船只图像并将其分类为正确的类别。
这是来自数据集的图像样本:
以下是类别计数:
由于货船,充气船和船只类别没有很多图像,因此在训练模型时将删除这些类别。
创建所需的目录结构
在训练深度学习模型之前,需要为图像创建所需的目录结构。现在数据目录结构如下所示:
images sailboat kayak . .
需要图像是在三个文件夹:train,val和test。然后将在train数据集中的图像上训练模型,在数据集中进行验证val,最后对test数据集进行测试。
data train sailboat kayak . . val sailboat kayak . . test sailboat kayak . .
数据可能采用不同的格式,除了常用的库之外,glob.globand os.system函数也非常有用。在这里,可以找到完整的数据准备代码。现在快速看一下一些在进行数据准备时发现有用的未使用的库。
什么是glob.glob?
简而言之使用glob,可以使用正则表达式获取目录中文件或文件夹的名称。例如可以执行以下操作:
from glob import globcategories = glob(“images/*”)print(categories)------------------------------------------------------------------['images/kayak', 'images/boats', 'images/gondola', 'images/sailboat', 'images/inflatable boat', 'images/paper boat', 'images/buoy', 'images/cruise ship', 'images/freight boat', 'images/ferry boat']
什么是os.system?
os.system是os库中的一个函数,可让在python本身中运行任何命令行函数。通常使用它来运行Linux函数,但也可以用来在python中运行R脚本,如下所示。例如,在从pandas数据框中获取信息后,在数据准备中使用它将文件从一个目录复制到另一个目录。也使用f字符串格式。
import osfor i,row in fulldf.iterrows(): # Boat category cat = row['category'] # section is train,val or test section = row['type'] # input filepath to copy ipath = row['filepath'] # output filepath to paste opath = ipath.replace(f"images/",f"data/{section}/") # running the cp command os.system(f"cp '{ipath}' '{opath}'")
现在,已将数据存储在所需的文件夹结构中,可以继续进行更令人兴奋的部分。
数据预处理
变身
1. Imagenet预处理
为了将图像与在Imagenet数据集上训练的网络一起使用,需要以与Imagenet网络相同的方式预处理图像。为此需要将图像重新缩放为224×224,并按照Imagenet标准对其进行归一化。可以使用torchvision transforms库来做到这一点。在这里,采用CenterCrop224×224的a并根据Imagenet标准进行归一化。以下定义的操作顺序发生。
transforms.Compose([ transforms.CenterCrop(size=224), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ])
2.数据扩充
可以为数据扩充做更多的预处理。神经网络可以更好地处理大量数据。数据扩充是在训练时使用的一种策略,用于增加拥有的数据量。
例如,可以水平翻转船的图像,但它仍然是船。或者可以随机裁剪图像或添加颜色抖动。这是使用过的图像变换字典,它既适用于Imagenet预处理也适用于增强。不对测试数据和验证数据应用水平翻转或其他数据增强转换,因为不想对增强图像进行预测。
# Image transformationsimage_transforms = { # Train uses data augmentation 'train': transforms.Compose([ transforms.RandomResizedCrop(size=256, scale=(0.8, 1.0)), transforms.RandomRotation(degrees=15), transforms.ColorJitter(), transforms.RandomHorizontalFlip(), transforms.CenterCrop(size=224), # Image net standards transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) # Imagenet standards ]), # Validation does not use augmentation 'valid': transforms.Compose([ transforms.Resize(size=256), transforms.CenterCrop(size=224), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]), # Test does not use augmentation 'test': transforms.Compose([ transforms.Resize(size=256), transforms.CenterCrop(size=224), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]),}
这是应用于训练数据集中图像的训练变换的示例。不仅可以从单个图像中获得大量不同的图像,而且还可以帮助网络针对对象的方向保持不变。
ex_img = Image.open('/home/rahul/projects/compvisblog/data/train/cruise ship/cruise-ship-oasis-of-the-seas-boat-water-482183.jpg')t = image_transforms['train']plt.figure(figsize=(24, 24))for i in range(16): ax = plt.subplot(4, 4, i + 1) _ = imshow_tensor(t(ex_img), ax=ax)plt.tight_layout()
数据加载器
下一步是向PyTorch提供训练,验证和测试数据集位置。可以通过使用PyTorch数据集和DataLoader类来做到这一点。如果数据位于所需的目录结构中,则这部分代码将基本保持不变。
# Datasets from folderstraindir = "data/train"validdir = "data/val"testdir = "data/test"data = { 'train': datasets.ImageFolder(root=traindir, transform=image_transforms['train']), 'valid': datasets.ImageFolder(root=validdir, transform=image_transforms['valid']), 'test': datasets.ImageFolder(root=testdir, transform=image_transforms['test'])}# Dataloader iterators, make sure to shuffledataloaders = { 'train': DataLoader(data['train'], batch_size=batch_size, shuffle=True,num_workers=10), 'val': DataLoader(data['valid'], batch_size=batch_size, shuffle=True,num_workers=10), 'test': DataLoader(data['test'], batch_size=batch_size, shuffle=True,num_workers=10)}
这些数据加载器帮助遍历数据集。例如将在模型训练中使用以下数据加载器。(batch_size, color_channels, height, width)当目标为形状时(batch_size),data变量将包含形式的数据,并保存标签信息。
train_loader = dataloaders['train']for ii, (data, target) in enumerate(train_loader):
创建模型
1.使用预先训练的模型创建模型
将在数据集上使用resnet50,可以根据自己的喜好有效使用其他模型。
from torchvision import modelsmodel = models.resnet50(pretrained=True)
先冻结模型权重,因为不想更改resnet50模型的权重。
# Freeze model weightsfor param in model.parameters(): param.requires_grad = False
接下来需要做的是用自定义分类器替换模型中的线性分类层。发现要这样做,最好先查看模型结构以确定最终的线性层。可以简单地通过打印模型对象来做到这一点:
print(model)------------------------------------------------------------------ResNet( (conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False) (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (relu): ReLU(inplace=True) . . . .(conv3): Conv2d(512, 2048, kernel_size=(1, 1), stride=(1, 1), bias=False) (bn3): BatchNorm2d(2048, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (relu): ReLU(inplace=True) ) )(avgpool): AdaptiveAvgPool2d(output_size=(1, 1)) (fc): Linear(in_features=2048, out_features=1000, bias=True))
在这里,发现从卷积层获取输入的最终线性层被命名为fc。
现在可以fc使用自定义神经网络简单地替换该层。该神经网络从上一层获取输入,fc并给出shape的对数softmax输出(batch_size x n_classes)。
n_inputs = model.fc.in_featuresmodel.fc = nn.Sequential( nn.Linear(n_inputs, 256), nn.ReLU(), nn.Dropout(0.4), nn.Linear(256, n_classes), nn.LogSoftmax(dim=1))
请注意,默认情况下,现在添加的新图层是完全可训练的。
2.在GPU上加载模型
可以使用PyTorch的DataParallel使用单个GPU或多个GPU(如果有)。这是可以用来检测GPU以及将GPU加载模型的GPU数量。现在正在双Titan RTX GPU上训练模型。
# Whether to train on a gputrain_on_gpu = cuda.is_available()print(f'Train on gpu: {train_on_gpu}')# Number of gpusif train_on_gpu: gpu_count = cuda.device_count() print(f'{gpu_count} gpus detected.') if gpu_count > 1: multi_gpu = True else: multi_gpu = Falseif train_on_gpu: model = model.to('cuda')if multi_gpu: model = nn.DataParallel(model)
3.定义标准和优化器
训练任何模型时要注意的最重要的事情之一是损失函数的选择和所使用的优化器。这里要使用分类交叉熵,因为有一个多类分类问题,而Adam最优化器是最常用的优化器。但是由于在模型的输出上应用了LogSoftmax操作,因此将使用NLL损失。
from torch import optimcriteration = nn.NLLLoss()optimizer = optim.Adam(model.parameters())
4.训练模型
在下面,将找到用于训练模型的完整代码。它本身看起来可能很大,但实际上正在做的事情如下:
这是运行上述代码的输出。仅显示最后几个时期。验证准确性在第一个时期开始于〜55%,最终验证准确性为〜90%。
这是显示损耗和准确性指标的训练曲线:
训练曲线
推论和模型结果
在使用模型时,希望以各种不同的方式获得结果。首先需要测试精度和混淆矩阵。用于创建这些结果的所有代码都在代码笔记本中。
1.测试结果
测试模型的整体准确性为:
Overall Accuracy: 88.65 %
这是测试数据集结果的混淆矩阵:
还可以查看类别精度。还添加了训练数量以从新的角度查看结果。
2.可视化单个图像的预测
出于部署目的,它有助于获得单个图像的预测。可以从笔记本中获取代码。
3.可视化类别的预测
还可以看到按类别的结果,以进行调试和演示。
4.测试时间增加的测试结果
还可以增加测试时间来提高测试准确性。在这里正在使用新的测试数据加载器和转换:
# Image transformationstta_random_image_transforms = transforms.Compose([ transforms.RandomResizedCrop(size=256, scale=(0.8, 1.0)), transforms.RandomRotation(degrees=15), transforms.ColorJitter(), transforms.RandomHorizontalFlip(), transforms.CenterCrop(size=224), # Image net standards transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) # Imagenet standards ])# Datasets from foldersttadata = { 'test': datasets.ImageFolder(root=testdir, transform=tta_random_image_transforms)}# Dataloader iteratorsttadataloader = { 'test': DataLoader(ttadata['test'], batch_size=512, shuffle=False,num_workers=10)}
然后,可以使用以下函数获得测试集上的预测:
在上面的函数中,将tta_random_image_transforms5次应用于每个图像,然后再进行预测。最终预测是所有五个预测的平均值。当在整个测试数据集上使用TTA时,注意到准确性提高了大约1%。
TTA Accuracy: 89.71%
此外,以下是与正常结果类别相比的TTA结果:
在这个小的数据集中,TTA似乎并没有增加太多价值,但是注意到它为大型数据集增加了价值。
结论
在本文中,讨论了使用PyTorch进行多类图像分类项目的端到端管道。致力于创建一些现成的代码,以使用迁移学习来训练模型,可视化结果,使用测试时间增加,并获得单个图像的预测,以便在需要时使用Streamlit之类的任何工具部署模型。