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
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 .'
]如果将 with_indices=True 设置为 filter(),它还可以按索引进行过滤:
>>> 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 支持分片(sharding),可将非常大的数据集划分为预定义数量的块。在 shard() 中指定 num_shards 参数,以确定将数据集拆分为多少个分片。此外,还需要通过 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')}类型转换仅在原始特征类型与新特征类型兼容时才能生效。例如,如果原始列仅包含 1 和 0,则可以将特征类型为
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() 的主要用途是加速处理函数。它允许你将处理函数独立地或按批次应用于数据集中的每个示例。该函数甚至可以创建新的行和列。
在以下示例中,为数据集中每个 sentence1 值添加前缀 'My sentence: '。
首先,创建一个函数,在每个句子的开头添加 '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() 来删除一列。当你删除一列时,只有在示例被提供给映射函数之后,该列才会被移除。这使得映射函数可以在列被删除之前使用其内容。
在 map() 中使用 remove_columns 参数指定要删除的列:
>>> 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() 函数,由于它不会复制剩余列的数据,因此速度更快。
如果设置 with_indices=True,你还可以使用带有索引的 map()。下面的示例将索引添加到每个句子的开头:
>>> 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 上的进程来显著加快处理速度。在 map() 中设置 num_proc 参数以指定要使用的进程数:
>>> updated_dataset = dataset.map(lambda example, idx: {"sentence2": f"{idx}: " + example["sentence2"]}, with_indices=True, num_proc=4)如果将 with_rank=True 设置为真,map() 也可以与进程的 rank 一起使用。这与 with_indices 参数类似。如果映射函数中已经存在 index 参数,那么 with_rank 参数将位于其后。
>>> 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() 函数支持以批处理(batches)方式处理示例。通过设置 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() 函数也可用于数据增强。以下示例为句子中的掩码标记生成额外的词。
在 🤗 Transformers 的 FillMaskPipeline 中加载并使用 RoBERTA 模型:
>>> 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"]}异步函数并行执行,可大幅加速处理过程。如果以顺序方式运行相同的代码,由于在等待模型响应期间不做任何操作,所需时间会显著增加。通常建议,当函数需要等待 API 响应(例如),或需要下载可能耗时较长的数据时,使用 async / await。
请注意其中存在一个 Semaphore:它用于设置可并行运行的查询数量上限。在调用 API 时,建议使用 Semaphore 以避免触发速率限制错误。
让我们使用它来调用 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 函数,因此请务必使用 Semaphore 设置可并行运行的 API 调用最大数量,否则模型可能会返回速率限制错误或导致过载。对于高级用例,您可以在 datasets.config 中更改并行查询的最大数量。
处理多个拆分
许多数据集包含可以并行处理的拆分,方法是使用 DatasetDict.map()。例如,通过以下方式对 train 和 test 拆分中的 sentence1 字段进行分词:
>>> 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() 方法允许你将数据集中的样本分组为批次。当你需要为训练或评估创建数据批次时,尤其是在处理深度学习模型时,这一点特别有用。
以下是使用 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对象。 有关如何交错IterableDataset对象的示例,请参阅Stream指南。
您可以为每个原始数据集定义采样概率,以指定如何交错这些数据集。 在这种情况下,新数据集的构建方式是:从随机选择的数据集中逐个获取样本,直到其中一个数据集的样本用尽。
>>> 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= 选择要格式化的列。格式化操作是即时应用的。
例如,通过设置 type="torch" 来创建 PyTorch 张量:
>>> 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”,更多信息请参阅使用 NumPy 处理数据集
- PyTorch:格式名称为“torch”,更多信息请参阅使用 PyTorch 加载数据集
- TensorFlow:格式名称为“tensorflow”,更多信息请参阅使用 TensorFlow 加载数据集
- JAX:格式名称为“jax”,更多信息请参阅使用 JAX 处理数据集
有关如何高效创建 TensorFlow 数据集的更多详细信息,请参阅 使用 Datasets 与 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]])}表格格式
您可以使用数据框或表格格式来优化数据加载和数据处理,因为它们通常提供零拷贝操作以及用低级语言编写的转换。
以下是支持的数据框或表格格式列表:
- Pandas:格式名称为“pandas”,更多信息请参阅 使用 Pandas 处理数据集
- Polars:格式名称为“polars”,更多信息请参阅 使用 Polars 处理数据集
- PyArrow:格式名称为“arrow”,更多信息请参阅 使用 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() 重新加载。
通过向 push_to_hub() 提供您希望保存到的 Hugging Face 数据集仓库名称,来保存您的数据集:
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 格式未压缩,因此重新加载速度更快,非常适合本地磁盘使用和临时缓存。但由于其文件体积更大且元数据较少,其上传、下载和查询速度比 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:// 路径导出到 Hugging Face 上的 Dataset 仓库 或 Storage Bucket:
>>> 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")