【深度学习计算机视觉】14:实战Kaggle比赛:狗的品种识别(ImageNet Dogs)

【作者主页】Francek Chen

【专栏介绍】⌈ ⌈ ⌈PyTorch深度学习 ⌋ ⌋ ⌋ 深度学习 (DL, Deep Learning) 特指基于深层神经网络模型和方法的机器学习。它是在统计机器学习、人工神经网络等算法模型基础上,结合当代大数据和大算力的发展而发展出来的。深度学习最重要的技术特征是具有自动提取特征的能力。神经网络算法、算力和数据是开展深度学习的三要素。深度学习在计算机视觉、自然语言处理、多模态数据分析、科学探索等领域都取得了很多成果。本专栏介绍基于PyTorch的深度学习算法实现。

【GitCode】专栏资源保存在我的GitCode仓库:https://gitcode.com/Morse_Chen/PyTorch_deep_learning

文章目录


本文我们将在Kaggle上实战狗品种识别问题。本次比赛网址是https://www.kaggle.com/c/dog-breed-identification。图1显示了鉴定比赛网页上的信息。需要一个Kaggle账户才能提交结果。

在这场比赛中,我们将识别120类不同品种的狗。这个数据集实际上是著名的ImageNet的数据集子集。与实战Kaggle比赛:图像分类 (CIFAR-10)中CIFAR-10数据集中的图像不同,ImageNet数据集中的图像更高更宽,且尺寸不一。


图1 狗的品种鉴定比赛网站,可以通过单击"数据"选项卡来获得比赛数据集

python 复制代码
import os
import torch
import torchvision
from torch import nn
from d2l import torch as d2l

一、获取和整理数据集

比赛数据集分为训练集和测试集,分别包含RGB(彩色)通道的10222张、10357张JPEG图像。在训练数据集中,有120种犬类,如拉布拉多、贵宾、腊肠、萨摩耶、哈士奇、吉娃娃和约克夏等。

(一)下载数据集

登录Kaggle后,可以点击图1中显示的竞争网页上的"数据"选项卡,然后点击"全部下载"按钮下载数据集。在../data中解压下载的文件后,将在以下路径中找到整个数据集:

  • .../data/dog-breed-identification/labels.csv
  • .../data/dog-breed-identification/sample_submission.csv
  • .../data/dog-breed-identification/train
  • .../data/dog-breed-identification/test

上述结构与实战Kaggle比赛:图像分类 (CIFAR-10)的CIFAR-10类似,其中文件夹train/test/分别包含训练和测试狗图像,labels.csv包含训练图像的标签。

同样,为了便于入门,我们提供完整数据集的小规模样本:train_valid_test_tiny.zip。如果要在Kaggle比赛中使用完整的数据集,则需要将下面的demo变量更改为False

python 复制代码
#@save
d2l.DATA_HUB['dog_tiny'] = (d2l.DATA_URL + 'kaggle_dog_tiny.zip',
                            '0cb91d09b814ecdc07b50f31f8dcad3e81d6a86d')

# 如果使用Kaggle比赛的完整数据集,请将下面的变量更改为False
demo = True
if demo:
    data_dir = d2l.download_extract('dog_tiny')
else:
    data_dir = os.path.join('..', 'data', 'dog-breed-identification')

(二)整理数据集

我们可以像实战Kaggle比赛:图像分类 (CIFAR-10)中所做的那样整理数据集,即从原始训练集中拆分验证集,然后将图像移动到按标签分组的子文件夹中。

下面的reorg_dog_data函数读取训练数据标签、拆分验证集并整理训练集。

python 复制代码
def reorg_dog_data(data_dir, valid_ratio):
    labels = d2l.read_csv_labels(os.path.join(data_dir, 'labels.csv'))
    d2l.reorg_train_valid(data_dir, labels, valid_ratio)
    d2l.reorg_test(data_dir)


batch_size = 32 if demo else 128
valid_ratio = 0.1
reorg_dog_data(data_dir, valid_ratio)

二、图像增广

回想一下,这个狗品种数据集是ImageNet数据集的子集,其图像大于实战Kaggle比赛:图像分类 (CIFAR-10)中CIFAR-10数据集的图像。下面我们看一下如何在相对较大的图像上使用图像增广。

python 复制代码
transform_train = torchvision.transforms.Compose([
    # 随机裁剪图像,所得图像为原始面积的0.08~1之间,高宽比在3/4和4/3之间。
    # 然后,缩放图像以创建224x224的新图像
    torchvision.transforms.RandomResizedCrop(224, scale=(0.08, 1.0),
                                             ratio=(3.0/4.0, 4.0/3.0)),
    torchvision.transforms.RandomHorizontalFlip(),
    # 随机更改亮度,对比度和饱和度
    torchvision.transforms.ColorJitter(brightness=0.4,
                                       contrast=0.4,
                                       saturation=0.4),
    # 添加随机噪声
    torchvision.transforms.ToTensor(),
    # 标准化图像的每个通道
    torchvision.transforms.Normalize([0.485, 0.456, 0.406],
                                     [0.229, 0.224, 0.225])])

