13.14. Kaggle Üzerinde Köpek Cinsi Tanımlama (ImageNet Köpekler)
Open the notebook in Colab
Open the notebook in Colab
Open the notebook in Colab
Open the notebook in SageMaker Studio Lab

Bu bölümde, Kaggle’da köpek cinsi tanımlama problemini uygulayacağız. Bu yarışmanın web adresi: https://www.kaggle.com/c/dog-breed-identification

Bu yarışmada 120 farklı köpek ırkı tanınacak. Aslında, bu yarışma için veri kümesi ImageNet veri kümesinin bir alt kümesidir. Section 13.13 içindeki CIFAR-10 veri kümesindeki imgelerin aksine, ImageNet veri kümesindeki imgeler hem daha yüksek hem de daha geniş farklı boyutlardadır. Fig. 13.14.1, yarışmanın web sayfasındaki bilgileri gösterir. Sonuçlarınızı göndermek için bir Kaggle hesabına ihtiyacınız var.

../_images/kaggle-dog.jpg

Fig. 13.14.1 Köpek cinsi tanımlama yarışması web sitesi. Yarışma veri kümesi “Data” (“Veri”) sekmesine tıklanarak elde edilebilir.

import os
from d2l import mxnet as d2l
from mxnet import autograd, gluon, init, npx
from mxnet.gluon import nn

npx.set_np()
import os
import torch
import torchvision
from torch import nn
from d2l import torch as d2l

13.14.1. Veri Kümesini Elde Etme ve Düzenleme

Yarışma veri kümesi, sırasıyla üç RGB (renkli) kanalın 10222 ve 10357 JPEG, imgelerini içeren birer eğitim kümesine ve test kümesine ayrılmıştır. Eğitim veri kümesinde Labradors, Kaniş, Dachshunds, Samoyeds, Huskies, Chihuahuas ve Yorkshire Terriers gibi 120 köpek ırkı vardır.

13.14.1.1. Veri Kümesini İndirme

Kaggle’a giriş yaptıktan sonra, Fig. 13.14.1 içinde gösterilen yarışma web sayfasındaki “Veri” (“Data”) sekmesine tıklayabilir ve “Tümünü İndir” (“Download All”) düğmesine tıklayarak veri kümesini indirebilirsiniz. İndirilen dosyayı ../data’da açtıktan sonra, tüm veri kümesini aşağıdaki yollarda bulacaksınız:

  • ../data/dog-breed-identification/labels.csv

  • ../data/dog-breed-identification/sample_submission.csv

  • ../data/dog-breed-identification/train

  • ../data/dog-breed-identification/test

Yukarıdaki yapının train/ ve test/ klasörlerinin sırasıyla eğitim ve test köpek imgeleri içeren Section 13.13 içindeki CIFAR-10 yarışmasına benzer olduğunu fark etmiş olabilirsiniz ve labels.csv eğitim imgeleri için etiketler içerir. Benzer şekilde, başlamayı kolaylaştırmak için, yukarıda belirtilen: train_valid_test_tiny.zip veri kümesinin küçük bir örneklemini sağlıyoruz. Kaggle yarışması için tam veri kümesini kullanacaksanız, aşağıdaki demo değişkenini False olarak değiştirmeniz gerekir.

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

# Kaggle yarışması için indirilen tam veri kümesini kullanıyorsanız,
# aşağıdaki değişkeni `False` olarak değiştirin
demo = True
if demo:
    data_dir = d2l.download_extract('dog_tiny')
else:
    data_dir = os.path.join('..', 'data', 'dog-breed-identification')
#@save
d2l.DATA_HUB['dog_tiny'] = (d2l.DATA_URL + 'kaggle_dog_tiny.zip',
                            '0cb91d09b814ecdc07b50f31f8dcad3e81d6a86d')

# Kaggle yarışması için indirilen tam veri kümesini kullanıyorsanız,
# aşağıdaki değişkeni `False` olarak değiştirin
demo = True
if demo:
    data_dir = d2l.download_extract('dog_tiny')
else:
    data_dir = os.path.join('..', 'data', 'dog-breed-identification')

13.14.1.2. Veri Kümesini Düzenleme

Veri kümesini Section 13.13 içinde yaptığımız şeye benzer şekilde düzenleyebiliriz, yani esas eğitim kümesindeki bir geçerleme kümesini ayırabilir ve imgeleri etiketlere göre gruplandırılmış alt klasörlere taşıyabiliriz.

Aşağıdaki reorg_dog_data işlevi eğitim veri etiketlerini okur, geçerleme kümesini böler ve eğitim kümesini düzenler.

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)
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)

13.14.2. İmge Artırma

