[PYTHON] [Herausforderer suchen] Das schnellste Laden und Erweitern von Daten (Kaggle-Notizbuch), denke ich

Einführung

Nun, wie der Titel schon sagt, habe ich das schnellste Laden und Erweitern von Daten auf dem Kaggle-Notebook in Betracht gezogen, also werde ich es vorstellen. Wenn Sie einen schnelleren Weg kennen, lassen Sie es mich bitte wissen! Das Thema für diese Zeit ist wie folgt eingestellt.

Sie können auch den in diesem Artikel verwendeten Code aus diesem Notizbuch ausprobieren. (Ich habe nicht so viel debuggt, also bitte auf Mängel hinweisen ...) https://www.kaggle.com/hirune924/the-fastest-data-loading-data-augmentation?scriptVersionId=41763394

Für diejenigen, die das Ergebnis wissen möchten, werde ich das Ergebnis zuerst veröffentlichen. ダウンロード.png

OpenCV + Albumentations Ich denke, es ist jetzt eine ziemlich einfache Kombination. Ich denke, es gibt mehr Leute, die dies verwenden als Torchvisons Transformation. Ich benutze diese Kombination oft zu Beginn des Kaggle-Wettbewerbs.

cv2_alb.py


import time
import torch
from torch.utils.data import Dataset, DataLoader
import pandas as pd
import cv2
import albumentations as A


class DogDataset(Dataset):
    def __init__(self, transform=None):
        self.img_list = pd.read_csv('../input/dog-breed-identification/labels.csv')
        self.transform = transform
        
        breeds=list(self.img_list['breed'].unique())
        self.breed2idx = {b: i for i, b in enumerate(breeds)}

    def __len__(self):
        return len(self.img_list)

    def __getitem__(self, idx):
        img_row = self.img_list.iloc[idx]
        image = cv2.imread('../input/dog-breed-identification/train/' + img_row['id'] + '.jpg')
        label = self.breed2idx[img_row['breed']]

        if self.transform is not None:
            image = self.transform(image=image)
        image = torch.from_numpy(image['image'].transpose(2, 0, 1))
        return image, label

transform = A.Compose([A.RandomResizedCrop(height=224, width=224, p=1),
                       A.HorizontalFlip(p=0.5),
                       A.VerticalFlip(p=0.5),
                       A.MotionBlur(blur_limit=3, p=1),
                       A.Rotate(limit=45, p=1),
                       A.Normalize(mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5), max_pixel_value=255.0, always_apply=True, p=1.0)])

data_loader = DataLoader(DogDataset(transform=transform), batch_size=64, shuffle=True, num_workers=2)

Lesen Sie dies wie folgt und messen Sie die Zeit.

cv2_alb_time.py


%%timeit -r 2 -n 5
opencv_alb_times = []
start_time = time.time()
for image, label in data_loader:
    image = image.cuda()
    label = label.cuda()
    pass
opencv_alb_time = time.time() - start_time
opencv_alb_times.append(opencv_alb_time)
print(str(opencv_alb_time) + ' sec')

Das Ergebnis ist wie folgt.

98.37442970275879 sec
70.52895092964172 sec
66.72178149223328 sec
61.30395317077637 sec
68.30901885032654 sec
69.6796133518219 sec
71.02722263336182 sec
70.88462662696838 sec
70.54376363754272 sec
65.67756700515747 sec
1min 11s ± 1.74 s per loop (mean ± std. dev. of 2 runs, 5 loops each)

jpeg4py + Albumentations Wann immer ich einen Artikel dieser Art schreibe, sagen mir einige Leute, ich solle versuchen, jpeg4py zu verwenden, also werde ich dies auch als Basis messen. Wenn das Bildformat JPEG ist, gibt es keine Möglichkeit, dies nicht zu verwenden. Zuerst von der Installation

install_jpeg4py.sh


!apt-get install libturbojpeg
!pip install jpeg4py

Dann ist hier der Code. Es ist fast das gleiche, außer dass Daten gelesen werden.

