Datasets documentation

프로세스

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

and get access to the augmented documentation experience

to get started

프로세스

🤗 Datasets는 데이터셋의 구조와 내용을 수정하기 위한 다양한 도구를 제공합니다. 이러한 도구는 데이터셋을 정리하고, 추가 열을 생성하며, 기능과 형식 간 변환을 수행하는 등 다양한 작업에 중요합니다.

이 가이드에서는 다음을 보여줍니다:

  • 행을 재배열하고 데이터셋을 분할합니다.
  • 열 이름 변경 및 삭제, 기타 일반적인 열 작업.
  • 데이터셋의 각 예제에 처리 함수를 적용합니다.
  • 데이터셋을 연결합니다.
  • 사용자 지정 포맷 변환을 적용합니다.
  • 처리된 데이터셋을 저장하고 내보냅니다.

다른 데이터셋 모달리티 처리에 대한 자세한 내용은 오디오 데이터셋 처리 가이드, 이미지 데이터셋 처리 가이드, 또는 텍스트 데이터셋 처리 가이드를 참고하세요.

이 가이드의 예제는 MRPC 데이터셋을 사용하지만, 원하는 데이터셋을 로드하여 따라 해보셔도 됩니다!

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

이 가이드의 모든 처리 메서드는 새로운 Dataset 객체를 반환합니다. 변경은 인플레이스(in-place)로 수행되지 않습니다. 이전 데이터셋을 덮어쓰지 않도록 주의하세요!

정렬, 섞기, 선택, 분할 및 샤딩

데이터셋의 구조를 재배치하는 여러 함수가 있습니다. 이러한 함수는 원하는 행만 선택하거나, 학습 및 테스트 분할을 생성하거나, 매우 큰 데이터셋을 더 작은 조각으로 샤딩하는 데 유용합니다.

정렬

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() 함수는 열의 값을 무작위로 재배열합니다. 데이터셋을 섞는 데 사용되는 알고리즘에 대한 더 세밀한 제어가 필요하면, 이 함수의 generator 매개변수를 지정하여 다른 numpy.random.Generator를 사용할 수 있습니다.

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

Shuffling은 인덱스 목록 [0:len(my_dataset)]을 섞어 인덱스 매핑을 생성합니다. 하지만 Dataset에 인덱스 매핑이 있는 순간 속도가 10배까지 느려질 수 있습니다. 이유는 인덱스 매핑을 사용해 읽을 행 인덱스를 가져오는 추가 단계가 발생하고, 무엇보다 더 이상 연속된 데이터 청크를 읽지 않기 때문입니다. 속도를 회복하려면 인덱스 매핑을 제거하는 Dataset.flatten_indices()를 사용해 디스크에 전체 데이터셋을 다시 작성해야 합니다. 대신 IterableDataset으로 전환하여 빠른 근사 셔플링 IterableDataset.shuffle()을 활용할 수도 있습니다:

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

선택 및 필터링

데이터셋에서 행을 필터링하는 두 가지 옵션이 있습니다: select()와 filter().

  • 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.56,havingearliersetarecordhighofA 4.56 , having earlier set a record high of A "} 4.57 .',
'Arison said Mann may have been one of the pioneers of the world music movement and he had a deep love of Brazilian music .',
'Arts helped coach the youth on an eighth-grade football team at Lombardi Middle School in Green Bay .',
'Around 9 : 00 a.m. EDT ( 1300 GMT ) , the euro was at $ 1.1566 against the dollar , up 0.07 percent on the day .',
"Arguing that the case was an isolated example , Canada has threatened a trade backlash if Tokyo 's ban is not justified on scientific grounds .",
'Artists are worried the plan would harm those who need help most - performers who have a difficult time lining up shows .'
]

with_indices=True를 설정하면 filter()는 인덱스로도 필터링할 수 있습니다:

>>> even_dataset = dataset.filter(lambda example, idx: idx % 2 == 0, with_indices=True)
>>> len(even_dataset)
1834
>>> len(dataset) / 2
1834.0

