Datasets documentation

Processo

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

Processo

🤗 O Datasets oferece diversas ferramentas para modificar a estrutura e o conteúdo de um conjunto de dados. Essas ferramentas são importantes para organizar um conjunto de dados, criar colunas adicionais, converter entre recursos e formatos, e muito mais.

Este guia mostrará como:

  • Reordene as linhas e divida o conjunto de dados.
  • Renomear e remover colunas, e outras operações comuns de colunas.
  • Aplique funções de processamento a cada exemplo em um conjunto de dados.
  • Concatene conjuntos de dados.
  • Aplicar uma transformação de formatação personalizada.
  • Salve e exporte conjuntos de dados processados.

Para mais detalhes específicos sobre o processamento de outras modalidades de conjuntos de dados, consulte o guia de processamento de conjuntos de dados de áudio, o guia de processamento de conjuntos de dados de imagem ou o guia de processamento de conjuntos de dados de texto.

Os exemplos neste guia usam o conjunto de dados MRPC, mas sinta-se à vontade para carregar qualquer conjunto de dados de sua escolha e seguir o tutorial!

>>> from datasets import load_dataset
>>> dataset = load_dataset("nyu-mll/glue", "mrpc", split="train")

Todos os métodos de processamento neste guia retornam um novo objeto Dataset. A modificação não é feita in-place. Tenha cuidado para não sobrescrever seu dataset anterior!

Ordenar, embaralhar, selecionar, dividir e fragmentar

Existem várias funções para reorganizar a estrutura de um conjunto de dados. Essas funções são úteis para selecionar apenas as linhas desejadas, criar divisões de treino e teste, e dividir conjuntos de dados muito grandes em blocos menores.

Ordenar

Use sort() para ordenar os valores da coluna de acordo com seus valores numéricos. A coluna fornecida deve ser compatível com 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]

Por baixo dos panos, isso cria uma lista de índices que é ordenada de acordo com os valores da coluna. Esse mapeamento de índices é então usado para acessar as linhas corretas na tabela Arrow subjacente.

Embaralhar

A função shuffle() reorganiza aleatoriamente os valores das colunas. Você pode especificar o parâmetro generator nesta função para usar um numpy.random.Generator diferente, caso queira ter mais controle sobre o algoritmo usado para embaralhar o conjunto de dados.

>>> shuffled_dataset = sorted_dataset.shuffle(seed=42)
>>> shuffled_dataset["label"][:10]
[1, 1, 1, 0, 1, 1, 1, 1, 1, 0]

Shuffling pega a lista de índices [0:len(my_dataset)] e embaralha-a para criar um mapeamento de índices. No entanto, assim que seu Dataset possui um mapeamento de índices, a velocidade pode ficar 10x mais lenta. Isso ocorre porque há uma etapa extra para obter o índice da linha a ser lido usando o mapeamento de índices e, o mais importante, você não está mais lendo blocos contíguos de dados. Para restaurar a velocidade, você precisaria reescrever o conjunto de dados inteiro no seu disco novamente usando Dataset.flatten_indices(), que remove o mapeamento de índices. Alternativamente, você pode alternar para um IterableDataset e aproveitar seu embaralhamento aproximado rápido IterableDataset.shuffle():

>>> iterable_dataset = dataset.to_iterable_dataset(num_shards=128)
>>> shuffled_iterable_dataset = iterable_dataset.shuffle(seed=42, buffer_size=1000)

Selecionar e filtrar

Há duas opções para filtrar linhas em um dataset: select() e filter().

  • select() retorna linhas de acordo com uma lista de índices:
>>> small_dataset = dataset.select([0, 10, 20, 30, 40, 50])
>>> len(small_dataset)
6
  • filter() retorna as linhas que correspondem a uma condição especificada:
>>> 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 .'
]

filter() também pode filtrar por índices se você definir 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

A menos que a lista de índices a serem mantidos seja contígua, esses métodos também criam um mapeamento de índices por baixo dos panos.

Dividir

A função train_test_split() cria as partições de treino e teste caso seu conjunto de dados ainda não as possua. Isso permite ajustar as proporções relativas ou o número absoluto de amostras em cada partição. No exemplo abaixo, use o parâmetro test_size para criar uma partição de teste que corresponde a 10% do conjunto de dados original:

