Datasets documentation
Процесс
Процесс
🤗 Datasets предоставляет множество инструментов для изменения структуры и содержимого набора данных. Эти инструменты важны для наведения порядка в наборе данных, создания дополнительных столбцов, преобразования между признаками и форматами, а также для многих других задач.
В этом руководстве показано, как:
- Переставьте строки и разделите набор данных.
- Переименование и удаление столбцов, а также другие общие операции со столбцами.
- Примените функции обработки к каждому примеру в наборе данных.
- Соедините наборы данных.
- Применить пользовательское преобразование форматирования.
- Сохранение и экспорт обработанных наборов данных.
Для получения подробной информации о обработке других модальностей данных ознакомьтесь с руководством по обработке аудиоданных, руководством по обработке изображений или руководством по обработке текстовых данных.
Примеры в этом руководстве используют набор данных MRPC, но вы можете загрузить любой другой набор данных по вашему выбору и следовать инструкциям!
>>> from datasets import load_dataset
>>> dataset = load_dataset("nyu-mll/glue", "mrpc", split="train")Все методы обработки в этом руководстве возвращают новый объект Dataset. Изменения не выполняются на месте. Будьте осторожны, чтобы не перезаписать предыдущий набор данных!
Сортировка, перемешивание, выбор, разбиение и шардирование
Существует несколько функций для изменения структуры набора данных. Эти функции полезны для выбора только нужных строк, создания обучающей и тестовой выборок, а также для разбиения очень больших наборов данных на более мелкие фрагменты.
Сортировка
Используйте sort() для сортировки значений столбца по их числовым значениям. Предоставленный столбец должен быть совместим с NumPy.
>>> dataset["label"][:10]
[1, 0, 1, 0, 1, 1, 0, 1, 0, 0]
>>> sorted_dataset = dataset.sort("label")
>>> sorted_dataset["label"][:10]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
>>> sorted_dataset["label"][-10:]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]Под капотом это создаёт список индексов, отсортированный по значениям столбца. Затем это отображение индексов используется для доступа к нужным строкам в базовой таблице Arrow.
Перемешивание
Функция shuffle() случайно переставляет значения в столбце. Вы можете указать параметр generator в этой функции, чтобы использовать другой numpy.random.Generator, если вам требуется больший контроль над алгоритмом, используемым для перемешивания набора данных.
>>> shuffled_dataset = sorted_dataset.shuffle(seed=42)
>>> shuffled_dataset["label"][:10]
[1, 1, 1, 0, 1, 1, 1, 1, 1, 0]Shuffling берёт список индексов [0:len(my_dataset)] и перемешивает его, чтобы создать отображение индексов.
Однако как только ваш Dataset получает отображение индексов, скорость может снизиться в 10 раз.
Это происходит из-за дополнительного шага получения индекса строки для чтения с использованием отображения индексов, и, что самое главное, вы больше не читаете непрерывные блоки данных.
Чтобы восстановить скорость, вам нужно будет снова переписать весь набор данных на диск с помощью Dataset.flatten_indices(), что удаляет отображение индексов.
В качестве альтернативы вы можете перейти на IterableDataset и использовать его быстрое приближённое перемешивание IterableDataset.shuffle():
>>> iterable_dataset = dataset.to_iterable_dataset(num_shards=128)
>>> shuffled_iterable_dataset = iterable_dataset.shuffle(seed=42, buffer_size=1000)Выбор и фильтрация
Существует два способа фильтрации строк в наборе данных: select() и filter().
- select() возвращает строки по списку индексов:
>>> small_dataset = dataset.select([0, 10, 20, 30, 40, 50])
>>> len(small_dataset)
6- filter() возвращает строки, соответствующие заданному условию:
>>> start_with_ar = dataset.filter(lambda example: example["sentence1"].startswith("Ar"))
>>> len(start_with_ar)
6
>>> start_with_ar["sentence1"]
['Around 0335 GMT , Tab shares were up 19 cents , or 4.4 % , at A {@html ""} 4.57 .',
'Arison said Mann may have been one of the pioneers of the world music movement and he had a deep love of Brazilian music .',
'Arts helped coach the youth on an eighth-grade football team at Lombardi Middle School in Green Bay .',
'Around 9 : 00 a.m. EDT ( 1300 GMT ) , the euro was at $ 1.1566 against the dollar , up 0.07 percent on the day .',
"Arguing that the case was an isolated example , Canada has threatened a trade backlash if Tokyo 's ban is not justified on scientific grounds .",
'Artists are worried the plan would harm those who need help most - performers who have a difficult time lining up shows .'
]filter() также может фильтровать по индексам, если установить with_indices=True:
>>> even_dataset = dataset.filter(lambda example, idx: idx % 2 == 0, with_indices=True)
>>> len(even_dataset)
1834
>>> len(dataset) / 2
1834.0Если список индексов для сохранения не является непрерывным, эти методы также создают отображение индексов под капотом.
Разделение
Функция train_test_split() создает обучающую и тестовую выборки, если их еще нет в вашем наборе данных. Это позволяет настраивать относительные пропорции или абсолютное количество образцов в каждой выборке. В примере ниже используйте параметр test_size, чтобы создать тестовую выборку, составляющую 10% от исходного набора данных:
>>> dataset.train_test_split(test_size=0.1)
{'train': Dataset(schema: {'sentence1': 'string', 'sentence2': 'string', 'label': 'int64', 'idx': 'int32'}, num_rows: 3301),
'test': Dataset(schema: {'sentence1': 'string', 'sentence2': 'string', 'label': 'int64', 'idx': 'int32'}, num_rows: 367)}
>>> 0.1 * len(dataset)
366.8Разметки перемешиваются по умолчанию, но вы можете установить shuffle=False, чтобы отключить перемешивание.
Шард
🤗 Datasets поддерживает шардирование для разделения очень большого набора данных на заранее определённое количество фрагментов. Укажите параметр num_shards в shard(), чтобы определить, на сколько шардов разбить набор данных. Также необходимо указать с помощью параметра index, какой именно шард нужно вернуть.
Например, набор данных stanfordnlp/imdb содержит 25000 примеров:
>>> from datasets import load_dataset
>>> dataset = load_dataset("stanfordnlp/imdb", split="train")
>>> print(dataset)
Dataset({
features: ['text', 'label'],
num_rows: 25000
})После разбиения набора данных на четыре фрагмента в первом фрагменте будет только 6250 примеров:
>>> dataset.shard(num_shards=4, index=0)
Dataset({
features: ['text', 'label'],
num_rows: 6250
})
>>> print(25000/4)
6250.0Переименование, удаление, приведение типов и разворачивание
Следующие функции позволяют изменять столбцы набора данных. Эти функции полезны для переименования или удаления столбцов, замены столбцов на новый набор признаков, а также для разворачивания вложенных структур столбцов.
Переименовать
Используйте rename_column(), когда нужно переименовать столбец в наборе данных. Признаки, связанные с исходным столбцом, фактически переносятся под новым именем столбца, а не просто заменяют исходный столбец на месте.
Передайте в rename_column() имя исходной колонки и новое имя колонки:
>>> dataset
Dataset({
features: ['sentence1', 'sentence2', 'label', 'idx'],
num_rows: 3668
})
>>> dataset = dataset.rename_column("sentence1", "sentenceA")
>>> dataset = dataset.rename_column("sentence2", "sentenceB")
>>> dataset
Dataset({
features: ['sentenceA', 'sentenceB', 'label', 'idx'],
num_rows: 3668
})Удалить
Если нужно удалить одну или несколько колонок, передайте имя удаляемой колонки в функцию remove_columns(). Чтобы удалить несколько колонок, передайте список имён колонок:
>>> dataset = dataset.remove_columns("label")
>>> dataset
Dataset({
features: ['sentence1', 'sentence2', 'idx'],
num_rows: 3668
})
>>> dataset = dataset.remove_columns(["sentence1", "sentence2"])
>>> dataset
Dataset({
features: ['idx'],
num_rows: 3668
})Напротив, select_columns() выбирает одну или несколько столбцов для сохранения и удаляет остальные. Эта функция принимает одно имя столбца или список имён:
>>> dataset
Dataset({
features: ['sentence1', 'sentence2', 'label', 'idx'],
num_rows: 3668
})
>>> dataset = dataset.select_columns(['sentence1', 'sentence2', 'idx'])
>>> dataset
Dataset({
features: ['sentence1', 'sentence2', 'idx'],
num_rows: 3668
})
>>> dataset = dataset.select_columns('idx')
>>> dataset
Dataset({
features: ['idx'],
num_rows: 3668
})Приведение
Функция cast() изменяет тип признаков одной или нескольких колонок. В качестве аргумента эта функция принимает ваши новые Features. Пример ниже показывает, как изменить признаки ClassLabel и Value:
>>> dataset.features
{'sentence1': Value('string'),
'sentence2': Value('string'),
'label': ClassLabel(names=['not_equivalent', 'equivalent']),
'idx': Value('int32')}
>>> from datasets import ClassLabel, Value
>>> new_features = dataset.features.copy()
>>> new_features["label"] = ClassLabel(names=["negative", "positive"])
>>> new_features["idx"] = Value("int64")
>>> dataset = dataset.cast(new_features)
>>> dataset.features
{'sentence1': Value('string'),
'sentence2': Value('string'),
'label': ClassLabel(names=['negative', 'positive']),
'idx': Value('int64')}Преобразование типов работает только в том случае, если исходный и новый типы признаков совместимы. Например, столбец с типом признака
Value("int32")можно преобразовать вValue("bool"), если исходный столбец содержит только единицы и нули.
Используйте функцию cast_column(), чтобы изменить тип признака в одном столбце. Передайте имя столбца и его новый тип признака в качестве аргументов:
>>> dataset.features
{'audio': Audio(sampling_rate=44100, mono=True)}
>>> dataset = dataset.cast_column("audio", Audio(sampling_rate=16000))
>>> dataset.features
{'audio': Audio(sampling_rate=16000, mono=True)}Свернуть
Иногда столбец может представлять собой вложенную структуру нескольких типов. Обратите внимание на следующую вложенную структуру из набора данных SQuAD:
>>> from datasets import load_dataset
>>> dataset = load_dataset("rajpurkar/squad", split="train")
>>> dataset.features
{'id': Value('string'),
'title': Value('string'),
'context': Value('string'),
'question': Value('string'),
'answers': {'text': List(Value('string')),
'answer_start': List(Value('int32'))}}Поле answers содержит два подполя: text и answer_start. Используйте функцию flatten(), чтобы извлечь подполя в отдельные столбцы:
>>> flat_dataset = dataset.flatten()
>>> flat_dataset
Dataset({
features: ['id', 'title', 'context', 'question', 'answers.text', 'answers.answer_start'],
num_rows: 87599
})Обратите внимание, что подполя теперь представляют собой собственные независимые столбцы: answers.text и answers.answer_start.
Карта
Некоторые из наиболее мощных применений 🤗 Datasets связаны с использованием функции map(). Основная цель map() — ускорить обработку данных. Она позволяет применять функцию обработки к каждому примеру в наборе данных, независимо или партиями. Эта функция даже может создавать новые строки и столбцы.
В следующем примере добавьте префикс 'My sentence: ' к каждому значению sentence1 в наборе данных.
Начните с создания функции, которая добавляет 'My sentence: ' в начало каждого предложения. Функция должна принимать и возвращать dict:
>>> def add_prefix(example):
... example["sentence1"] = 'My sentence: ' + example["sentence1"]
... return exampleТеперь используйте map(), чтобы применить функцию add_prefix ко всему набору данных:
>>> updated_dataset = small_dataset.map(add_prefix)
>>> updated_dataset["sentence1"][:5]
['My sentence: Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .',
"My sentence: Yucaipa owned Dominick 's before selling the chain to Safeway in 1998 for $ 2.5 billion .",
'My sentence: They had published an advertisement on the Internet on June 10 , offering the cargo for sale , he added .',
'My sentence: Around 0335 GMT , Tab shares were up 19 cents , or 4.4 % , at A {@html ""} 4.57 .',
]Рассмотрим ещё один пример, но на этот раз вы удалите столбец с помощью map(). При удалении столбца он удаляется только после того, как пример был передан в отображаемую функцию. Это позволяет отображаемой функции использовать содержимое столбцов до их удаления.
Укажите столбец для удаления с помощью параметра remove_columns в map():
>>> updated_dataset = dataset.map(lambda example: {"new_sentence": example["sentence1"]}, remove_columns=["sentence1"])
>>> updated_dataset.column_names
['sentence2', 'label', 'idx', 'new_sentence']🤗 В Datasets также есть функция remove_columns(), которая работает быстрее, поскольку не копирует данные оставшихся столбцов.
Вы также можете использовать map() с индексами, если установите with_indices=True. В примере ниже индекс добавляется в начало каждого предложения:
>>> updated_dataset = dataset.map(lambda example, idx: {"sentence2": f"{idx}: " + example["sentence2"]}, with_indices=True)
>>> updated_dataset["sentence2"][:5]
['0: Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .',
"1: Yucaipa bought Dominick 's in 1995 for {@html ""} 1.8 billion in 1998 .",
"2: On June 10 , the ship 's owners had published an advertisement on the Internet , offering the explosives for sale .",
'3: Tab shares jumped 20 cents , or 4.6 % , to set a record closing high at A $ 4.57 .',
'4: PG & E Corp. shares jumped {@html ""} 21.03 on the New York Stock Exchange on Friday .'
]Многопроцессность
Многопоточность значительно ускоряет обработку за счёт параллельного выполнения процессов на CPU. Установите параметр num_proc в map(), чтобы задать количество процессов:
>>> updated_dataset = dataset.map(lambda example, idx: {"sentence2": f"{idx}: " + example["sentence2"]}, with_indices=True, num_proc=4)Метод map() также работает с рангом процесса, если установить with_rank=True. Это аналогично параметру with_indices. Параметр with_rank в отображаемой функции располагается после параметра index, если тот уже присутствует.
>>> import torch
>>> from multiprocess import set_start_method
>>> from transformers import AutoTokenizer, AutoModelForCausalLM
>>> from datasets import load_dataset
>>>
>>> # Get an example dataset
>>> dataset = load_dataset("fka/awesome-chatgpt-prompts", split="train")
>>>
>>> # Get an example model and its tokenizer
>>> model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen1.5-0.5B-Chat").eval()
>>> tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen1.5-0.5B-Chat")
>>>
>>> def gpu_computation(batch, rank):
... # Move the model on the right GPU if it's not there already
... device = f"cuda:{(rank or 0) % torch.cuda.device_count()}"
... model.to(device)
...
... # Your big GPU call goes here, for example:
... chats = [[
... {"role": "system", "content": "You are a helpful assistant."},
... {"role": "user", "content": prompt}
... ] for prompt in batch["prompt"]]
... texts = [tokenizer.apply_chat_template(
... chat,
... tokenize=False,
... add_generation_prompt=True
... ) for chat in chats]
... model_inputs = tokenizer(texts, padding=True, return_tensors="pt").to(device)
... with torch.no_grad():
... outputs = model.generate(**model_inputs, max_new_tokens=512)
... batch["output"] = tokenizer.batch_decode(outputs, skip_special_tokens=True)
... return batch
>>>
>>> if __name__ == "__main__":
... set_start_method("spawn")
... updated_dataset = dataset.map(
... gpu_computation,
... batched=True,
... batch_size=16,
... with_rank=True,
... num_proc=torch.cuda.device_count(), # one process per GPU
... )Основное применение rank — параллелизация вычислений на нескольких GPU. Для этого необходимо установить multiprocess.set_start_method("spawn"). В противном случае вы получите следующую ошибку CUDA:
RuntimeError: Cannot re-initialize CUDA in forked subprocess. To use CUDA with multiprocessing, you must use the 'spawn' start method.Пакетная обработка
Функция map() поддерживает работу с батчами примеров. Чтобы оперировать батчами, установите batched=True. Размер батча по умолчанию составляет 1000, но его можно изменить с помощью параметра batch_size. Обработка батчами позволяет реализовать интересные приложения, такие как разбиение длинных предложений на более короткие фрагменты и аугментация данных.
Разделение длинных примеров
Если примеры слишком длинные, их можно разбить на несколько более мелких фрагментов. Начните с создания функции, которая:
Разбивает поле
sentence1на фрагменты по 50 символов.Собирает все фрагменты воедино, чтобы создать новый набор данных.
>>> def chunk_examples(examples):
... chunks = []
... for sentence in examples["sentence1"]:
... chunks += [sentence[i:i + 50] for i in range(0, len(sentence), 50)]
... return {"chunks": chunks}Примените функцию с помощью map():
>>> chunked_dataset = dataset.map(chunk_examples, batched=True, remove_columns=dataset.column_names)
>>> chunked_dataset[:10]
{'chunks': ['Amrozi accused his brother , whom he called " the ',
'witness " , of deliberately distorting his evidenc',
'e .',
"Yucaipa owned Dominick 's before selling the chain",
' to Safeway in 1998 for $ 2.5 billion .',
'They had published an advertisement on the Interne',
't on June 10 , offering the cargo for sale , he ad',
'ded .',
'Around 0335 GMT , Tab shares were up 19 cents , or',
' 4.4 % , at A $ 4.56 , having earlier set a record']}Обратите внимание, что теперь предложения разбиты на более короткие фрагменты, а в наборе данных стало больше строк.
>>> dataset
Dataset({
features: ['sentence1', 'sentence2', 'label', 'idx'],
num_rows: 3668
})
>>> chunked_dataset
Dataset({
features: ['chunks'],
num_rows: 10470
})Аугментация данных
Функция map() также может использоваться для аугментации данных. В следующем примере генерируются дополнительные слова для замаскированного токена в предложении.
Загрузите и используйте модель RoBERTa в FillMaskPipeline библиотеки 🤗 Transformers:
>>> from random import randint
>>> from transformers import pipeline
>>> fillmask = pipeline("fill-mask", model="roberta-base")
>>> mask_token = fillmask.tokenizer.mask_token
>>> smaller_dataset = dataset.filter(lambda e, i: i<100, with_indices=True)Создайте функцию для случайного выбора слова, которое нужно замаскировать в предложении. Функция также должна возвращать исходное предложение и две лучшие замены, сгенерированные RoBERTA.
>>> def augment_data(examples):
... outputs = []
... for sentence in examples["sentence1"]:
... words = sentence.split(' ')
... K = randint(1, len(words)-1)
... masked_sentence = " ".join(words[:K] + [mask_token] + words[K+1:])
... predictions = fillmask(masked_sentence)
... augmented_sequences = [predictions[i]["sequence"] for i in range(3)]
... outputs += [sentence] + augmented_sequences
...
... return {"data": outputs}Используйте map(), чтобы применить функцию ко всему набору данных:
>>> augmented_dataset = smaller_dataset.map(augment_data, batched=True, remove_columns=dataset.column_names, batch_size=8)
>>> augmented_dataset[:9]["data"]
['Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .',
'Amrozi accused his brother, whom he called " the witness ", of deliberately withholding his evidence.',
'Amrozi accused his brother, whom he called " the witness ", of deliberately suppressing his evidence.',
'Amrozi accused his brother, whom he called " the witness ", of deliberately destroying his evidence.',
"Yucaipa owned Dominick 's before selling the chain to Safeway in 1998 for $ 2.5 billion .",
'Yucaipa owned Dominick Stores before selling the chain to Safeway in 1998 for $ 2.5 billion.',
"Yucaipa owned Dominick's before selling the chain to Safeway in 1998 for $ 2.5 billion.",
'Yucaipa owned Dominick Pizza before selling the chain to Safeway in 1998 for $ 2.5 billion.'
]Для каждого исходного предложения RoBERTa дополняла случайное слово тремя альтернативами. Исходное слово distorting дополняется словами withholding, suppressing и destroying.
Асинхронная обработка
Асинхронные функции полезны для параллельного вызова конечных точек API, например, для загрузки контента, такого как изображения, или для обращения к конечной точке модели.
Вы можете определить асинхронную функцию с помощью ключевых слов async и await. Ниже приведён пример функции для вызова чат-модели из Hugging Face:
>>> import aiohttp
>>> import asyncio
>>> from huggingface_hub import get_token
>>> sem = asyncio.Semaphore(20) # max number of simultaneous queries
>>> async def query_model(model, prompt):
... api_url = f"https://api-inference.huggingface.co/models/{model}/v1/chat/completions"
... headers = {"Authorization": f"Bearer {get_token()}", "Content-Type": "application/json"}
... json = {"messages": [{"role": "user", "content": prompt}], "max_tokens": 20, "seed": 42}
... async with sem, aiohttp.ClientSession() as session, session.post(api_url, headers=headers, json=json) as response:
... output = await response.json()
... return {"Output": output["choices"][0]["message"]["content"]}Асинхронные функции выполняются параллельно, что значительно ускоряет процесс. Тот же код займёт гораздо больше времени при последовательном выполнении, так как он простаивает, ожидая ответа модели. Как правило, рекомендуется использовать async / await, если функция должна ждать ответа от API, например, или если она загружает данные, на что может уйти некоторое время.
Обратите внимание на наличие Semaphore: он задает максимальное количество запросов, которые могут выполняться параллельно. Рекомендуется использовать Semaphore при вызове API, чтобы избежать ошибок, связанных с лимитами частоты запросов.
Используем его для вызова модели microsoft/Phi-3-mini-4k-instruct и попросим её вернуть основную тему каждой математической задачи из набора данных Maxwell-Jia/AIME_2024:
>>> from datasets import load_dataset
>>> ds = load_dataset("Maxwell-Jia/AIME_2024", split="train")
>>> model = "microsoft/Phi-3-mini-4k-instruct"
>>> prompt = 'What is this text mainly about ? Here is the text:\n\n```\n{Problem}\n```\n\nReply using one or two words max, e.g. "The main topic is Linear Algebra".'
>>> async def get_topic(example):
... return await query_model(model, prompt.format(Problem=example['Problem']))
>>> ds = ds.map(get_topic)
>>> ds[0]
{'ID': '2024-II-4',
'Problem': 'Let {@html ""} and {@html ""} be positive real numbers that...',
'Solution': 'Denote $\\log_2(x) = a$, $\\log_2(y) = b$, and...,
'Answer': 33,
'Output': 'The main topic is Logarithms.'}Здесь Dataset.map() выполняет множество функций get_topic асинхронно, поэтому не приходится ждать каждый ответ модели, что заняло бы много времени при последовательном выполнении.
По умолчанию Dataset.map() запускает до одной тысячи функций map параллельно, поэтому не забудьте ограничить максимальное количество параллельных вызовов API с помощью Semaphore, иначе модель может возвращать ошибки rate limit или перегружаться. Для продвинутых сценариев использования вы можете изменить максимальное количество параллельных запросов в datasets.config.
Обработка нескольких разбиений
Многие наборы данных содержат разбиения, которые можно обрабатывать одновременно с помощью DatasetDict.map(). Например, чтобы токенизировать поле sentence1 в разбиениях train и test, выполните:
>>> from datasets import load_dataset
# load all the splits
>>> dataset = load_dataset('nyu-mll/glue', 'mrpc')
>>> encoded_dataset = dataset.map(lambda examples: tokenizer(examples["sentence1"]), batched=True)
>>> encoded_dataset["train"][0]
{'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .',
'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .',
'label': 1,
'idx': 0,
'input_ids': [ 101, 7277, 2180, 5303, 4806, 1117, 1711, 117, 2292, 1119, 1270, 107, 1103, 7737, 107, 117, 1104, 9938, 4267, 12223, 21811, 1117, 2554, 119, 102],
'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
}Использование в распределённых системах
При использовании map() в распределённой среде также следует применять torch.distributed.barrier. Это гарантирует, что отображение выполняет основной процесс, а остальные процессы загружают результаты, что позволяет избежать дублирования работы.
В следующем примере показано, как можно использовать torch.distributed.barrier для синхронизации процессов:
>>> from datasets import Dataset
>>> import torch.distributed
>>> dataset1 = Dataset.from_dict({"a": [0, 1, 2]})
>>> if training_args.local_rank > 0:
... print("Waiting for main process to perform the mapping")
... torch.distributed.barrier()
>>> dataset2 = dataset1.map(lambda x: {"a": x["a"] + 1})
>>> if training_args.local_rank == 0:
... print("Loading results from main process")
... torch.distributed.barrier()Пакет
Метод batch() позволяет группировать образцы из набора данных в батчи. Это особенно полезно, когда нужно создавать батчи данных для обучения или оценки, особенно при работе с моделями глубокого обучения.
Вот пример использования метода batch():
>>> from datasets import load_dataset
>>> dataset = load_dataset("cornell-movie-review-data/rotten_tomatoes", split="train")
>>> batched_dataset = dataset.batch(batch_size=4)
>>> batched_dataset[0]
{'text': ['the rock is destined to be the 21st century\'s new " conan " and that he\'s going to make a splash even greater than arnold schwarzenegger , jean-claud van damme or steven segal .',
'the gorgeously elaborate continuation of " the lord of the rings " trilogy is so huge that a column of words cannot adequately describe co-writer/director peter jackson\'s expanded vision of j . r . r . tolkien\'s middle-earth .',
'effective but too-tepid biopic',
'if you sometimes like to go to the movies to have fun , wasabi is a good place to start .'],
'label': [1, 1, 1, 1]}Метод batch() принимает следующие параметры:
batch_size(int): количество образцов в каждой партии.drop_last_batch(bool, по умолчаниюFalse): определяет, следует ли отбрасывать последний неполный батч, если размер набора данных не делится на размер батча.num_proc(int, необязательный параметр, по умолчаниюNone): количество процессов для использования при многопроцессной обработке. Если значение равно None, многопроцессная обработка не используется. Это может значительно ускорить формирование пакетов для больших наборов данных.
Обратите внимание, что Dataset.batch() возвращает новый Dataset, в котором каждый элемент представляет собой батч из нескольких образцов исходного набора данных. Если вы хотите обрабатывать данные батчами, следует использовать батчированный map() напрямую: он применяет функцию к батчам, но возвращаемый набор данных не является батчированным.
Соединить
Раздельные наборы данных можно объединить, если у них совпадают типы столбцов. Объедините наборы данных с помощью concatenate_datasets():
>>> from datasets import concatenate_datasets, load_dataset
>>> stories = load_dataset("ajibawa-2023/General-Stories-Collection", split="train")
>>> stories = stories.select_columns(["text"]) # only keep the 'text' column
>>> wiki = load_dataset("wikimedia/wikipedia", "20231101.en", split="train")
>>> wiki = wiki.select_columns(["text"]) # only keep the 'text' column
>>> assert stories.features.type == wiki.features.type
>>> bert_dataset = concatenate_datasets([stories, wiki])Также можно объединить два набора данных по горизонтали, установив axis=1, при условии, что у наборов одинаковое количество строк:
>>> from datasets import Dataset
>>> stories_ids = Dataset.from_dict({"ids": list(range(len(stories)))})
>>> stories_with_ids = concatenate_datasets([stories, stories_ids], axis=1)Чередование
Вы также можете объединять несколько наборов данных, чередуя примеры из каждого из них, чтобы создать новый набор данных. Этот процесс называется interleaving и выполняется с помощью функции interleave_datasets(). Обе функции — interleave_datasets() и concatenate_datasets() — работают с обычными объектами Dataset и IterableDataset. См. руководство по Stream, чтобы узнать, как чередовать объекты IterableDataset.
Вы можете определить вероятности выборки для каждого из исходных наборов данных, чтобы указать, как чередовать наборы данных. В этом случае новый набор данных формируется путём последовательного извлечения примеров из случайного набора данных, пока один из наборов не исчерпает все свои примеры.
>>> from datasets import Dataset, interleave_datasets
>>> seed = 42
>>> probabilities = [0.3, 0.5, 0.2]
>>> d1 = Dataset.from_dict({"a": [0, 1, 2]})
>>> d2 = Dataset.from_dict({"a": [10, 11, 12, 13]})
>>> d3 = Dataset.from_dict({"a": [20, 21, 22]})
>>> dataset = interleave_datasets([d1, d2, d3], probabilities=probabilities, seed=seed)
>>> dataset["a"]
[10, 11, 20, 12, 0, 21, 13]Вы также можете указать stopping_strategy. Стратегия по умолчанию, first_exhausted, является стратегией подвыборки, то есть построение набора данных останавливается, как только один из наборов исчерпывает свои примеры.
Вы можете указать stopping_strategy=all_exhausted, чтобы применить стратегию оверсэмплинга. В этом случае построение набора данных останавливается, как только каждый пример из каждого набора был добавлен хотя бы один раз. На практике это означает, что если набор данных исчерпан, он возвращается к началу этого набора, пока не будет достигнут критерий остановки.
Обратите внимание, что если вероятности выборки не указаны, новый набор данных будет содержать max_length_datasets*nb_dataset samples.
Также существует stopping_strategy=all_exhausted_without_replacement, чтобы гарантировать, что каждый пример будет просмотрен ровно один раз.
>>> d1 = Dataset.from_dict({"a": [0, 1, 2]})
>>> d2 = Dataset.from_dict({"a": [10, 11, 12, 13]})
>>> d3 = Dataset.from_dict({"a": [20, 21, 22]})
>>> dataset = interleave_datasets([d1, d2, d3], stopping_strategy="all_exhausted")
>>> dataset["a"]
[0, 10, 20, 1, 11, 21, 2, 12, 22, 0, 13, 20]Формат
Функция with_format() изменяет формат столбца, чтобы сделать его совместимым с некоторыми распространёнными форматами данных. Укажите желаемый результат в параметре type. Также можно выбрать столбцы, которые нужно отформатировать, с помощью columns=. Форматирование применяется на лету.
Например, создайте тензоры PyTorch, установив type="torch":
>>> dataset = dataset.with_format(type="torch")Функция set_format() также изменяет формат столбца, но делает это на месте:
>>> dataset.set_format(type="torch")Если вам нужно вернуть набор данных к исходному формату, установите формат в None (или используйте reset_format()):
>>> dataset.format
{'type': 'torch', 'format_kwargs': {}, 'columns': [...], 'output_all_columns': False}
>>> dataset = dataset.with_format(None)
>>> dataset.format
{'type': None, 'format_kwargs': {}, 'columns': [...], 'output_all_columns': False}Форматы тензоров
Поддерживается несколько форматов тензоров или массивов. Как правило, рекомендуется использовать эти форматы вместо ручного преобразования выходов набора данных в тензоры или массивы, чтобы избежать ненужного копирования данных и ускорить загрузку.
Вот список поддерживаемых форматов тензоров или массивов:
- NumPy: имя формата — «numpy», подробная информация в разделе Использование Datasets с NumPy
- PyTorch: имя формата — “torch”, для получения дополнительной информации см. Использование наборов данных с PyTorch
- TensorFlow: имя формата — “tensorflow”, подробная информация в разделе Использование наборов данных с TensorFlow
- JAX: имя формата — «jax», подробная информация в разделе Использование Datasets с JAX
См. руководство Использование Datasets с TensorFlow для получения подробной информации о том, как эффективно создать набор данных TensorFlow.
Когда набор данных имеет формат тензора или массива, все данные представлены в виде тензоров или массивов (за исключением неподдерживаемых типов, например, строк для PyTorch):
>>> ds = Dataset.from_dict({"text": ["foo", "bar"], "tokens": [[0, 1, 2], [3, 4, 5]]})
>>> ds = ds.with_format("torch")
>>> ds[0]
{'text': 'foo', 'tokens': tensor([0, 1, 2])}
>>> ds[:2]
{'text': ['foo', 'bar'],
'tokens': tensor([[0, 1, 2],
[3, 4, 5]])}Табличные форматы
Вы можете использовать формат dataframes или таблиц для оптимизации загрузки и обработки данных, поскольку они, как правило, обеспечивают операции без копирования и трансформации, написанные на низкоуровневых языках.
Вот список поддерживаемых форматов dataframes или таблиц:
- Pandas: имя формата — «pandas», подробности см. в Использование наборов данных с Pandas
- Polars: имя формата — «polars», подробная информация в разделе Использование Datasets с Polars
- PyArrow: имя формата — «arrow», подробнее см. Использование Datasets с PyArrow
Когда набор данных имеет формат dataframe или таблицы, каждая строка набора данных или пачки строк форматируются как dataframe или таблица, а столбцы набора данных — как series или массив:
>>> ds = Dataset.from_dict({"text": ["foo", "bar"], "label": [0, 1]})
>>> ds = ds.with_format("pandas")
>>> ds[:2]
text label
0 foo 0
1 bar 1Эти форматы позволяют быстрее работать с данными, избегая их копирования, а также обеспечивают более быструю обработку данных в map() или filter():
>>> ds = ds.map(lambda df: df.assign(upper_text=df.text.str.upper()), batched=True)
>>> ds[:2]
text label upper_text
0 foo 0 FOO
1 bar 1 BARПреобразование пользовательского формата
Функция with_transform() применяет пользовательское преобразование форматирования на лету. Эта функция заменяет любой ранее указанный формат. Например, с помощью этой функции можно токенизировать и дополнять токены на лету. Токенизация применяется только при обращении к примерам:
>>> from transformers import AutoTokenizer
>>> tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
>>> def encode(batch):
... return tokenizer(batch["sentence1"], batch["sentence2"], padding="longest", truncation=True, max_length=512, return_tensors="pt")
>>> dataset = dataset.with_transform(encode)
>>> dataset.format
{'type': 'custom', 'format_kwargs': {'transform': <function __main__.encode(batch)>}, 'columns': ['idx', 'label', 'sentence1', 'sentence2'], 'output_all_columns': False}Также существует set_transform(), который выполняет то же самое, но изменяет данные на месте.
Вы также можете использовать функцию with_transform() для пользовательского декодирования в Features.
Пример ниже использует пакет pydub в качестве альтернативы декодированию с помощью torchcodec:
>>> import numpy as np
>>> from pydub import AudioSegment
>>> audio_dataset_amr = Dataset.from_dict({"audio": ["audio_samples/audio.amr"]})
>>> def decode_audio_with_pydub(batch, sampling_rate=16_000):
... def pydub_decode_file(audio_path):
... sound = AudioSegment.from_file(audio_path)
... if sound.frame_rate != sampling_rate:
... sound = sound.set_frame_rate(sampling_rate)
... channel_sounds = sound.split_to_mono()
... samples = [s.get_array_of_samples() for s in channel_sounds]
... fp_arr = np.array(samples).T.astype(np.float32)
... fp_arr /= np.iinfo(samples[0].typecode).max
... return fp_arr
...
... batch["audio"] = [pydub_decode_file(audio_path) for audio_path in batch["audio"]]
... return batch
>>> audio_dataset_amr.set_transform(decode_audio_with_pydub)Сохранить
Когда ваш набор данных будет готов, вы можете сохранить его в формате Parquet как Hugging Face Dataset и использовать его позже с помощью load_dataset().
Сохраните набор данных, указав имя репозитория набора данных на Hugging Face, в который вы хотите его сохранить, в push_to_hub():
encoded_dataset.push_to_hub("username/my_dataset")Вы можете использовать несколько процессов для параллельной загрузки. Это особенно полезно, если вы хотите ускорить процесс:
dataset.push_to_hub("username/my_dataset", num_proc=8)Используйте функцию load_dataset() для повторной загрузки набора данных (в режиме потоковой обработки или без него):
from datasets import load_dataset
reloaded_dataset = load_dataset("username/my_dataset", streaming=True)В качестве альтернативы вы можете сохранить его локально на диске в формате Arrow. В отличие от Parquet, Arrow не сжимается, что значительно ускоряет повторную загрузку данных, что идеально подходит для локального использования на диске и временного кэширования. Однако из-за большего размера и меньшего количества метаданных загрузка, выгрузка и запросы к данным в формате Arrow выполняются медленнее, чем в Parquet, а также он менее подходит для долгосрочного хранения.
Use the save_to_disk() and load_from_disk() function to reload the dataset from your disk:
>>> encoded_dataset.save_to_disk("path/of/my/dataset/directory")
>>> # later
>>> from datasets import load_from_disk
>>> reloaded_dataset = load_from_disk("path/of/my/dataset/directory")Экспорт
🤗 Datasets поддерживает экспорт, что позволяет работать с вашим набором данных в других приложениях. В таблице ниже перечислены поддерживаемые на данный момент форматы файлов для экспорта:
| Тип файла | Метод экспорта |
|---|---|
| CSV | Dataset.to_csv() |
| JSON | Dataset.to_json() |
| Parquet | Dataset.to_parquet() |
| SQL | Dataset.to_sql() |
| Объект Python в памяти | Dataset.to_pandas(), Dataset.to_polars() or Dataset.to_dict() |
Например, экспортируйте ваш набор данных в файл CSV следующим образом:
>>> encoded_dataset.to_csv("path/of/my/dataset.csv")Используйте путь hf://, чтобы экспортировать в репозиторий Dataset или Storage Bucket на Hugging Face:
>>> encoded_dataset.to_csv("hf://datasets/username/dataset_name/path/of/my/dataset.csv")
>>> encoded_dataset.to_csv("hf://buckets/username/raw_data_bucket/path/of/my/dataset.csv")