С помощью нескольких строк кода вы научитесь передавать в потоковом режиме очень большие наборы данных с минимальным использованием диска и обрабатывать их «на лету» во время обучения.

Хотите ли вы обучить свою собственную модель с нуля или адаптировать предварительно обученную модель для собственного варианта использования, обычно большая часть инженерных усилий уходит на предварительную обработку набора данных для обучения.
Для задач НЛП это обычно включает в себя очистку, преобразование и токенизацию. Для задач машинного зрения это может включать изменение размера, увеличение, нормализацию и т. д.

Пара библиотек Hugging Face под названием Трансформеры и Наборы данных очень популярны для машинного обучения с перцептивными данными (зрение, язык, аудио). Частично это связано с широкой доступностью предварительно обученных весов моделей в 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]).

Однако при работе с большими наборами данных этот режим сталкивается с некоторыми ограничениями. А именно - проблемы с емкостью памяти (ОЗУ) и диска (HDD/SSD). Весь набор данных должен храниться на вашем диске или в оперативной памяти. Как вы можете себе представить, это создает две отдельные проблемы: оперативная память имеет ограниченный объем памяти, а ввод-вывод с диска выполняется медленно. Например, если в вашей системе 64 ГБ ОЗУ и набор данных объемом 100 ГБ, вы не сможете загружать его в память или читать/записывать на диск во время каждого использования из-за задержки.

Библиотека набора данных успешно решает первую проблему, используя Apache Arrow в качестве системы кэширования с отображением в памяти для быстрого поиска. По сути, теперь большой набор данных можно хранить на диске, но просматривать его достаточно быстро, чтобы это не было проблемой. Поздравляем, теперь вы можете загрузить набор данных объемом 100 ГБ в систему с 64 ГБ (или даже 32 ГБ) ОЗУ. Вы переходите к следующему шагу вашего конвейера — предварительной обработке набора данных. Это может выглядеть так-

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 ГБ свободного места. Но десятки гигабайт сжатых файлов изображений могут выйти из-под контроля при преобразовании в файлы со стрелками.

Кроме того, выполнение приведенного выше блока кода может занять несколько часов. Таким образом, задержка входа в цикл отладки поезда и сохранение контрольной точки на диске после каждого шага преобразования окажется очень дорогостоящим хранилищем, если не невозможным из-за ограничений места на жестком диске. Загруженный набор данных COCO занимает на диске около 39 ГБ. Если мы сохраним контрольные точки двух преобразований, объем будет значительно выше 100 ГБ. С этим все еще можно справиться, но представьте, если бы это был гораздо больший набор данных. Кроме того, этапы предварительной обработки могут занять до 10 часов.

Стриминг в помощь

Теперь мы рассмотрим обходной путь. Huggingface называет объект набора данных с поддержкой потоковой передачи «Итерируемый набор данных». Для обработки в режиме обработки на пару вы можете передать флаг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

Подводя итог плюсам потоковой передачи (итеративные наборы данных) —

  • Загружает данные постепенно по мере обхода набора данных.
  • Подходит для очень больших наборов данных (сотни ГБ и более) из-за его ленивого поведения и преимуществ в скорости.
  • Более эффективное использование памяти, поскольку одновременно загружается только часть примеров.
  • Хорошо подходит для потоковой передачи данных из удаленных источников или файлов.
  • Можно объединить несколько этапов обработки и применять их «на лету» во время итерации.
  • Поддержка быстрого приблизительного перемешивания с использованием буфера перемешивания.

Чтобы узнать больше об этом и использовать обновленную документацию -

  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=You%20can%20enable%20dataset%20streaming,be%20downloaded%20before%20using%20it.