Question answering
質問応答タスクは、質問に対して回答を返します。 Alexa、Siri、Google などの仮想アシスタントに天気を尋ねたことがあるなら、質問応答モデルを使用したことがあるはずです。質問応答タスクには一般的に 2 つのタイプがあります。
- 抽出: 与えられたコンテキストから回答を抽出します。
- 抽象的: 質問に正しく答えるコンテキストから回答を生成します。
このガイドでは、次の方法を説明します。
- 抽出的質問応答用に SQuAD データセット上の DistilBERT を微調整します。
- 微調整したモデルを推論に使用します。
このタスクと互換性のあるすべてのアーキテクチャとチェックポイントを確認するには、タスクページ を確認することをお勧めします。
始める前に、必要なライブラリがすべてインストールされていることを確認してください。
pip install transformers datasets evaluate
モデルをアップロードしてコミュニティと共有できるように、Hugging Face アカウントにログインすることをお勧めします。プロンプトが表示されたら、トークンを入力してログインします。
>>> from huggingface_hub import notebook_login
>>> notebook_login()
Load SQuAD dataset
まず、🤗 データセット ライブラリから SQuAD データセットの小さいサブセットを読み込みます。これにより、完全なデータセットのトレーニングにさらに時間を費やす前に、実験してすべてが機能することを確認する機会が得られます。
>>> from datasets import load_dataset
>>> squad = load_dataset("squad", split="train[:5000]")
train_test_split
メソッドを使用して、データセットの train
分割をトレイン セットとテスト セットに分割します。
>>> squad = squad.train_test_split(test_size=0.2)
次に、例を見てみましょう。
>>> squad["train"][0]
{'answers': {'answer_start': [515], 'text': ['Saint Bernadette Soubirous']},
'context': 'Architecturally, the school has a Catholic character. Atop the Main Building\'s gold dome is a golden statue of the Virgin Mary. Immediately in front of the Main Building and facing it, is a copper statue of Christ with arms upraised with the legend "Venite Ad Me Omnes". Next to the Main Building is the Basilica of the Sacred Heart. Immediately behind the basilica is the Grotto, a Marian place of prayer and reflection. It is a replica of the grotto at Lourdes, France where the Virgin Mary reputedly appeared to Saint Bernadette Soubirous in 1858. At the end of the main drive (and in a direct line that connects through 3 statues and the Gold Dome), is a simple, modern stone statue of Mary.',
'id': '5733be284776f41900661182',
'question': 'To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France?',
'title': 'University_of_Notre_Dame'
}
ここにはいくつかの重要なフィールドがあります。
answers
: 回答トークンと回答テキストの開始位置。context
: モデルが答えを抽出するために必要な背景情報。question
: モデルが答える必要がある質問。
Preprocess
次のステップでは、DistilBERT トークナイザーをロードしてquestion
フィールドとcontext
フィールドを処理します。
>>> from transformers import AutoTokenizer
>>> tokenizer = AutoTokenizer.from_pretrained("distilbert/distilbert-base-uncased")
質問応答タスクに特有の、注意すべき前処理手順がいくつかあります。
- データセット内の一部の例には、モデルの最大入力長を超える非常に長い「コンテキスト」が含まれる場合があります。より長いシーケンスを処理するには、
truncation="only_second"
を設定してcontext
のみを切り捨てます。 - 次に、設定によって、回答の開始位置と終了位置を元の
context
にマッピングします。 「return_offset_mapping=True
」。 - マッピングが手元にあるので、答えの開始トークンと終了トークンを見つけることができます。
sequence_ids
メソッドを使用して、 オフセットのどの部分がquestion
に対応し、どの部分がcontext
に対応するかを見つけます。
以下に、answer
の開始トークンと終了トークンを切り詰めてcontext
にマッピングする関数を作成する方法を示します。
>>> def preprocess_function(examples):
... questions = [q.strip() for q in examples["question"]]
... inputs = tokenizer(
... questions,
... examples["context"],
... max_length=384,
... truncation="only_second",
... return_offsets_mapping=True,
... padding="max_length",
... )
... offset_mapping = inputs.pop("offset_mapping")
... answers = examples["answers"]
... start_positions = []
... end_positions = []
... for i, offset in enumerate(offset_mapping):
... answer = answers[i]
... start_char = answer["answer_start"][0]
... end_char = answer["answer_start"][0] + len(answer["text"][0])
... sequence_ids = inputs.sequence_ids(i)
... # Find the start and end of the context
... idx = 0
... while sequence_ids[idx] != 1:
... idx += 1
... context_start = idx
... while sequence_ids[idx] == 1:
... idx += 1
... context_end = idx - 1
... # If the answer is not fully inside the context, label it (0, 0)
... if offset[context_start][0] > end_char or offset[context_end][1] < start_char:
... start_positions.append(0)
... end_positions.append(0)
... else:
... # Otherwise it's the start and end token positions
... idx = context_start
... while idx <= context_end and offset[idx][0] <= start_char:
... idx += 1
... start_positions.append(idx - 1)
... idx = context_end
... while idx >= context_start and offset[idx][1] >= end_char:
... idx -= 1
... end_positions.append(idx + 1)
... inputs["start_positions"] = start_positions
... inputs["end_positions"] = end_positions
... return inputs
データセット全体に前処理関数を適用するには、🤗 Datasets map
関数を使用します。 batched=True
を設定してデータセットの複数の要素を一度に処理することで、map
関数を高速化できます。不要な列を削除します。
>>> tokenized_squad = squad.map(preprocess_function, batched=True, remove_columns=squad["train"].column_names)
次に、DefaultDataCollator
を使用してサンプルのバッチを作成します。 🤗 Transformers の他のデータ照合器とは異なり、DefaultDataCollator
はパディングなどの追加の前処理を適用しません。
>>> from transformers import DefaultDataCollator
>>> data_collator = DefaultDataCollator()
>>> from transformers import DefaultDataCollator
>>> data_collator = DefaultDataCollator(return_tensors="tf")
Train
これでモデルのトレーニングを開始する準備が整いました。 AutoModelForQuestionAnswering を使用して DitilBERT をロードします。
>>> from transformers import AutoModelForQuestionAnswering, TrainingArguments, Trainer
>>> model = AutoModelForQuestionAnswering.from_pretrained("distilbert/distilbert-base-uncased")
この時点で残っている手順は次の 3 つだけです。
- TrainingArguments でトレーニング ハイパーパラメータを定義します。唯一の必須パラメータは、モデルの保存場所を指定する
output_dir
です。push_to_hub=True
を設定して、このモデルをハブにプッシュします (モデルをアップロードするには、Hugging Face にサインインする必要があります)。 - トレーニング引数をモデル、データセット、トークナイザー、データ照合器とともに Trainer に渡します。
- train() を呼び出してモデルを微調整します。
>>> training_args = TrainingArguments(
... output_dir="my_awesome_qa_model",
... eval_strategy="epoch",
... learning_rate=2e-5,
... per_device_train_batch_size=16,
... per_device_eval_batch_size=16,
... num_train_epochs=3,
... weight_decay=0.01,
... push_to_hub=True,
... )
>>> trainer = Trainer(
... model=model,
... args=training_args,
... train_dataset=tokenized_squad["train"],
... eval_dataset=tokenized_squad["test"],
... tokenizer=tokenizer,
... data_collator=data_collator,
... )
>>> trainer.train()
トレーニングが完了したら、 push_to_hub() メソッドを使用してモデルをハブに共有し、誰もがモデルを使用できるようにします。
>>> trainer.push_to_hub()
Keras を使用したモデルの微調整に慣れていない場合は、こちら の基本的なチュートリアルをご覧ください。
</ヒント> TensorFlow でモデルを微調整するには、オプティマイザー関数、学習率スケジュール、およびいくつかのトレーニング ハイパーパラメーターをセットアップすることから始めます。
>>> from transformers import create_optimizer
>>> batch_size = 16
>>> num_epochs = 2
>>> total_train_steps = (len(tokenized_squad["train"]) // batch_size) * num_epochs
>>> optimizer, schedule = create_optimizer(
... init_lr=2e-5,
... num_warmup_steps=0,
... num_train_steps=total_train_steps,
... )
次に、TFAutoModelForQuestionAnswering を使用して DistilBERT をロードできます。
>>> from transformers import TFAutoModelForQuestionAnswering
>>> model = TFAutoModelForQuestionAnswering("distilbert/distilbert-base-uncased")
prepare_tf_dataset() を使用して、データセットを tf.data.Dataset
形式に変換します。
>>> tf_train_set = model.prepare_tf_dataset(
... tokenized_squad["train"],
... shuffle=True,
... batch_size=16,
... collate_fn=data_collator,
... )
>>> tf_validation_set = model.prepare_tf_dataset(
... tokenized_squad["test"],
... shuffle=False,
... batch_size=16,
... collate_fn=data_collator,
... )
compile
を使用してトレーニング用のモデルを設定します。
>>> import tensorflow as tf
>>> model.compile(optimizer=optimizer)
トレーニングを開始する前に最後にセットアップすることは、モデルをハブにプッシュする方法を提供することです。これは、モデルとトークナイザーを PushToHubCallback でプッシュする場所を指定することで実行できます。
>>> from transformers.keras_callbacks import PushToHubCallback
>>> callback = PushToHubCallback(
... output_dir="my_awesome_qa_model",
... tokenizer=tokenizer,
... )
ついに、モデルのトレーニングを開始する準備が整いました。トレーニングおよび検証データセット、エポック数、コールバックを指定して fit
を呼び出し、モデルを微調整します。
>>> model.fit(x=tf_train_set, validation_data=tf_validation_set, epochs=3, callbacks=[callback])
トレーニングが完了すると、モデルは自動的にハブにアップロードされ、誰でも使用できるようになります。
質問応答用のモデルを微調整する方法の詳細な例については、対応するドキュメントを参照してください。 PyTorch ノートブック または TensorFlow ノートブック。
Evaluate
質問応答の評価には、大量の後処理が必要です。時間がかかりすぎないように、このガイドでは評価ステップを省略しています。 Trainer はトレーニング中に評価損失を計算するため、モデルのパフォーマンスについて完全に分からないわけではありません。
もっと時間があり、質問応答用のモデルを評価する方法に興味がある場合は、質問応答 の章を参照してください。 🤗ハグフェイスコースから!
Inference
モデルを微調整したので、それを推論に使用できるようになりました。
質問と、モデルに予測させたいコンテキストを考え出します。
>>> question = "How many programming languages does BLOOM support?"
>>> context = "BLOOM has 176 billion parameters and can generate text in 46 languages natural languages and 13 programming languages."
推論用に微調整されたモデルを試す最も簡単な方法は、それを pipeline() で使用することです。モデルを使用して質問応答用のpipeline
をインスタンス化し、それにテキストを渡します。
>>> from transformers import pipeline
>>> question_answerer = pipeline("question-answering", model="my_awesome_qa_model")
>>> question_answerer(question=question, context=context)
{'score': 0.2058267742395401,
'start': 10,
'end': 95,
'answer': '176 billion parameters and can generate text in 46 languages natural languages and 13'}
必要に応じて、pipeline
の結果を手動で複製することもできます。
テキストをトークン化して PyTorch テンソルを返します。
>>> from transformers import AutoTokenizer
>>> tokenizer = AutoTokenizer.from_pretrained("my_awesome_qa_model")
>>> inputs = tokenizer(question, context, return_tensors="pt")
入力をモデルに渡し、logits
を返します。
>>> import torch
>>> from transformers import AutoModelForQuestionAnswering
>>> model = AutoModelForQuestionAnswering.from_pretrained("my_awesome_qa_model")
>>> with torch.no_grad():
... outputs = model(**inputs)
モデル出力から開始位置と終了位置の最も高い確率を取得します。
>>> answer_start_index = outputs.start_logits.argmax()
>>> answer_end_index = outputs.end_logits.argmax()
予測されたトークンをデコードして答えを取得します。
>>> predict_answer_tokens = inputs.input_ids[0, answer_start_index : answer_end_index + 1]
>>> tokenizer.decode(predict_answer_tokens)
'176 billion parameters and can generate text in 46 languages natural languages and 13'
テキストをトークン化し、TensorFlow テンソルを返します。
>>> from transformers import AutoTokenizer
>>> tokenizer = AutoTokenizer.from_pretrained("my_awesome_qa_model")
>>> inputs = tokenizer(question, text, return_tensors="tf")
入力をモデルに渡し、logits
を返します。
>>> from transformers import TFAutoModelForQuestionAnswering
>>> model = TFAutoModelForQuestionAnswering.from_pretrained("my_awesome_qa_model")
>>> outputs = model(**inputs)
モデル出力から開始位置と終了位置の最も高い確率を取得します。
>>> answer_start_index = int(tf.math.argmax(outputs.start_logits, axis=-1)[0])
>>> answer_end_index = int(tf.math.argmax(outputs.end_logits, axis=-1)[0])
予測されたトークンをデコードして答えを取得します。
>>> predict_answer_tokens = inputs.input_ids[0, answer_start_index : answer_end_index + 1]
>>> tokenizer.decode(predict_answer_tokens)
'176 billion parameters and can generate text in 46 languages natural languages and 13'