keep할 인덱스 목록이 연속적이지 않은 경우, 해당 메서드들은 내부적으로 인덱스 매핑을 생성합니다.

분할

데이터셋에 이미 학습/테스트 분할이 없는 경우 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

기본적으로 splits는 섞여 있지만, shuffle=False를 설정하여 섞지 않도록 할 수 있습니다.

Shard

🤗 Datasets는 매우 큰 데이터셋을 미리 정의된 개수의 청크로 나누는 샤딩을 지원합니다. shard()에서 num_shards 매개변수를 지정하여 데이터셋을 몇 개의 샤드로 나눌지 결정합니다. 또한 index 매개변수를 통해 반환할 샤드를 지정해야 합니다.

예를 들어, stanfordnlp/imdb 데이터셋에는 25,000개의 예시가 있습니다:

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

데이터셋을 4개의 청크로 샤딩한 후, 첫 번째 샤드에는 6250개의 예시만 포함됩니다:

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

rename, remove, cast, flatten

다음 함수들은 데이터셋의 열을 수정할 수 있습니다. 이러한 함수는 열 이름 변경 또는 열 제거, 열을 새로운 특징 집합으로 변경, 중첩된 열 구조를 평탄화하는 데 유용합니다.

이름 변경

데이터셋의 컬럼 이름을 변경해야 할 때는 rename_column()을 사용합니다. 원래 컬럼에 연결된 기능은 단순히 원본 컬럼을 제자리에서 대체하는 것이 아니라, 실제로 새 컬럼 이름 아래로 이동됩니다.

rename_column()에 원본 열 이름과 새 열 이름을 제공합니다:

>>> dataset
Dataset({
    features: ['sentence1', 'sentence2', 'label', 'idx'],
    num_rows: 3668
})
>>> dataset = dataset.rename_column("sentence1", "sentenceA")
>>> dataset = dataset.rename_column("sentence2", "sentenceB")
>>> dataset
Dataset({
    features: ['sentenceA', 'sentenceB', 'label', 'idx'],
    num_rows: 3668
})

제거

하나 이상의 컬럼을 제거해야 하는 경우, 제거할 컬럼 이름을 remove_columns() 함수에 전달합니다. 여러 컬럼을 제거하려면 컬럼 이름 목록을 전달합니다:

>>> dataset = dataset.remove_columns("label")
>>> dataset
Dataset({
    features: ['sentence1', 'sentence2', 'idx'],
    num_rows: 3668
})
>>> dataset = dataset.remove_columns(["sentence1", "sentence2"])
>>> dataset
Dataset({
    features: ['idx'],
    num_rows: 3668
})

반대로, select_columns()는 유지할 하나 이상의 컬럼을 선택하고 나머지를 제거합니다. 이 함수는 컬럼 이름 하나 또는 컬럼 이름 목록을 인수로 받습니다:

>>> dataset
Dataset({
    features: ['sentence1', 'sentence2', 'label', 'idx'],
    num_rows: 3668
})
>>> dataset = dataset.select_columns(['sentence1', 'sentence2', 'idx'])
>>> dataset
Dataset({
    features: ['sentence1', 'sentence2', 'idx'],
    num_rows: 3668
})
>>> dataset = dataset.select_columns('idx')
>>> dataset
Dataset({
    features: ['idx'],
    num_rows: 3668
})

캐스팅

cast() 함수는 하나 이상의 열의 feature type을 변환합니다. 이 함수는 새로운 Features를 인수로 받습니다. 아래 예제는 ClassLabel와 Value feature를 변경하는 방법을 보여줍니다:

>>> 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은 원래 feature type과 새 feature type이 호환되는 경우에만 작동합니다. 예를 들어, 원래 컬럼에 1과 0만 포함된 경우, feature type이 Value("int32")인 컬럼을 Value("bool")로 cast할 수 있습니다.

cast_column() 함수를 사용하여 단일 열의 feature type을 변경합니다. 열 이름과 새로운 feature type을 인수로 전달합니다:

