Datasets documentation

Quy trình

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

Quy trình

🤗 Datasets cung cấp nhiều công cụ để thay đổi cấu trúc và nội dung của một dataset. Các công cụ này rất quan trọng để dọn dẹp dataset, tạo thêm các cột, chuyển đổi giữa các feature và định dạng, và nhiều hơn nữa.

Hướng dẫn này sẽ cho bạn thấy cách:

  • Sắp xếp lại các hàng và chia bộ dữ liệu.
  • Đổi tên và xóa các cột, cùng các thao tác cột phổ biến khác.
  • Áp dụng các hàm xử lý cho từng ví dụ trong một tập dữ liệu.
  • Nối các bộ dữ liệu.
  • Áp dụng một phép biến đổi định dạng tùy chỉnh.
  • Lưu và xuất các bộ dữ liệu đã xử lý.

Để biết thêm chi tiết cụ thể về việc xử lý các chế độ dữ liệu khác, hãy xem hướng dẫn xử lý tập dữ liệu âm thanh, hướng dẫn xử lý tập dữ liệu hình ảnh hoặc hướng dẫn xử lý tập dữ liệu văn bản.

Các ví dụ trong hướng dẫn này sử dụng bộ dữ liệu MRPC, nhưng bạn có thể tự do tải bất kỳ bộ dữ liệu nào bạn muốn và làm theo!

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

Tất cả các phương thức xử lý trong hướng dẫn này đều trả về một đối tượng Dataset mới. Việc sửa đổi không được thực hiện tại chỗ. Hãy cẩn thận khi ghi đè lên dataset trước đó của bạn!

Sắp xếp, xáo trộn, chọn, tách và chia

Có một số hàm để sắp xếp lại cấu trúc của một dataset. Các hàm này hữu ích để chỉ chọn các dòng bạn muốn, tạo các phần chia train và test, và chia nhỏ các dataset rất lớn thành các phần nhỏ hơn.

Sắp xếp

Sử dụng sort() để sắp xếp các giá trị trong cột theo giá trị số của chúng. Cột được cung cấp phải tương thích với 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]

Bên trong, thao tác này tạo ra một danh sách các chỉ số được sắp xếp theo giá trị của cột. Bản ánh xạ chỉ số này sau đó được dùng để truy cập các hàng tương ứng trong bảng Arrow bên dưới.

Xáo trộn

Hàm shuffle() sắp xếp lại ngẫu nhiên các giá trị trong cột. Bạn có thể chỉ định tham số generator trong hàm này để sử dụng một numpy.random.Generator khác nếu bạn muốn kiểm soát nhiều hơn thuật toán được dùng để xáo trộn bộ dữ liệu.

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

Shuffling lấy danh sách các chỉ số [0:len(my_dataset)] và xáo trộn nó để tạo ra một ánh xạ chỉ số. Tuy nhiên, ngay khi Dataset của bạn có một ánh xạ chỉ số, tốc độ có thể chậm đi 10 lần. Điều này là do có một bước bổ sung để lấy chỉ số hàng cần đọc bằng cách sử dụng ánh xạ chỉ số, và quan trọng nhất là bạn không còn đọc các khối dữ liệu liên tục nữa. Để khôi phục tốc độ, bạn cần ghi đè lại toàn bộ dataset trên đĩa của mình bằng cách sử dụng Dataset.flatten_indices(), hàm này sẽ loại bỏ ánh xạ chỉ số. Ngoài ra, bạn có thể chuyển sang IterableDataset và tận dụng tính năng xáo trộn gần đúng nhanh của nó IterableDataset.shuffle():

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

Chọn và Lọc

Có hai tùy chọn để lọc các dòng trong một tập dữ liệu: select() và filter().

  • select() trả về các hàng theo một danh sách các chỉ số:
>>> small_dataset = dataset.select([0, 10, 20, 30, 40, 50])
>>> len(small_dataset)
6
  • filter() trả về các dòng khớp với điều kiện được chỉ định:
