見出し画像

Huggingface Datasets 入門 (2) - データセットローディングスクリプトの作成

以下の記事を参考に書いてます。

Huggingface Datasets - Writing a dataset loading script

・Huggingface Transformers 4.1.1
・Huggingface Datasets 1.2

前回

1. データセットローディングスクリプトの作成

独自のデータセットローディングスクリプトを作成する主な理由は2つです。

・ローカルファイルを使用したいが、汎用データローダーでは十分対応できない。
・HuggingFace Hubで新しいデータセットをコミュニティと共有。

データセットの生成に関連するクラスとメソッドの概要図は、次のとおりです。

画像1

左側は、datasets.Datasetインスタンスを作成するためのライブラリ内の一般的な構成です。右側は、各データセット読み込みスクリプトに固有の要素です。新しいデータセットローディングスクリプトを作成するには、ほとんどの場合、datasets.DatasetBuilderクラスで3つのメソッドを定義する必要があります。

・datasets.DatasetBuilder._info() : データセットの属性の追加。
・datasets.DatasetBuilder._split_generator() : データセットの取得と分割。
・datasets.DatasetBuilder._generate_examples() : 各分割のサンプルの生成。

2. データセットの属性の追加

datasets.DatasetBuilder._info()は、データセットの属性を追加します。属性一覧はパッケージリファレンスにあります。

主な属性は、次のとおりです。

・datasets.DatasetInfo.features (datasets.Features) : データセットの各列の名前と型。 データセットの各列の名前と型。
・datasets.DatasetInfo.description (str) : データセットの説明。
・datasets.DatasetInfo.citation (str) : データセットの引用。
・datasets.DatasetInfo.homepage (str) : ホームページのURL。

SQuADデータセットのdatasets.Dataset._info()は次のとおりです。

def _info(self):
    return datasets.DatasetInfo(
        description=_DESCRIPTION,
        features=datasets.Features(
            {
                "id": datasets.Value("string"),
                "title": datasets.Value("string"),
                "context": datasets.Value("string"),
                "question": datasets.Value("string"),
                "answers": datasets.features.Sequence(
                    {"text": datasets.Value("string"), "answer_start": datasets.Value("int32"),}
                ),
            }
        ),
        # デフォルトのsupervised_keysはない(質問とコンテキストの両方を入力として渡す必要があるため)
        supervised_keys=None,
        homepage="https://rajpurkar.github.io/SQuAD-explorer/",
        citation=_CITATION,
    )

datasets.Featuresは、各サンプルの構造を定義し、様々なタイプのフィールドを持つ任意のネストされたオブジェクトを定義できます。使用可能なfeaturesの詳細については、ガイドおよびリファレンスを参照してください。

SQuADデータセットローディングスクリプトから取得したSQuADデータセットのfeaturesは、次のとおりです。

datasets.Features(
    {
        "id": datasets.Value("string"),
        "title": datasets.Value("string"),
        "context": datasets.Value("string"),
        "question": datasets.Value("string"),
        "answers": datasets.Sequence(
            {"text": datasets.Value("string"),
            "answer_start": datasets.Value("int32"),
            }
        ),
    }
)

これらのfeaturesはほとんど自明です。ここでの特定の動作の1つは、「answers」のSequenceフィールドにサブフィールドの辞書が与えられるという事実です。この場合、このfeatursは実際にはリストの辞書に変換されます。これは、SQuADデータセットローディングスクリプトの最後にある生成メソッドによって生成されたサンプルの構造で確認できます。

answer_starts = [answer["answer_start"] for answer in qa["answers"]]
answers = [answer["text"].strip() for answer in qa["answers"]]

yield id_, {
    "title": title,
    "context": context,
    "question": question,
    "id": id_,
    "answers": {"answer_start": answer_starts, "text": answers,},
}

したがって、ここで「answers」には、辞書のリストではなく、リストの辞書が提供されます。

large-scale reading comprehension dataset Race」から他のfeaturesの例を見てみます。

features=datasets.Features(
    {
        "article": datasets.Value("string"),
        "answer": datasets.Value("string"),
        "question": datasets.Value("string"),
        "options": datasets.features.Sequence({"option": datasets.Value("string")})
    }
)

データセット内の対応する最初の例を、次に示します。