>>> 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로 분리되었음을 주목해 보세요.

맵

🤗 Datasets의 가장 강력한 활용 중 하나는 map() 함수를 사용하는 것입니다. map()의 주요 목적은 처리 함수의 속도를 높이는 것입니다. 이 함수를 사용하면 데이터셋의 각 예시에 대해 처리 함수를 독립적으로 또는 배치 단위로 적용할 수 있습니다. 이 함수는 새로운 행과 열을 생성하는 것도 가능합니다.

다음 예시에서는 데이터셋의 각 sentence1 값 앞에 'My sentence: '를 접두사로 붙입니다.

각 문장의 앞에 'My sentence: '를 추가하는 함수를 만들어 시작합니다. 이 함수는 dict를 받아서 출력해야 합니다:

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

이제 map()을 사용하여 add_prefix 함수를 전체 데이터셋에 적용합니다:

>>> updated_dataset = small_dataset.map(add_prefix)
>>> updated_dataset["sentence1"][:5]
['My sentence: Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .',
"My sentence: Yucaipa owned Dominick 's before selling the chain to Safeway in 1998 for $ 2.5 billion .",
'My sentence: They had published an advertisement on the Internet on June 10 , offering the cargo for sale , he added .',
'My sentence: Around 0335 GMT , Tab shares were up 19 cents , or 4.4 % , at A {@html "4.56,havingearliersetarecordhighofA 4.56 , having earlier set a record high of A "} 4.57 .',
]

다른 예제를 살펴보겠습니다. 이번에는 map()을 사용하여 컬럼을 제거합니다. 컬럼을 제거하면, 해당 예제가 매핑된 함수에 전달된 후에야 실제로 제거됩니다. 이를 통해 매핑된 함수는 컬럼이 제거되기 전에 해당 컬럼의 내용을 사용할 수 있습니다.

map()에서 remove_columns 매개변수를 사용하여 제거할 열을 지정합니다:

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

🤗 Datasets에는 나머지 열의 데이터를 복사하지 않아 더 빠른 remove_columns() 함수도 있습니다.

with_indices=True를 설정하면 인덱스를 사용하여 map()을 사용할 수도 있습니다. 아래 예제에서는 각 문장의 앞에 인덱스를 추가합니다:

>>> updated_dataset = dataset.map(lambda example, idx: {"sentence2": f"{idx}: " + example["sentence2"]}, with_indices=True)
>>> updated_dataset["sentence2"][:5]
['0: Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .',
 "1: Yucaipa bought Dominick 's in 1995 for {@html "693millionandsoldittoSafewayfor 693 million and sold it to Safeway for "} 1.8 billion in 1998 .",
 "2: On June 10 , the ship 's owners had published an advertisement on the Internet , offering the explosives for sale .",
 '3: Tab shares jumped 20 cents , or 4.6 % , to set a record closing high at A $ 4.57 .',
 '4: PG & E Corp. shares jumped {@html "1.63or8percentto 1.63 or 8 percent to "} 21.03 on the New York Stock Exchange on Friday .'
]

멀티프로세싱

멀티프로세싱은 CPU에서 프로세스를 병렬화하여 처리 속도를 크게 높입니다. 사용할 프로세스 수를 설정하려면 map()의 num_proc 매개변수를 설정하세요:

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

with_rank=True로 설정하면 map()은 프로세스의 rank와 함께 작동합니다. 이는 with_indices 매개변수와 유사합니다. 매핑된 함수에서 with_rank 매개변수는 이미 존재하는 index 매개변수 뒤에 위치합니다.