>>> 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() cũng có thể lọc theo chỉ số nếu bạn đặt 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

Trừ khi danh sách các chỉ số cần giữ là liên tục, các phương pháp đó cũng tạo ra một ánh xạ chỉ số bên trong.

Tách

Hàm train_test_split() tạo ra các tập huấn luyện và kiểm tra nếu bộ dữ liệu của bạn chưa có sẵn. Điều này cho phép bạn điều chỉnh tỷ lệ tương đối hoặc số lượng mẫu tuyệt đối trong mỗi tập. Trong ví dụ bên dưới, hãy sử dụng tham số test_size để tạo một tập kiểm tra chiếm 10% bộ dữ liệu gốc:

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

Các phần chia (splits) được xáo trộn (shuffle) theo mặc định, nhưng bạn có thể đặt shuffle=False để ngăn việc xáo trộn.

Shard

🤗 Datasets hỗ trợ sharding để chia một dataset rất lớn thành một số lượng chunk đã định trước. Chỉ định tham số num_shards trong shard() để xác định số shard cần chia dataset thành. Bạn cũng cần cung cấp shard muốn trả về bằng tham số index.

Ví dụ, bộ dữ liệu stanfordnlp/imdb có 25000 ví dụ:

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

Sau khi chia bộ dữ liệu thành bốn phần, phần đầu tiên sẽ chỉ có 6250 ví dụ:

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

Đổi tên, xóa, chuyển kiểu và làm phẳng

Các hàm sau cho phép bạn sửa đổi các cột của một dataset. Những hàm này hữu ích để đổi tên hoặc xóa các cột, thay đổi các cột thành một tập hợp các đặc trưng mới, và làm phẳng các cấu trúc cột lồng nhau.

Đổi tên

Sử dụng rename_column() khi bạn cần đổi tên một cột trong dataset của mình. Các đặc trưng (features) liên quan đến cột gốc thực chất được chuyển sang dưới tên cột mới, thay vì chỉ thay thế cột gốc tại chỗ.

Cung cấp cho rename_column() tên của cột gốc và tên cột mới:

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

Xóa

Khi cần xóa một hoặc nhiều cột, hãy cung cấp tên cột cần xóa cho hàm remove_columns(). Để xóa nhiều hơn một cột, hãy cung cấp một danh sách các tên cột:

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

Ngược lại, select_columns() chọn một hoặc nhiều cột cần giữ lại và xóa các cột còn lại. Hàm này nhận một tên cột hoặc một danh sách các tên cột:

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

Hàm cast() chuyển đổi kiểu đặc trưng của một hoặc nhiều cột. Hàm này nhận Features mới của bạn làm đối số. Ví dụ dưới đây minh họa cách thay đổi các đặc trưng ClassLabel và 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 chỉ hoạt động nếu kiểu đặc trưng gốc và kiểu đặc trưng mới tương thích. Ví dụ, bạn có thể cast một cột có kiểu đặc trưng Value("int32") thành Value("bool") nếu cột gốc chỉ chứa các giá trị một và không.

Sử dụng hàm cast_column() để thay đổi kiểu đặc trưng của một cột duy nhất. Truyền tên cột và kiểu đặc trưng mới của nó dưới dạng các đối số:

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

Dàn phẳng

Đôi khi một cột có thể là một cấu trúc lồng nhau của nhiều kiểu dữ liệu. Hãy xem cấu trúc lồng nhau bên dưới từ bộ dữ liệu 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'))}}

Trường answers chứa hai trường con: text và answer_start. Sử dụng hàm flatten() để trích xuất các trường con thành các cột riêng biệt:

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

Chú ý rằng các trường con giờ đã là các cột độc lập: answers.text và answers.answer_start.

Bản đồ

Một số ứng dụng mạnh mẽ hơn của 🤗 Datasets đến từ việc sử dụng hàm map(). Mục đích chính của map() là tăng tốc các hàm xử lý. Nó cho phép bạn áp dụng một hàm xử lý cho từng ví dụ trong bộ dữ liệu, độc lập hoặc theo các lô. Hàm này thậm chí có thể tạo ra các hàng và cột mới.

