Datasets documentation
Proceso
Proceso
🤗 Datasets ofrece muchas herramientas para modificar la estructura y el contenido de un conjunto de datos. Estas herramientas son importantes para ordenar un conjunto de datos, crear columnas adicionales, convertir entre características y formatos, y mucho más.
Esta guía te mostrará cómo:
- Reordena las filas y divide el conjunto de datos.
- Renombrar y eliminar columnas, y otras operaciones comunes de columnas.
- Aplicar funciones de procesamiento a cada ejemplo de un conjunto de datos.
- Concatenar conjuntos de datos.
- Aplicar una transformación de formato personalizada.
- Guardar y exportar conjuntos de datos procesados.
Para más detalles específicos sobre el procesamiento de otras modalidades de conjuntos de datos, consulte la guía para procesar conjuntos de datos de audio, la guía para procesar conjuntos de datos de imágenes o la guía para procesar conjuntos de datos de texto.
Los ejemplos de esta guía utilizan el conjunto de datos MRPC, pero no dude en cargar cualquier conjunto de datos de su elección y seguir el proceso.
>>> from datasets import load_dataset
>>> dataset = load_dataset("nyu-mll/glue", "mrpc", split="train")Todos los métodos de procesamiento de esta guía devuelven un nuevo objeto Dataset. La modificación no se realiza in situ. ¡Tenga cuidado de no sobrescribir su conjunto de datos anterior!
Ordenar, barajar, seleccionar, dividir y fragmentar
Existen varias funciones para reorganizar la estructura de un conjunto de datos. Estas funciones son útiles para seleccionar solo las filas que se desean, crear divisiones de entrenamiento y prueba, y dividir conjuntos de datos muy grandes en fragmentos más pequeños.
Ordenar
Use sort() para ordenar los valores de la columna según sus valores numéricos. La columna proporcionada debe ser compatible con 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 debajo, esto crea una lista de índices ordenada según los valores de la columna. Este mapeo de índices se utiliza luego para acceder a las filas correctas en la tabla Arrow subyacente.
Barajar
La función shuffle() reordena los valores de las columnas de forma aleatoria. Puede especificar el parámetro generator en esta función para utilizar un numpy.random.Generator diferente si desea tener más control sobre el algoritmo utilizado para barajar el conjunto de datos.
>>> shuffled_dataset = sorted_dataset.shuffle(seed=42)
>>> shuffled_dataset["label"][:10]
[1, 1, 1, 0, 1, 1, 1, 1, 1, 0]Shuffling toma la lista de índices [0:len(my_dataset)] y la baraja para crear un mapeo de índices.
Sin embargo, en cuanto tu Dataset tiene un mapeo de índices, la velocidad puede ser 10 veces más lenta.
Esto se debe a que hay un paso adicional para obtener el índice de la fila a leer usando el mapeo de índices y, lo más importante, ya no estás leyendo fragmentos de datos contiguos.
Para restaurar la velocidad, necesitarías reescribir todo el dataset en tu disco de nuevo usando Dataset.flatten_indices(), que elimina el mapeo de índices.
Alternativamente, puedes cambiar a un IterableDataset y aprovechar su barajado 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)Seleccionar y filtrar
Hay dos opciones para filtrar filas en un conjunto de datos: select() y filter().
- select() devuelve filas según una lista de índices:
>>> small_dataset = dataset.select([0, 10, 20, 30, 40, 50])
>>> len(small_dataset)
6- filter() devuelve las filas que cumplen una condición 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.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() también puede filtrar por índices si estableces with_indices=True:
>>> even_dataset = dataset.filter(lambda example, idx: idx % 2 == 0, with_indices=True)
>>> len(even_dataset)
1834
>>> len(dataset) / 2
1834.0A menos que la lista de índices a conservar sea contigua, esos métodos también crean un mapeo de índices por debajo.
Dividir
La función train_test_split() crea divisiones de entrenamiento y prueba si tu conjunto de datos no las tiene ya. Esto te permite ajustar las proporciones relativas o un número absoluto de muestras en cada división. En el ejemplo siguiente, usa el parámetro test_size para crear una división de prueba que sea el 10% del conjunto de datos 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.8Los conjuntos de datos se barajan de forma predeterminada, pero puedes establecer shuffle=False para evitar el barajado.
Fragmento
🤗 Datasets admite el particionado (sharding) para dividir un conjunto de datos muy grande en un número predefinido de fragmentos. Especifique el parámetro num_shards en shard() para determinar el número de fragmentos en los que se dividirá el conjunto de datos. También deberá proporcionar el fragmento que desea devolver mediante el parámetro index.
Por ejemplo, el conjunto de datos stanfordnlp/imdb tiene 25000 ejemplos:
>>> from datasets import load_dataset
>>> dataset = load_dataset("stanfordnlp/imdb", split="train")
>>> print(dataset)
Dataset({
features: ['text', 'label'],
num_rows: 25000
})Tras dividir el conjunto de datos en cuatro fragmentos, el primer fragmento solo tendrá 6250 ejemplos:
>>> dataset.shard(num_shards=4, index=0)
Dataset({
features: ['text', 'label'],
num_rows: 6250
})
>>> print(25000/4)
6250.0Renombrar, eliminar, convertir y aplanar
Las siguientes funciones permiten modificar las columnas de un conjunto de datos. Estas funciones son útiles para renombrar o eliminar columnas, cambiar columnas a un nuevo conjunto de características y aplanar estructuras de columnas anidadas.
Renombrar
Use rename_column() cuando necesite renombrar una columna en su conjunto de datos. Las características asociadas con la columna original se mueven de hecho bajo el nuevo nombre de columna, en lugar de simplemente reemplazar la columna original en su lugar.
Proporcione a rename_column() el nombre de la columna original y el nuevo nombre de la columna:
>>> 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
})Eliminar
Cuando necesite eliminar una o más columnas, proporcione el nombre de la columna a eliminar a la función remove_columns(). Para eliminar más de una columna, proporcione una lista de nombres de columnas:
>>> 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 el contrario, select_columns() selecciona una o más columnas para conservar y elimina el resto. Esta función acepta un nombre de columna o una lista de nombres de columnas:
>>> 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
La función cast() transforma el tipo de característica de una o más columnas. Esta función acepta sus nuevas Features como argumento. El ejemplo de abajo demuestra cómo cambiar las características ClassLabel y 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')}El casting solo funciona si el tipo de característica original y el nuevo tipo de característica son compatibles. Por ejemplo, puedes hacer un casting de una columna con el tipo de característica
Value("int32")aValue("bool")si la columna original solo contiene unos y ceros.
Use la función cast_column() para cambiar el tipo de característica de una sola columna. Pasa el nombre de la columna y su nuevo tipo de característica 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)}Aplanar
A veces, una columna puede ser una estructura anidada de varios tipos. Echa un vistazo a la estructura anidada a continuación del conjunto de datos 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'))}}El campo answers contiene dos subcampos: text y answer_start. Utilice la función flatten() para extraer los subcampos en columnas separadas:
>>> flat_dataset = dataset.flatten()
>>> flat_dataset
Dataset({
features: ['id', 'title', 'context', 'question', 'answers.text', 'answers.answer_start'],
num_rows: 87599
})Fíjate en cómo los subcampos ahora son columnas independientes: answers.text y answers.answer_start.
Map
Algunas de las aplicaciones más potentes de 🤗 Datasets provienen del uso de la función map(). El propósito principal de map() es acelerar las funciones de procesamiento. Permite aplicar una función de procesamiento a cada ejemplo de un conjunto de datos, de forma independiente o en lotes. Esta función incluso puede crear nuevas filas y columnas.
En el siguiente ejemplo, antepóngase 'My sentence: ' a cada valor de sentence1 en el conjunto de datos.
Comience creando una función que añade 'My sentence: ' al principio de cada oración. La función debe aceptar y devolver un dict:
>>> def add_prefix(example):
... example["sentence1"] = 'My sentence: ' + example["sentence1"]
... return exampleAhora usa map() para aplicar la función add_prefix a todo el conjunto de datos:
>>> 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 .',
]Veamos otro ejemplo, pero esta vez eliminarás una columna con map(). Al eliminar una columna, esta solo se elimina después de que el ejemplo se haya proporcionado a la función mapeada. Esto permite que la función mapeada utilice el contenido de las columnas antes de que se eliminen.
Especifica la columna a eliminar con el parámetro remove_columns en map():
>>> updated_dataset = dataset.map(lambda example: {"new_sentence": example["sentence1"]}, remove_columns=["sentence1"])
>>> updated_dataset.column_names
['sentence2', 'label', 'idx', 'new_sentence']🤗 Datasets también tiene una función remove_columns() que es más rápida porque no copia los datos de las columnas restantes.
También puede usar map() con índices si establece with_indices=True. El ejemplo siguiente añade el índice al principio de cada oración:
>>> 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 .'
]Multiprocesamiento
El procesamiento multiproceso acelera significativamente el procesamiento al paralelizar los procesos en la CPU. Establezca el parámetro num_proc en map() para definir el número de procesos a utilizar:
>>> updated_dataset = dataset.map(lambda example, idx: {"sentence2": f"{idx}: " + example["sentence2"]}, with_indices=True, num_proc=4)El método map() también funciona con el rango del proceso si se establece with_rank=True. Esto es análogo al parámetro with_indices. El parámetro with_rank en la función mapeada va después del parámetro index si este último ya está 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
... )El caso de uso principal de rank es paralelizar la computación en varias GPU. Esto requiere establecer multiprocess.set_start_method("spawn"). Si no lo haces, recibirás el siguiente error de CUDA:
RuntimeError: Cannot re-initialize CUDA in forked subprocess. To use CUDA with multiprocessing, you must use the 'spawn' start method.Procesamiento por lotes
La función map() permite trabajar con lotes de ejemplos. Para operar con lotes, establezca batched=True. El tamaño de lote predeterminado es 1000, pero puede ajustarlo con el parámetro batch_size. El procesamiento por lotes habilita aplicaciones interesantes, como dividir oraciones largas en fragmentos más cortos y el aumento de datos.
Dividir ejemplos largos
Cuando los ejemplos son demasiado largos, es posible que desee dividirlos en varios fragmentos más pequeños. Comience creando una función que:
Divide el campo
sentence1en fragmentos de 50 caracteres.Apila todos los fragmentos para crear el nuevo conjunto de datos.
>>> 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}Aplica la función con 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']}Fíjate en cómo las frases se dividen ahora en fragmentos más cortos y hay más filas en el conjunto de datos.
>>> dataset
Dataset({
features: ['sentence1', 'sentence2', 'label', 'idx'],
num_rows: 3668
})
>>> chunked_dataset
Dataset({
features: ['chunks'],
num_rows: 10470
})Aumento de datos
La función map() también se puede utilizar para el aumento de datos. El siguiente ejemplo genera palabras adicionales para un token enmascarado en una oración.
Carga y usa el modelo RoBERTa en FillMaskPipeline de 🤗 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)Cree una función para seleccionar aleatoriamente una palabra para enmascarar en la oración. La función también debe devolver la oración original y los dos reemplazos principales generados por 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 la función a todo el conjunto de datos:
>>> 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 oración original, RoBERTA aumentó una palabra aleatoria con tres alternativas. La palabra original distorting se complementa con withholding, suppressing y destroying.
Procesamiento asíncrono
Las funciones asíncronas son útiles para llamar a puntos finales de API en paralelo, por ejemplo, para descargar contenido como imágenes o llamar a un punto final de un modelo.
Puede definir una función asíncrona utilizando las palabras clave async y await; a continuación, se muestra una función de ejemplo para llamar a un modelo de chat de 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"]}Las funciones asíncronas se ejecutan en paralelo, lo que acelera mucho el proceso. El mismo código tarda mucho más tiempo si se ejecuta de forma secuencial, ya que no hace nada mientras espera la respuesta del modelo. En general, se recomienda usar async / await cuando la función debe esperar una respuesta de una API, por ejemplo, o si descarga datos y puede tardar algún tiempo.
Tenga en cuenta la presencia de un Semaphore: establece el número máximo de consultas que pueden ejecutarse en paralelo. Se recomienda usar un Semaphore al llamar a APIs para evitar errores de límite de tasa.
Usémoslo para llamar al modelo microsoft/Phi-3-mini-4k-instruct y pedirle que devuelva el tema principal de cada problema de matemáticas del conjunto de datos 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.'}Aquí, Dataset.map() ejecuta muchas funciones get_topic de forma asíncrona, por lo que no tiene que esperar a cada respuesta individual del modelo, lo cual tomaría mucho tiempo si se hiciera de manera secuencial.
Por defecto, Dataset.map() ejecuta hasta mil funciones de mapeo en paralelo, por lo que no olvides establecer el número máximo de llamadas a la API que pueden ejecutarse en paralelo con un Semaphore; de lo contrario, el modelo podría devolver errores de límite de tasa o sobrecarga. Para casos de uso avanzados, puedes cambiar el número máximo de consultas en paralelo en datasets.config.
Procesar múltiples divisiones
Muchos conjuntos de datos tienen divisiones que pueden procesarse simultáneamente con DatasetDict.map(). Por ejemplo, tokenice el campo sentence1 en las divisiones de entrenamiento y prueba mediante:
>>> 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 distribuido
Cuando se utiliza map() en un entorno distribuido, también se debe usar torch.distributed.barrier. Esto garantiza que el proceso principal realice el mapeo, mientras que los demás procesos cargan los resultados, evitando así el trabajo duplicado.
El siguiente ejemplo muestra cómo puedes usar torch.distributed.barrier para sincronizar los procesos:
>>> 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
El método batch() permite agrupar muestras del conjunto de datos en lotes. Esto resulta especialmente útil cuando se desea crear lotes de datos para el entrenamiento o la evaluación, en particular al trabajar con modelos de aprendizaje profundo.
A continuación se muestra un ejemplo de cómo usar el 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]}El método batch() acepta los siguientes parámetros:
batch_size(int): El número de muestras en cada lote.drop_last_batch(bool, por defectoFalse): Si se debe descartar el último lote incompleto cuando el tamaño del conjunto de datos no es divisible por el tamaño del lote.num_proc(int, opcional, valor predeterminadoNone): El número de procesos a utilizar para la multiprocesación. Si es None, no se utiliza multiprocesación. Esto puede acelerar significativamente el procesamiento por lotes para conjuntos de datos grandes.
Tenga en cuenta que Dataset.batch() devuelve un nuevo Dataset en el que cada elemento es un lote de varias muestras del conjunto de datos original. Si desea procesar datos por lotes, debe usar directamente un map() por lotes, que aplica una función a los lotes, pero el conjunto de datos de salida no está loteado.
Concatenar
Los conjuntos de datos separados pueden concatenarse si comparten los mismos tipos de columnas. Concatene los conjuntos de datos con 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])También puede concatenar dos conjuntos de datos horizontalmente estableciendo axis=1, siempre que los conjuntos de datos tengan el mismo número de filas:
>>> from datasets import Dataset
>>> stories_ids = Dataset.from_dict({"ids": list(range(len(stories)))})
>>> stories_with_ids = concatenate_datasets([stories, stories_ids], axis=1)Interleaved
También puedes combinar varios conjuntos de datos tomando ejemplos alternos de cada uno para crear un nuevo conjunto de datos. Esto se conoce como interleaving, que se habilita mediante la función interleave_datasets(). Tanto interleave_datasets() como concatenate_datasets() funcionan con objetos Dataset y IterableDataset regulares. Consulta la guía de Stream para ver un ejemplo de cómo intercalar objetos IterableDataset.
Puede definir probabilidades de muestreo para cada uno de los conjuntos de datos originales para especificar cómo intercalar los conjuntos de datos. En este caso, el nuevo conjunto de datos se construye obteniendo ejemplos uno a uno de un conjunto de datos aleatorio hasta que uno de los conjuntos de datos se quede sin muestras.
>>> 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]También puede especificar stopping_strategy. La estrategia predeterminada, first_exhausted, es una estrategia de muestreo parcial, es decir, la construcción del conjunto de datos se detiene en cuanto uno de los conjuntos de datos se queda sin muestras.
Puede especificar stopping_strategy=all_exhausted para ejecutar una estrategia de muestreo excesivo. En este caso, la construcción del conjunto de datos se detiene en cuanto todas las muestras de todos los conjuntos de datos se hayan añadido al menos una vez. En la práctica, esto significa que si un conjunto de datos se agota, volverá al principio de dicho conjunto de datos hasta que se alcance el criterio de parada.
Tenga en cuenta que si no se especifican probabilidades de muestreo, el nuevo conjunto de datos tendrá max_length_datasets*nb_dataset samples.
También existe stopping_strategy=all_exhausted_without_replacement para garantizar que cada muestra se vea exactamente una vez.
>>> 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
La función with_format() cambia el formato de una columna para que sea compatible con algunos formatos de datos comunes. Especifique el formato de salida deseado en el parámetro type. También puede elegir qué columnas desea formatear usando columns=. El formateo se aplica al vuelo.
Por ejemplo, crea tensores de PyTorch estableciendo type="torch":
>>> dataset = dataset.with_format(type="torch")La función set_format() también cambia el formato de una columna, pero se ejecuta in situ:
>>> dataset.set_format(type="torch")Si necesitas restablecer el conjunto de datos a su formato original, establece el formato en None (o usa 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
Se admiten varios formatos de tensores o matrices. En general, se recomienda utilizar estos formatos en lugar de convertir manualmente las salidas de un conjunto de datos a tensores o matrices, para evitar copias de datos innecesarias y acelerar la carga de datos.
Esta es la lista de formatos de tensores o matrices admitidos:
- NumPy: el nombre del formato es “numpy”, para más información consulte Uso de Datasets con NumPy
- PyTorch: el nombre del formato es “torch”, para más información consulte Uso de Datasets con PyTorch
- TensorFlow: el nombre del formato es “tensorflow”, para más información consulte Uso de Datasets con TensorFlow
- JAX: el nombre del formato es “jax”, para más información consulte Uso de Datasets con JAX
Consulte la guía Uso de Datasets con TensorFlow para obtener más detalles sobre cómo crear un dataset de TensorFlow de manera eficiente.
Cuando un conjunto de datos está formateado en formato de tensor o matriz, todos los datos se formatean como tensores o matrices (excepto los tipos no compatibles, como las cadenas de texto, por ejemplo, para 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
Puede usar un formato de dataframes o tablas para optimizar la carga y el procesamiento de datos, ya que generalmente ofrecen operaciones sin copia y transformaciones escritas en lenguajes de bajo nivel.
Esta es la lista de formatos de dataframes o tablas admitidos:
- Pandas: el nombre del formato es “pandas”, para más información consulte Uso de conjuntos de datos con Pandas
- Polars: el nombre del formato es “polars”, para más información consulte Uso de Datasets con Polars
- PyArrow: el nombre del formato es “arrow”, para más información consulte Uso de Datasets con PyArrow
Cuando un conjunto de datos está formateado en formato de dataframe o tabla, cada fila del conjunto de datos o lotes de filas se formatean como un dataframe o tabla, y las columnas del conjunto de datos se formatean como una serie o un 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 1Esos formatos permiten iterar sobre los datos más rápido al evitar copias de datos, y también habilitan un procesamiento de datos más rápido en map() o 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 BARTransformación de formato personalizado
La función with_transform() aplica una transformación de formato personalizada al vuelo. Esta función reemplaza cualquier formato especificado previamente. Por ejemplo, puede usar esta función para tokenizar y rellenar tokens al vuelo. La tokenización solo se aplica cuando se accede a los ejemplos:
>>> 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}También existe set_transform(), que realiza la misma función pero de forma in situ.
También puede usar la función with_transform() para decodificación personalizada en Features.
El ejemplo siguiente utiliza el paquete pydub como alternativa a la decodificación de 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)Guardar
Una vez que tu conjunto de datos esté listo, puedes guardarlo como un Dataset de Hugging Face en formato Parquet y reutilizarlo más adelante con load_dataset().
Guarde su conjunto de datos proporcionando el nombre del repositorio de conjuntos de datos en Hugging Face al que desea guardarlo en push_to_hub():
encoded_dataset.push_to_hub("username/my_dataset")Puedes usar varios procesos para subirlo en paralelo. Esto es especialmente útil si quieres acelerar el proceso:
dataset.push_to_hub("username/my_dataset", num_proc=8)Use la función load_dataset() para recargar el conjunto de datos (en modo streaming o no):
from datasets import load_dataset
reloaded_dataset = load_dataset("username/my_dataset", streaming=True)Como alternativa, puede guardarlo localmente en formato Arrow en el disco. En comparación con Parquet, Arrow no está comprimido, lo que lo hace mucho más rápido de recargar, ideal para uso local en disco y caché efímera. Sin embargo, al ser más grande y tener menos metadatos, es más lento para subir, descargar o consultar que Parquet y es menos adecuado para almacenamiento a largo plazo.
Use las funciones save_to_disk() y load_from_disk() para recargar el conjunto de datos desde su 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
🤗 Datasets también permite la exportación, de modo que puedas trabajar con tu conjunto de datos en otras aplicaciones. La siguiente tabla muestra los formatos de archivo actualmente admitidos para la exportación:
| Tipo de archivo | Método de exportación |
|---|---|
| CSV | Dataset.to_csv() |
| JSON | Dataset.to_json() |
| Parquet | Dataset.to_parquet() |
| SQL | Dataset.to_sql() |
| Objeto de Python en memoria | Dataset.to_pandas(), Dataset.to_polars() or Dataset.to_dict() |
Por ejemplo, exporte su conjunto de datos a un archivo CSV de la siguiente manera:
>>> encoded_dataset.to_csv("path/of/my/dataset.csv")Use una ruta hf:// para exportar a un repositorio de Dataset o a un Storage Bucket en 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")