pytorch DataLoader extrêmement lent à la première époque
Lorsque je crée un PyTorch DataLoader et que je commence à itérer, j'obtiens une première époque extrêmement lente (x10 - x30 plus lente que toutes les époques suivantes). De plus, ce problème se produit uniquement avec l'ensemble de données de train de la reconnaissance de repère Google 2020 de Kaggle. Je ne peux pas reproduire cela sur des images synthétiques, j'ai également essayé de créer un dossier avec des images 500k de GLR2020, et tout a bien fonctionné. Trouvé quelques problèmes similaires dans le forum PyTorch sans aucune solution.
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')
Voici quelques exemples de vitesse d'exécution avec différents num_workersdans 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
Il semble que le problème ne soit pas avec DataLoader, mais avec l'ensemble de données. Lorsque je supprime et réinitialise l'objet DataLoader après la première itération "longue", tout fonctionne toujours bien. Lorsque je réinitialise l'ensemble de données, la première itération longue apparaît à nouveau. De plus, j'ai suivi mon utilisation du processeur via htoppendant ces époques avec une valeur num_workersde 32, et pendant la première époque, l'utilisation est vraiment faible; seuls 1-2 des 32 noyaux fonctionnent, pendant d'autres époques ~ tous les noyaux fonctionnent.
Réponses
Slavka,
Je n'ai pas téléchargé l'ensemble de données GLR2020 mais j'ai pu observer cet effet sur l'ensemble de données d'image que j'avais localement (80000 images jpg d'environ 400x400).
Pour trouver les raisons de la différence de performances, j'ai essayé ce qui suit:
- réduire l'augmentation à un simple redimensionnement
- tester juste la
ImgDataset.__getitem__()fonction ImgDataset.__getitem__()sans augmentation- il suffit de charger l'image jpg brute et de la transmettre à partir de l'ensemble de données sans même conversion numpy.
Il s'avère que la différence vient du moment de chargement de l'image. Python (ou le système d'exploitation lui-même) implémente une sorte de mise en cache qui est observée lors du chargement de l'image plusieurs fois dans le test suivant.
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
la même chose est observée lors de la lecture d'un fichier à une 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
Une façon de réduire la vitesse de chargement est de conserver les données sur un SSD local très rapide. Si la taille le permet, essayez de charger une partie de l'ensemble de données dans la RAM et d'écrire un chargeur de données personnalisé pour alimenter à partir de là ...
BTW Sur la base de mes conclusions, cet effet devrait être reproductible avec n'importe quel ensemble de données - voyez si vous avez utilisé différents lecteurs ou une mise en cache.
Il semble que le système d'exploitation met en cache l'accès IO à l'ensemble de données. Pour vérifier si c'est vraiment le problème, essayez d'exécuter sync; echo 3 > /proc/sys/vm/drop_caches(sur Ubuntu) après la première époque. Si la deuxième époque est également lente lorsque vous faites cela, c'est la mise en cache qui accélère tellement les lectures suivantes.
Si vous utilisez un disque dur, vous pouvez obtenir des améliorations de vitesse significatives pour votre première époque en co-localisant tous vos petits fichiers image sur le disque.
Vous pouvez utiliser SquashFS (il est pré-installé avec Ubuntu) pour compresser l'ensemble de votre ensemble de données en un seul fichier, puis monter ce fichier en tant que répertoire et y accéder comme vous l'étiez auparavant (sauf que maintenant les images sont co-localisées sur le disque). Le répertoire monté est en lecture seule.
par exemple
mksquashfs /path/to/data data.sqsh
mount data.sqsh /path/to/data_sqsh -t squashfs -o loop
Ensuite, vous pouvez utiliser /path/to/data_sqshexactement la même manière que vous avez utilisée /path/to/data. Vous devrez le remonter au redémarrage de votre ordinateur
Voir: https://tldp.org/HOWTO/SquashFS-HOWTO/creatingandusing.html