資料集文件
流程
並獲得增強的文件體驗
開始使用
處理資料集 (Process)
🤗 Datasets 提供了許多用於修改資料集結構與內容的工具。這些工具對於整理資料集、建立額外欄位、在特徵與格式間進行轉換,以及執行其他各類操作相當重要。
本指南將示範如何:
- 重新排列資料列並分割資料集。
- 重新命名與移除欄位,以及執行其他常見的欄位操作。
- 將處理函數應用於資料集中的每個樣本。
- 串接 (Concatenate) 資料集。
- 應用自訂格式轉換。
- 儲存與匯出已處理的資料集。
若需了解針對其他資料集型態的處理細節,請參閱 音訊資料集處理指南、影像資料集處理指南,或 文字資料集處理指南。
本指南中的範例使用 MRPC 資料集,但歡迎您載入任何您選擇的資料集並跟著操作!
>>> from datasets import load_dataset
>>> dataset = load_dataset("nyu-mll/glue", "mrpc", split="train")本指南中的所有處理方法都會回傳一個新的 Dataset 物件。修改並非原地 (in-place) 進行。請小心不要覆蓋掉您之前的資料集!
排序、洗牌、選取、分割與分片
有幾種函數可用於重排資料集的結構。這些函數對於僅選取您需要的資料列、建立訓練與測試集分割,以及將超大型資料集分片為較小的區塊非常有用。
排序 (Sort)
使用 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]洗牌會取得索引列表 [0:len(my_dataset)] 並將其洗亂以建立索引映射。然而,一旦您的 Dataset 具有索引映射,其速度可能會變慢 10 倍。這是因為使用索引映射來讀取資料列索引多了一道額外的步驟,且最重要的是,您不再讀取連續的資料區塊。若要恢復速度,您需要使用 Dataset.flatten_indices() 將整個資料集重新寫入磁碟,這會移除索引映射。或者,您可以切換至 IterableDataset 並利用其快速的近似洗牌方法 IterableDataset.shuffle()。
>>> iterable_dataset = dataset.to_iterable_dataset(num_shards=128)
>>> shuffled_iterable_dataset = iterable_dataset.shuffle(seed=42, buffer_size=1000)選取與篩選
在資料集中篩選資料列有兩個選項:select() 與 filter()。
- select() 根據索引列表回傳資料列。
>>> small_dataset = dataset.select([0, 10, 20, 30, 40, 50])
>>> len(small_dataset)
6- filter() 回傳符合特定條件的資料列。
>>> start_with_ar = dataset.filter(lambda example: example["sentence1"].startswith("Ar"))
>>> len(start_with_ar)
6
>>> start_with_ar["sentence1"]
['Around 0335 GMT , Tab shares were up 19 cents , or 4.4 % , at A {@html ""} 4.57 .',
'Arison said Mann may have been one of the pioneers of the world music movement and he had a deep love of Brazilian music .',
'Arts helped coach the youth on an eighth-grade football team at Lombardi Middle School in Green Bay .',
'Around 9 : 00 a.m. EDT ( 1300 GMT ) , the euro was at $ 1.1566 against the dollar , up 0.07 percent on the day .',
"Arguing that the case was an isolated example , Canada has threatened a trade backlash if Tokyo 's ban is not justified on scientific grounds .",
'Artists are worried the plan would harm those who need help most - performers who have a difficult time lining up shows .'
]若設定 with_indices=True,filter() 也可以根據索引進行篩選。
>>> even_dataset = dataset.filter(lambda example, idx: idx % 2 == 0, with_indices=True)
>>> len(even_dataset)
1834
>>> len(dataset) / 2
1834.0除非要保留的索引列表是連續的,否則這些方法也會在內部建立索引映射。
分割 (Split)
如果您的資料集尚未有分割,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 來禁止洗牌。
分片 (Shard)
🤗 Datasets 支援分片功能,可將超大型資料集分割為預定義數量的區塊。在 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)
當您需要重新命名資料集中的欄位時,請使用 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)
當您需要移除一個或多個欄位時,請將要移除的欄位名稱提供給 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)
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')}只有在原始特徵型別與新特徵型別相容時,轉換型別 (Casting) 才會生效。例如,如果原始欄位僅包含 0 與 1,您可以將特徵型別為
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)}扁平化 (Flatten)
有時欄位可能是包含多種型別的巢狀結構。請參考以下來自 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。
映射 (Map)
🤗 Datasets 的一些強大應用來自於 map() 函數。 map() 的主要用途是加速處理函數。它允許您將處理函數獨立地或批次地應用於資料集中的每個樣本。此函數甚至可以建立新的資料列與欄位。
在下方的範例中,將資料集中每個 sentence1 的值前面加上 'My sentence: '。
首先建立一個函數,將 'My sentence: ' 新增到每個句子的開頭。此函數需要接受並輸出一個 dict。
>>> def add_prefix(example):
... example["sentence1"] = 'My sentence: ' + example["sentence1"]
... return example現在使用 map() 將 add_prefix 函數應用於整個資料集。
>>> updated_dataset = small_dataset.map(add_prefix)
>>> updated_dataset["sentence1"][:5]
['My sentence: Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .',
"My sentence: Yucaipa owned Dominick 's before selling the chain to Safeway in 1998 for $ 2.5 billion .",
'My sentence: They had published an advertisement on the Internet on June 10 , offering the cargo for sale , he added .',
'My sentence: Around 0335 GMT , Tab shares were up 19 cents , or 4.4 % , at A {@html ""} 4.57 .',
]讓我們看看另一個範例,這次您將使用 map() 來移除欄位。當您移除欄位時,它只有在樣本被提供給對映 (mapped) 函數後才會被移除。這允許對映函數在欄位被移除前使用其內容。
使用 map() 中的 remove_columns 參數來指定要移除的欄位。
>>> updated_dataset = dataset.map(lambda example: {"new_sentence": example["sentence1"]}, remove_columns=["sentence1"])
>>> updated_dataset.column_names
['sentence2', 'label', 'idx', 'new_sentence']🤗 Datasets 還有一個 remove_columns() 函數,因為它不會複製剩餘欄位的資料,所以速度更快。
如果您設定 with_indices=True,也可以將 map() 與索引搭配使用。下方的範例將索引新增到每個句子的開頭。
>>> updated_dataset = dataset.map(lambda example, idx: {"sentence2": f"{idx}: " + example["sentence2"]}, with_indices=True)
>>> updated_dataset["sentence2"][:5]
['0: Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .',
"1: Yucaipa bought Dominick 's in 1995 for {@html ""} 1.8 billion in 1998 .",
"2: On June 10 , the ship 's owners had published an advertisement on the Internet , offering the explosives for sale .",
'3: Tab shares jumped 20 cents , or 4.6 % , to set a record closing high at A $ 4.57 .',
'4: PG & E Corp. shares jumped {@html ""} 21.03 on the New York Stock Exchange on Friday .'
]多重處理 (Multiprocessing)
多重處理透過在 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.批次處理 (Batch processing)
map() 函數支援處理樣本批次。透過設定 batched=True 來進行批次操作。預設批次大小為 1000,但您可以使用 batch_size 參數進行調整。批次處理可實現有趣的操作,例如將長句子分割為較短的區塊以及資料增強。
分割長樣本
當樣本太長時,您可能希望將它們分割成多個較小的區塊。首先建立一個函數,該函數:
將
sentence1欄位分割為 50 個字元的區塊。將所有區塊堆疊起來以建立新的資料集。
>>> def chunk_examples(examples):
... chunks = []
... for sentence in examples["sentence1"]:
... chunks += [sentence[i:i + 50] for i in range(0, len(sentence), 50)]
... return {"chunks": chunks}使用 map() 應用該函數。
>>> chunked_dataset = dataset.map(chunk_examples, batched=True, remove_columns=dataset.column_names)
>>> chunked_dataset[:10]
{'chunks': ['Amrozi accused his brother , whom he called " the ',
'witness " , of deliberately distorting his evidenc',
'e .',
"Yucaipa owned Dominick 's before selling the chain",
' to Safeway in 1998 for $ 2.5 billion .',
'They had published an advertisement on the Interne',
't on June 10 , offering the cargo for sale , he ad',
'ded .',
'Around 0335 GMT , Tab shares were up 19 cents , or',
' 4.4 % , at A $ 4.56 , having earlier set a record']}請注意現在句子是如何被分割成較短區塊的,且資料集中的資料列也變多了。
>>> dataset
Dataset({
features: ['sentence1', 'sentence2', 'label', 'idx'],
num_rows: 3668
})
>>> chunked_dataset
Dataset({
features: ['chunks'],
num_rows: 10470
})資料增強 (Data augmentation)
map() 函數也可用於資料增強。下方的範例為句子中的遮蔽標記 (masked token) 產生額外的單字。
在 🤗 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 所補充。
非同步處理 (Asynchronous processing)
非同步函數對於並行呼叫 API 端點很有用,例如下載影像等內容或呼叫模型端點。
您可以使用 async 與 await 關鍵字定義一個非同步函數,這裡是一個呼叫 Hugging Face 聊天模型的範例函數。
>>> import aiohttp
>>> import asyncio
>>> from huggingface_hub import get_token
>>> sem = asyncio.Semaphore(20) # max number of simultaneous queries
>>> async def query_model(model, prompt):
... api_url = f"https://api-inference.huggingface.co/models/{model}/v1/chat/completions"
... headers = {"Authorization": f"Bearer {get_token()}", "Content-Type": "application/json"}
... json = {"messages": [{"role": "user", "content": prompt}], "max_tokens": 20, "seed": 42}
... async with sem, aiohttp.ClientSession() as session, session.post(api_url, headers=headers, json=json) as response:
... output = await response.json()
... return {"Output": output["choices"][0]["message"]["content"]}非同步函數並行執行,這大大加速了處理程序。如果順序執行,同樣的程式碼會花費更多時間,因為它在等待模型回應時處於閒置狀態。通常建議在函數必須等待 API 回應(例如下載資料)且可能會花費一些時間時使用 async / await。
請注意 Semaphore 的存在:它設定了可以並行執行的最大查詢數量。建議在呼叫 API 時使用 Semaphore 以避免速率限制錯誤。
讓我們使用它來呼叫 microsoft/Phi-3-mini-4k-instruct 模型,並要求它回傳 Maxwell-Jia/AIME_2024 資料集中每個數學問題的主題。
>>> from datasets import load_dataset
>>> ds = load_dataset("Maxwell-Jia/AIME_2024", split="train")
>>> model = "microsoft/Phi-3-mini-4k-instruct"
>>> prompt = 'What is this text mainly about ? Here is the text:\n\n```\n{Problem}\n```\n\nReply using one or two words max, e.g. "The main topic is Linear Algebra".'
>>> async def get_topic(example):
... return await query_model(model, prompt.format(Problem=example['Problem']))
>>> ds = ds.map(get_topic)
>>> ds[0]
{'ID': '2024-II-4',
'Problem': 'Let {@html ""} and {@html ""} be positive real numbers that...',
'Solution': 'Denote $\\log_2(x) = a$, $\\log_2(y) = b$, and...,
'Answer': 33,
'Output': 'The main topic is Logarithms.'}在這裡,Dataset.map() 非同步執行了許多 get_topic 函數,因此不需要等待每次模型回應,這在順序執行時會耗費大量時間。
預設情況下,Dataset.map() 最多可並行執行一千個對映函數,因此請記得用 Semaphore 設定可並行執行的最大 API 呼叫數量,否則模型可能會回傳速率限制錯誤或過載。對於進階使用情境,您可以在 datasets.config 中變更並行查詢的最大數量。
處理多個分割
許多資料集擁有多個分割,可以使用 DatasetDict.map() 同時處理。例如,將訓練與測試分割中的 sentence1 欄位 Tokenize:
>>> 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]
}分散式使用 (Distributed usage)
當您在分散式環境中使用 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)
如果獨立的資料集共用相同的欄位型別,則可以將它們串接在一起。使用 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)交錯 (Interleave)
您也可以透過從每個資料集中輪流選取樣本來混合多個資料集,以建立一個新的資料集。這稱為交錯 (interleaving),由 interleave_datasets() 函數啟用。interleave_datasets() 與 concatenate_datasets() 皆可處理一般的 Dataset 與 IterableDataset 物件。請參閱 串流 (Stream) 指南,以獲取關於如何交錯 IterableDataset 物件的範例。
您可以為每個原始資料集定義取樣機率,以指定如何交錯資料集。在此情況下,新的資料集是透過從隨機資料集中逐一獲取樣本來建構的,直到其中一個資料集的樣本用盡為止。
>>> from datasets import Dataset, interleave_datasets
>>> seed = 42
>>> probabilities = [0.3, 0.5, 0.2]
>>> d1 = Dataset.from_dict({"a": [0, 1, 2]})
>>> d2 = Dataset.from_dict({"a": [10, 11, 12, 13]})
>>> d3 = Dataset.from_dict({"a": [20, 21, 22]})
>>> dataset = interleave_datasets([d1, d2, d3], probabilities=probabilities, seed=seed)
>>> dataset["a"]
[10, 11, 20, 12, 0, 21, 13]您也可以指定 stopping_strategy。預設策略 first_exhausted 是一種次取樣 (subsampling) 策略,即一旦有其中一個資料集樣本用盡,就會停止資料集建構。您可以指定 stopping_strategy=all_exhausted 來執行過度取樣 (oversampling) 策略。在此情況下,一旦每個資料集中的所有樣本至少被新增過一次,資料集建構就會停止。實際上,這意味著如果一個資料集用盡,它將回到該資料集的開頭,直到達到停止條件。請注意,如果未指定取樣機率,新的資料集將有 max_length_datasets*nb_dataset 個樣本。還有 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]格式 (Format)
with_format() 函數會變更欄位格式以與某些常見資料格式相容。請在 type 參數中指定您想要的輸出。您也可以使用 columns= 選擇要格式化的欄位。格式化是即時 (on-the-fly) 應用的。
例如,設定 type="torch" 來建立 PyTorch 張量。
>>> dataset = dataset.with_format(type="torch")set_format() 函數也會變更欄位格式,差別在於它是原地 (in-place) 執行的。
>>> 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}張量格式 (Tensors formats)
支援多種張量或陣列格式。通常建議使用這些格式,而不是手動將資料集的輸出轉換為張量或陣列,以避免不必要的資料複製並加速資料載入。
以下是支援的張量或陣列格式列表:
- NumPy:格式名稱為 “numpy”,更多資訊請見 Using Datasets with NumPy
- PyTorch:格式名稱為 “torch”,更多資訊請見 Using Datasets with PyTorch
- TensorFlow:格式名稱為 “tensorflow”,更多資訊請見 Using Datasets with TensorFlow
- JAX:格式名稱為 “jax”,更多資訊請見 Using Datasets with JAX
請查閱 Using Datasets with TensorFlow 指南,以獲取關於如何高效建立 TensorFlow 資料集的更多詳細資訊。
當資料集以張量或陣列格式化時,所有資料都會格式化為張量或陣列 (不支援的型別除外,例如 PyTorch 不支援字串)。
>>> ds = Dataset.from_dict({"text": ["foo", "bar"], "tokens": [[0, 1, 2], [3, 4, 5]]})
>>> ds = ds.with_format("torch")
>>> ds[0]
{'text': 'foo', 'tokens': tensor([0, 1, 2])}
>>> ds[:2]
{'text': ['foo', 'bar'],
'tokens': tensor([[0, 1, 2],
[3, 4, 5]])}表格格式 (Tabular formats)
您可以使用 DataFrame 或表格格式來優化資料載入與處理,因為它們通常提供零複製 (zero-copy) 操作,並以低階語言編寫轉換過程。
以下是支援的 DataFrame 或表格格式列表:
- Pandas:格式名稱為 “pandas”,更多資訊請見 Using Datasets with Pandas
- Polars:格式名稱為 “polars”,更多資訊請見 Using Datasets with Polars
- PyArrow:格式名稱為 “arrow”,更多資訊請見 Using Datasets with PyArrow
當資料集以 DataFrame 或表格格式格式化時,每個資料集資料列或批次資料列會格式化為 DataFrame 或表格,且資料集欄位會格式化為 Series 或陣列。
>>> ds = Dataset.from_dict({"text": ["foo", "bar"], "label": [0, 1]})
>>> ds = ds.with_format("pandas")
>>> ds[:2]
text label
0 foo 0
1 bar 1這些格式透過避免資料複製,使得對資料進行迭代的速度更快,同時也啟用了在 map() 或 filter() 中更快的資料處理。
>>> ds = ds.map(lambda df: df.assign(upper_text=df.text.str.upper()), batched=True)
>>> ds[:2]
text label upper_text
0 foo 0 FOO
1 bar 1 BAR自訂格式轉換 (Custom format transform)
with_transform() 函數會即時應用自訂格式轉換。此函數會取代先前指定的任何格式。例如,您可以使用此函數來即時 Tokenize 並填充 (pad) 標記。Tokenization 僅在存取樣本時才會應用。
>>> 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(),其執行相同操作但為原地 (in-place) 執行。
您也可以使用 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)儲存 (Save)
當您的資料集準備好後,可以將其儲存為 Parquet 格式的 Hugging Face 資料集,並稍後使用 load_dataset() 進行重複使用。
透過將您想要儲存到的 Hugging Face 資料集儲存庫名稱提供給 push_to_hub() 來儲存您的資料集。
encoded_dataset.push_to_hub("username/my_dataset")您可以使用多個處理程序來並行上傳,如果您想加速過程,這特別有用。
dataset.push_to_hub("username/my_dataset", num_proc=8)使用 load_dataset() 函數來重新載入資料集 (無論是否在串流模式下)。
from datasets import load_dataset
reloaded_dataset = load_dataset("username/my_dataset", streaming=True)或者,您可以將其本地儲存在磁碟上的 Arrow 格式中。與 Parquet 相比,Arrow 是未壓縮的,這使得重新載入速度更快,非常適合在磁碟上本地使用及臨時快取。但由於其體積較大且元資料較少,它在上傳/下載/查詢時比 Parquet 慢,較不適合長期儲存。
使用 save_to_disk() 與 load_from_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")匯出 (Export)
🤗 Datasets 也支援匯出,以便您可以在其他應用程式中使用資料集。下表顯示了目前支援的匯出檔案格式:
| 檔案類型 | 匯出方法 |
|---|---|
| CSV | Dataset.to_csv() |
| JSON | Dataset.to_json() |
| Parquet | Dataset.to_parquet() |
| SQL | Dataset.to_sql() |
| 記憶體中 Python 物件 | Dataset.to_pandas(), Dataset.to_polars() 或 Dataset.to_dict() |
例如,像這樣將您的資料集匯出為 CSV 檔案:
>>> encoded_dataset.to_csv("path/of/my/dataset.csv")使用 hf:// 路徑來匯出到 資料集儲存庫 (Dataset repository) 或 Hugging Face 上的 儲存桶 (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")