from datasets import load_dataset
dataset = load_dataset('race', split='train')
dataset[0]
{'article': 'My husband is a born shopper. He loves to look at things and to touch them. He likes to compare prices between the same items in different shops. He would never think of buying anything without looking around in several
...
sadder. When he saw me he said, "I\'m sorry, Mum. I have forgotten to buy oranges and the meat. I only remembered to buy six eggs, but I\'ve dropped three of them."',
'answer': 'C',
'question': 'The husband likes shopping because   _  .',
'options': {
   'option':['he has much money.',
             'he likes the shops.',
             'he likes to compare the prices between the same items.',
             'he has nothing to do but shopping.'
           ]
   }
}

3. データセットの取得と分割

datasets.DatasetBuilder._split_generator()は、データセットの取得(ダウンロードまたはローカルファイル読み込み)と分割を行います。必要に応じて生成プロセスの特定の引数を定義します。

このメソッドは、ファイルを取得するためのユーティリティdatasets.DownloadManagerを入力として受け取り、datasets.SplitGeneratorのリストを返します。datasets.SplitGeneratorは、次のセクションで説明するdatasets.DatasetBuilder._generate_examples()に提供されるデータクラスで、分割名とキーワード引数を含みます。これらの引数は各分割に固有であり、通常、少なくとも各分割でロードするデータファイルへのローカルパスを設定します。

datasets.DatasetBuilder._split_generator()の例は、次のとおりです。

class Squad(datasets.GeneratorBasedBuilder):
    """SQUAD: The Stanford Question Answering Dataset. Version 1.1."""

    _URL = "https://rajpurkar.github.io/SQuAD-explorer/dataset/"
    _URLS = {
        "train": _URL + "train-v1.1.json",
        "dev": _URL + "dev-v1.1.json",
    }

    def _split_generators(self, dl_manager: datasets.DownloadManager) -> List[datasets.SplitGenerator]:
        urls_to_download = self._URLS
        downloaded_files = dl_manager.download_and_extract(urls_to_download)

        return [
            datasets.SplitGenerator(name=datasets.Split.TRAIN, gen_kwargs={"filepath": downloaded_files["train"]}),
            datasets.SplitGenerator(name=datasets.Split.VALIDATION, gen_kwargs={"filepath": downloaded_files["dev"]}),
        ]

このメソッドは最初にSQuADの元のデータファイルへのURLの辞書を準備します。次に、この辞書をdatasets.DownloadManager.download_and_extract()に渡し、データセットを取得します。

データセットが取得できたら、各分割のdatasets.SplitGeneratorを準備します。これは、datasets.DatasetBuilder._generate_examples()の呼び出しに使用されます。

datasets.SplitGeneratorは、以下を含む単純なデータクラスです。

・name (string) : 分割名。
 ・datasets.Split.TRAIN
 ・datasets.Split.VALIDATION
 ・datasets.Split.TEST
・gen_kwargs (dict) : 指定の分割でサンプルを生成するためにdatasets.DatasetBuilder._generate_examples()に渡すキーワード引数。この引数は各分割に固有であり、通常、分割ごとにロードするローカルパスを含む。

4. 各分割のサンプルの生成

datasets.DatasetBuilder._generate_examples()は、分割用のデータセットの読み込みと、datasets.DatasetBuilder._info()で設定されたfeaturesで指定された形式のサンプルの生成を行います。

datasets.DatasetBuilder._generate_examples()の入力引数は、上記で説明したdatasets.DatasetBuilder._split_generator()によって返されるgen_kwargs辞書によって定義されます。

def _generate_examples(self, filepath):
    """この関数は、サンプルをテキスト形式で返す"""
    logger.info("generating examples from = %s", filepath)
    with open(filepath) as f:
        squad = json.load(f)
        for article in squad["data"]:
            title = article.get("title", "").strip()
            for paragraph in article["paragraphs"]:
                context = paragraph["context"].strip()
                for qa in paragraph["qas"]:
                    question = qa["question"].strip()
                    id_ = qa["id"]

                    answer_starts = [answer["answer_start"] for answer in qa["answers"]]
                    answers = [answer["text"].strip() for answer in qa["answers"]]

                    # 現在使用されているfeaturesは「context」「question」「answers」
                    # その他は、将来の拡張を容易にするために抽出
                    yield id_, {
                        "title": title,
                        "context": context,
                        "question": question,
                        "id": id_,
                        "answers": {"answer_start": answer_starts, "text": answers,},
                    }

入力引数は、datasets.DatasetBuilder._split_generator()によって返される各datasets.SplitGeneratorのgen_kwargsで提供されるファイルパスです。

このメソッドは、入力ファイルを読み取って解析し、id_とサンプルで構成されるタプルを生成します。サンプルは、featuresと同じ構造の辞書です。



この記事が気に入ったらサポートをしてみませんか?