Bu köpek cins veri kümesinin, imgeleri Section 13.13 içindeki CIFAR-10 veri kümesinden daha büyük olan ImageNet veri kümesinin bir alt kümesi olduğunu hatırlayın. Aşağıda, nispeten daha büyük imgeler için yararlı olabilecek birkaç imge artırma işlemi listelenmektedir.

transform_train = gluon.data.vision.transforms.Compose([
    # Orijinal alanın 0.08 ila 1'i arasında bir alana ve 3/4 ile 4/3 arasında
    # yükseklik-genişlik oranına sahip bir imge elde etmek için imgeyi
    # rastgele kırpın. Ardından, yeni bir 224 x 224 imge oluşturmak için
    # imgeyi ölçeklendirin.
    gluon.data.vision.transforms.RandomResizedCrop(224, scale=(0.08, 1.0),
                                                   ratio=(3.0/4.0, 4.0/3.0)),
    gluon.data.vision.transforms.RandomFlipLeftRight(),
    # Parlaklığı, zıtlığı ve doygunluğu rastgele değiştirin
    gluon.data.vision.transforms.RandomColorJitter(brightness=0.4,
                                                   contrast=0.4,
                                                   saturation=0.4),
    # Rastgele gürültü ekle
    gluon.data.vision.transforms.RandomLighting(0.1),
    gluon.data.vision.transforms.ToTensor(),
    # İmgenin her kanalını standartlaştırın
    gluon.data.vision.transforms.Normalize([0.485, 0.456, 0.406],
                                           [0.229, 0.224, 0.225])])
transform_train = torchvision.transforms.Compose([
    # Orijinal alanın 0.08 ila 1'i arasında bir alana ve 3/4 ile 4/3 arasında
    # yükseklik-genişlik oranına sahip bir imge elde etmek için imgeyi
    # rastgele kırpın. Ardından, yeni bir 224 x 224 imge oluşturmak için
    # imgeyi ölçeklendirin.
    torchvision.transforms.RandomResizedCrop(224, scale=(0.08, 1.0),
                                             ratio=(3.0/4.0, 4.0/3.0)),
    torchvision.transforms.RandomHorizontalFlip(),
    # Parlaklığı, zıtlığı ve doygunluğu rastgele değiştirin
    torchvision.transforms.ColorJitter(brightness=0.4,
                                       contrast=0.4,
                                       saturation=0.4),
    # Rastgele gürültü ekle
    torchvision.transforms.ToTensor(),
    # İmgenin her kanalını standartlaştırın
    torchvision.transforms.Normalize([0.485, 0.456, 0.406],
                                     [0.229, 0.224, 0.225])])

Tahmin sırasında yalnızca imge ön işleme işlemlerini rastgelelik olmadan kullanıyoruz.

transform_test = gluon.data.vision.transforms.Compose([
    gluon.data.vision.transforms.Resize(256),
    # İmgenin ortasından 224 x 224 karelik bir alanı kırpın
    gluon.data.vision.transforms.CenterCrop(224),
    gluon.data.vision.transforms.ToTensor(),
    gluon.data.vision.transforms.Normalize([0.485, 0.456, 0.406],
                                           [0.229, 0.224, 0.225])])
transform_test = torchvision.transforms.Compose([
    torchvision.transforms.Resize(256),
    # İmgenin ortasından 224 x 224 karelik bir alanı kırpın
    torchvision.transforms.CenterCrop(224),
    torchvision.transforms.ToTensor(),
    torchvision.transforms.Normalize([0.485, 0.456, 0.406],
                                     [0.229, 0.224, 0.225])])

13.14.3. Veri Kümesi Okuma

Section 13.13 içinde olduğu gibi, ham imge dosyalarından oluşan düzenlenmiş veri kümesini okuyabiliriz.

train_ds, valid_ds, train_valid_ds, test_ds = [
    gluon.data.vision.ImageFolderDataset(
        os.path.join(data_dir, 'train_valid_test', folder))
    for folder in ('train', 'valid', 'train_valid', 'test')]
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']]

Aşağıda, Section 13.13 içinde olduğu gibi veri yineleyici örneklerini oluşturuyoruz.

train_iter, train_valid_iter = [gluon.data.DataLoader(
    dataset.transform_first(transform_train), batch_size, shuffle=True,
    last_batch='discard') for dataset in (train_ds, train_valid_ds)]

valid_iter = gluon.data.DataLoader(
    valid_ds.transform_first(transform_test), batch_size, shuffle=False,
    last_batch='discard')

test_iter = gluon.data.DataLoader(
    test_ds.transform_first(transform_test), batch_size, shuffle=False,
    last_batch='keep')
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)

13.14.4. Önceden Eğitilmiş Modelleri İnce Ayarlama