>>> import torch
>>> from multiprocess import set_start_method
>>> from transformers import AutoTokenizer, AutoModelForCausalLM
>>> from datasets import load_dataset
>>>
>>> # Get an example dataset
>>> dataset = load_dataset("fka/awesome-chatgpt-prompts", split="train")
>>>
>>> # Get an example model and its tokenizer
>>> model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen1.5-0.5B-Chat").eval()
>>> tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen1.5-0.5B-Chat")
>>>
>>> def gpu_computation(batch, rank):
...     # Move the model on the right GPU if it's not there already
...     device = f"cuda:{(rank or 0) % torch.cuda.device_count()}"
...     model.to(device)
...
...     # Your big GPU call goes here, for example:
...     chats = [[
...         {"role": "system", "content": "You are a helpful assistant."},
...         {"role": "user", "content": prompt}
...     ] for prompt in batch["prompt"]]
...     texts = [tokenizer.apply_chat_template(
...         chat,
...         tokenize=False,
...         add_generation_prompt=True
...     ) for chat in chats]
...     model_inputs = tokenizer(texts, padding=True, return_tensors="pt").to(device)
...     with torch.no_grad():
...         outputs = model.generate(**model_inputs, max_new_tokens=512)
...     batch["output"] = tokenizer.batch_decode(outputs, skip_special_tokens=True)
...     return batch
>>>
>>> if __name__ == "__main__":
...     set_start_method("spawn")
...     updated_dataset = dataset.map(
...         gpu_computation,
...         batched=True,
...         batch_size=16,
...         with_rank=True,
...         num_proc=torch.cuda.device_count(),  # one process per GPU
...     )

rank의 주요 사용 사례는 여러 GPU에 걸쳐 계산을 병렬화하는 것입니다. 이를 위해 multiprocess.set_start_method("spawn")을 설정해야 합니다. 설정하지 않으면 다음 CUDA 오류가 발생합니다:

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

배치 처리

map() 함수는 예시(batch)를 처리하는 작업을 지원합니다. batched=True를 설정하여 배치 단위로 작업을 수행합니다. 기본 배치 크기는 1000이지만, batch_size 매개변수를 통해 조정할 수 있습니다. 배치 처리를 통해 긴 문장을 더 짧은 청크로 나누거나 데이터 증강과 같은 다양한 응용이 가능합니다.

긴 예시 분할

예문이 너무 길면 여러 개의 더 작은 청크로 나누고 싶을 수 있습니다. 다음을 수행하는 함수를 만들어 시작하세요:

  1. sentence1 필드를 50자씩의 청크로 나눕니다.

  2. chunks를 모두 스택하여 새로운 데이터셋을 생성합니다.

>>> def chunk_examples(examples):
...     chunks = []
...     for sentence in examples["sentence1"]:
...         chunks += [sentence[i:i + 50] for i in range(0, len(sentence), 50)]
...     return {"chunks": chunks}

map()으로 함수를 적용합니다:

>>> chunked_dataset = dataset.map(chunk_examples, batched=True, remove_columns=dataset.column_names)
>>> chunked_dataset[:10]
{'chunks': ['Amrozi accused his brother , whom he called " the ',
            'witness " , of deliberately distorting his evidenc',
            'e .',
            "Yucaipa owned Dominick 's before selling the chain",
            ' to Safeway in 1998 for $ 2.5 billion .',
            'They had published an advertisement on the Interne',
            't on June 10 , offering the cargo for sale , he ad',
            'ded .',
            'Around 0335 GMT , Tab shares were up 19 cents , or',
            ' 4.4 % , at A $ 4.56 , having earlier set a record']}

이제 문장이 더 짧은 청크로 나뉘어 있고 데이터셋의 행 수가 더 많아진 것을 확인해 보세요.

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

데이터 증강

map() 함수는 데이터 증강에도 사용할 수 있습니다. 다음 예제는 문장의 마스킹된 토큰에 대해 추가적인 단어를 생성합니다.

🤗 Transformers의 FillMaskPipeline에서 RoBERTA 모델을 로드하고 사용하는 방법:

>>> from random import randint
>>> from transformers import pipeline

>>> fillmask = pipeline("fill-mask", model="roberta-base")
>>> mask_token = fillmask.tokenizer.mask_token
>>> smaller_dataset = dataset.filter(lambda e, i: i<100, with_indices=True)

… 문장에서 마스킹할 단어를 무작위로 선택하는 함수를 생성합니다. 이 함수는 원래 문장과 RoBERTA가 생성한 상위 두 개의 대체어도 반환해야 합니다.

