С няколко реда код се научете да предавате много големи набори от данни с минимално използване на диска и да ги обработвате в движение по време на обучение.

Независимо дали искате да обучите свой собствен модел от нулата или да адаптирате предварително обучен модел за вашия собствен случай на употреба, обикновено по-голямата част от инженерните усилия отиват в предварителната обработка на набора от данни за обучение.
За задачите на НЛП това обикновено включва почистване, трансформиране и токенизиране. За визуални задачи това може да включва преоразмеряване, увеличаване, нормализиране и т.н.

Двойката библиотеки на Hugging Face, озаглавена „Transformers“ и „Datasets“, е много популярна за машинно обучение с перцептивни данни (визия, език, аудио). Това отчасти се дължи на широката наличност на предварително обучени тежести на моделите в „Hugging Face Hub“ и на лесното пренасочване на тези модели. Трябва да напишете много малко редове код, за да изтеглите предварително обучен модел от хъба и да го обучите на персонализиран набор от данни.

Сега, на различни етапи от вашия работен процес, трябва да приложите трансформации към вашия набор от данни. Функцията map() на библиотеката с набори от данни може да приеме всяка произволна функция като аргумент. Въпреки това, режимът на стил на карта по подразбиране (стил на карта се отнася до това как наборите от данни се съхраняват и адресират, не е свързан с функцията .map()), който Datasets използва, се сблъсква с ограничения на паметта при прилагане на трансформации върху голям набор от данни. Ще обсъдим как обработката на набора от данни в режим на поточно предаване може да помогне за облекчаване на това. Документацията за това е малко разпръсната и информацията може да бъде трудна за пресяване.

В тази статия ще се опитаме да активираме стрийминг с минимални промени в нашия код. Следният кодов блок ще зареди набор от данни в режим на карта стил от.

from datasets import load_dataset
DATASETS_CACHE='/content/hf_data/datasets'

dataset = load_dataset("HuggingFaceM4/COCO",cache_dir=DATASETS_CACHE)

Аргументът „cache_dir“ променя папката на кеша за съхраняване на набора от данни в предпочитаната от вас папка. Някои предимства на режима в стил карта са-

  • Чудесно за експериментиране с по-малки количества данни.
  • Като цяло е по-отстраним. Резултатите от трансформациите се виждат веднага.
  • Позволява произволен достъп до всеки ред от данни. (напр. dataset[0]).

Въпреки това, когато се работи с големи набори от данни, този режим е изправен пред някои ограничения. А именно - проблеми с капацитета на паметта (RAM) и диска (HDD/SSD съхранение). Целият набор от данни трябва да се съхранява на вашия диск или ram. Сега, както можете да си представите, това създава два отделни проблема - RAM паметта има ограничена памет и I/O на диска е бавен. Например, ако имате 64 GB RAM във вашата система и 100 GB набор от данни, не можете да го заредите в паметта или да четете/записвате на диск по време на всяка употреба поради забавяне.

Библиотеката с набори от данни се справя успешно с първия проблем, като използва Apache Arrow като система за кеширане с карта на паметта за бързо търсене. По същество големият набор от данни вече може да се съхранява на диск, но да се търси достатъчно бързо, така че да не е проблем. Поздравления, сега можете да заредите своя набор от данни от 100 GB на система, която има 64 GB (или дори 32 GB) RAM. Преминавате напред към следващата стъпка от вашия конвейер - предварителна обработка на набора от данни. Което може да изглежда така-

from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")

def resize(example):
    example["pixel_values"] = example["image"].convert("RGB").resize((340,512)) 
    return example

def tokenize(example):
    example["labels"]=tokenizer(example['prompt'])['input_ids']
    return example

dataset_resized = dataset.map(resize)
dataset_tokenized= dataset_resized.map(tokenize)

Въпреки това умното използване на стрелката от библиотеката не е достатъчно, за да ви предпази от проблеми, ако наборът от данни е достатъчно голям. Под капака Arrow съхранява данните в парчета с еднакъв размер на диска, които се съхраняват като временни файлове в директорията /temp/ по подразбиране. Ако сумата от тези парчета надвишава наличното ви пространство във временната директория или директорията на HF кеша, тогава операцията по картографиране ще се провали с грешка „изчерпана памет“ или „изчерпана памет“. Може да звучи неправдоподобно, ако имате 100 GB свободно пространство. Но десетки гигабайта компресирани файлове с изображения могат да излязат извън контрол, когато се преобразуват във файлове със стрелки.