Yine, bu yarışma için veri kümesi ImageNet veri kümesinin bir alt kümedir. Bu nedenle, tam ImageNet veri kümesinde önceden eğitilmiş bir model seçmek için Section 13.2 içinde tartışılan yaklaşımı kullanabilir ve bunu özel bir küçük ölçekli çıktı ağına beslenecek imge özniteliklerini ayıklamak için kullanabiliriz. Derin öğrenme çerçevelerinin üst seviye API’leri, ImageNet veri kümesi üzerinde önceden eğitilmiş geniş bir model yelpazesi sunar. Burada, bu modelin çıktı katmanının girdisini (yani ayıklanan öznitelikler) yeniden kullandığımız önceden eğitilmiş bir ResNet-34 modeli seçiyoruz. Daha sonra orijinal çıktı katmanını, iki tam bağlı katman yığını gibi eğitilebilecek küçük bir özel çıktı ağı ile değiştirebiliriz. Section 13.2 içindeki deneyden farklı olarak, aşağıdaki öznitelik ayıklamak için kullanılan önceden eğitilmiş modeli yeniden eğitmez. Bu, gradyanların depolanması için eğitim süresini ve hafızasını azaltır.

Tüm ImageNet veri kümesi için üç RGB kanalının araçlarını ve standart sapmalarını kullanarak imgeleri standartlaştırdığımızı hatırlayın. Aslında, bu aynı zamanda ImageNet’te önceden eğitilmiş model tarafından uygulanan standartlaştırma işlemi ile de tutarlıdır.

def get_net(devices):
    finetune_net = gluon.model_zoo.vision.resnet34_v2(pretrained=True)
    # Yeni bir çıktı ağı tanımlayın
    finetune_net.output_new = nn.HybridSequential(prefix='')
    finetune_net.output_new.add(nn.Dense(256, activation='relu'))
    # 120 çıktı kategorisi var
    finetune_net.output_new.add(nn.Dense(120))
    # Çıktı ağını ilklet
    finetune_net.output_new.initialize(init.Xavier(), ctx=devices)
    # Model parametrelerini hesaplama için kullanılan CPU'lara veya
    # GPU'lara dağıtın
    finetune_net.collect_params().reset_ctx(devices)
    return finetune_net
def get_net(devices):
    finetune_net = nn.Sequential()
    finetune_net.features = torchvision.models.resnet34(pretrained=True)
    # Yeni bir çıktı ağı tanımlayın (120 çıktı kategorisi var)
    finetune_net.output_new = nn.Sequential(nn.Linear(1000, 256),
                                            nn.ReLU(),
                                            nn.Linear(256, 120))
    # Modeli cihazlara taşı
    finetune_net = finetune_net.to(devices[0])
    # Öznitelik katmanlarının parametrelerini dondur
    for param in finetune_net.features.parameters():
        param.requires_grad = False
    return finetune_net

Kaybın hesaplanmasından önce, önce önceden eğitilmiş modelin çıktı katmanının girdisini, yani ayıklanan özniteliği, elde ederiz. Daha sonra bu özniteliği, kaybı hesaplamak için küçük özel çıktı ağımızın girdisi olarak kullanırız.

loss = gluon.loss.SoftmaxCrossEntropyLoss()

def evaluate_loss(data_iter, net, devices):
    l_sum, n = 0.0, 0
    for features, labels in data_iter:
        X_shards, y_shards = d2l.split_batch(features, labels, devices)
        output_features = [net.features(X_shard) for X_shard in X_shards]
        outputs = [net.output_new(feature) for feature in output_features]
        ls = [loss(output, y_shard).sum() for output, y_shard
              in zip(outputs, y_shards)]
        l_sum += sum([float(l.sum()) for l in ls])
        n += labels.size
    return l_sum / n
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

13.14.5. Eğitim Fonksiyonunu Tanımlama

Modeli seçip, modelin geçerleme kümesindeki performansına göre hiper parametreleri ayarlayacağız. train model eğitim işlevi yalnızca küçük özel çıktı ağının parametrelerini yineler.

def train(net, train_iter, valid_iter, num_epochs, lr, wd, devices, lr_period,
          lr_decay):
    # Yalnızca küçük özel çıktı ağını eğitin
    trainer = gluon.Trainer(net.output_new.collect_params(), 'sgd',
                            {'learning_rate': lr, 'momentum': 0.9, 'wd': wd})
    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)
        if epoch > 0 and epoch % lr_period == 0:
            trainer.set_learning_rate(trainer.learning_rate * lr_decay)
        for i, (features, labels) in enumerate(train_iter):
            timer.start()
            X_shards, y_shards = d2l.split_batch(features, labels, devices)
            output_features = [net.features(X_shard) for X_shard in X_shards]
            with autograd.record():
                outputs = [net.output_new(feature)
                           for feature in output_features]
                ls = [loss(output, y_shard).sum() for output, y_shard
                      in zip(outputs, y_shards)]
            for l in ls:
                l.backward()
            trainer.step(batch_size)
            metric.add(sum([float(l.sum()) for l in ls]), 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))
        if valid_iter is not None:
            valid_loss = evaluate_loss(valid_iter, net, devices)
            animator.add(epoch + 1, (None, valid_loss))
    measures = f'train loss {metric[0] / metric[1]:.3f}'
    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)}')