Trong ví dụ sau, thêm tiền tố 'My sentence: ' cho mỗi giá trị sentence1 trong bộ dữ liệu.

Bắt đầu bằng cách tạo một hàm thêm 'My sentence: ' vào đầu mỗi câu. Hàm cần nhận và trả về một dict:

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

Bây giờ hãy sử dụng map() để áp dụng hàm add_prefix lên toàn bộ tập dữ liệu:

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

Hãy xem một ví dụ khác, lần này bạn sẽ xóa một cột bằng map(). Khi bạn xóa một cột, nó chỉ bị xóa sau khi ví dụ đã được cung cấp cho hàm được map. Điều này cho phép hàm được map sử dụng nội dung của các cột trước khi chúng bị xóa.

Chỉ định cột cần xóa bằng tham số remove_columns trong map():

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

🤗 Datasets cũng có hàm remove_columns() nhanh hơn vì nó không sao chép dữ liệu của các cột còn lại.

Bạn cũng có thể sử dụng map() với các chỉ số nếu bạn đặt with_indices=True. Ví dụ bên dưới thêm chỉ số vào đầu mỗi câu:

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

Xử lý đa tiến trình

Multiprocessing tăng tốc đáng kể quá trình xử lý bằng cách song song hóa các tiến trình trên CPU. Đặt tham số num_proc trong map() để xác định số lượng tiến trình cần sử dụng:

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

map() cũng hoạt động với rank của tiến trình nếu bạn đặt with_rank=True. Điều này tương tự như tham số with_indices. Tham số with_rank trong hàm được ánh xạ sẽ đứng sau tham số index nếu tham số đó đã có mặt.

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

Use-case chính của rank là song song hóa tính toán trên nhiều GPU. Điều này yêu cầu thiết lập multiprocess.set_start_method("spawn"). Nếu không, bạn sẽ nhận được lỗi CUDA sau:

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

Xử lý theo lô

Hàm map() hỗ trợ làm việc với các lô (batch) ví dụ. Thao tác trên các lô bằng cách đặt batched=True. Kích thước lô mặc định là 1000, nhưng bạn có thể điều chỉnh bằng tham số batch_size. Xử lý theo lô cho phép các ứng dụng thú vị như chia các câu dài thành các đoạn ngắn hơn và tăng cường dữ liệu.

Tách các ví dụ dài

Khi các ví dụ quá dài, bạn có thể muốn chia chúng thành nhiều phần nhỏ hơn. Hãy bắt đầu bằng cách tạo một hàm mà:

  1. Chia trường sentence1 thành các đoạn 50 ký tự.

  2. Ghép tất cả các phần (chunks) lại với nhau để tạo bộ dữ liệu mới.

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

Áp dụng hàm bằng 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']}

Hãy chú ý cách các câu được chia thành các đoạn ngắn hơn, và có nhiều hàng hơn trong bộ dữ liệu.

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

Tăng cường dữ liệu

Hàm map() cũng có thể được sử dụng để tăng cường dữ liệu. Ví dụ sau tạo thêm các từ cho một token bị che trong một câu.

Tải và sử dụng mô hình RoBERTa trong FillMaskPipeline của 🤗 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)

Tạo một hàm để chọn ngẫu nhiên một từ để che trong câu. Hàm cũng cần trả về câu gốc và hai từ thay thế hàng đầu do RoBERTa tạo ra.

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

Sử dụng map() để áp dụng hàm cho toàn bộ tập dữ liệu:

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

Với mỗi câu gốc, RoBERTA đã bổ sung ba lựa chọn thay thế cho một từ ngẫu nhiên. Từ gốc distorting được bổ sung bởi withholding, suppressing và destroying.

Xử lý bất đồng bộ