>>> def augment_data(examples):
...     outputs = []
...     for sentence in examples["sentence1"]:
...         words = sentence.split(' ')
...         K = randint(1, len(words)-1)
...         masked_sentence = " ".join(words[:K]  + [mask_token] + words[K+1:])
...         predictions = fillmask(masked_sentence)
...         augmented_sequences = [predictions[i]["sequence"] for i in range(3)]
...         outputs += [sentence] + augmented_sequences
...
...     return {"data": outputs}

map()을 사용하여 함수를 전체 데이터셋에 적용합니다:

>>> augmented_dataset = smaller_dataset.map(augment_data, batched=True, remove_columns=dataset.column_names, batch_size=8)
>>> augmented_dataset[:9]["data"]
['Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .',
 'Amrozi accused his brother, whom he called " the witness ", of deliberately withholding his evidence.',
 'Amrozi accused his brother, whom he called " the witness ", of deliberately suppressing his evidence.',
 'Amrozi accused his brother, whom he called " the witness ", of deliberately destroying his evidence.',
 "Yucaipa owned Dominick 's before selling the chain to Safeway in 1998 for $ 2.5 billion .",
 'Yucaipa owned Dominick Stores before selling the chain to Safeway in 1998 for $ 2.5 billion.',
 "Yucaipa owned Dominick's before selling the chain to Safeway in 1998 for $ 2.5 billion.",
 'Yucaipa owned Dominick Pizza before selling the chain to Safeway in 1998 for $ 2.5 billion.'
]

각 원문 문장에 대해 RoBERTA는 무작위로 선택된 단어를 세 가지 대안으로 증강했습니다. 원래 단어 distorting은 withholding, suppressing, destroying으로 보완됩니다.

비동기 처리

비동기 함수는 이미지와 같은 콘텐츠를 다운로드하거나 모델 엔드포인트를 호출하는 등 API 엔드포인트를 병렬로 호출하는 데 유용합니다.

async와 await 키워드를 사용하여 비동기 함수를 정의할 수 있습니다. Hugging Face에서 채팅 모델을 호출하는 예제 함수는 다음과 같습니다:

>>> import aiohttp
>>> import asyncio
>>> from huggingface_hub import get_token
>>> sem = asyncio.Semaphore(20)  # max number of simultaneous queries
>>> async def query_model(model, prompt):
...     api_url = f"https://huggingface.co/proxy/api-inference.huggingface.co/models/{model}/v1/chat/completions"
...     headers = {"Authorization": f"Bearer {get_token()}", "Content-Type": "application/json"}
...     json = {"messages": [{"role": "user", "content": prompt}], "max_tokens": 20, "seed": 42}
...     async with sem, aiohttp.ClientSession() as session, session.post(api_url, headers=headers, json=json) as response:
...         output = await response.json()
...         return {"Output": output["choices"][0]["message"]["content"]}

비동기 함수는 병렬로 실행되어 프로세스를 크게 가속화합니다. 동일한 코드를 순차적으로 실행하면 모델 응답을 기다리는 동안 아무 작업도 수행하지 않으므로 훨씬 더 많은 시간이 소요됩니다. 일반적으로 함수가 API 응답을 기다려야 하거나, 시간이 걸릴 수 있는 데이터를 다운로드하는 경우 async / await를 사용하는 것이 권장됩니다.

Semaphore가 존재하는 점에 유의하세요. 이는 병렬로 실행할 수 있는 쿼리의 최대 수를 설정합니다. API를 호출할 때 속도 제한 오류를 피하기 위해 Semaphore를 사용하는 것이 권장됩니다.

이것을 사용해 microsoft/Phi-3-mini-4k-instruct 모델을 호출하고 Maxwell-Jia/AIME_2024 데이터셋의 각 수학 문제의 주요 주제를 반환하도록 요청해 보겠습니다:

>>> from datasets import load_dataset
>>> ds = load_dataset("Maxwell-Jia/AIME_2024", split="train")
>>> model = "microsoft/Phi-3-mini-4k-instruct"
>>> prompt = 'What is this text mainly about ? Here is the text:\n\n```\n{Problem}\n```\n\nReply using one or two words max, e.g. "The main topic is Linear Algebra".'
>>> async def get_topic(example):
...     return await query_model(model, prompt.format(Problem=example['Problem']))
>>> ds = ds.map(get_topic)
>>> ds[0]
{'ID': '2024-II-4',
 'Problem': 'Let {@html "x,yx,y"} and {@html "zz"} be positive real numbers that...',
 'Solution': 'Denote $\\log_2(x) = a$, $\\log_2(y) = b$, and...,
 'Answer': 33,
 'Output': 'The main topic is Logarithms.'}

여기서 Dataset.map()은 get_topic 함수를 비동기적으로 여러 개 실행하여, 순차적으로 처리할 경우 상당한 시간이 소요되는 모든 모델 응답을 기다릴 필요가 없습니다.

기본적으로 Dataset.map()은 최대 1,000개의 map 함수를 병렬로 실행하므로, Semaphore를 사용하여 병렬로 실행할 수 있는 API 호출의 최대 수를 설정하는 것을 잊지 마세요. 그렇지 않으면 모델이 rate limit 오류를 반환하거나 과부하가 발생할 수 있습니다. 고급 사용 사례의 경우, datasets.config에서 병렬로 실행할 수 있는 쿼리의 최대 수를 변경할 수 있습니다.

여러 개의 split 처리

많은 데이터셋에는 DatasetDict.map()으로 동시에 처리할 수 있는 스플릿이 있습니다. 예를 들어, train과 test 스플릿의 sentence1 필드를 다음과 같이 토큰화할 수 있습니다:

>>> from datasets import load_dataset

# load all the splits
>>> dataset = load_dataset('nyu-mll/glue', 'mrpc')
>>> encoded_dataset = dataset.map(lambda examples: tokenizer(examples["sentence1"]), batched=True)
>>> encoded_dataset["train"][0]
{'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .',
'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .',
'label': 1,
'idx': 0,
'input_ids': [  101,  7277,  2180,  5303,  4806,  1117,  1711,   117,  2292, 1119,  1270,   107,  1103,  7737,   107,   117,  1104,  9938, 4267, 12223, 21811,  1117,  2554,   119,   102],
'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
}

분산 사용

분산 환경에서 map()을 사용할 때는 torch.distributed.barrier도 함께 사용해야 합니다. 이를 통해 메인 프로세스가 매핑을 수행하고 다른 프로세스는 결과를 로드하도록 하여 중복 작업을 방지합니다.

다음 예제는 torch.distributed.barrier를 사용하여 프로세스를 동기화하는 방법을 보여줍니다:

>>> from datasets import Dataset
>>> import torch.distributed

>>> dataset1 = Dataset.from_dict({"a": [0, 1, 2]})

>>> if training_args.local_rank > 0:
...     print("Waiting for main process to perform the mapping")
...     torch.distributed.barrier()

>>> dataset2 = dataset1.map(lambda x: {"a": x["a"] + 1})

>>> if training_args.local_rank == 0:
...     print("Loading results from main process")
...     torch.distributed.barrier()

배치

batch() 메서드를 사용하면 데이터셋의 샘플을 배치로 그룹화할 수 있습니다. 이는 특히 딥러닝 모델을 다룰 때 학습이나 평가를 위한 데이터 배치를 생성하려는 경우에 유용합니다.

batch() 메서드를 사용하는 예시는 다음과 같습니다:

>>> 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, optional, 기본값 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)

교차

각 데이터셋에서 번갈아 가며 예시를 가져와 새 데이터셋을 만들 수 있습니다. 이를 interleaving이라고 하며, interleave_datasets() 함수를 통해 사용할 수 있습니다. interleave_datasets()와 concatenate_datasets()는 모두 일반 Dataset 객체와 IterableDataset 객체에서 작동합니다. IterableDataset 객체를 interleave하는 예시는 Stream 가이드를 참고하세요.

