pytorch DataLoader extrem langsame erste Epoche

Aug 30 2020

Wenn ich einen PyTorch DataLoader erstelle und mit der Iteration beginne, erhalte ich eine extrem langsame erste Epoche (x10 - x30 langsamer als alle nächsten Epochen). Darüber hinaus tritt dieses Problem nur beim Zugdatensatz aus der Google Landmark Recognition 2020 von Kaggle auf. Ich kann dies nicht auf synthetischen Bildern reproduzieren. Außerdem habe ich versucht, einen Ordner mit 500.000 Bildern aus GLR2020 zu erstellen, und alles hat gut funktioniert. Es wurden nur wenige ähnliche Probleme im PyTorch-Forum ohne Lösungen gefunden.

import argparse
import pandas as pd
import numpy as np
import os, sys
import multiprocessing, ray
import time
import cv2
import logging
import albumentations as albu
from torch.utils.data import Dataset, DataLoader

samples = 50000 # count of samples to speed up test
bs = 64 # batch size
dir = '/hdd0/datasets/ggl_landmark_recognition_2020/train' # directory with train data
all_files = pd.read_csv('/hdd0/datasets/ggl_landmark_recognition_2020/train.csv')
files = np.random.choice(all_files.id.values, 50000)
files = [os.path.join(_[0], _[1], _[2], _+'.jpg') for _ in files]

# augmentations
aug =  albu.Compose([albu.Resize(400, 400),
        albu.Rotate(limit=15),
        albu.ChannelDropout(p=0.1),
        albu.Normalize(),])

class ImgDataset:
    def __init__(self, path, files, augmentation = None):
        self.path = path
        self.files = {k:v for k, v in enumerate(files)}
        self.augmentation = augmentation

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

    def __getitem__(self, idx):
        img_name = self.files[idx]
        img = np.array(cv2.imread(os.path.join(self.path, img_name)))
        if self.augmentation is not None:
            return self.augmentation(image=img)['image']


dtset = ImgDataset(dir,files, aug)
torchloader = DataLoader(dataset= dtset, batch_size=64, num_worker=16, shuffle=True)
for _ in range(3):
   t1 = time.time()
   for idx, val in enumerate(torchloader):
       pass
   t2 = time.time()
   print(str(t2-t1) +' sec')

Hier sind einige Beispiele für die Ausführungsgeschwindigkeit mit unterschiedlichen num_workersDaten in DataLoader

#num_workers=0
273.1584792137146 sec
83.15653467178345 sec
83.67923021316528 sec

# num_workers = 8 
165.62366938591003 sec
10.405716896057129 sec
10.495309114456177 sec

# num_workers = 16
156.60744667053223 sec
8.051618099212646 sec
7.922858238220215 sec

Das Problem liegt anscheinend nicht bei DataLoader, sondern bei einem Dataset. Wenn ich das DataLoader-Objekt nach der ersten "langen" Iteration lösche und neu initialisiere, funktioniert immer noch alles einwandfrei. Wenn ich den Datensatz neu initialisiere, wird die lange erste Iteration erneut angezeigt. Darüber hinaus habe ich meine CPU-Auslastung htopwährend dieser Epochen mit num_workers32 verfolgt, und während der ersten Epoche ist die Auslastung sehr gering. Nur 1-2 von 32 Kernen arbeiten, während anderer Epochen ~ arbeiten alle Kerne.

Antworten

10 PoeDator Sep 04 2020 at 01:51

Slavka,

Ich habe nicht den gesamten GLR2020-Datensatz heruntergeladen, konnte diesen Effekt jedoch auf den lokal vorhandenen Bilddatensatz (80000 JPG-Bilder mit einer Größe von ca. 400 x 400) beobachten.

Um die Gründe für den Leistungsunterschied zu finden, habe ich Folgendes versucht:

  1. Reduzieren der Vergrößerung auf die Größenänderung
  2. Testen Sie einfach die ImgDataset.__getitem__()Funktion
  3. ImgDataset.__getitem__() ohne Vergrößerung
  4. Laden Sie einfach das unformatierte JPG-Bild und übergeben Sie es aus dem Datensatz, ohne auch nur eine Numpy-Konvertierung durchzuführen.

Es stellt sich heraus, dass der Unterschied vom Bildladezeitpunkt herrührt. Python (oder das Betriebssystem selbst) implementiert eine Art Caching, das beim mehrmaligen Laden des Bildes im folgenden Test beobachtet wird.

for i in range(5):    
    t0 = time.time()
    data = cv2.imread(filename)
    print (time.time() - t0)
    
0.03395271301269531
0.0010004043579101562
0.0010004043579101562
0.0010008811950683594
0.001001119613647461

Gleiches gilt für das Lesen von Datei zu Variable

for i in range(5):    
    t0 = time.time()
    with open(filename, mode='rb') as file: 
        data = file.read()
    print (time.time() - t0)

0.036234378814697266
0.0028831958770751953
0.0020024776458740234
0.0031833648681640625
0.0028734207153320312

Eine Möglichkeit, die Ladegeschwindigkeit zu verringern, besteht darin, die Daten auf einer sehr schnellen lokalen SSD zu speichern. Wenn die Größe dies zulässt, versuchen Sie, einen Teil des Datensatzes in den Arbeitsspeicher zu laden und einen benutzerdefinierten Datenlader zu schreiben, um von dort aus zu füttern ...

Übrigens Nach meinen Erkenntnissen sollte dieser Effekt mit jedem Datensatz reproduzierbar sein - prüfen Sie, ob Sie andere Laufwerke oder Caching verwendet haben.

2 Multihunter Sep 10 2020 at 12:26

Es scheint, dass das Betriebssystem den E / A-Zugriff auf das Dataset zwischenspeichert. Um zu überprüfen, ob dies definitiv das Problem ist, versuchen Sie, sync; echo 3 > /proc/sys/vm/drop_cachesnach der ersten Epoche (unter Ubuntu) zu laufen . Wenn die zweite Epoche dabei genauso langsam ist, ist es das Caching, das die nachfolgenden Lesevorgänge so viel schneller macht.

Wenn Sie eine Festplatte verwenden, können Sie in Ihrer ersten Epoche erhebliche Geschwindigkeitsverbesserungen erzielen, indem Sie alle Ihre kleinen Bilddateien auf der Festplatte zusammenstellen.

Sie können SquashFS (es ist mit Ubuntu vorinstalliert) verwenden, um Ihr gesamtes Dataset in eine einzelne Datei zu komprimieren. Anschließend können Sie diese Datei als Verzeichnis bereitstellen und wie zuvor darauf zugreifen (außer, dass sich die Images jetzt auf der Festplatte befinden). Das bereitgestellte Verzeichnis ist schreibgeschützt.

z.B

mksquashfs /path/to/data data.sqsh
mount data.sqsh /path/to/data_sqsh -t squashfs -o loop

Dann können Sie /path/to/data_sqshgenau so verwenden, wie Sie es verwendet haben /path/to/data. Sie müssen es erneut bereitstellen, wenn Sie Ihren Computer neu starten

Sehen: https://tldp.org/HOWTO/SquashFS-HOWTO/creatingandusing.html