Datasets documentation

プロセス

Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

プロセス

🤗 Datasetsは、データセットの構造や内容を変更するための多くのツールを提供しています。これらのツールは、データセットの整理、追加カラムの作成、機能や形式間の変換など、多くの場面で重要です。

このガイドでは、次の方法について説明します:

  • 行の順序を入れ替え、データセットを分割します。
  • 列の名前変更や削除、その他の一般的な列操作。
  • Apply processing functions to each example in a dataset.
  • データセットを連結します。
  • カスタムなフォーマット変換を適用します。
  • 処理済みデータセットを保存してエクスポートします。

他のデータセットのモダリティの処理に関する詳細については、オーディオデータセットの処理ガイド、画像データセットの処理ガイド、またはテキストデータセットの処理ガイドをご覧ください。

このガイドの例ではMRPCデータセットを使用していますが、お好みのデータセットを読み込んで、そのまま進めてください。

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

このガイドのすべての処理メソッドは、新しいDatasetオブジェクトを返します。変更はインプレースでは行われません。以前のデータセットを上書きしないようご注意ください。

ソート、シャッフル、選択、分割、シャーディング

データセットの構造を再配置するための関数がいくつかあります。 これらの関数は、必要な行のみを選択したり、trainとtestの分割を作成したり、非常に大きなデータセットをより小さなチャンクにシャーディングしたりする際に役立ちます。

ソート

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()の2つのオプションがあります。

  • 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

保持するインデックスのリストが連続していない場合、これらのメソッドは内部でインデックスのマッピングも作成します。

分割

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を設定することでシャッフルを無効にできます。

シャード

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

データセットを4つのチャンクにシャーディングした後、最初のシャードには6250件のサンプルしか含まれません。

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

リネーム、削除、キャスト、フラット化

以下の関数を使用すると、データセットの列を変更できます。これらの関数は、列の名前を変更したり削除したり、列を新しい特徴量のセットに変更したり、ネストされた列の構造をフラットにしたりする際に便利です。

名前の変更

データセットの列名を変更する必要がある場合は、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
})

削除

1つ以上の列を削除する必要がある場合は、削除する列名を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()は保持する1つ以上の列を選択し、残りの列を削除します。この関数は、列名を1つまたはリストとして受け取ります:

>>> 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()関数は、1つ以上のカラムのフィーチャー型を変換します。この関数は、新しい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')}

キャストは、元のフィーチャー型と新しいフィーチャー型が互換性がある場合にのみ機能します。例えば、元の列が1と0のみを含む場合、フィーチャー型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)}

フラット化

…

>>> 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 という2つのサブフィールドが含まれています。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() はプロセスのランクでも動作します。これは 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()関数は、サンプルのバッチでの処理をサポートしています。batched=Trueを設定することで、バッチ単位で操作できます。デフォルトのバッチサイズは1000ですが、batch_sizeパラメータで調整できます。バッチ処理により、長い文を短いチャンクに分割したり、データ拡張を行ったりするなどの興味深いアプリケーションが可能になります。

長い例を分割する

例が長すぎる場合、いくつかの小さなチャンクに分割したい場合があります。そのために、次のような関数を作成します。

  1. sentence1フィールドを50文字ずつのチャンクに分割します。

  2. すべてのチャンクをスタックして、新しいデータセットを作成します。

>>> 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によって生成された上位2つの置換候補も返す必要があります。

>>> 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はランダムに選択した単語に3つの代替案を追加しました。元の単語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/static-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呼び出しの最大数を設定することを忘れないでください。そうしないと、モデルがレート制限エラーを返すか、オーバーロードする可能性があります。高度なユースケースでは、datasets.config で並列実行可能なクエリの最大数を変更できます。

複数のスプリットを処理する

多くのデータセットには、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、任意、デフォルトはNone): マルチプロセッシングに使用するプロセス数です。None の場合は、マルチプロセッシングは使用されません。これにより、大規模なデータセットのバッチ処理を大幅に高速化できます。

Dataset.batch() は、元のデータセットから複数のサンプルで構成されるバッチを各アイテムとする新しい Dataset を返します。バッチ単位でデータを処理する場合は、バッチに直接関数を適用するバッチ化された map() を使用してください。この場合、出力されるデータセットはバッチ化されていません。

連結

同じカラムタイプを共有する場合は、別々のデータセットを連結できます。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を設定することで、行数が同じであれば、2つのデータセットを水平方向に連結することもできます:

>>> 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_datasets()関数によって有効になります。interleave_datasets()とconcatenate_datasets()は、通常のDatasetオブジェクトとIterableDatasetオブジェクトの両方で動作します。IterableDatasetオブジェクトをインターリーブする方法の例については、Streamガイドを参照してください。

元の各データセットごとにサンプリング確率を定義することで、データセットのインターリーブ(交互配置)方法を指定できます。 この場合、新しいデータセットは、いずれかのデータセットのサンプルが尽きるまで、ランダムに選ばれたデータセットから1件ずつ例を取得して構築されます。

>>> 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 を指定すると、オーバーサンプリング戦略が実行されます。この場合、すべてのデータセットのすべてのサンプルが少なくとも1回追加された時点で、データセットの構築が停止します。実際には、あるデータセットが尽きた場合、停止条件が満たされるまで、そのデータセットの先頭に戻って処理が続行されます。 サンプリング確率が指定されていない場合、新しいデータセットには max_length_datasets*nb_dataset samples が含まれることに注意してください。 また、すべてのサンプルがちょうど1回だけ参照されることを保証するために、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}

テンソル形式

複数のテンソルまたは配列の形式がサポートされています。不要なデータコピーを回避し、データ読み込みを高速化するために、データセットの出力を手動でテンソルや配列に変換する代わりに、これらの形式を使用することが一般的に推奨されています。

サポートされているテンソルまたは配列の形式のリストは以下の通りです:

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

表形式

データフレームやテーブル形式は、ゼロコピー操作や低レベル言語で記述された変換を一般的にサポートしているため、データ読み込みとデータ処理の最適化に利用できます。

サポートされているデータフレームまたはテーブルの形式のリストは以下の通りです:

データセットがデータフレームまたはテーブル形式でフォーマットされている場合、データセットの各行または行のバッチはデータフレームまたはテーブルとして、データセットの各列はシリーズまたは配列としてフォーマットされます:

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

カスタム形式変換

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}

同じ処理をインプレースで実行する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はエクスポートにも対応しているため、他のアプリケーションでデータセットを扱うことができます。以下の表は、現在エクスポートに対応しているファイル形式を示しています。

ファイル形式エクスポート方法
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")

hf:// パスを使用して、Hugging Face のDataset リポジトリまたは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")
Update on GitHub