Preprocessの翻訳です。
本書は抄訳であり内容の正確性を保証するものではありません。正確な内容に関しては原文を参照ください。
データセットでモデルをトレーニングする前に、期待されるモデル入力フォーマットに前処理を行う必要があります。データがテキスト、画像、音声であろうが、これらはtensorのバッチに変換、組み立てられる必要があります。🤗 Transformersは、モデルのデータの準備に役立つ一連の前処理クラスを提供しています。このチュートリアルでは以下を学びます:
- テキストでは、テキストをトークンのシーケンスに変換するためにTokenizerを使用し、トークンの数値表現を作成し、tensorを組み立てます。
- スピーチや音声では、音声波形からシーケンスの特徴量を抽出するためにFeature extractorを使用し、tensorに変換します。
- 画像入力では、画像をtensorに変換するためにImageProcessorを使用します。
- マルチモーダルの入力では、トークナイザーと特徴量抽出器や画像プロセッサーを組み合わせるためにProcessorを使用します。
AutoProcessorは、あなたがトークナイザー、画像プロセッサー、特徴量抽出器やプロセッサーを使用していたとしても、常に動作し、使用しているモデルに適切なクラスを自動で選択します。
始める前に、実験するデータセットをロードできるように🤗 Datasetsをインストールします:
pip install datasets
自然言語処理
テキストデータを前処理する主要なツールがtokenizerです。トークナイザーは一連のルールに沿ってテキストをトークン
に分割します。トークンは数値、そしてtensorに変換され、これがモデルの入力となります。モデルに必要となる追加の入力はトークナイザーによって追加されます。
事前学習済みモデルを使用しようとしているのであれば、関連づけられている事前学習済みトークナイザーを使用することが重要です。これによって、テキストは事前学習されたコーパスと同じ方法で分割されるようになり、事前学習で用いられたトークンのインデックスを使用するようになります。
AutoTokenizer.from_pretrained()メソッドで事前トレーニング済みトークナイザーをロードするところからスタートします。これは、モデルが事前学習で用いたvocab
をダウンロードします:
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
そして、トークナイザーにテキストを渡します:
encoded_input = tokenizer("Do not meddle in the affairs of wizards, for they are subtle and quick to anger.")
print(encoded_input)
{'input_ids': [101, 2079, 2025, 19960, 10362, 1999, 1996, 3821, 1997, 16657, 1010, 2005, 2027, 2024, 11259, 1998, 4248, 2000, 4963, 1012, 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],
'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}
トークナイザーは3つの重要なアイテムを持つディクショナリーを返却します:
- input_idsは、センテンスにおけるそれぞれのトークンに対応するインデックスです。
- attention_maskは、トークンがアテンションされるかどうかを示します。
- token_type_idsは、センテンスが複数ある場合、トークンがどのセンテンスに属するのかを示します。
input_ids
をデコードすることで入力に戻します:
tokenizer.decode(encoded_input["input_ids"])
'[CLS] Do not meddle in the affairs of wizards, for they are subtle and quick to anger. [SEP]'
見てわかるように、トークナイザーは2つの特殊トークン、CLS
とSEP
(classifierとseparator)をセンテンスに追加しています。全てのモデルで特殊トークンは必要ではありませんが、モデルが必要とする際にはトークナイザーは自動でそれらを追加します。
前処理したいセンテンスが複数ある場合には、トークナイザーにリストを渡します:
batch_sentences = [
"But what about second breakfast?",
"Don't think he knows about second breakfast, Pip.",
"What about elevensies?",
]
encoded_inputs = tokenizer(batch_sentences)
print(encoded_inputs)
{'input_ids': [[101, 1252, 1184, 1164, 1248, 6462, 136, 102],
[101, 1790, 112, 189, 1341, 1119, 3520, 1164, 1248, 6462, 117, 21902, 1643, 119, 102],
[101, 1327, 1164, 5450, 23434, 136, 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, 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, 1, 1, 1, 1, 1]]}
パディング
センテンスは常に同じ長さというわけではなく、これはモデル入力のtensorが同じ形状を持つ必要があるため問題となることがあります。短いセンテンスに特殊なパディングトークン
を追加することで、tensorが長方形になるようにするための戦略がパディングです。
最長のシーケンスにマッチするようにバッチの短いシーケンスにパディングするように、padding
パラメーターをTrue
に設定します。
batch_sentences = [
"But what about second breakfast?",
"Don't think he knows about second breakfast, Pip.",
"What about elevensies?",
]
encoded_input = tokenizer(batch_sentences, padding=True)
print(encoded_input)
{'input_ids': [[101, 1252, 1184, 1164, 1248, 6462, 136, 102, 0, 0, 0, 0, 0, 0, 0],
[101, 1790, 112, 189, 1341, 1119, 3520, 1164, 1248, 6462, 117, 21902, 1643, 119, 102],
[101, 1327, 1164, 5450, 23434, 136, 102, 0, 0, 0, 0, 0, 0, 0, 0]],
'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, 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, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]]}
これで、短い最初と三番目のセンテンスに0のパディングが追加されます。
切り取り
逆の観点では、時にはあるシーケンスがモデルがハンドリングするには長すぎることがあります。この場合、シーケンスを短い長さで切り取る必要があります。
モデルで受け付ける最大長にシーケンスを切り取るために、truncation
パラメーターをTrue
に設定します:
batch_sentences = [
"But what about second breakfast?",
"Don't think he knows about second breakfast, Pip.",
"What about elevensies?",
]
encoded_input = tokenizer(batch_sentences, padding=True, truncation=True)
print(encoded_input)
{'input_ids': [[101, 1252, 1184, 1164, 1248, 6462, 136, 102, 0, 0, 0, 0, 0, 0, 0],
[101, 1790, 112, 189, 1341, 1119, 3520, 1164, 1248, 6462, 117, 21902, 1643, 119, 102],
[101, 1327, 1164, 5450, 23434, 136, 102, 0, 0, 0, 0, 0, 0, 0, 0]],
'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, 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, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]]}
より多くのパディングや切り取りの引数の詳細については、Padding and truncationのコンセプトガイドをご覧ください。
tensorの構築
最後に、モデルに入力される実際のtensorを返却するようにトークナイザーに指示します。
return_tensors
パラメーターに、PyTorchならpt
、TensorFlowならtf
を指定します:
PyTorch
batch_sentences = [
"But what about second breakfast?",
"Don't think he knows about second breakfast, Pip.",
"What about elevensies?",
]
encoded_input = tokenizer(batch_sentences, padding=True, truncation=True, return_tensors="pt")
print(encoded_input)
{'input_ids': tensor([[101, 1252, 1184, 1164, 1248, 6462, 136, 102, 0, 0, 0, 0, 0, 0, 0],
[101, 1790, 112, 189, 1341, 1119, 3520, 1164, 1248, 6462, 117, 21902, 1643, 119, 102],
[101, 1327, 1164, 5450, 23434, 136, 102, 0, 0, 0, 0, 0, 0, 0, 0]]),
'token_type_ids': tensor([[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, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]),
'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]])}
TensorFlow
batch_sentences = [
"But what about second breakfast?",
"Don't think he knows about second breakfast, Pip.",
"What about elevensies?",
]
encoded_input = tokenizer(batch_sentences, padding=True, truncation=True, return_tensors="tf")
print(encoded_input)
{'input_ids': <tf.Tensor: shape=(2, 9), dtype=int32, numpy=
array([[101, 1252, 1184, 1164, 1248, 6462, 136, 102, 0, 0, 0, 0, 0, 0, 0],
[101, 1790, 112, 189, 1341, 1119, 3520, 1164, 1248, 6462, 117, 21902, 1643, 119, 102],
[101, 1327, 1164, 5450, 23434, 136, 102, 0, 0, 0, 0, 0, 0, 0, 0]],
dtype=int32)>,
'token_type_ids': <tf.Tensor: shape=(2, 9), dtype=int32, numpy=
array([[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, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=int32)>,
'attention_mask': <tf.Tensor: shape=(2, 9), dtype=int32, numpy=
array([[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=int32)>}
音声
音声タスクでは、モデルにデータセットを準備するためにfeature extractorが必要となります。特徴量抽出器は、生の音声データから特徴量を抽出し、tensorに変換するように設計されています。
音声データセットに対してどのように特徴量抽出器を活用できるのかを見るために、MInDS-14データセットをロードします(どのようにデータセットをロードするのかの詳細については🤗 Datasets tutorialをご覧ください):
from datasets import load_dataset, Audio
dataset = load_dataset("PolyAI/minds14", name="en-US", split="train")
入力を確認するためにaudio
カラムの最初の要素にアクセスします。audio
カラムを呼び出すことで、自動で音声ファイルをロードし、再サンプルします:
dataset[0]["audio"]
{'array': array([ 0. , 0.00024414, -0.00024414, ..., -0.00024414,
0. , 0. ], dtype=float32),
'path': '/root/.cache/huggingface/datasets/downloads/extracted/f14948e0e84be638dd7943ac36518a4cf3324e8b7aa331c5ab11541518e9368c/en-US~JOINT_ACCOUNT/602ba55abb1e6d0fbce92065.wav',
'sampling_rate': 8000}
これは3つのアイテムを返却します:
-
array
は、ロードされたスピーチの信号であり、場合によっては再サンプリングされる1Dの配列です。 -
path
は、音声ファイルへのパスです。 -
sampling_rate
は、スピーチ信号で秒間いくつのデータポイントを計測しているのかを示します。
このチュートリアルでは、Wav2Vec2モデルを使用します。モデルカードを参照すると、Wav2Vec2は16kHzでサンプリングされたスピーチオーディオであることがわかります。ご自身の音声データのサンプリングレートが、モデルの事前学習で使用されたデータセットのサンプリングレートと一致することが重要です。データのサンプリングレートが同じでない場合には、データの再サンプリングが必要です。
-
サンプリングレートを16kHzにアップサンプルするために、🤗 Datasetsのcast_columnメソッドを使用します:
Pythondataset = dataset.cast_column("audio", Audio(sampling_rate=16_000))
-
音声ファイルを再サンプリングするために再度
audio
カラムを呼び出します:Pythondataset[0]["audio"]
{'array': array([ 2.3443763e-05, 2.1729663e-04, 2.2145823e-04, ..., 3.8356509e-05, -7.3497440e-06, -2.1754686e-05], dtype=float32), 'path': '/root/.cache/huggingface/datasets/downloads/extracted/f14948e0e84be638dd7943ac36518a4cf3324e8b7aa331c5ab11541518e9368c/en-US~JOINT_ACCOUNT/602ba55abb1e6d0fbce92065.wav', 'sampling_rate': 16000}
次に、入力を正規化、パディングするために特徴量抽出器をロードします。テキストデータをパディングする際、短いシーケンスには0
が追加されます。同じ考え方が音声データにも適用されます。特徴量抽出器は、沈黙を意味する0
をarray
に追加します。
AutoFeatureExtractor.from_pretrained()で特徴量抽出器をロードします:
from transformers import AutoFeatureExtractor
feature_extractor = AutoFeatureExtractor.from_pretrained("facebook/wav2vec2-base")
特徴量抽出器に音声のarray
を渡します。また、発生する可能性のあるサイレントエラーをよりデバックしやすくするために、特徴量抽出器にsampling_rate
引数を追加することをお勧めします。
audio_input = [dataset[0]["audio"]["array"]]
feature_extractor(audio_input, sampling_rate=16000)
{'input_values': [array([ 3.8106556e-04, 2.7506407e-03, 2.8015103e-03, ...,
5.6335266e-04, 4.6588284e-06, -1.7142107e-04], dtype=float32)]}
トークナイザーと同じように、バッチに対してばらつきのあるシーケンスを取り扱えるようにパディングや切り取りを適用することができます。二つの音声サンプルのシーケンス長を見てみましょう:
dataset[0]["audio"]["array"].shape
(173398,)
dataset[1]["audio"]["array"].shape
(106496,)
音声サンプルが同じ長さになるようにデータセットを前処理する関数を作成します。最大サンプル長を指定すると、特徴量抽出器はマッチするようにシーケンスに対してパディング、切り取りを行います:
def preprocess_function(examples):
audio_arrays = [x["array"] for x in examples["audio"]]
inputs = feature_extractor(
audio_arrays,
sampling_rate=16000,
padding=True,
max_length=100000,
truncation=True,
)
return inputs
データセットの最初の数サンプルにpreprocess_function
を適用します:
processed_dataset = preprocess_function(dataset[:5])
これで、サンプルの長さは指定された最大長にマッチするようになりました。これで、処理されたデータセットをモデルに与えられるようになります!
processed_dataset["input_values"][0].shape
(100000,)
processed_dataset["input_values"][1].shape
(100000,)
コンピュータービジョン
コンピュータービジョンタスクでは、モデルにデータセットを準備するためにimage processorが必要となります。画像処理は、モデルによって期待される入力に画像を変換する数ステップから構成されます。これらのステップには、リサイズ、正規化、カラーチャネル修正、画像からtensorへの変換などが含まれます。
画像処理は、多くの場合ある種の画像拡張を伴います。画像の前処理と画像拡張の両方は画像データを変換しますが、いくつかの目的が異なります:
- 画像拡張はモデルの過学習を避け、堅牢性を高めるための方法で画像を変更します。明度や色の調整、切り取り、改訂ん、リサイズ、ズームなど、画像をどのように拡張するのかに関してクリエイティブになることができます。しかし、ご自身の拡張処理で画像の意味を変更しないことに気をつけてください。
- 画像の前処理は画像がモデルの期待する入力にマッチすることを確実にします。コンピュータービジョンモデルをファインチューニングする際、画像は最初にモデルがトレーニングされた際と全く同じように前処理される必要があります。
画像拡張にはお好きなライブラリを活用することができます。画像の前処理では、モデルに関連づけられているImageProcessor
を使ってください。
コンピュータービジョンデータセットにどのように画像プロセッサーを使えるのかを見るために、food101データセットをロードします(どのようにデータセットをロードするのかの詳細については🤗 Datasets tutorialをご覧ください):
このデータセットは非常に大きいので、トレーニングスプリットから小規模なサンプルのみをロードするには🤗 Datasetsのsplit
パラメーターを使ってください!
from datasets import load_dataset
dataset = load_dataset("food101", split="train[:100]")
次に、🤗 DatasetsのImage機能を用いて画像を見てみます:
dataset[0]["image"]
AutoImageProcessor.from_pretrained()で画像プロセッサーをロードします:
from transformers import AutoImageProcessor
image_processor = AutoImageProcessor.from_pretrained("google/vit-base-patch16-224")
最初に、いくつかの画像拡張処理を追加しましょう。好きなライブラリを活用することができますが、このチュートリアルでは、torchvisionのtransformsモジュールを使用します。他のデータ拡張ライブラリに興味があるのであれば、AlbumentationsやKornia notebooksをご覧ください。
-
ここでは、いくつかの変換処理、RandomResizedCropやColorJitterをチェーンするためにComposeを使用します。リサイズでは
image_processor
の画像サイズ要件を取得できることに注意してください。いくつかのモデルでは、正確な高さ、幅が期待され、他のモデルではshortest_edge
のみが定義されます。Pythonfrom torchvision.transforms import RandomResizedCrop, ColorJitter, Compose size = ( image_processor.size["shortest_edge"] if "shortest_edge" in image_processor.size else (image_processor.size["height"], image_processor.size["width"]) ) _transforms = Compose([RandomResizedCrop(size), ColorJitter(brightness=0.5, hue=0.5)])
-
このモデルは入力としてpixel_valuesを受け付けます。
ImageProcessor
は画像の正規化と適切なtensorの作成をケアすることができます。画像のバッチに対して画像拡張と前処理を組み合わせ、pixel_values
を生成する関数を作成します:Pythondef transforms(examples): images = [_transforms(img.convert("RGB")) for img in examples["image"]] examples["pixel_values"] = image_processor(images, do_resize=False, return_tensors="pt")["pixel_values"] return examples
上の例では、画像拡張変換処理ですでに画像をリサイズしており、適切な
image_processor
からのsize
属性を活用するために、do_resize=False
を設定しています。画像拡張で画像のリサイズを行っていない場合、このパラメーターはそのままにしてください。デフォルトでImageProcessor
はリサイズをハンドリングします。拡張変換処理の一部で画像を正規化したいのであれば、
image_processor.image_mean
やimage_processor.image_std
の値を活用してください。 -
そして、オンザフライで変換処理を適用するために、🤗 Datasetsのset_transformを使用します:
Pythondataset.set_transform(transforms)
-
これで、画像にアクセスすると画像プロセッサが
pixel_values
を追加していることに気づくことでしょう。これで、モデルに処理したデータセットを渡せるようになります!Pythondataset[0].keys()
変換処理を適用した後の画像がどのようになるのかを以下に示します。画像はランダムに切り取られ、色のプロパティが異なっています。
import numpy as np
import matplotlib.pyplot as plt
img = dataset[0]["pixel_values"]
plt.imshow(img.permute(1, 2, 0))
物体検知、セマンティックセグメンテーション、インスタンスセグメンテーション、パノプティックセグメンテーションのようなタスクにおいて、ImageProcessor
は後処理のメソッドを提供します。これらのメソッドは、モデルの生のアウトプットを、領域ボックスやセグメンテーションマップのように意味のある予測結果に変換します。
パディング
あるケースでは、例えば、DETRをファインチューニングする際、モデルはトレーニング時にスケール拡張を適用します。これによって、バッチにおける画像サイズが異なる場合があります。DetrImageProcessorのDetrImageProcessor.pad_and_create_pixel_mask()を使用し、バッチ画像を一緒にするためにカスタムのcollate_fn
を定義することができます。
def collate_fn(batch):
pixel_values = [item["pixel_values"] for item in batch]
encoding = image_processor.pad_and_create_pixel_mask(pixel_values, return_tensors="pt")
labels = [item["labels"] for item in batch]
batch = {}
batch["pixel_values"] = encoding["pixel_values"]
batch["pixel_mask"] = encoding["pixel_mask"]
batch["labels"] = labels
return batch
マルチモーダル
マルチモーダルの入力を含むタスクにおいては、モデルのデータセットを準備するためにprocessorが必要となります。プロセッサーは、トークナイザーや特徴量抽出器のような2つの処理オブジェクトを一緒にまとめることができます。
自動スピーチ認識(ASR)のために、どのようにプロセッサーを活用できるのかを見るために、LJ Speechデータセットをロードします(どのようにデータセットをロードするのかの詳細については🤗 Datasets tutorialをご覧ください):
from datasets import load_dataset
lj_speech = load_dataset("lj_speech", split="train")
ASRでは、主にaudio
とtext
にフォーカスするので他のカラムを削除することができます:
lj_speech = lj_speech.map(remove_columns=["file", "id", "normalized_text"])
それでは、audio
とtext
カラムを見てみましょう:
lj_speech[0]["audio"]
{'array': array([-7.3242188e-04, -7.6293945e-04, -6.4086914e-04, ...,
7.3242188e-04, 2.1362305e-04, 6.1035156e-05], dtype=float32),
'path': '/root/.cache/huggingface/datasets/downloads/extracted/917ece08c95cf0c4115e45294e3cd0dee724a1165b7fc11798369308a465bd26/LJSpeech-1.1/wavs/LJ001-0001.wav',
'sampling_rate': 22050}
lj_speech[0]["text"]
'Printing, in the only sense with which we are at present concerned, differs from most if not from all the arts and crafts represented in the Exhibition'
モデルの事前学習で使用されたデータセットのサンプリングレートとご自身の音声データセットのサンプリングレートが一致するように、常に再サンプルを行うことを覚えておいてください!
lj_speech = lj_speech.cast_column("audio", Audio(sampling_rate=16_000))
AutoProcessor.from_pretrained()でプロセッサーをロードします:
from transformers import AutoProcessor
processor = AutoProcessor.from_pretrained("facebook/wav2vec2-base-960h")
-
array
に含まれる音声データをinput_values
に処理し、text
をlabels
にトークナイズする関数を作成します。これらはモデルの入力になります:Pythondef prepare_dataset(example): audio = example["audio"] example.update(processor(audio=audio["array"], text=example["text"], sampling_rate=16000)) return example
-
サンプルに
prepare_dataset
関数を適用します:Pythonprepare_dataset(lj_speech[0])
プロセッサーはinput_values
とlabels
を追加し、サンプリングレートは適切に16kHzにダウンサンプルします。これで、処理されたデータセットをモデルに与えられるようになりました!