Các hàm bất đồng bộ (asynchronous) hữu ích để gọi các điểm cuối API song song, chẳng hạn như để tải xuống nội dung như hình ảnh hoặc gọi một điểm cuối mô hình.

Bạn có thể định nghĩa một hàm bất đồng bộ bằng cách sử dụng từ khóa async và await, dưới đây là một ví dụ về hàm để gọi một mô hình chat từ 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"/static-proxy?url=https%3A%2F%2Fapi-inference.huggingface.co%2Fmodels%2F%3Cspan class="hljs-subst">{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"]}

Các hàm bất đồng bộ chạy song song, giúp tăng tốc quy trình đáng kể. Cùng một đoạn mã sẽ mất nhiều thời gian hơn rất nhiều nếu chạy tuần tự, vì nó không làm gì trong khi chờ phản hồi từ mô hình. Nhìn chung, bạn nên sử dụng async / await khi hàm của bạn phải chờ phản hồi từ một API, chẳng hạn, hoặc khi nó tải dữ liệu và có thể mất một khoảng thời gian.

Lưu ý sự hiện diện của Semaphore: nó đặt ra số lượng tối đa các truy vấn có thể chạy song song. Khuyến nghị sử dụng Semaphore khi gọi API để tránh các lỗi giới hạn tần suất (rate limit).

Hãy dùng nó để gọi mô hình microsoft/Phi-3-mini-4k-instruct và yêu cầu nó trả về chủ đề chính của từng bài toán trong bộ dữ liệu 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.'}

Ở đây, Dataset.map() chạy nhiều hàm get_topic bất đồng bộ để không phải chờ từng phản hồi của mô hình, điều sẽ mất rất nhiều thời gian nếu thực hiện tuần tự.

Mặc định, Dataset.map() chạy tối đa một nghìn hàm map song song, vì vậy đừng quên đặt số lượng tối đa các cuộc gọi API có thể chạy song song bằng Semaphore, nếu không mô hình có thể trả về lỗi giới hạn tần suất hoặc quá tải. Đối với các trường hợp sử dụng nâng cao, bạn có thể thay đổi số lượng truy vấn song song tối đa trong datasets.config.

Xử lý nhiều phần chia

Nhiều dataset có các split có thể được xử lý đồng thời bằng DatasetDict.map(). Ví dụ, tokenize trường sentence1 trong split train và test bằng cách:

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

Sử dụng phân tán

Khi bạn sử dụng map() trong môi trường phân tán, bạn cũng nên sử dụng torch.distributed.barrier. Điều này đảm bảo tiến trình chính thực hiện ánh xạ, trong khi các tiến trình khác tải kết quả, từ đó tránh việc thực hiện trùng lặp.

Ví dụ sau cho thấy cách bạn có thể sử dụng torch.distributed.barrier để đồng bộ hóa các tiến trình:

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

Phương thức batch() cho phép bạn nhóm các mẫu từ tập dữ liệu thành các lô. Điều này đặc biệt hữu ích khi bạn muốn tạo các lô dữ liệu để huấn luyện hoặc đánh giá, nhất là khi làm việc với các mô hình học sâu.

Dưới đây là một ví dụ về cách sử dụng phương thức 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]}

Phương thức batch() chấp nhận các tham số sau:

  • batch_size (int): Số lượng mẫu trong mỗi batch.
  • drop_last_batch (bool, mặc định là False): Xác định có loại bỏ lô cuối cùng không hoàn chỉnh hay không nếu kích thước bộ dữ liệu không chia hết cho kích thước lô.
  • num_proc (int, tùy chọn, mặc định là None): Số lượng tiến trình để sử dụng cho đa tiến trình. Nếu là None, sẽ không sử dụng đa tiến trình. Điều này có thể tăng tốc đáng kể việc tạo batch cho các bộ dữ liệu lớn.

Lưu ý rằng Dataset.batch() trả về một Dataset mới, trong đó mỗi phần tử là một batch gồm nhiều mẫu từ dataset gốc. Nếu bạn muốn xử lý dữ liệu theo batch, bạn nên sử dụng map() dạng batch trực tiếp, trong đó một hàm được áp dụng lên các batch nhưng dataset đầu ra không ở dạng batch.