jpeg4py_alb.py


import time
import torch
from torch.utils.data import Dataset, DataLoader
import pandas as pd
import cv2
import albumentations as A
import jpeg4py as jpeg


class DogDataset(Dataset):
    def __init__(self, transform=None):
        self.img_list = pd.read_csv('../input/dog-breed-identification/labels.csv')
        self.transform = transform
        
        breeds=list(self.img_list['breed'].unique())
        self.breed2idx = {b: i for i, b in enumerate(breeds)}

    def __len__(self):
        return len(self.img_list)

    def __getitem__(self, idx):
        img_row = self.img_list.iloc[idx]
        image = jpeg.JPEG('../input/dog-breed-identification/train/' + img_row['id'] + '.jpg').decode()
        label = self.breed2idx[img_row['breed']]

        if self.transform is not None:
            image = self.transform(image=image)
        image = torch.from_numpy(image['image'].transpose(2, 0, 1))
        return image, label

transform = A.Compose([A.RandomResizedCrop(height=224, width=224, p=1),
                       A.HorizontalFlip(p=0.5),
                       A.VerticalFlip(p=0.5),
                       A.MotionBlur(blur_limit=3, p=1),
                       A.Rotate(limit=45, p=1),
                       A.Normalize(mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5), max_pixel_value=255.0, always_apply=True, p=1.0)])

data_loader = DataLoader(DogDataset(transform=transform), batch_size=64, shuffle=True, num_workers=2)

Der Code zum Lesen und Messen der Zeit ist fast der gleiche wie der vorherige, daher werde ich ihn weglassen. Das Ergebnis ist wie folgt. Immerhin ist jpeg4py schnell.

43.14848828315735 sec
42.78340029716492 sec
41.33797478675842 sec
43.24748754501343 sec
41.11549472808838 sec
41.17329430580139 sec
40.58435940742493 sec
41.16935634613037 sec
40.92542815208435 sec
39.6163330078125 sec
41.5 s ± 816 ms per loop (mean ± std. dev. of 2 runs, 5 loops each)

jpeg4py + Kornia Verwenden Sie Kornia zur Datenerweiterung. Dies ermöglicht die Verarbeitung der Datenerweiterung auf der GPU. Nur beim ersten Random Resized Crop werden Albumentationen verwendet, sodass Sie eine Reihe von Tensoren mit gut geformten Bildern erstellen können. Da Kornia jeden Stapel verarbeiten kann, wird die Datenerweiterung für den von DataLoader geladenen Stapel ausgeführt.

Der Code ist hier.

jpeg4py_kornia.py


import time
import torch
from torch.utils.data import Dataset, DataLoader
import pandas as pd
import numpy as np
import cv2
import jpeg4py as jpeg

import albumentations as A
import kornia.augmentation as K
import torch.nn as nn


class DogDataset(Dataset):
    def __init__(self, transform=None):
        self.img_list = pd.read_csv('../input/dog-breed-identification/labels.csv')
        self.transform = transform
        
        breeds=list(self.img_list['breed'].unique())
        self.breed2idx = {b: i for i, b in enumerate(breeds)}

    def __len__(self):
        return len(self.img_list)

    def __getitem__(self, idx):
        img_row = self.img_list.iloc[idx]
        image = jpeg.JPEG('../input/dog-breed-identification/train/' + img_row['id'] + '.jpg').decode()
        label = self.breed2idx[img_row['breed']]

        if self.transform is not None:
            image = self.transform(image=image)
        image = torch.from_numpy(image['image'].transpose(2, 0, 1).astype(np.float32))
        return image, label

alb_transform = A.Compose([A.RandomResizedCrop(height=224, width=224, p=1)])

mean_std = torch.Tensor([0.5, 0.5, 0.5])*255
kornia_transform = nn.Sequential(
    K.RandomHorizontalFlip(),
    K.RandomVerticalFlip(),
    K.RandomMotionBlur(3, 35., 0.5),
    K.RandomRotation(degrees=45.0),
    K.Normalize(mean=mean_std,std=mean_std)
)