원본 데이터셋 각각에 대해 샘플링 확률을 정의하여 데이터셋을 어떻게 교차(interleave)할지 지정할 수 있습니다. 이 경우, 새로운 데이터셋은 데이터셋 중 하나가 샘플을 모두 소진할 때까지 무작위로 선택된 데이터셋에서 예시를 하나씩 가져와서 구성됩니다.

>>> from datasets import Dataset, interleave_datasets
>>> seed = 42
>>> probabilities = [0.3, 0.5, 0.2]
>>> d1 = Dataset.from_dict({"a": [0, 1, 2]})
>>> d2 = Dataset.from_dict({"a": [10, 11, 12, 13]})
>>> d3 = Dataset.from_dict({"a": [20, 21, 22]})
>>> dataset = interleave_datasets([d1, d2, d3], probabilities=probabilities, seed=seed)
>>> dataset["a"]
[10, 11, 20, 12, 0, 21, 13]

stopping_strategy도 지정할 수 있습니다. 기본 전략인 first_exhausted는 서브샘플링 전략으로, 즉 어떤 데이터셋의 샘플이 먼저 소진되면 데이터셋 구성이 중단됩니다. stopping_strategy=all_exhausted를 지정하여 오버샘플링 전략을 실행할 수 있습니다. 이 경우, 모든 데이터셋의 모든 샘플이 최소 한 번씩 추가되면 데이터셋 구성이 중단됩니다. 실제로는 데이터셋이 소진되면 중단 조건이 충족될 때까지 해당 데이터셋의 처음부터 다시 시작합니다. 샘플링 확률이 지정되지 않은 경우, 새로운 데이터셋에는 max_length_datasets*nb_dataset samples이 포함됩니다. 모든 샘플이 정확히 한 번만 보이도록 보장하는 stopping_strategy=all_exhausted_without_replacement도 있습니다.

>>> d1 = Dataset.from_dict({"a": [0, 1, 2]})
>>> d2 = Dataset.from_dict({"a": [10, 11, 12, 13]})
>>> d3 = Dataset.from_dict({"a": [20, 21, 22]})
>>> dataset = interleave_datasets([d1, d2, d3], stopping_strategy="all_exhausted")
>>> dataset["a"]
[0, 10, 20, 1, 11, 21, 2, 12, 22, 0, 13, 20]

형식

with_format() 함수는 컬럼의 형식을 일반적인 데이터 형식과 호환되도록 변경합니다. type 매개변수에 원하는 출력을 지정합니다. 또한 columns=를 사용하여 형식을 지정할 컬럼을 선택할 수 있습니다. 형식화는 즉시 적용됩니다.

예를 들어, type="torch"를 설정하여 PyTorch 텐서를 생성합니다:

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

set_format() 함수는 컬럼의 형식도 변경하지만, 인플레이스로 실행됩니다:

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

데이터셋을 원래 형식으로 재설정해야 하는 경우, 형식을 None으로 설정하거나 reset_format()을 사용하세요:

>>> dataset.format
{'type': 'torch', 'format_kwargs': {}, 'columns': [...], 'output_all_columns': False}
>>> dataset = dataset.with_format(None)
>>> dataset.format
{'type': None, 'format_kwargs': {}, 'columns': [...], 'output_all_columns': False}

Tensors 형식

여러 텐서 또는 배열 형식이 지원됩니다. 불필요한 데이터 복사를 방지하고 데이터 로딩을 가속화하기 위해, 데이터셋의 출력을 수동으로 텐서 또는 배열로 변환하는 대신 이러한 형식을 사용하는 것이 일반적으로 권장됩니다.

지원되는 텐서 또는 배열 형식의 목록은 다음과 같습니다:

TensorFlow 데이터셋을 효율적으로 생성하는 방법에 대한 자세한 내용은 TensorFlow와 Datasets 사용 가이드를 참고하세요.

데이터셋이 텐서 또는 배열 형식으로 포맷팅되면, 모든 데이터가 텐서 또는 배열로 포맷팅됩니다(예: 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]])}