def train(net, train_iter, valid_iter, num_epochs, lr, wd, devices, lr_period,
          lr_decay):
    # Yalnızca küçük özel çıktı ağını eğitin
    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)}')

13.14.6. Modeli Eğitme ve Geçerleme

Şimdi modeli eğitebilir ve geçerleyebiliriz. Aşağıdaki hiper parametrelerin tümü ayarlanabilir. Örneğin, dönem sayısı artırılabilir. lr_period ve lr_decay sırasıyla 2 ve 0.9 olarak ayarlandığından, eniyileme algoritmasının öğrenme oranı her 2 dönem sonrasında 0.9 ile çarpılır.

devices, num_epochs, lr, wd = d2l.try_all_gpus(), 10, 5e-3, 1e-4
lr_period, lr_decay, net = 2, 0.9, get_net(devices)
net.hybridize()
train(net, train_iter, valid_iter, num_epochs, lr, wd, devices, lr_period,
      lr_decay)
train loss 0.938, valid loss 0.967
215.0 examples/sec on [gpu(0), gpu(1)]
../_images/output_kaggle-dog_571091_93_1.svg
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)
train loss 1.289, valid loss 1.443
611.3 examples/sec on [device(type='cuda', index=0), device(type='cuda', index=1)]
../_images/output_kaggle-dog_571091_96_1.svg

13.14.7. Test Kümesini Sınıflandırma ve Kaggle’da Sonuçları Teslim Etme

Section 13.13 içindeki son adıma benzer şekilde, sonunda tüm etiketli veriler (geçerleme kümesi dahil) modeli eğitmek ve test kümesini sınıflandırmak için kullanılır. Sınıflandırma için eğitilmiş özel çıktı ağını kullanacağız.

net = get_net(devices)
net.hybridize()
train(net, train_valid_iter, None, num_epochs, lr, wd, devices, lr_period,
      lr_decay)

preds = []
for data, label in test_iter:
    output_features = net.features(data.as_in_ctx(devices[0]))
    output = npx.softmax(net.output_new(output_features))
    preds.extend(output.asnumpy())
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.synsets) + '\n')
    for i, output in zip(ids, preds):
        f.write(i.split('.')[0] + ',' + ','.join(
            [str(num) for num in output]) + '\n')
train loss 0.986
163.8 examples/sec on [gpu(0), gpu(1)]
../_images/output_kaggle-dog_571091_102_1.svg
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=0)
    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')
train loss 1.290
911.8 examples/sec on [device(type='cuda', index=0), device(type='cuda', index=1)]
../_images/output_kaggle-dog_571091_105_1.svg

Yukarıdaki kod, Section 4.10 içinde açıklanan şekilde Kaggle’a teslim edilecek bir submission.csv dosyası oluşturacaktır.

13.14.8. Özet

  • ImageNet veri kümelerindeki imgeler CIFAR-10 imgelerinden (farklı boyutlarda) daha büyüktür. Farklı bir veri kümesindeki görevler için imge artırma işlemlerini değiştirebiliriz.

  • ImageNet veri kümesinin bir alt kümesini sınıflandırmak için, öznitelikleri ayıklamak ve yalnızca özel bir küçük ölçekli çıktı ağı eğitebilmek için ImageNet veri kümesinin tüm ImageNet veri kümesinde önceden eğitilmiş modellerini kullanabiliriz. Bu, daha az hesaplama süresi ve bellek maliyetine yol açacaktır.

13.14.9. Alıştırmalar

  1. Tüm Kaggle yarışma veri kümesini kullanırken, diğer bazı hiper parametreleri lr = 0.01, lr_period = 10 ve lr_period = 10 ve lr_decay = 0.1 olarak ayarlarken batch_size (toplu iş boyutu) ve num_epochs (dönem sayısı) artırdığınızda hangi sonuçları elde edebilirsiniz?

  2. Bir önceden eğitilmiş daha derin model kullanırsanız daha iyi sonuçlar alır mısınız? Hiper parametreleri nasıl ayarlarsınız? Sonuçları daha da iyileştirebilir misiniz?