测试时,我们只使用确定性的图像预处理操作。

python 复制代码
transform_test = torchvision.transforms.Compose([
    torchvision.transforms.Resize(256),
    # 从图像中心裁切224x224大小的图片
    torchvision.transforms.CenterCrop(224),
    torchvision.transforms.ToTensor(),
    torchvision.transforms.Normalize([0.485, 0.456, 0.406],
                                     [0.229, 0.224, 0.225])])

三、读取数据集

实战Kaggle比赛:图像分类 (CIFAR-10)一样,我们可以读取整理后的含原始图像文件的数据集。

python 复制代码
train_ds, train_valid_ds = [torchvision.datasets.ImageFolder(
    os.path.join(data_dir, 'train_valid_test', folder),
    transform=transform_train) for folder in ['train', 'train_valid']]

valid_ds, test_ds = [torchvision.datasets.ImageFolder(
    os.path.join(data_dir, 'train_valid_test', folder),
    transform=transform_test) for folder in ['valid', 'test']]

下面我们创建数据加载器实例的方式与实战Kaggle比赛:图像分类 (CIFAR-10)相同。

python 复制代码
train_iter, train_valid_iter = [torch.utils.data.DataLoader(
    dataset, batch_size, shuffle=True, drop_last=True)
    for dataset in (train_ds, train_valid_ds)]

valid_iter = torch.utils.data.DataLoader(valid_ds, batch_size, shuffle=False,
                                         drop_last=True)

test_iter = torch.utils.data.DataLoader(test_ds, batch_size, shuffle=False,
                                        drop_last=False)

四、微调预训练模型

同样,本次比赛的数据集是ImageNet数据集的子集。因此,我们可以使用微调中讨论的方法在完整ImageNet数据集上选择预训练的模型,然后使用该模型提取图像特征,以便将其输入到定制的小规模输出网络中。深度学习框架的高级API提供了在ImageNet数据集上预训练的各种模型。在这里,我们选择预训练的ResNet-34模型,我们只需重复使用此模型的输出层(即提取的特征)的输入。然后,我们可以用一个可以训练的小型自定义输出网络替换原始输出层,例如堆叠两个完全连接的图层。与微调中的实验不同,以下内容不重新训练用于特征提取的预训练模型,这节省了梯度下降的时间和内存空间。

回想一下,我们使用三个RGB通道的均值和标准差来对完整的ImageNet数据集进行图像标准化。事实上,这也符合ImageNet上预训练模型的标准化操作。

python 复制代码
def get_net(devices):
    finetune_net = nn.Sequential()
    finetune_net.features = torchvision.models.resnet34(pretrained=True)
    # 定义一个新的输出网络,共有120个输出类别
    finetune_net.output_new = nn.Sequential(nn.Linear(1000, 256),
                                            nn.ReLU(),
                                            nn.Linear(256, 120))
    # 将模型参数分配给用于计算的CPU或GPU
    finetune_net = finetune_net.to(devices[0])
    # 冻结参数
    for param in finetune_net.features.parameters():
        param.requires_grad = False
    return finetune_net

在计算损失之前,我们首先获取预训练模型的输出层的输入,即提取的特征。然后我们使用此特征作为我们小型自定义输出网络的输入来计算损失。

python 复制代码
loss = nn.CrossEntropyLoss(reduction='none')

def evaluate_loss(data_iter, net, devices):
    l_sum, n = 0.0, 0
    for features, labels in data_iter:
        features, labels = features.to(devices[0]), labels.to(devices[0])
        outputs = net(features)
        l = loss(outputs, labels)
        l_sum += l.sum()
        n += labels.numel()
    return (l_sum / n).to('cpu')

五、定义训练函数

我们将根据模型在验证集上的表现选择模型并调整超参数。模型训练函数train只迭代小型自定义输出网络的参数。