data_loader = DataLoader(DogDataset(transform=alb_transform), batch_size=64, shuffle=True, num_workers=2)

Die Lesung wird wie folgt sein. Sie können sehen, dass die Konvertierung nach dem Laden aus dem Data Loader in Batch-Einheiten angewendet wird.

jpeg4py_kornia_time.py


%%timeit -r 2 -n 5
jpeg4py_kornia_times = []
start_time = time.time()
for image, label in data_loader:
    image = kornia_transform(image.cuda())
    label = label.cuda()
    pass
jpeg4py_kornia_time = time.time() - start_time
jpeg4py_kornia_times.append(jpeg4py_kornia_time)
print(str(jpeg4py_kornia_time) + ' sec')

Das Ergebnis ist wie folgt. Es wird ziemlich schnell.

28.150899171829224 sec
24.104888916015625 sec
25.490058183670044 sec
24.111201763153076 sec
22.999730587005615 sec
25.16165590286255 sec
26.496272325515747 sec
27.150801420211792 sec
28.757362365722656 sec
29.331339836120605 sec
26.2 s ± 1.2 s per loop (mean ± std. dev. of 2 runs, 5 loops each)

DALI + Kornia Dies ist die schnellste Kombination, die mir derzeit einfällt. Ich habe diesen Artikel geschrieben, weil ich das sagen wollte. Wenn Sie DALI verwenden, können Sie die GPU ab dem Zeitpunkt des Ladens des Images verwenden. Wenn es geladen wird, können Sie sagen, dass sich das Image bereits auf der GPU befindet. Dies machte die Verwendung von Albumentationen usw. schwierig und die Verwendung schwierig, da in DALI nur wenige Arten von Erweiterungen implementiert sind. Diese Kombination wurde jedoch realisiert, da die Erweiterung der GPU durch Kornia ein praktisches Niveau erreicht hat. Installieren Sie zunächst NVIDIA DALI.

install_dali.sh


!pip install --extra-index-url https://developer.download.nvidia.com/compute/redist nvidia-dali-cuda100

Und hier ist der Code. Die Haarfarbe unterscheidet sich ein wenig von den vorherigen. Es ist so, als würde man eine Pipeline in DALI definieren, erstellen und einen Iterator erstellen, der einen PyTorch-Tensor zurückgibt. RandomResizedCrop wird in DALI und danach in Kornia durchgeführt.

dali_kornia.py


import time
import torch
from torch.utils.data import Dataset, DataLoader
import pandas as pd

import kornia.augmentation as K
import torch.nn as nn

from nvidia.dali.pipeline import Pipeline
from nvidia.dali.plugin.pytorch import DALIGenericIterator
import nvidia.dali.ops as ops
import nvidia.dali.types as types

class DALIPipeline(Pipeline):
    def __init__(self, batch_size, num_threads, device_id):
        super(DALIPipeline, self).__init__(batch_size, num_threads, device_id)
        self.img_list = pd.read_csv('../input/dog-breed-identification/labels.csv')

        breeds=list(self.img_list['breed'].unique())
        self.breed2idx = {b: i for i, b in enumerate(breeds)}
        
        self.img_list['label'] = self.img_list['breed'].map(self.breed2idx)
        self.img_list['data'] = '../input/dog-breed-identification/train/' + self.img_list['id'] + '.jpg'
        
        self.img_list[['data', 'label']].to_csv('dali.txt', header=False, index=False, sep=' ')
        
        self.input = ops.FileReader(file_root='.', file_list='dali.txt')
        self.decode = ops.ImageDecoder(device = "mixed", output_type = types.DALIImageType.RGB)
        #self.decode = ops.ImageDecoderRandomCrop(device = "mixed", output_type = types.DALIImageType.RGB)
        self.resize = ops.RandomResizedCrop(device = "gpu", size=(224, 224))
        self.transpose = ops.Transpose(device='gpu', perm = [2, 0, 1])
        self.cast = ops.Cast(device='gpu', dtype=types.DALIDataType.FLOAT)

    def define_graph(self):
        images, labels = self.input(name="Reader")
        images = self.decode(images)
        images = self.resize(images)
        images = self.cast(images)
        output = self.transpose(images)
        return (output, labels)