>>> 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

Os splits são embaralhados por padrão, mas você pode definir shuffle=False para evitar o embaralhamento.

Shard

🤗 O Datasets oferece suporte a sharding para dividir um conjunto de dados muito grande em um número predefinido de blocos. Especifique o parâmetro num_shards em shard() para determinar o número de shards em que o conjunto de dados será dividido. Você também precisará fornecer o shard que deseja retornar com o parâmetro index.

Por exemplo, o conjunto de dados stanfordnlp/imdb tem 25000 exemplos:

>>> from datasets import load_dataset
>>> dataset = load_dataset("stanfordnlp/imdb", split="train")
>>> print(dataset)
Dataset({
    features: ['text', 'label'],
    num_rows: 25000
})

Após dividir o conjunto de dados em quatro fragmentos, o primeiro fragmento terá apenas 6250 exemplos:

>>> dataset.shard(num_shards=4, index=0)
Dataset({
    features: ['text', 'label'],
    num_rows: 6250
})
>>> print(25000/4)
6250.0

Renomear, remover, converter e achatar

As funções a seguir permitem modificar as colunas de um conjunto de dados. Essas funções são úteis para renomear ou remover colunas, alterar colunas para um novo conjunto de recursos e aplanar estruturas de colunas aninhadas.

Renomear

Use rename_column() quando precisar renomear uma coluna no seu dataset. Os recursos associados à coluna original são, na verdade, movidos para o novo nome da coluna, em vez de apenas substituir a coluna original no local.

Forneça rename_column() com o nome da coluna original e o novo nome da coluna:

>>> 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
})

Remover

Quando você precisa remover uma ou mais colunas, forneça o nome da coluna a ser removida para a função remove_columns(). Remova mais de uma coluna fornecendo uma lista de nomes de colunas:

>>> 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
})

Por outro lado, select_columns() seleciona uma ou mais colunas para manter e remove as demais. Esta função aceita um nome de coluna ou uma lista de nomes de colunas:

>>> 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

A função cast() transforma o tipo de recurso de uma ou mais colunas. Esta função aceita seus novos Features como argumento. O exemplo abaixo demonstra como alterar os recursos ClassLabel e 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')}

O casting só funciona se o tipo de feature original e o novo tipo de feature forem compatíveis. Por exemplo, você pode fazer o casting de uma coluna com o tipo de feature Value("int32") para Value("bool") se a coluna original contiver apenas uns e zeros.

Use a função cast_column() para alterar o tipo de recurso de uma única coluna. Passe o nome da coluna e seu novo tipo de recurso como argumentos:

>>> 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)}

Achatar

Às vezes, uma coluna pode ser uma estrutura aninhada de vários tipos. Observe a estrutura aninhada abaixo do conjunto de dados 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'))}}

O campo answers contém dois subcampos: text e answer_start. Use a função flatten() para extrair os subcampos em colunas separadas:

>>> flat_dataset = dataset.flatten()
>>> flat_dataset
Dataset({
    features: ['id', 'title', 'context', 'question', 'answers.text', 'answers.answer_start'],
 num_rows: 87599
})

Observe como os subcampos agora são colunas independentes: answers.text e answers.answer_start.

Map

Algumas das aplicações mais poderosas do 🤗 Datasets vêm do uso da função map(). O objetivo principal do map() é acelerar as funções de processamento. Ele permite aplicar uma função de processamento a cada exemplo de um conjunto de dados, de forma independente ou em lotes. Essa função pode até criar novas linhas e colunas.

No exemplo a seguir, prefixe cada valor de sentence1 no conjunto de dados com 'My sentence: '.

Comece criando uma função que adiciona 'My sentence: ' ao início de cada frase. A função precisa aceitar e retornar um dict:

>>> def add_prefix(example):
...     example["sentence1"] = 'My sentence: ' + example["sentence1"]
...     return example

Agora use map() para aplicar a função add_prefix ao conjunto de dados inteiro:

>>> 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 .',
]

Vamos examinar outro exemplo, mas desta vez você removerá uma coluna com map(). Ao remover uma coluna, ela só é removida depois que o exemplo tiver sido fornecido à função mapeada. Isso permite que a função mapeada utilize o conteúdo das colunas antes que sejam removidas.

