このシリーズでは、自然言語処理において主流であるTransformerを中心に、環境構築から学習の方法までまとめます。
今回の記事ではHuggingface Transformersによる日本語の質問応答タスクに関する実装について、学習から推論までの基本的な流れを紹介します。
なお、英語の質問応答の実装はこちらの記事をご覧ください。
Google colabを使用して、簡単に最新の自然言語処理モデルを実装することができますので、ぜひ最後までご覧ください。
今回の内容
・質問応答のデータセット
・質問応答の学習(日本語)
・質問応答の推論(日本語)
質問応答とは
自然言語処理における質問応答とは、入力した文章に関する質問について解答を抽出するタスクです。
下の例のように、元の文章に関する質問に対して、解答を出力することができます。
元の文章 |
---|
Transformers (以前は pytorch-transformers および pytorch-pretrained-bert と呼ばれていた) は、汎用の自然言語理解 (NLU) および Natural 用のアーキテクチャー (BERT、GPT-2、RoBERTa、XLM、DistilBert、XLNet など)、100 以上の言語で 32 以上の事前トレーニング済みモデルを備えた言語生成 (NLG) 性能と、TensorFlow 2.0 と PyTorchとの間での深い相互運用性を有する。 |
質問 | 解答 | |
---|---|---|
Transformers で使用できる事前トレーニング済みのモデルは いくつありますか? | → | ??? |
Transformers は何を提供しますか? | → | ??? |
Transformers は、どのフレームワーク間の相互運用性を提供しますか? | → | ??? |
今回の記事では、「運転ドメインQAデータセット」データセットを用いた学習から推論までの基本的な流れを紹介します。
Transformerとは
概要
「Transformer」は2017年にGoogleが「Attention is all you need」で発表した深層学習モデルです。
現在では、自然言語処理に利用する深層学習モデルの主流になっています。
これまでの自然言語処理分野で多く使われていた「RNN」(Recurrent Neural Network)や「CNN」(Convolutional Neural Network)を利用せず、Attentionのみを用いたEncoder-Decoder型のモデルとなっています。
「Transformer」が登場して以降、多くの自然言語処理モデルが再構築され、過去を上回る成果を上げています。
最近では自然言語処理だけでなく、ViTやDETRなどといった画像認識にも応用されています。
詳細は以下の記事をご覧ください。
Huggingface Transformersとは
概要
「Hugging Face」とは米国のHugging Face社が提供している、自然言語処理に特化したディープラーニングのフレームワークです。
「Huggingface Transformers」は、先ほど紹介したTransformerを実装するためのフレームワークであり、「自然言語理解」と「自然言語生成」の最先端の汎用アーキテクチャ(BERT、GPT-2など)と、何千もの事前学習済みモデルを提供しています。
ソースコードは全てGitHub上で公開されており、誰でも無料で使うことができます。
事前学習済みモデル
Hugging Faceではタスクに応じた様々な事前学習済みモデルが提供されています。
こちらからご確認ください。
データセットの準備
ここからは質問応答に用いるデータセットを紹介します。
SQuAD
SQuAD は The Stanford Question Answering Datasetは質問応答タスクの主流のデータセットで、機械がパッセージを読み、それに関する質問に答える能力をテストするために用いられます。
現在はSQuAD2.0が公開されています。
データの内容はこちらの記事で紹介していますので、興味がある方はご覧ください。
運転ドメインQAデータセット
日本語の質問応答のデータセットとして公開されている「運転ドメインQAデータセット」を引用して使用します。
運転ドメインQAデータセットは、ウェブ上で公開されている運転ドメインのブログ記事を基に構築しており、述語項構造QAデータセット(PAS-QAデータセット)と文章読解QAデータセット(RC-QAデータセット)から構成されています。
データセットの形式はSQuAD 2.0と同じになっているため、そのまま学習することができます。
詳細はリンクよりご確認下さい。
データセット引用:運転ドメインQAデータセット
Google colabによる導入
ここからはGoogle colabを使用して実装していきます。
まずはGPUを使用できるように設定をします。
「ランタイムのタイプを変更」→「ハードウェアアクセラレータ」をGPUに変更
今回紹介するコードは以下のボタンからコピーして使用していただくことも可能です。
まずはGoogleドライブをマウントして、作業フォルダを作成します。
from google.colab import drive
drive.mount('/content/drive')
!mkdir -p '/content/drive/My Drive/huggingface_transformers_demo/'
%cd '/content/drive/My Drive/huggingface_transformers_demo/'
!git clone https://github.com/huggingface/transformers
%cd transformers
次に必要なライブラリをインストールします。
!pip install git+https://github.com/huggingface/transformers
データセットを格納する
まずは「運転ドメインQAデータセット」から、データをダウンロードします。
「RC-QA」フォルダ内にある「DDQA-1.0_RC-QA_train.json」と「DDQA-1.0_RC-QA_dev.json」をGoogleドライブから「huggingface_transformers_demo」にアップします。
データセットを確認する
まずは「DDQA-1.0_RC-QA_train.json」の内容を見てみましょう。
# 日本語のデータセットの内容を確認
import json
json_file = open('DDQA-1.0_RC-QA_train.json')
json_object = json.load(json_file)
print(json_object["data"][0]["paragraphs"][0])
実行すると、以下のような結果が出力されます。
{'context': '自転 車 は ドコ 走りゃ イイ の さ ! 自転 車 は そもそも 車道 の 左端 を 走る の が ルール な の です が 、 いざ 車道 を 走る と ものすごい 車 に 邪険 に さ れ ます 。 クロス バイク で 往復 32 km 程 の 道のり を 自転 車 通勤 し て た 時 の 事 。 赤 信号 で 左側 に 自転 車 が 止め られ ない よう に 左 に 幅寄せ し て き たり と か 、 渋滞 し てる 時 に 抜き つ 抜 かれつ に なる の が 嫌 な の か 、 やっぱり 幅寄せ さ れる 事 が よく あり ます 。',
'qas': [{'id': '57590011580005_00', 'question': '自転 車 が 走る の は 車道 の どちら 側 が ルール です か ?',
'answers': [{'text': '左端', 'answer_start': 40}, {'text': '左端 を 走る', 'answer_start': 40}, {'text': '車道 の 左端 を 走る の が ルール', 'answer_start': 35}], 'is_impossible': False},
{'id': '57590011580005_01', 'question': '自転 車 は どこ を 走る の が ルール です か ?',
'answers': [{'text': '車道 の 左端', 'answer_start': 35}, {'text': '車道 の 左端 を 走る', 'answer_start': 35}, {'text': '車道 の 左端 を 走る の が ルール', 'answer_start': 35}], 'is_impossible': False}]}
次に比較のため、SQuAD 2.0の内容を見てみましょう。
# Huggingface Datasetsのインストール
!pip install datasets
# 英語のデータセット「squad」の内容を確認
from datasets import load_dataset
import pandas as pd
test = load_dataset("squad")
dfs = {split: dset.to_pandas() for split, dset in test.flatten().items()}
qa_cols = ["title", "question", "answers.text",
"answers.answer_start", "context"]
sample_df = dfs["train"][qa_cols][:1]
sample_df
実行すると、以下のような結果が出力されます。
title | question | answers.text | answers.answer_start | context |
---|---|---|---|---|
University_of_Notre_Dame | To whom did the Virgin Mary allegedly appear i… | [Saint Bernadette Soubirous] | [515] | Architecturally, the school has a Catholic cha… |
データセットを比較する
今回使用するデータセットが「SQuAD 2.0」と同じ形式になっていることを確認するため、並べて比較してみます。
# 日本語のデータセットの内容
print('title:',sample_df["title"][0],"\n"
'question:',sample_df["question"][0],"\n"
'answers.text:',sample_df["answers.text"][0],"\n"
'answers.answer_start :',sample_df["answers.answer_start"][0],"\n"
'context:',sample_df["context"][0],"\n")
# 英語のデータセットの内容
print('title:',json_object["data"][0]["title"],"\n"
'question:',json_object["data"][0]["paragraphs"][0]["qas"][0]["question"],"\n"
'answers.text:',json_object["data"][0]["paragraphs"][0]["qas"][0]["answers"][0]["text"],"\n"
'answers.answer_start :',json_object["data"][0]["paragraphs"][0]["qas"][0]["answers"][0]["answer_start"],"\n"
'context:',json_object["data"][0]["paragraphs"][0]["context"],"\n")
実行すると、以下のような結果が出力されます。
title: University_of_Notre_Dame
question: To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France?
answers.text: ['Saint Bernadette Soubirous']
answers.answer_start : [515]
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.
title: 運転ドメイン
question: 自転 車 が 走る の は 車道 の どちら 側 が ルール です か ?
answers.text: 左端
answers.answer_start : 40
context: 自転 車 は ドコ 走りゃ イイ の さ ! 自転 車 は そもそも 車道 の 左端 を 走る の が ルール な の です が 、 いざ 車道 を 走る と ものすごい 車 に 邪険 に さ れ ます 。 クロス バイク で 往復 32 km 程 の 道のり を 自転 車 通勤 し て た 時 の 事 。 赤 信号 で 左側 に 自転 車 が 止め られ ない よう に 左 に 幅寄せ し て き たり と か 、 渋滞 し てる 時 に 抜き つ 抜 かれつ に なる の が 嫌 な の か 、 やっぱり 幅寄せ さ れる 事 が よく あり ます 。
今回使用するデータセットは「SQuAD 2.0」と同じであることがわかりました。
日本語の質問応答の学習
データセットの準備ができたので、早速学習をしていきましょう。
事前学習済モデルは「cl-tohoku/bert-base-japanese-whole-word-masking」を使用します。
!python ./examples/legacy/question-answering/run_squad.py \
--model_type=bert \
--model_name_or_path=cl-tohoku/bert-base-japanese-whole-word-masking \
--do_train \
--do_eval \
--max_seq_length=384 \
--per_gpu_train_batch_size=12 \
--learning_rate=3e-5 \
--num_train_epochs=10 \
--train_file=DDQA-1.0_RC-QA_train.json \
--predict_file=DDQA-1.0_RC-QA_dev.json \
--output_dir=tmp/DDQA-1.0_RC-QA_/ \
--overwrite_output_dir
学習が終わると、「/tmp/DDQA-1.0_RC-QA_/」に結果が保存されます。
日本語の質問応答の推論
先ほど学習したモデルを使用して、推論を実装してましょう。
「運転ドメインQAデータセット」内の「DDQA-1.0_RC-QA_test.json」ファイルからランダムで抽出した質問について、解答を出力してみます。
まずは、元になる文章と質問を定義します。
元の文章 |
---|
今年に入ってから、自分が乗っている車に関する事件が絶えません。雪が降ったため、ガソリンスタンドでタイヤ交換を頼んだところ、荷台の部分の扉を支える伸縮棒が無理な力で折れ曲がり、上へ上がるはずの扉が半分までしか開かない状態に。どうやら、スタンドのスタッフが、交換したタイヤを車に積み込んだ際に、無理に扉を閉めたことが原因のようでした。この件は、ガソリンスタンド側の過失ということで、修理をしてもらいました。 |
質問 | 解答 | |
---|---|---|
今年に入ってから、何に関する事件が絶えないのか? | → | ??? |
from transformers import BertJapaneseTokenizer, AutoModelForQuestionAnswering
import torch
# トークナイザーとモデルをインスタンス化
model = AutoModelForQuestionAnswering.from_pretrained('tmp/DDQA-1.0_RC-QA_/')
tokenizer = BertJapaneseTokenizer.from_pretrained('cl-tohoku/bert-base-japanese-whole-word-masking')
# 元の文章(入力)を定義
context = "今年に入ってから、自分が乗っている車に関する事件が絶えません。雪が降ったため、ガソリンスタンドでタイヤ交換を頼んだところ、荷台の部分の扉を支える伸縮棒が無理な力で折れ曲がり、上へ上がるはずの扉が半分までしか開かない状態に。どうやら、スタンドのスタッフが、交換したタイヤを車に積み込んだ際に、無理に扉を閉めたことが原因のようでした。この件は、ガソリンスタンド側の過失ということで、修理をしてもらいました。"
# 質問を定義
question="今年に入ってから、何に関する事件が絶えないのか?"
各質問に対して、解答を出力します。
# 質問を反復処理し、テキストと現在の質問からシーケンスを構築
inputs = tokenizer.encode_plus(question, context, add_special_tokens=True, return_tensors="pt")
input_ids = inputs["input_ids"].tolist()[0]
# このシーケンスをモデルに渡して、開始位置と終了位置の両方について、シーケンストークン全体 (質問とテキスト) のスコアの範囲を出力
output = model(**inputs)
# スコアのargmaxで最も可能性の高い解答の先頭を取得する
answer_start = torch.argmax(output.start_logits)
# スコアのargmaxで最も可能性の高い解答の末尾を取得する
answer_end = torch.argmax(output.end_logits) + 1
# top_kメソッドを使用して上位 5 つのトークンを取得
answer = tokenizer.convert_tokens_to_string(tokenizer.convert_ids_to_tokens(input_ids[answer_start:answer_end]))
# 結果出力
print("質問: "+question)
print("応答: "+answer)
実行すると、以下のような結果が出力されます。
質問: 今年に入ってから、何に関する事件が絶えないのか?
応答: 自分 が 乗っ て いる 車
まとめると以下のようになります。
元の文章 |
---|
今年に入ってから、自分が乗っている車に関する事件が絶えません。雪が降ったため、ガソリンスタンドでタイヤ交換を頼んだところ、荷台の部分の扉を支える伸縮棒が無理な力で折れ曲がり、上へ上がるはずの扉が半分までしか開かない状態に。どうやら、スタンドのスタッフが、交換したタイヤを車に積み込んだ際に、無理に扉を閉めたことが原因のようでした。この件は、ガソリンスタンド側の過失ということで、修理をしてもらいました。 |
質問 | 解答 | |
---|---|---|
今年に入ってから、何に関する事件が絶えないのか? | → | 自分 が 乗っている車 |
日本語の質問応答タスクに関する実装について、学習したモデルから推論までの実装ができました。
まとめ
最後までご覧いただきありがとうございました。
今回の記事ではHuggingface Transformersによる日本語の質問応答タスクに関する実装について、学習から推論までの基本的な流れを紹介しました。
このシリーズでは、自然言語処理全般に関するより詳細な実装や学習の方法を紹介しておりますので、是非ご覧ください。