def DALIDataLoader(batch_size):
    num_gpus = 1
    pipes = [DALIPipeline(batch_size=batch_size, num_threads=2, device_id=device_id) for device_id in range(num_gpus)]

    pipes[0].build()
    dali_iter = DALIGenericIterator(pipelines=pipes, output_map=['data', 'label'], 
                                    size=pipes[0].epoch_size("Reader"), reader_name=None, 
                                    auto_reset=True, fill_last_batch=True, dynamic_shape=False, 
                                    last_batch_padded=True)
    return dali_iter

data_loader = DALIDataLoader(batch_size=64)

mean_std = torch.Tensor([0.5, 0.5, 0.5])*255
kornia_transform = nn.Sequential(
    K.RandomHorizontalFlip(),
    K.RandomVerticalFlip(),
    K.RandomMotionBlur(3, 35., 0.5),
    K.RandomRotation(degrees=45.0),
    K.Normalize(mean=mean_std,std=mean_std)
)

Der Leseteil ist wie folgt.

dali_kornia_time.py


%%timeit -r 2 -n 5
dali_kornia_times = []
start_time = time.time()
for feed in data_loader:
    # image is already on GPU
    image = kornia_transform(feed[0]['data'])
    label = feed[0]['label'].cuda()
    pass
dali_kornia_time = time.time() - start_time
dali_kornia_times.append(dali_kornia_time)
print(str(dali_kornia_time) + ' sec')

Das Ergebnis ist wie folgt: Es ist explosiv! Es ist zu schnell! Es ist schon eine Geschwindigkeitsverletzung!

8.865531921386719 sec
7.996037721633911 sec
8.494542598724365 sec
8.241464853286743 sec
8.093241214752197 sec
8.12808108329773 sec
7.846079587936401 sec
7.849750280380249 sec
7.848227024078369 sec
7.633721828460693 sec
8.1 s ± 238 ms per loop (mean ± std. dev. of 2 runs, 5 loops each)

Aggregierte Ergebnisse

Ich habe versucht, einfach ein Balkendiagramm zu erstellen. ダウンロード.png

schließlich

Dieses Mal habe ich die Methode zum Laden explosiver Daten und zur Datenerweiterung mit NVIDIA DALI und Kornia eingeführt. Kornias Augmentation ist auch der Ebene der Albumentationen unterlegen, aber die Roadmap enthält Beschreibungen, die Albumentationen berücksichtigen, und ich freue mich darauf in Zukunft! https://github.com/kornia/kornia/issues/434

Recommended Posts

[Herausforderer suchen] Das schnellste Laden und Erweitern von Daten (Kaggle-Notizbuch), denke ich
Ich wollte nur die Daten des gewünschten Datums und der gewünschten Uhrzeit mit Django extrahieren
Ich habe die gleiche Datenanalyse mit kaggle notebook (python) und PowerBI gleichzeitig versucht ②
Ich habe die gleiche Datenanalyse mit kaggle notebook (python) und PowerBI gleichzeitig versucht ①
Die Python-Projektvorlage, an die ich denke.
Ich habe die Varianten von UKR gelesen und implementiert
Es ist Zeit, ernsthaft über die Definition und die Fähigkeiten von Datenwissenschaftlern nachzudenken
Der Jupiter-Notebook-Kernel kann keine Verbindung mehr herstellen
Denken Sie an das Rack und WSGI der nächsten Generation
Ich habe mir die Versionen von Blender und Python angesehen
Ich habe die Datenzuordnung zwischen ArangoDB und Java untersucht
Ich habe versucht, die API von Sakenowa Data Project zu verwenden
Ich analysierte die Rangkampfdaten des Pokemon-Schwertschilds und visualisierte sie auf Tableau