Especifique a coluna a ser removida com o parâmetro remove_columns em map():

>>> updated_dataset = dataset.map(lambda example: {"new_sentence": example["sentence1"]}, remove_columns=["sentence1"])
>>> updated_dataset.column_names
['sentence2', 'label', 'idx', 'new_sentence']

🤗 O Datasets também possui uma função remove_columns() que é mais rápida porque não copia os dados das colunas restantes.

Você também pode usar map() com índices se definir with_indices=True. O exemplo abaixo adiciona o índice ao início de cada frase:

>>> 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 .'
]

Multiprocessamento

O multiprocessing acelera significativamente o processamento ao paralelizar processos na CPU. Defina o parâmetro num_proc em map() para definir o número de processos a serem usados:

>>> updated_dataset = dataset.map(lambda example, idx: {"sentence2": f"{idx}: " + example["sentence2"]}, with_indices=True, num_proc=4)

O map() também funciona com o rank do processo se você definir with_rank=True. Isso é análogo ao parâmetro with_indices. O parâmetro with_rank na função mapeada vem depois do index, se este já estiver presente.

>>> 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
...     )

O principal caso de uso de rank é paralelizar a computação entre várias GPUs. Isso requer definir multiprocess.set_start_method("spawn"). Caso contrário, você receberá o seguinte erro de CUDA:

RuntimeError: Cannot re-initialize CUDA in forked subprocess. To use CUDA with multiprocessing, you must use the 'spawn' start method.

Processamento em lote

A função map() suporta o trabalho com lotes de exemplos. Opere sobre lotes definindo batched=True. O tamanho padrão do lote é 1000, mas você pode ajustá-lo com o parâmetro batch_size. O processamento em lote permite aplicações interessantes, como dividir frases longas em trechos menores e aumentar os dados.

Dividir exemplos longos

Quando os exemplos são longos demais, você pode querer dividi-los em vários blocos menores. Comece criando uma função que:

  1. Divide o campo sentence1 em blocos de 50 caracteres.

  2. Empilha todos os blocos para criar o novo conjunto de dados.

>>> 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}

Aplique a função com 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']}

Observe como as frases agora são divididas em trechos mais curtos e há mais linhas no conjunto de dados.

>>> dataset
Dataset({
 features: ['sentence1', 'sentence2', 'label', 'idx'],
 num_rows: 3668
})
>>> chunked_dataset
Dataset({
    features: ['chunks'],
    num_rows: 10470
})

Aumento de dados

A função map() também pode ser usada para aumento de dados. O exemplo a seguir gera palavras adicionais para um token mascarado em uma frase.

Carregue e use o modelo RoBERTa no FillMaskPipeline do 🤗 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)

Crie uma função para selecionar aleatoriamente uma palavra para mascarar na frase. A função também deve retornar a frase original e as duas principais substituições geradas pelo 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}

Use map() para aplicar a função em todo o conjunto de dados:

>>> 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.'
]

Para cada frase original, o RoBERTA aumentou uma palavra aleatória com três alternativas. A palavra original distorting é complementada por withholding, suppressing e destroying.

Processamento assíncrono

Funções assíncronas são úteis para chamar endpoints de API em paralelo, por exemplo, para baixar conteúdo como imagens ou chamar um endpoint de modelo.

Você pode definir uma função assíncrona usando as palavras-chave async e await. Aqui está um exemplo de função para chamar um modelo de chat do 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"]}

Funções assíncronas são executadas em paralelo, o que acelera muito o processo. O mesmo código leva muito mais tempo se for executado sequencialmente, pois não faz nada enquanto aguarda a resposta do modelo. Em geral, recomenda-se usar async / await quando a sua função precisa aguardar uma resposta de uma API, por exemplo, ou se ela baixa dados e isso pode levar algum tempo.

Observe a presença de um Semaphore: ele define o número máximo de consultas que podem ser executadas em paralelo. É recomendável usar um Semaphore ao chamar APIs para evitar erros de limite de taxa.

Vamos usá-lo para chamar o modelo microsoft/Phi-3-mini-4k-instruct e pedir que ele retorne o tópico principal de cada problema de matemática no conjunto de dados 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.'}