Също така изпълнението на кодовия блок по-горе може да отнеме часове. По този начин забавянето на влизането ни в цикъла за отстраняване на грешки и запазването на контролна точка на диск след всяка стъпка на трансформация ще се окаже много скъпо за съхранение, ако не и невъзможно поради ограниченията на пространството на твърдия диск. Зареденият набор от данни COCO е около 39 GB на диск. Ако запазим контролни точки на двете трансформации, това отива доста над 100 GB. Това все още може да е управляемо, но си представете, че беше много по-голям набор от данни. Освен това стъпките на предварителна обработка може да отнемат до 10 часа.

Поточно предаване към The Rescue

Сега ще разгледаме заобиколното решение. Huggingface нарича обекта на набор от данни с активиран стрийминг „Iterable Dataset“. За да обработвате в режим на пара, можете да подадете флага streaming=True, докато зареждате набора от данни за първи път.

dataset = load_dataset("HuggingFaceM4/COCO",streaming=True)

Като алтернатива можете също да конвертирате вече зареден набор от данни в стил карта, като извикате-

dataset= dataset.to_iterable_dataset()

За ускоряване можем да опитаме пакетен режим и паралелна обработка с някои модификации на кода по-горе. Вместо един ред, трябва да очакваме речник от списъци. Което може да изглежда така за размер на партида от 4-

{'pixel_values':[[1st],[2nd],[3rd],[4th]],
 'labels':[[1st],[2nd],[3rd],[4th]]}

Модифицираният код ще изглежда като-

from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")

def resize(example):
    pixel_values=[]
   
    for image in example["image"]:
      pixel_values.append(image.convert("RGB").resize((340,512)))
    example["pixel_values"]=pixel_values
    return example

def tokenize(example):
    labels=[]
    for sample_text in example['prompt']:
      labels.append(tokenizer(sample_text)['input_ids'])
    example["labels"]=labels
    return example

dataset_resized = dataset.map(resize, batched=True)
dataset_tokenized= dataset_resized.map(tokenize, batched=True)

Това е!

Сега, след като изпратите своя набор от данни към цикъла на обучение, едва тогава той ще започне да прилага тези трансформации към всяка партида данни за обучение, докато обучението продължава – спестявайки ви време и дисково пространство!

Много е важно да проверите надеждността на вашия тръбопровод за обработка, за да сте сигурни, че данните текат в правилния формат към тренировъчния цикъл. В идеалния случай проверете последното състояние на точките от данни преди тренировъчния цикъл и опитайте да извършите обратна трансформация към оригиналната форма. I.E - конвертиране на идентификатори на токени в изречения на естествен език или стойности на пиксели в изображение.

Можете бързо да направите това с този кодов блок-

num_examples_to_print = 5

# Use the .take() method to retrieve and print examples
for example in dataset.take(num_examples_to_print):
    print(inverse_transform(example)) # arbitrary inverse_transform function

За да обобщим предимствата на поточното предаване (Iterable Datasets)-

  • Зарежда данни прогресивно, докато обикаляте набора от данни.
  • Подходящ за много големи набори от данни (стотици GB или повече) поради мързеливото поведение и предимствата на скоростта.
  • По-ефективно за паметта, тъй като само малка част от примерите се зареждат наведнъж.
  • Подходящ за поточно предаване на данни от отдалечени източници или файлове.
  • Може да свързва множество стъпки на обработка и да ги прилага в движение по време на итерация.
  • Поддръжка за бързо приблизително разбъркване с помощта на буфер за разбъркване.

За да научите повече за това и да използвате актуализираната документация -

  1. https://huggingface.co/docs/datasets/about_mapstyle_vs_iterable
  2. https://huggingface.co/docs/datasets/v2.14.4/en/package_reference/main_classes#datasets.IterableDataset.map
  3. https://huggingface.co/docs/datasets/v1.9.0/dataset_streaming.html#:~:text=Можете%20да%20разрешите%20dataset%20streaming, да бъдете%20изтеглени%20преди%20използване%20го.