테이블 형식

데이터 로딩 및 데이터 처리를 최적화하기 위해 dataframes 또는 테이블 형식을 사용할 수 있습니다. 이러한 형식은 일반적으로 제로-복사(Zero-copy) 연산과 저수위 언어로 작성된 변환을 제공합니다.

지원되는 dataframe 또는 테이블 형식의 목록은 다음과 같습니다:

데이터셋이 dataframe 또는 테이블 형식으로 포맷팅된 경우, 데이터셋의 각 행 또는 행의 배치(batches)가 dataframe 또는 테이블로 포맷팅되고, 데이터셋의 컬럼은 series 또는 array로 포맷팅됩니다:

>>> ds = Dataset.from_dict({"text": ["foo", "bar"], "label": [0, 1]})
>>> ds = ds.with_format("pandas")
>>> ds[:2]
  text  label
0  foo      0
1  bar      1

이러한 포맷은 데이터 복사를 피함으로써 데이터를 더 빠르게 반복 처리할 수 있게 하며, 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() 함수는 커스텀 포맷 변환을 온더플라이로 적용합니다. 이 함수는 이전에 지정된 포맷을 대체합니다. 예를 들어, 이 함수를 사용하여 토큰을 온더플라이로 토큰화하고 패딩할 수 있습니다. 토큰화는 예제가 액세스될 때만 적용됩니다:

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

동일한 작업을 수행하지만 in-place로 실행되는 set_transform()도 있습니다.

Features에 대한 사용자 정의 디코딩을 위해 with_transform() 함수를 사용할 수도 있습니다.

아래 예제는 torchcodec 디코딩의 대안으로 pydub 패키지를 사용합니다:

>>> import numpy as np
>>> from pydub import AudioSegment

>>> audio_dataset_amr = Dataset.from_dict({"audio": ["audio_samples/audio.amr"]})

>>> def decode_audio_with_pydub(batch, sampling_rate=16_000):
...     def pydub_decode_file(audio_path):
...         sound = AudioSegment.from_file(audio_path)
...         if sound.frame_rate != sampling_rate:
...             sound = sound.set_frame_rate(sampling_rate)
...         channel_sounds = sound.split_to_mono()
...         samples = [s.get_array_of_samples() for s in channel_sounds]
...         fp_arr = np.array(samples).T.astype(np.float32)
...         fp_arr /= np.iinfo(samples[0].typecode).max
...         return fp_arr
...
...     batch["audio"] = [pydub_decode_file(audio_path) for audio_path in batch["audio"]]
...     return batch

>>> audio_dataset_amr.set_transform(decode_audio_with_pydub)

저장

데이터셋이 준비되면 Parquet 형식의 Hugging Face Dataset으로 저장하여 나중에 load_dataset()로 재사용할 수 있습니다.

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보다 업로드/다운로드/쿼리가 느리며, 장기 저장에는 적합하지 않습니다.

Use the save_to_disk() and load_from_disk() function to reload the dataset from your disk:

>>> encoded_dataset.save_to_disk("path/of/my/dataset/directory")
>>> # later
>>> from datasets import load_from_disk
>>> reloaded_dataset = load_from_disk("path/of/my/dataset/directory")

내보내기

🤗 Datasets는 내보내기를 지원하므로 다른 애플리케이션에서 데이터셋을 사용할 수 있습니다. 다음 표는 현재 내보낼 수 있는 지원 파일 형식을 보여줍니다:

파일 형식Export method
CSVDataset.to_csv()
JSONDataset.to_json()
ParquetDataset.to_parquet()
SQLDataset.to_sql()
메모리 내 Python 객체Dataset.to_pandas(), Dataset.to_polars() or Dataset.to_dict()

예를 들어, 데이터셋을 다음과 같이 CSV 파일로 내보낼 수 있습니다:

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

Hugging Face의 Dataset 저장소 또는 Storage Bucket로 내보내려면 hf:// 경로를 사용하세요:

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