Aqui, Dataset.map() executa a função get_topic de forma assíncrona, de modo que não precisa esperar por cada resposta individual do modelo, o que levaria muito tempo se fosse feito sequencialmente.

Por padrão, Dataset.map() executa até mil funções de mapeamento em paralelo, então não se esqueça de definir o número máximo de chamadas de API que podem ser executadas em paralelo com um Semaphore, caso contrário o modelo pode retornar erros de limite de taxa ou sobrecarga. Para casos de uso avançados, você pode alterar o número máximo de consultas em paralelo em datasets.config.

Processar vários splits

Muitos conjuntos de dados possuem divisões que podem ser processadas simultaneamente com DatasetDict.map(). Por exemplo, tokenizar o campo sentence1 nas divisões de treino e teste por:

>>> 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]
}

Uso distribuído

Ao usar map() em um ambiente distribuído, você também deve usar torch.distributed.barrier. Isso garante que o processo principal execute o mapeamento, enquanto os outros processos carregam os resultados, evitando assim trabalho duplicado.

O exemplo a seguir mostra como você pode usar torch.distributed.barrier para sincronizar os processos:

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

Lote

O método batch() permite agrupar amostras do conjunto de dados em lotes. Isso é particularmente útil quando você deseja criar lotes de dados para treinamento ou avaliação, especialmente ao trabalhar com modelos de aprendizado profundo.

Aqui está um exemplo de como usar o método 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]}

O método batch() aceita os seguintes parâmetros:

  • batch_size (int): O número de amostras em cada lote.
  • drop_last_batch (bool, padrão False): Se deve descartar o último lote incompleto caso o tamanho do conjunto de dados não seja divisível pelo tamanho do lote.
  • num_proc (int, opcional, padrão None): O número de processos a serem usados para multiprocessamento. Se for None, nenhum multiprocessamento é usado. Isso pode acelerar significativamente o loteamento para grandes conjuntos de dados.

Observe que Dataset.batch() retorna um novo Dataset em que cada item é um lote de várias amostras do dataset original. Se você deseja processar dados em lotes, deve usar um map() em lote diretamente, que aplica uma função a lotes, mas o dataset de saída não está em lote.

Concatenar

Conjuntos de dados separados podem ser concatenados se compartilharem os mesmos tipos de colunas. Concatene conjuntos de dados com 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])

Você também pode concatenar dois conjuntos de dados horizontalmente definindo axis=1, desde que os conjuntos de dados tenham o mesmo número de linhas:

>>> from datasets import Dataset
>>> stories_ids = Dataset.from_dict({"ids": list(range(len(stories)))})
>>> stories_with_ids = concatenate_datasets([stories, stories_ids], axis=1)

Interlevar

Você também pode misturar vários conjuntos de dados, alternando exemplos de cada um para criar um novo conjunto de dados. Isso é conhecido como interleaving, habilitado pela função interleave_datasets(). Tanto interleave_datasets() quanto concatenate_datasets() funcionam com objetos Dataset e IterableDataset regulares. Consulte o guia de Stream para um exemplo de como intercalar objetos IterableDataset.

Você pode definir probabilidades de amostragem para cada um dos conjuntos de dados originais para especificar como intercalar os conjuntos de dados. Nesse caso, o novo conjunto de dados é construído obtendo exemplos um a um de um conjunto de dados aleatório até que um dos conjuntos de dados fique sem amostras.

>>> 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]

You can also specify the stopping_strategy. The default strategy, first_exhausted, is a subsampling strategy, i.e the dataset construction is stopped as soon one of the dataset runs out of samples. You can specify stopping_strategy=all_exhausted to execute an oversampling strategy. In this case, the dataset construction is stopped as soon as every samples in every dataset has been added at least once. In practice, it means that if a dataset is exhausted, it will return to the beginning of this dataset until the stop criterion has been reached. Note that if no sampling probabilities are specified, the new dataset will have max_length_datasets*nb_dataset samples. There is also stopping_strategy=all_exhausted_without_replacement to ensure that every sample is seen exactly once.

>>> 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]

Formato

A função with_format() altera o formato de uma coluna para que seja compatível com alguns formatos de dados comuns. Especifique a saída desejada no parâmetro type. Você também pode escolher quais colunas deseja formatar usando columns=. A formatação é aplicada em tempo real.

Por exemplo, crie tensores do PyTorch definindo type="torch":

