pytorch DataLoader bardzo wolna pierwsza epoka
Kiedy tworzę PyTorch DataLoader i zaczynam iterację - otrzymuję wyjątkowo wolną pierwszą epokę (x10 - x30 wolniej niż wszystkie następne epoki). Co więcej, ten problem występuje tylko w przypadku zestawu danych pociągu z rozpoznania punktów orientacyjnych Google 2020 firmy Kaggle. Nie mogę tego odtworzyć na syntetycznych obrazach, próbowałem też stworzyć folder z 500k obrazami z GLR2020 i wszystko działało dobrze. Znaleziono kilka podobnych problemów na forum PyTorch bez żadnych rozwiązań.
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')
Oto kilka przykładów szybkości wykonywania z różnymi num_workersw 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
Wygląda na to, że problem nie dotyczy DataLoadera, ale zestawu danych. Kiedy usuwam i ponownie inicjuję obiekt DataLoader po pierwszej „długiej” iteracji, wszystko nadal działa poprawnie. Kiedy ponownie inicjuję zbiór danych - pojawia się ponownie długa pierwsza iteracja. Co więcej, prześledziłem wykorzystanie procesora przez htopte epoki z ustawieniem num_workersna 32, a podczas pierwszej epoki wykorzystanie jest naprawdę niskie; pracują tylko 1-2 z 32 rdzeni, w innych epokach działają wszystkie rdzenie.
Odpowiedzi
Slavka,
Nie pobrałem całego zbioru danych GLR2020, ale udało mi się zaobserwować ten efekt na zestawie danych obrazu, który miałem lokalnie (80000 obrazów jpg o rozmiarze około 400x400).
Aby znaleźć przyczyny różnicy w wydajności, wypróbowałem następujące rozwiązania:
- zmniejszenie rozszerzenia do zmiany rozmiaru
- testowanie tylko
ImgDataset.__getitem__()funkcji ImgDataset.__getitem__()bez wzmocnienia- wystarczy załadować surowy obraz jpg i przekazać go z zestawu danych bez nawet konwersji numpy.
Okazuje się, że różnica wynika z czasu ładowania obrazu. Python (lub sam system operacyjny) implementuje pewien rodzaj buforowania, który jest obserwowany podczas wielokrotnego ładowania obrazu w poniższym teście.
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
to samo można zaobserwować podczas czytania z pliku do zmiennej
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
Jednym ze sposobów zmniejszenia szybkości ładowania jest przechowywanie danych na bardzo szybkim lokalnym dysku SSD. Jeśli pozwala na to rozmiar, spróbuj załadować część zestawu danych do pamięci RAM i napisać niestandardowy moduł ładujący dane, aby stamtąd pobierał ...
BTW Na podstawie moich ustaleń ten efekt powinien być odtwarzalny z dowolnym zestawem danych - sprawdź, czy użyłeś innych dysków lub jakiegoś buforowania.
Wygląda na to, że system operacyjny buforuje dostęp we / wy do zestawu danych. Aby sprawdzić, czy to na pewno jest problem, spróbuj uruchomić sync; echo 3 > /proc/sys/vm/drop_caches(na Ubuntu) po pierwszej epoce. Jeśli druga epoka jest równie wolna, kiedy to robisz, to buforowanie sprawia, że kolejne odczyty są o wiele szybsze.
Jeśli używasz dysku twardego, możesz uzyskać znaczną poprawę szybkości w pierwszej epoce, umieszczając wszystkie małe pliki obrazów na dysku.
Możesz użyć SquashFS (jest fabrycznie zainstalowany z Ubuntu), aby skompresować cały zestaw danych do jednego pliku, a następnie zamontować ten plik jako katalog i uzyskać do niego dostęp tak, jak wcześniej (z wyjątkiem tego, że obrazy są umieszczone na dysku). Zamontowany katalog jest tylko do odczytu.
na przykład
mksquashfs /path/to/data data.sqsh
mount data.sqsh /path/to/data_sqsh -t squashfs -o loop
Następnie możesz używać /path/to/data_sqshdokładnie w taki sam sposób, w jaki użyłeś /path/to/data. Będziesz musiał ponownie go zamontować po ponownym uruchomieniu komputera
Widzieć: https://tldp.org/HOWTO/SquashFS-HOWTO/creatingandusing.html