Nối

Các tập dữ liệu riêng biệt có thể được nối với nhau nếu chúng có cùng kiểu cột. Nối các tập dữ liệu bằng 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])

Bạn cũng có thể nối hai dataset theo chiều ngang bằng cách đặt axis=1 miễn là các dataset có cùng số dòng:

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

Xen kẽ

Bạn cũng có thể trộn lẫn nhiều tập dữ liệu với nhau bằng cách lấy các ví dụ xen kẽ từ mỗi tập để tạo thành một tập dữ liệu mới. Cách này được gọi là interleaving, được thực hiện bằng hàm interleave_datasets(). Cả interleave_datasets() và concatenate_datasets() đều hoạt động với các đối tượng Dataset và IterableDataset thông thường. Tham khảo hướng dẫn Stream để xem ví dụ về cách interleave các đối tượng IterableDataset.

Bạn có thể xác định xác suất lấy mẫu cho từng bộ dữ liệu gốc để chỉ định cách xen kẽ các bộ dữ liệu. Trong trường hợp này, bộ dữ liệu mới được xây dựng bằng cách lấy các ví dụ lần lượt từ một bộ dữ liệu ngẫu nhiên cho đến khi một trong các bộ dữ liệu hết mẫu.

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

Bạn cũng có thể chỉ định stopping_strategy. Chiến lược mặc định, first_exhausted, là chiến lược lấy mẫu con, tức là việc xây dựng tập dữ liệu sẽ dừng lại ngay khi một trong các tập dữ liệu hết mẫu. Bạn có thể chỉ định stopping_strategy=all_exhausted để thực thi chiến lược lấy mẫu quá mức. Trong trường hợp này, việc xây dựng tập dữ liệu sẽ dừng lại ngay khi mọi mẫu trong mọi tập dữ liệu đã được thêm vào ít nhất một lần. Trên thực tế, điều này có nghĩa là nếu một tập dữ liệu bị cạn, nó sẽ quay lại đầu tập dữ liệu đó cho đến khi đạt được tiêu chí dừng. Lưu ý rằng nếu không chỉ định xác suất lấy mẫu, tập dữ liệu mới sẽ có max_length_datasets*nb_dataset samples. Cũng có stopping_strategy=all_exhausted_without_replacement để đảm bảo rằng mỗi mẫu được xem đúng một lần.

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

Định dạng

Hàm with_format() thay đổi định dạng của một cột để tương thích với một số định dạng dữ liệu phổ biến. Chỉ định đầu ra mong muốn trong tham số type. Bạn cũng có thể chọn các cột cần định dạng bằng cách sử dụng columns=. Việc định dạng được áp dụng theo thời gian thực.

Ví dụ, tạo các tensor PyTorch bằng cách đặt type="torch":

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

Hàm set_format() cũng thay đổi định dạng của một cột, ngoại trừ việc nó chạy tại chỗ:

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

Nếu bạn cần đặt lại bộ dữ liệu về định dạng ban đầu, hãy đặt format thành None (hoặc sử dụng 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}

Định dạng Tensors

Nhiều định dạng tensor hoặc mảng được hỗ trợ. Về mặt chung, việc sử dụng các định dạng này được khuyến nghị thay vì tự động chuyển đổi đầu ra của một dataset sang tensor hoặc mảng, nhằm tránh các bản sao dữ liệu không cần thiết và tăng tốc quá trình nạp dữ liệu.

Dưới đây là danh sách các định dạng tensor hoặc mảng được hỗ trợ:

Xem hướng dẫn Sử dụng Datasets với TensorFlow để biết thêm chi tiết về cách tạo hiệu quả một dataset TensorFlow.

Khi một tập dữ liệu được định dạng ở dạng tensor hoặc mảng, tất cả dữ liệu đều được định dạng dưới dạng tensor hoặc mảng (ngoại trừ các kiểu không được hỗ trợ, chẳng hạn như chuỗi trong trường hợp của 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]])}

