BERTにセンター試験や文章生成をやらせてみる

1.はじめに

 BERTは、事前学習+ファインチューニングの2段階学習によって、自然言語処理分野における11のタスクでSoTAを記録した革命的なモデルです。

 今回はお手軽に、事前学習済みBERTモデルをファインチューニング無しでそのまま使ってセンター試験文章生成がどれくらい出来るかやらせてみたいと思います。

 今回のコードは Google Colab で作成し Github に上げてありますので、自分でやってみたい方は、この 「リンク」 をクリックし表示されたシートの先頭にある「Colab on Web」ボタンをクリックすると動かせます。

2.BERTとは?

 2017年12月にEncoder-Decoder翻訳モデルを並列処理が可能であるAttentionのみで構成したTransformerが発表されました。その後研究が進むにつれてTransformerに使われている Self Attention による文章の意味抽出能力が相当強力であることが分かりました。

 そこで、TransformerのEncoder部分のみを使ったOpenAI GPTが発表されSelf Attentionで性能が向上することが分かりましたが、前の文脈しか利用できませんでした。一方、双方向LSTMを採用した翻訳モデルELMoは、前後の文脈の活用で性能が向上することが分かりましたが、並列処理が行えませんでした。

 そして2018年10月にBERTが発表されました。BERTは、Self Attention並列処理を行い、かつ事前学習の工夫によって前後の文脈を活用することを可能にしました。そして、事前学習+ファインチューニングの2段階学習によって様々なタスクへの対応を可能にし、現在主流のモデルになりました。

 まず、Self Attentionについて簡単に説明します。入力(単語数, 次元数)に3つの全結合層を通してQuery, Key, Valueを作ります。つまり、入力に3つの重み(Wq, Wk, Wv)を掛けたものが、それぞれ Query, Key, Value となります。この3つの重みが学習の中で最適化されて行くわけです。

  QueryとKey(転置)の内積によって、Queryの各ベクトルがKeyのどのベクトルと関連度が高いかを表す重み(Weight)を求めます。その後、ルートdkで割っているのは、Softmaxをかけた時に大き過ぎる値があるとそれ以外の数字が0になってしまうので、それを防止するためのものです。

 そして、その重みとValueの内積によって、Valueの各ベクトルの重み付け和(Context vector)を求めます。結果、Contextvector は、QueryとKeyの掛かり受け構造がどうなっているのかを表すことになり、これが文の意味抽出において絶大な力を発揮します。

 次に、前後の文脈を利用する事前学習の工夫を2つ説明します。

 1つ目は、Masked Language Modelで、穴埋め問題を解かせることで前後の文脈を利用できるようにします。文全体の15%の単語を選び、その内80%マスク、10%を他の単語に置き換え、10%をそのままにします。15%に抑えているのは、この後のファインチューニングでマスクは登場しないのでギャップが大きいとファインチューニングが上手く行かないためです。

 2つ目は、Next Sentence Predictionで、2つの文が意味的に繋がっているかを判定するものです。

 BERTの事前学習は、穴埋め問題を解く時に文の15%しか使わないため学習には相当時間が掛かりますが、その分ファインチューニングは学習量が少なく短時間で済みます。つまり、誰かが時間を掛けて事前学習をしてしまえば、色々な人が短時間のファインチューニングでBERTの恩恵に預かれると言うわけです。

 ついでに、BERTの入力形態についても触れておくと、

 入力は、単語のベクトル表現(Token Embedding)に、文の区分を表すベクトル(Segment Embedding)と、単語位置を表すベクトル(Position Embedding)を加えたものです。

 Segment Embedding と Psition Embedding が必要なのは、Self Attention で重み和を計算する時に、単語の位置情報を失うことを防ぐためです。

3. BERTにセンター試験を解かせる

 なぜBERTにセンター試験を解かせようと思ったかと言うと、今回使うモデルが日本語Wikipediaを使って学習しているからです。つまり、今回使うモデルは、古今東西の様々な知識を穴埋め問題で解く形で勉強をしているわけで、それならセンター試験の穴埋め問題くらい解けるかもしれないと思って無茶振りした次第です。

 まず、今回使うモジュール形態素解析システム(JUMMAN++)をインストールし、京都大学が提供しているBERT事前学習済みモデルをダウンロードします。詳細は、Google Colabのコードを参照下さい。

 さて、BERTに解かせる問題は、平成30年度の世界史Bの第2問の問1です。

 正解は①の「貴族」と「カエサル」なわけですが、さてBERTはどう解答するでしょうか。

 まず、必要なライブラリーをインポートし、BERTの設定をします。

 BERTへの入力は、単語リストの先頭に[CLS]を入れ、文の区切りに[SEP]を入れ、予測したい単語は[MASK]に置き換える仕様になっているので、それを行う関数を定義します。

 この関数は、単語リストの先頭に[CLS]を挿入し、最後に[SEP]を付加し、途中は「。」の後に[SEP]を挿入します。そうやって単語位置を決めてから、予測箇所「□」を[MASK]に置き換え、単語リストと[MASK]位置を返します。

 次に、テキストをIDテンソルに変換します。

 テキストを分かち書きして単語リストにし、先程の関数を使って[CLS],[SEP],[MASK]を追加したら、IDリストに変換し、Pytorchが読めるIDテンソルに変換しています。

 こんな感じで変換をして行くわけです。

 それでは、下記のコードを実行して、[MASK]箇所を推論(上位5つの候補)します。

 無茶振りでしたが、1つ目の予測にはちゃんと正解の「貴族」が含まれています! 残念ながら2つ目の「カエサル」は正解できませんでしたが、BERT思ったよりやりますね。

4.BERTに文章を生成させる

 事前学習しかしていないBERTは穴埋め問題2つの文の繋がりしか学習していないので、そのままでは文章生成には向かないです。しかし原理上やれないことはないです。

 あるテキストを用意して、先頭の単語に[MASK]を掛け予測をしたら、先頭の単語を予測結果に置き換え、次の単語に[MASK]を掛け予測する、ということを繰り返すとテキストに似た新たな文が生成ができるはずです。

 では、やってみましょう。題材は、「ケネディ大統領がアポロ計画の支援を表明した演説(和訳)」です。

 まず、テキストをIDテンソルに変換します。

 先程同様、テキストを分かち書きして単語リストにし、定義した関数を使って[CLS],[SEP]を追加したら、IDリストに変換し、Pytorchが読めるIDテンソルに変換します。

 今度は、何度も単語を1つだけ予測するので、1単語予測関数を定義します。

 これは、[MASK]を掛けた単語を予測し、予測した単語とIDを返す関数です。

 そして、文生成をするコードを書きます

 tokens_tensorを一端tmpにコピーして、tmpに順次[MASK]を掛け予測した結果で、tokens_tensorの該当箇所を上書きする、ということを繰り返します。さて、このコードを実行すると、

 オリジナルが「10年以内に月に行こう」と言っているのに、文生成は「1年以内に海外に行くべきだ」と、やたらこじんまりしてしまいました(笑)。文の中身は、ちょっと意味不明な感じです。事前学習だけだと、文生成はあまり上手く行かないようです。

 では、また。

(参考)
BERT日本語モデルを使って、クリスマスプレゼントに欲しいものを推測してみた
ColabでJUMAN++を使う

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)