Datasets documentation

流程

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v4.8.4).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

流程

🤗 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()。

>>> 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.56,havingearliersetarecordhighofA 4.56 , having earlier set a record high of A "} 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.56,havingearliersetarecordhighofA 4.56 , having earlier set a record high of A "} 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 "693millionandsoldittoSafewayfor 693 million and sold it to Safeway for "} 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 "1.63or8percentto 1.63 or 8 percent to "} 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 参数进行调整。批处理支持一些有趣的应用,例如将长句子拆分为较短的片段以及数据增强。

拆分长示例

当示例过长时,您可能希望将其拆分为多个较小的块。首先,创建一个函数,该函数:

  1. 将 sentence1 字段拆分为 50 个字符的块。

  2. 将所有分块堆叠在一起以创建新的数据集。

>>> 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 "x,yx,y"} and {@html "zz"} 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}

张量格式

支持多种张量或数组格式。通常建议使用这些格式,而不是手动将数据集的输出转换为张量或数组,以避免不必要的数据拷贝并加速数据加载。

以下是支持的张量或数组格式列表:

有关如何高效创建 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]])}

表格格式

您可以使用数据框或表格格式来优化数据加载和数据处理,因为它们通常提供零拷贝操作以及用低级语言编写的转换。

以下是支持的数据框或表格格式列表:

当数据集采用 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 支持导出功能,因此您可以在其他应用程序中处理您的数据集。下表列出了当前支持的导出文件格式:

文件类型导出方法
CSVDataset.to_csv()
JSONDataset.to_json()
ParquetDataset.to_parquet()
SQLDataset.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")
Update on GitHub