Datasets documentation
TensorFlowでのDatasetsの使用
TensorFlowでのDatasetsの使用
このドキュメントは、datasets を TensorFlow で使用する際の簡単な入門ガイドです。特に、datasets から tf.Tensor オブジェクトを取得する方法、および Hugging Face の Dataset オブジェクトから model.fit() などの Keras メソッドにデータをストリーミングする方法に焦点を当てています。
データセットの形式
デフォルトでは、datasetsは通常のPythonオブジェクト(整数、浮動小数点数、文字列、リストなど)を返します。
代わりにTensorFlowのテンソルを取得するには、データセットの形式をtfに設定できます:
>>> from datasets import Dataset
>>> data = [[1, 2],[3, 4]]
>>> ds = Dataset.from_dict({"data": data})
>>> ds = ds.with_format("tf")
>>> ds[0]
{'data': <tf.Tensor: shape=(2,), dtype=int64, numpy=array([1, 2])>}
>>> ds[:2]
{'data': <tf.Tensor: shape=(2, 2), dtype=int64, numpy=
array([[1, 2],
[3, 4]])>}Dataset オブジェクトは Arrow テーブルのラッパーであり、データセット内の配列から TensorFlow テンソルへの高速な読み取りを可能にします。
これは、データセットをTensorオブジェクトの辞書に変換したり、そこからTFサンプルを読み込むジェネレーターを書いたりする場合に便利です。データセット全体をTensorに変換したい場合は、単にデータセット全体をクエリしてください:
>>> ds[:]
{'data': <tf.Tensor: shape=(2, 2), dtype=int64, numpy=
array([[1, 2],
[3, 4]])>}N次元配列
データセットがN次元の配列で構成されている場合、デフォルトでは、shapeが固定されている場合、それらは同じtensorとして扱われることに注意してください。
>>> from datasets import Dataset
>>> data = [[[1, 2],[3, 4]],[[5, 6],[7, 8]]] # fixed shape
>>> ds = Dataset.from_dict({"data": data})
>>> ds = ds.with_format("tf")
>>> ds[0]
{'data': <tf.Tensor: shape=(2, 2), dtype=int64, numpy=
array([[1, 2],
[3, 4]])>}それ以外の場合、TensorFlow形式のデータセットは単一のテンソルではなくRaggedTensorを出力します:
>>> from datasets import Dataset
>>> data = [[[1, 2],[3]],[[4, 5, 6],[7, 8]]] # varying shape
>>> ds = Dataset.from_dict({"data": data})
>>> ds = ds.with_format("torch")
>>> ds[0]
{'data': <tf.RaggedTensor [[1, 2], [3]]>}ただし、このロジックは多くの場合、遅い形状の比較とデータのコピーを必要とします。
これを回避するには、Array機能型を明示的に使用し、テンソルの形状を指定する必要があります:
>>> from datasets import Dataset, Features, Array2D
>>> data = [[[1, 2],[3, 4]],[[5, 6],[7, 8]]]
>>> features = Features({"data": Array2D(shape=(2, 2), dtype='int32')})
>>> ds = Dataset.from_dict({"data": data}, features=features)
>>> ds = ds.with_format("tf")
>>> ds[0]
{'data': <tf.Tensor: shape=(2, 2), dtype=int64, numpy=
array([[1, 2],
[3, 4]])>}
>>> ds[:2]
{'data': <tf.Tensor: shape=(2, 2, 2), dtype=int64, numpy=
array([[[1, 2],
[3, 4]],
[[5, 6],
[7, 8]]])>}その他の特徴タイプ
ClassLabel データは正しくテンソルに変換されます:
>>> from datasets import Dataset, Features, ClassLabel
>>> labels = [0, 0, 1]
>>> features = Features({"label": ClassLabel(names=["negative", "positive"])})
>>> ds = Dataset.from_dict({"label": labels}, features=features)
>>> ds = ds.with_format("tf")
>>> ds[:3]
{'label': <tf.Tensor: shape=(3,), dtype=int64, numpy=array([0, 0, 1])>}文字列とバイナリオブジェクトもサポートされています:
>>> from datasets import Dataset, Features
>>> text = ["foo", "bar"]
>>> data = [0, 1]
>>> ds = Dataset.from_dict({"text": text, "data": data})
>>> ds = ds.with_format("tf")
>>> ds[:2]
{'text': <tf.Tensor: shape=(2,), dtype=string, numpy=array([b'foo', b'bar'], dtype=object)>,
'data': <tf.Tensor: shape=(2,), dtype=int64, numpy=array([0, 1])>}特定の列のみを明示的にフォーマットし、他の列はフォーマットしないこともできます。
>>> ds = ds.with_format("tf", columns=["data"], output_all_columns=True)
>>> ds[:2]
{'data': <tf.Tensor: shape=(2,), dtype=int64, numpy=array([0, 1])>,
'text': ['foo', 'bar']}PyTorchは数値のみをサポートするため、文字列オブジェクトとバイナリオブジェクトは変更されません。
ImageおよびAudioの機能タイプもサポートされています。
Image機能タイプを使用するには、
visionエクストラをpip install datasets[vision]としてインストールする必要があります。
>>> from datasets import Dataset, Features, Audio, Image
>>> images = ["path/to/image.png"] * 10
>>> features = Features({"image": Image()})
>>> ds = Dataset.from_dict({"image": images}, features=features)
>>> ds = ds.with_format("tf")
>>> ds[0]
{'image': <tf.Tensor: shape=(512, 512, 4), dtype=uint8, numpy=
array([[[255, 215, 106, 255],
[255, 215, 106, 255],
...,
[255, 255, 255, 255],
[255, 255, 255, 255]]], dtype=uint8)>}
>>> ds[:2]
{'image': <tf.Tensor: shape=(2, 512, 512, 4), dtype=uint8, numpy=
array([[[[255, 215, 106, 255],
[255, 215, 106, 255],
...,
[255, 255, 255, 255],
[255, 255, 255, 255]]]], dtype=uint8)>}Audio機能タイプを使用するには、
audioエクストラをpip install datasets[audio]としてインストールする必要があります。
>>> from datasets import Dataset, Features, Audio, Image
>>> audio = ["path/to/audio.wav"] * 10
>>> features = Features({"audio": Audio()})
>>> ds = Dataset.from_dict({"audio": audio}, features=features)
>>> ds = ds.with_format("tf")
>>> ds[0]["audio"]["array"]
<tf.Tensor: shape=(202311,), dtype=float32, numpy=
array([ 6.1035156e-05, 1.5258789e-05, 1.6784668e-04, ...,
-1.5258789e-05, -1.5258789e-05, 1.5258789e-05], dtype=float32)>
>>> ds[0]["audio"]["sampling_rate"]
<tf.Tensor: shape=(), dtype=int32, numpy=44100>データ読み込み
データセットのインデックスで個々のサンプルやバッチを読み込むことは可能ですが、fit() や predict() などの Keras メソッドを使用する場合は機能しません。データセットからバッチをシャッフルして読み込むジェネレーター関数を書いて、それに対して fit() を実行することもできますが、それは不要な作業が多すぎます。代わりに、データセットからデータをオンザフライでストリーミングしたい場合は、to_tf_dataset() メソッドを使用してデータセットを tf.data.Dataset に変換することを推奨します。
tf.data.Dataset クラスは幅広いユースケースに対応しており、メモリ上のテンソルから作成されたり、ロード関数を使用してディスクや外部ストレージ上のファイルを読み込んだりすることがよくあります。データセットは map() メソッドで自由に変換でき、batch() や shuffle() のようなメソッドを使用して、トレーニングにすぐ使えるデータセットを作成することもできます。これらのメソッドは、保存されたデータをいかなる形でも変更しません。代わりに、メソッドはデータパイプラインのグラフを構築し、データセットが反復される際に(通常はモデルのトレーニングや推論中に)実行されます。これは、Hugging Face の Dataset オブジェクトの map() メソッドとは異なり、後者は map 関数を即座に実行し、新規または変更されたカラムを保存します。
データ前処理パイプライン全体をtf.data.Datasetにコンパイルできるため、このアプローチにより、大規模な並列かつ非同期なデータ読み込みと学習が可能になります。ただし、グラフコンパイルの要件は制約となることがあり、特にHugging Faceのトークナイザは、TFグラフの一部として(まだ)コンパイルできないことが一般的です。そのため、通常は、任意のPython関数を使用できるHugging Faceのデータセットとしてデータを前処理し、その後to_tf_dataset()を使用してtf.data.Datasetに変換することで、学習に備えたバッチ化されたデータセットを取得することを推奨しています。このアプローチの例については、transformersのexamplesまたはnotebooksを参照してください。
to_tf_dataset()の使用
to_tf_dataset()の使用は簡単です。データセットの前処理が完了し、準備が整ったら、次のように呼び出します。
>>> from datasets import Dataset
>>> data = {"inputs": [[1, 2],[3, 4]], "labels": [0, 1]}
>>> ds = Dataset.from_dict(data)
>>> tf_ds = ds.to_tf_dataset(
columns=["inputs"],
label_cols=["labels"],
batch_size=2,
shuffle=True
)ここで返されるtf_dsオブジェクトは、トレーニングに完全に準備が整っており、model.fit()に直接渡すことができます。バッチサイズはデータセットの作成時に設定するため、fit()を呼び出す際に指定する必要はありません。
>>> model.fit(tf_ds, epochs=2)引数の詳細については、to_tf_dataset()のドキュメントを参照してください。多くの場合、呼び出しにcollate_fnを追加する必要があります。これは、データセットの複数の要素を受け取り、それらを単一のバッチに結合する関数です。すべての要素が同じ長さである場合、組み込みのデフォルトのコレクターで十分ですが、より複雑なタスクではカスタムコレクターが必要になる場合があります。特に、シーケンス長が異なるサンプルを持つタスクでは、バッチを正しくパディングできるdata collatorが必要です。transformersのNLPexamplesやnotebooksには、可変長シーケンスが非常に一般的なこれらの例が示されています。
to_tf_dataset での読み込みが遅いと感じる場合は、num_workers 引数を使用することもできます。これにより、複数のサブプロセスを起動してデータを並列で読み込みます。この機能は比較的新しく、まだ実験的な段階です。使用時にバグに遭遇した場合は、ぜひ issue を投稿してください。
to_tf_dataset を使用する場合
鋭い読者であれば、この時点で同じ目的を達成するための2つのアプローチが提示されていることに気づいているかもしれません。TensorFlowモデルにデータセットを渡す場合、.with_format('tf')を使用してデータセットをTensorまたはTensorsのdictに変換するか、to_tf_dataset()を使用してデータセットをtf.data.Datasetに変換するかのどちらかです。どちらの方法でもmodel.fit()に渡すことができるため、どちらを選ぶべきでしょうか?
重要なのは、データセット全体をTensorに変換すると、それが静的になり、完全にRAMに読み込まれるという点です。これはシンプルで便利ですが、以下のいずれかに該当する場合は、代わりにto_tf_dataset()を使用すべきです。
- データセットがRAMに収まりません。
to_tf_dataset()は一度に1バッチのみをストリーミングするため、非常に大きなデータセットでもこの方法で処理できます。 dataset.with_transform()またはcollate_fnを使用してランダムな変換を適用したい場合があります。これは、ビジョンモデルの学習時の画像拡張や、マスク言語モデルの学習時のランダムなマスキングなど、複数のモダリティで一般的です。to_tf_dataset()を使用すると、バッチが読み込まれる時点でこれらの変換が適用されるため、同じサンプルが読み込まれるたびに異なる拡張が適用されます。通常、これが望ましい動作です。- データの変数が可変長の場合、例えばNLPにおける入力テキストのようにトークン数が様々である場合、可変長の変数を持つサンプルでバッチを作成する際の標準的な解決策は、短いサンプルを最も長いサンプルの長さにパディングすることです。
to_tf_datasetでデータセットからサンプルをストリーミングする際、collate_fnを介して各バッチにこのパディングを適用できます。しかし、そのようなデータセットを密なTensorに変換したい場合、データセット全体で最も長いサンプルの長さにサンプルをパディングする必要があります。これにより膨大なパディングが発生し、メモリを浪費し、モデルの速度を低下させる可能性があります。
注意事項と制限
現在、to_tf_dataset() は常にバッチ化されたデータセットを返します。まもなく、バッチ化されていないデータセットへのサポートを追加する予定です。