Định dạng bảng

Bạn có thể sử dụng định dạng dataframes hoặc tables để tối ưu hóa việc tải và xử lý dữ liệu, vì chúng thường cung cấp các thao tác và biến đổi zero-copy được viết bằng các ngôn ngữ cấp thấp.

Dưới đây là danh sách các định dạng dataframe hoặc bảng được hỗ trợ:

Khi một tập dữ liệu được định dạng dưới dạng dataframe hoặc bảng, mỗi hàng hoặc các lô hàng trong tập dữ liệu sẽ được định dạng dưới dạng dataframe hoặc bảng, và các cột của tập dữ liệu sẽ được định dạng dưới dạng series hoặc mảng:

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

Các định dạng này cho phép lặp lại trên dữ liệu nhanh hơn bằng cách tránh sao chép dữ liệu, đồng thời cho phép xử lý dữ liệu nhanh hơn trong map() hoặc 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

Biến đổi định dạng tùy chỉnh

Hàm with_transform() áp dụng một phép biến đổi định dạng tùy chỉnh theo thời gian thực. Hàm này thay thế bất kỳ định dạng nào đã được chỉ định trước đó. Ví dụ, bạn có thể sử dụng hàm này để thực hiện token hóa và đệm (padding) các token theo thời gian thực. Token hóa chỉ được áp dụng khi các ví dụ được truy cập:

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

Ngoài ra còn có set_transform() thực hiện cùng chức năng nhưng chạy tại chỗ.

Bạn cũng có thể sử dụng hàm with_transform() để giải mã tùy chỉnh trên Features.

Ví dụ bên dưới sử dụng gói pydub như một phương án thay thế cho việc giải mã bằng 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)

Lưu

Khi bộ dữ liệu của bạn đã sẵn sàng, bạn có thể lưu nó dưới dạng Hugging Face Dataset ở định dạng Parquet và sử dụng lại sau này với load_dataset().

Lưu bộ dữ liệu của bạn bằng cách cung cấp tên kho dữ liệu trên Hugging Face mà bạn muốn lưu vào cho push_to_hub():

encoded_dataset.push_to_hub("username/my_dataset")

Bạn có thể sử dụng nhiều tiến trình để tải lên song song. Điều này đặc biệt hữu ích nếu bạn muốn tăng tốc quá trình:

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

Sử dụng hàm load_dataset() để tải lại bộ dữ liệu (chế độ streaming hoặc không):

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

Ngoài ra, bạn có thể lưu cục bộ trên đĩa ở định dạng Arrow. So với Parquet, Arrow không nén nên tốc độ nạp lại nhanh hơn nhiều, rất phù hợp cho việc sử dụng cục bộ trên đĩa và bộ nhớ đệm tạm thời. Tuy nhiên, do kích thước lớn hơn và ít metadata hơn, Arrow chậm hơn Parquet khi tải lên/tải xuống/truy vấn và kém phù hợp hơn cho lưu trữ dài hạn.

Sử dụng hàm save_to_disk() và load_from_disk() để tải lại bộ dữ liệu từ ổ đĩa của bạn:

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

Xuất

🤗 Datasets hỗ trợ xuất dữ liệu để bạn có thể làm việc với bộ dữ liệu của mình trong các ứng dụng khác. Bảng dưới đây liệt kê các định dạng tệp hiện được hỗ trợ để xuất:

Loại tệpPhương thức xuất
CSVDataset.to_csv()
JSONDataset.to_json()
ParquetDataset.to_parquet()
SQLDataset.to_sql()
Đối tượng Python trong bộ nhớDataset.to_pandas(), Dataset.to_polars() or Dataset.to_dict()

Ví dụ, xuất bộ dữ liệu của bạn ra tệp CSV như sau:

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

Sử dụng đường dẫn hf:// để xuất ra kho Dataset hoặc Storage Bucket trên 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