前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用Pytorch和转移学习进行端到端多类图像分类

使用Pytorch和转移学习进行端到端多类图像分类

作者头像
代码医生工作室
发布2020-06-06 21:18:21
1K0
发布2020-06-06 21:18:21
举报
文章被收录于专栏:相约机器人相约机器人

数据探索

将从Kaggle 的Boat数据集开始,以了解多类图像分类问题。该数据集包含约1,500种不同类型的船的图片:浮标,游轮,渡船,货船,吊船,充气船,皮划艇,纸船和帆船。目标是创建一个模型,以查看船只图像并将其分类为正确的类别。

这是来自数据集的图像样本:

以下是类别计数:

由于货船,充气船和船只类别没有很多图像,因此在训练模型时将删除这些类别。

创建所需的目录结构

在训练深度学习模型之前,需要为图像创建所需的目录结构。现在数据目录结构如下所示:

代码语言:javascript
复制
images    sailboat    kayak    .    .

需要图像是在三个文件夹:train,val和test。然后将在train数据集中的图像上训练模型,在数据集中进行验证val,最后对test数据集进行测试。

代码语言:javascript
复制
data    train        sailboat        kayak        .        .    val        sailboat        kayak        .        .    test        sailboat        kayak        .        .

数据可能采用不同的格式,除了常用的库之外,glob.globand os.system函数也非常有用。在这里,可以找到完整的数据准备代码。现在快速看一下一些在进行数据准备时发现有用的未使用的库。

什么是glob.glob?

简而言之使用glob,可以使用正则表达式获取目录中文件或文件夹的名称。例如可以执行以下操作:

代码语言:javascript
复制
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字符串格式。

代码语言:javascript
复制
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标准进行归一化。以下定义的操作顺序发生。

代码语言:javascript
复制
transforms.Compose([        transforms.CenterCrop(size=224),        transforms.ToTensor(),        transforms.Normalize([0.485, 0.456, 0.406],                             [0.229, 0.224, 0.225])    ])

2.数据扩充

可以为数据扩充做更多的预处理。神经网络可以更好地处理大量数据。数据扩充是在训练时使用的一种策略,用于增加拥有的数据量。

例如,可以水平翻转船的图像,但它仍然是船。或者可以随机裁剪图像或添加颜色抖动。这是使用过的图像变换字典,它既适用于Imagenet预处理也适用于增强。不对测试数据和验证数据应用水平翻转或其他数据增强转换,因为不想对增强图像进行预测。

代码语言:javascript
复制
# 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])    ]),}

这是应用于训练数据集中图像的训练变换的示例。不仅可以从单个图像中获得大量不同的图像,而且还可以帮助网络针对对象的方向保持不变。

代码语言:javascript
复制
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类来做到这一点。如果数据位于所需的目录结构中,则这部分代码将基本保持不变。

代码语言:javascript
复制
# 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变量将包含形式的数据,并保存标签信息。

代码语言:javascript
复制
train_loader = dataloaders['train']for ii, (data, target) in enumerate(train_loader):

创建模型

1.使用预先训练的模型创建模型

将在数据集上使用resnet50,可以根据自己的喜好有效使用其他模型。

代码语言:javascript
复制
from torchvision import modelsmodel = models.resnet50(pretrained=True)

先冻结模型权重,因为不想更改resnet50模型的权重。

代码语言:javascript
复制
# Freeze model weightsfor param in model.parameters():    param.requires_grad = False

接下来需要做的是用自定义分类器替换模型中的线性分类层。发现要这样做,最好先查看模型结构以确定最终的线性层。可以简单地通过打印模型对象来做到这一点:

代码语言:javascript
复制
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)。

代码语言:javascript
复制
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上训练模型。

代码语言:javascript
复制
# 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损失。

代码语言:javascript
复制
from torch import optimcriteration = nn.NLLLoss()optimizer = optim.Adam(model.parameters())

4.训练模型

在下面,将找到用于训练模型的完整代码。它本身看起来可能很大,但实际上正在做的事情如下:

  • 开始运行纪元。在每个时代
  • 将模型模式设置为使用训练model.train()。
  • 使用训练数据加载器循环遍历数据。
  • 使用以下data, target = data.cuda(), target.cuda()命令将数据加载到GPU
  • 使用以下命令将优化器中的现有渐变设置为零 optimizer.zero_grad()
  • 使用以下命令运行批处理的前向传递 output = model(data)
  • 使用计算损失 loss = criterion(output, target)
  • 使用网络反向传播损失 loss.backward()
  • 采取优化程序步骤,使用以下方法更改整个网络的权重 optimizer.step()
  • 训练循环中的所有其他步骤只是为了保持历史记录并计算准确性。
  • 使用将模型模式设置为eval model.eval()。
  • 使用valid_loader并计算valid_loss和获得验证数据的预测valid_acc
  • 每次打印验证损失和验证准确性结果print_every。
  • 根据验证损失保存最佳模型。
  • 提前停止:如果交叉验证损失没有因max_epochs_stop停止训练而改善,并以最小的验证损失加载最佳可用模型。

这是运行上述代码的输出。仅显示最后几个时期。验证准确性在第一个时期开始于〜55%,最终验证准确性为〜90%。

这是显示损耗和准确性指标的训练曲线:

训练曲线

推论和模型结果

在使用模型时,希望以各种不同的方式获得结果。首先需要测试精度和混淆矩阵。用于创建这些结果的所有代码都在代码笔记本中。

1.测试结果

测试模型的整体准确性为:

代码语言:javascript
复制
Overall Accuracy: 88.65 %

这是测试数据集结果的混淆矩阵:

还可以查看类别精度。还添加了训练数量以从新的角度查看结果。

2.可视化单个图像的预测

出于部署目的,它有助于获得单个图像的预测。可以从笔记本中获取代码。

3.可视化类别的预测

还可以看到按类别的结果,以进行调试和演示。

4.测试时间增加的测试结果

还可以增加测试时间来提高测试准确性。在这里正在使用新的测试数据加载器和转换:

代码语言:javascript
复制
# 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%。

代码语言:javascript
复制
TTA Accuracy: 89.71%

此外,以下是与正常结果类别相比的TTA结果:

在这个小的数据集中,TTA似乎并没有增加太多价值,但是注意到它为大型数据集增加了价值。

结论

在本文中,讨论了使用PyTorch进行多类图像分类项目的端到端管道。致力于创建一些现成的代码,以使用迁移学习来训练模型,可视化结果,使用测试时间增加,并获得单个图像的预测,以便在需要时使用Streamlit之类的任何工具部署模型。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-06-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 相约机器人 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
数据保险箱
数据保险箱(Cloud Data Coffer Service,CDCS)为您提供更高安全系数的企业核心数据存储服务。您可以通过自定义过期天数的方法删除数据,避免误删带来的损害,还可以将数据跨地域存储,防止一些不可抗因素导致的数据丢失。数据保险箱支持通过控制台、API 等多样化方式快速简单接入,实现海量数据的存储管理。您可以使用数据保险箱对文件数据进行上传、下载,最终实现数据的安全存储和提取。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档