>>> dataset = dataset.with_format(type="torch")

A função set_format() também altera o formato de uma coluna, exceto que ela é executada in-place:

>>> dataset.set_format(type="torch")

Se você precisar redefinir o dataset para seu formato original, defina o formato como None (ou use 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}

Formatos de tensores

Vários formatos de tensores ou arrays são suportados. Em geral, recomenda-se usar esses formatos em vez de converter manualmente as saídas de um dataset para tensores ou arrays, a fim de evitar cópias desnecessárias de dados e acelerar o carregamento dos dados.

Aqui está a lista de formatos de tensores ou arrays suportados:

Confira o guia Usando Datasets com TensorFlow para mais detalhes sobre como criar um dataset do TensorFlow de forma eficiente.

Quando um conjunto de dados está formatado em formato de tensor ou array, todos os dados são formatados como tensors ou arrays (exceto tipos não suportados, como strings, por exemplo, para o 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]])}

Formatos tabulares

Você pode usar o formato de dataframes ou tabelas para otimizar o carregamento e o processamento de dados, pois eles geralmente oferecem operações de cópia zero e transformações escritas em linguagens de baixo nível.

Aqui está a lista de formatos de dataframes ou tabelas suportados:

Quando um conjunto de dados está formatado em formato de dataframe ou tabela, cada linha do conjunto de dados ou lotes de linhas são formatados como um dataframe ou tabela, e as colunas do conjunto de dados são formatadas como uma série ou array:

>>> 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

Esses formatos permitem iterar sobre os dados mais rapidamente, evitando cópias de dados, e também habilitam um processamento de dados mais rápido em map() ou 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

Transformação de formato personalizado

A função with_transform() aplica uma transformação de formatação personalizada em tempo real. Esta função substitui qualquer formato especificado anteriormente. Por exemplo, você pode usar esta função para tokenizar e aplicar padding aos tokens em tempo real. A tokenização é aplicada apenas quando os exemplos são acessados:

>>> 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}

Há também set_transform(), que faz o mesmo, mas executa in-place.

Você também pode usar a função with_transform() para decodificação personalizada em Features.

O exemplo abaixo usa o pacote pydub como alternativa à decodificação do 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)

Salvar

Quando seu conjunto de dados estiver pronto, você pode salvá-lo como um Dataset do Hugging Face no formato Parquet e reutilizá-lo mais tarde com load_dataset().

Salve seu dataset fornecendo o nome do repositório do dataset no Hugging Face para o qual você deseja salvá-lo em push_to_hub():

encoded_dataset.push_to_hub("username/my_dataset")

Você pode usar múltiplos processos para enviá-lo em paralelo. Isso é especialmente útil se você quiser acelerar o processo:

dataset.push_to_hub("username/my_dataset", num_proc=8)

Use a função load_dataset() para recarregar o conjunto de dados (em modo de streaming ou não):

from datasets import load_dataset
reloaded_dataset = load_dataset("username/my_dataset", streaming=True)

Como alternativa, você pode salvá-lo localmente em formato Arrow no disco. Em comparação com Parquet, o Arrow não é compactado, o que o torna muito mais rápido para recarregar, o que é ótimo para uso local em disco e cache efêmero. Mas, como é maior e possui menos metadados, é mais lento para upload/download/consulta do que Parquet e menos adequado para armazenamento de longo prazo.

Use as funções save_to_disk() e load_from_disk() para recarregar o dataset do seu disco:

>>> 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")

Exportar

🤗 O Datasets também suporta exportação, para que você possa trabalhar com seu conjunto de dados em outros aplicativos. A tabela a seguir mostra os formatos de arquivo atualmente suportados para exportação:

Tipo de arquivoMétodo de exportação
CSVDataset.to_csv()
JSONDataset.to_json()
ParquetDataset.to_parquet()
SQLDataset.to_sql()
Objeto Python em memóriaDataset.to_pandas(), Dataset.to_polars() or Dataset.to_dict()

Por exemplo, exporte seu conjunto de dados para um arquivo CSV da seguinte forma:

>>> encoded_dataset.to_csv("path/of/my/dataset.csv")

Use um caminho hf:// para exportar para um repositório de Dataset ou um Storage Bucket no 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")
Update on GitHub