pytorch DataLoader estremamente lenta prima epoca

Aug 30 2020

Quando creo un PyTorch DataLoader e inizio a iterare, ottengo una prima epoca estremamente lenta (x10 - x30 più lenta di tutte le epoche successive). Inoltre, questo problema si verifica solo con il set di dati del treno dal riconoscimento del punto di riferimento di Google 2020 da Kaggle. Non posso riprodurlo su immagini sintetiche, inoltre, ho provato a creare una cartella con 500k immagini da GLR2020 e tutto ha funzionato bene. Ho trovato alcuni problemi simili nel forum di PyTorch senza alcuna soluzione.

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

Di seguito sono riportati alcuni esempi di velocità di esecuzione con differenti num_workersin 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

Sembra che il problema non sia con DataLoader, ma con il set di dati. Quando elimino e reinizializzo l'oggetto DataLoader dopo la prima iterazione "lunga", tutto funziona ancora bene. Quando reinizializzo il set di dati, viene nuovamente visualizzata la prima iterazione lunga. Inoltre, ho tracciato l'utilizzo della mia CPU tramite htopdurante queste epoche con num_workerssettato a 32, e durante la prima epoca, l'utilizzo è veramente basso; solo 1-2 core su 32 funzionano, in altre epoche ~ tutti i core funzionano.

Risposte

10 PoeDator Sep 04 2020 at 01:51

Slavka,

Non ho scaricato l'intero set di dati GLR2020 ma sono stato in grado di osservare questo effetto sul set di dati dell'immagine che avevo localmente (80000 immagini jpg di circa 400x400 dimensioni).

Per trovare i motivi della differenza di prestazioni ho provato quanto segue:

  1. riducendo l'aumento al solo ridimensionamento
  2. testare solo la ImgDataset.__getitem__()funzione
  3. ImgDataset.__getitem__() senza aumento
  4. basta caricare l'immagine jpg grezza e passarla dal set di dati senza nemmeno una conversione numpy.

Si scopre che la differenza deriva dal tempo di caricamento dell'immagine. Python (o il sistema operativo stesso) implementa una sorta di memorizzazione nella cache che viene osservata durante il caricamento dell'immagine più volte nel test seguente.

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

lo stesso si osserva quando si legge da file a variabile

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

Un modo per ridurre la velocità di caricamento è mantenere i dati su SSD locali molto veloci. Se le dimensioni lo consentono, prova a caricare parte del set di dati nella RAM e a scrivere un caricatore di dati personalizzato per alimentare da lì ...

BTW In base alle mie scoperte, questo effetto dovrebbe essere riproducibile con qualsiasi set di dati - controlla se hai utilizzato unità diverse o un po 'di cache.

2 Multihunter Sep 10 2020 at 12:26

Sembra che il sistema operativo stia memorizzando nella cache l'accesso IO al set di dati. Per verificare se questo è sicuramente il problema, prova a eseguire sync; echo 3 > /proc/sys/vm/drop_caches(su Ubuntu) dopo la prima epoca. Se la seconda epoca è ugualmente lenta quando si esegue questa operazione, è il caching che rende le letture successive molto più veloci.

Se stai usando un HDD, potresti ottenere miglioramenti significativi della velocità per la tua prima epoca co-localizzando tutti i tuoi piccoli file di immagine sul disco.

Puoi usare SquashFS (è preinstallato con Ubuntu) per comprimere l'intero set di dati in un singolo file, quindi montare quel file come una directory e accedervi proprio come eri prima (tranne che ora le immagini si trovano sul disco). La directory montata è di sola lettura.

per esempio

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

Quindi puoi usare /path/to/data_sqshesattamente nello stesso modo in cui hai usato /path/to/data. Dovrai reinstallarlo quando riavvii il computer

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