python 复制代码
def train(net, train_iter, valid_iter, num_epochs, lr, wd, devices, lr_period,
          lr_decay):
    # 只训练小型自定义输出网络
    net = nn.DataParallel(net, device_ids=devices).to(devices[0])
    trainer = torch.optim.SGD((param for param in net.parameters()
                               if param.requires_grad), lr=lr,
                              momentum=0.9, weight_decay=wd)
    scheduler = torch.optim.lr_scheduler.StepLR(trainer, lr_period, lr_decay)
    num_batches, timer = len(train_iter), d2l.Timer()
    legend = ['train loss']
    if valid_iter is not None:
        legend.append('valid loss')
    animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs],
                            legend=legend)
    for epoch in range(num_epochs):
        metric = d2l.Accumulator(2)
        for i, (features, labels) in enumerate(train_iter):
            timer.start()
            features, labels = features.to(devices[0]), labels.to(devices[0])
            trainer.zero_grad()
            output = net(features)
            l = loss(output, labels).sum()
            l.backward()
            trainer.step()
            metric.add(l, labels.shape[0])
            timer.stop()
            if (i + 1) % (num_batches // 5) == 0 or i == num_batches - 1:
                animator.add(epoch + (i + 1) / num_batches,
                             (metric[0] / metric[1], None))
        measures = f'train loss {metric[0] / metric[1]:.3f}'
        if valid_iter is not None:
            valid_loss = evaluate_loss(valid_iter, net, devices)
            animator.add(epoch + 1, (None, valid_loss.detach().cpu()))
        scheduler.step()
    if valid_iter is not None:
        measures += f', valid loss {valid_loss:.3f}'
    print(measures + f'\n{metric[1] * num_epochs / timer.sum():.1f}'
          f' examples/sec on {str(devices)}')

六、训练和验证模型

现在我们可以训练和验证模型了,以下超参数都是可调的。例如,我们可以增加迭代轮数。另外,由于lr_periodlr_decay分别设置为2和0.9,因此优化算法的学习速率将在每2个迭代后乘以0.9。

python 复制代码
devices, num_epochs, lr, wd = d2l.try_all_gpus(), 10, 1e-4, 1e-4
lr_period, lr_decay, net = 2, 0.9, get_net(devices)
train(net, train_iter, valid_iter, num_epochs, lr, wd, devices, lr_period,
      lr_decay)


七、对测试集分类并在Kaggle提交结果

实战Kaggle比赛:图像分类 (CIFAR-10)中的最后一步类似,最终所有标记的数据(包括验证集)都用于训练模型和对测试集进行分类。我们将使用训练好的自定义输出网络进行分类。

python 复制代码
net = get_net(devices)
train(net, train_valid_iter, None, num_epochs, lr, wd, devices, lr_period,
      lr_decay)

preds = []
for data, label in test_iter:
    output = torch.nn.functional.softmax(net(data.to(devices[0])), dim=1)
    preds.extend(output.cpu().detach().numpy())
ids = sorted(os.listdir(
    os.path.join(data_dir, 'train_valid_test', 'test', 'unknown')))
with open('submission.csv', 'w') as f:
    f.write('id,' + ','.join(train_valid_ds.classes) + '\n')
    for i, output in zip(ids, preds):
        f.write(i.split('.')[0] + ',' + ','.join(
            [str(num) for num in output]) + '\n')


上面的代码将生成一个submission.csv文件,以实战Kaggle比赛:预测房价中描述的方式提在Kaggle上提交。

小结

  • ImageNet数据集中的图像比CIFAR-10图像尺寸大,我们可能会修改不同数据集上任务的图像增广操作。
  • 要对ImageNet数据集的子集进行分类,我们可以利用完整ImageNet数据集上的预训练模型来提取特征并仅训练小型自定义输出网络,这将减少计算时间和节省内存空间。
相关推荐
NAGNIP10 小时前
一文搞懂深度学习中的通用逼近定理!
人工智能·算法·面试
冬奇Lab11 小时前
一天一个开源项目(第36篇):EverMemOS - 跨 LLM 与平台的长时记忆 OS,让 Agent 会记忆更会推理
人工智能·开源·资讯
冬奇Lab11 小时前
OpenClaw 源码深度解析(一):Gateway——为什么需要一个"中枢"
人工智能·开源·源码阅读
AngelPP15 小时前
OpenClaw 架构深度解析:如何把 AI 助手搬到你的个人设备上
人工智能
宅小年15 小时前
Claude Code 换成了Kimi K2.5后,我再也回不去了
人工智能·ai编程·claude
九狼15 小时前
Flutter URL Scheme 跨平台跳转
人工智能·flutter·github
ZFSS15 小时前
Kimi Chat Completion API 申请及使用
前端·人工智能
天翼云开发者社区17 小时前
春节复工福利就位!天翼云息壤2500万Tokens免费送,全品类大模型一键畅玩!
人工智能·算力服务·息壤
知识浅谈17 小时前
教你如何用 Gemini 将课本图片一键转为精美 PPT
人工智能
Ray Liang17 小时前
被低估的量化版模型,小身材也能干大事
人工智能·ai·ai助手·mindx