オブジェクトの広場はオージス総研グループのエンジニアによる技術発表サイトです

AI

はじめての自然言語処理

第3回 BERT を用いた自然言語処理における転移学習
オージス総研 技術部 アドバンストテクノロジセンター
鵜野 和也
2019年6月25日

前回は Rasa NLU を用いて文章分類と固有表現抽出について紹介しました。今回は昨年後半に話題となった BERT について説明し、chABSAデータセットを用いた感情分析での実験結果、アプリケーションへの組み込み方などを紹介します。

1. 始めに

本記事では Google の BERT について、その概要を紹介し、BERT の事前学習済みモデルを用いてファインチューニングにより独自のモデルを構築することを念頭に、BERT の入出力インタフェースや学習データの構造を説明します。そして、ファインチューニングにより独自のモデルを構築する例として、chABSA データセットを用いた感情分析モデル生成の実験結果およびアプリケーションから利用する際のポイントを紹介します。

2. BERTの概要

BERT (Bidirectional Encoder Representations from Transformers) は Google の研究チームが 2018年10月に公開した論文で、文章分類、質問応答、固有表現抽出等の多様なタスクで公開当時の最高性能(SOTA: State of the Art)を達成しました。筆者はビジネス活用の視点で考えた BERT の意義は、その「事前学習」にあると考えます。

BERT の学習は、大量のデータを用いる「事前学習」と比較的少量のデータを用いる「ファインチューニング」の二段階で構成されます1。 ここで「事前学習」に用いる大量のデータは 、教師ラベルの付いていない普通の文章でよいというのが重要なポイントです。

普通の文章でよければ、日本語版Wikipediaのダンプや(有料ではありますが)新聞のデータなど比較的容易に大量入手が可能です。こうしたラベルなしデータで「事前学習」を済ましてしまえば、その事前学習済みモデルをベースとして、比較的少量のラベル付きデータで「ファインチューニング」を行うことによって、多様なタスクに対応できるという訳です。

自然言語処理に限らず、近年の大規模化した深層学習モデルは非常に大量の学習データを必要とします。画像であれば海外で公開されている巨大なデータセットを利用することもできますが、自然言語処理ではそうはいきません。大規模モデルの学習に十分な量のラベル付き日本語データセットは、ほぼ存在しないというのが現状です。BERT はラベルなしデータによる事前学習を用いることで、大規模モデルにつきものの大量データの必要性という課題に対応しうる手法と言えるでしょう。

3. BERT の構造

では、ここからは BERT の構造について簡単に説明していきます。下図は BERT と BERTと同様に事前学習を利用する他のモデル(OpenAI GPT, ELMo)を比較したものです。BERT の構造的な特徴はその名の示すとおり双方向(=Bidirectional)であるということで、図中では黒矢印で示されていますが OpenAI GPT は左から右への学習、ELMo は左から右と、右から左への学習を最終層で連結する形であり、BERT だけが全ての層で双方向の学習を行っています。

アーキテクチャの比較 ここで、BERT について以下の2点を押さえておいて下さい。

  • ベクトルのシーケンス(E1 … EN)を入力とし、ベクトルのシーケンス(T1 … TN)が出力されるモデル。
  • 入力層と出力層の間に隠れ層(Trm=Transformer)を複数重ねた構造。

これまで自然言語処理を行うニューラルネットワークには RNN や CNN が利用されていましたが、2017年に発表された論文 “Attention is All You Need” 2以降、Transformer と呼ばれる構造が非常に流行しており、BERT の隠れ層も Transformer が利用されています。

ただし、"Attention is All You Need" の Transformer は Encoder と Decoder の二つで構成されるのですが、 BERT では Encoder部分のみを使用します。 毎回「Transformer の Encoder」と記述するのも面倒なので、これ以降は「Transformer の Encoder」を「Transformer」と略記することにします。

それでは Transformer の中身を見てみましょう。

3.1 Transformer の構造

下図の通り、BERT は ベクトルのシーケンスを Transformer と呼ばれる構造に入力して、ベクトルのシーケンスが出力されます。それを縦に複数重ねた構造をしています。水色の網掛け部分が Transformer に相当し、実際は複数の Transformer が数珠つなぎにされます。図中の “[]"書きは各層から出力されるテンソルのシェイプを示します。BERT の論文には BERTLARGE, BERTBASE の二つの構成が記載されており、BERTLARGE は Google が State of the art を目指した際の構成、BERTBASE は OpenAI GPT との比較用に同規模の構成にしたものです。 BERTのアーキテクチャ

Multi-Head Self-Attention は一旦脇に置いておくとして、残りの reshape, dense, dropout, gelu, layer_norm といった処理についてごく簡単に説明すると3reshape はテンソルの形を 2 x 3 x 4=> 6 x 4 のように変形するだけ、dense は全結合層、gelu は relu に代表される活性化関数の一種、dropoutlayer_norm は汎化性能や学習速度を上げるための工夫くらいに理解しておいて下さい。 二か所程入っている残差接続4を除けば一本道のシンプルな構造をしていることが見て取れます。

ここからは、Transformer の中核である Multi-Head Self-Attention について簡単に説明します。

3.2 Self-Attention

(Self-)Attention は一言でいうと「出力シーケンスの生成時に入力シーケンスのどこに注目するべきか」を判断する仕組みです。

例えば入力シーケンス X = {x1, x2, …, xN} から出力シーケンス Y = {y1, y2, …, yN} (xi, yi はベクトルとします) を導出する局面で、位置 t における出力値 yt を求めるとき、当該位置 t における入力シーケンス X に対する注目度合を示すベクトル at があったとすると、yt を at と X の加重平均で導出します(①)。

Attentionの計算

重み at の導出にはニューラルネットワークを利用する等、様々な手法があるのですが、BERT ではシンプルに注目の対象となる X = {x1, x2, …, xN} の各要素(xi)と出力位置に対応する xt の内積を計算(②)した上で、at の総和が1になるように Softmax関数で正規化します(③)。

内積の計算

softmaxによる正規化

内積の計算において d の平方根で除算していますが、これは d が大きいと内積が大きくなり過ぎ、Softmax関数の勾配が小さくなりすぎることを抑制する為の物です。

Attention の一般的な説明では注目の対象である xi に相当する情報を Key, xt に相当する情報を Query, 注目度合の重みを掛け合わせる対象を Value と呼びます。 Query で Key の集合を検索して、Query と Key の類似度が高い部分を注目すべき箇所とし、その重みを加味して Value から情報を抽出する感覚です。 Query(=xi), Key(=xt) が共に同じ情報源(=X)から来ている為、Self-Attention と呼ばれます5

3.3 Multi-Head Attention

残りは "Multi-Head” の話です。「ある観点ではこちら、べつの観点ではあちら」と観点によって注目するべき箇所が異なるような状況を考えて下さい。Multi-Head Attention では、前項の Self-Attention を一つのヘッドとみなし、複数のヘッドを用意することでこの問題に対応します。

BERT の場合はH次元ベクトルのシーケンスを、Query, Key, Value それぞれに densereshape を用いて線形写像しA個のH/A次元ベクトルのシーケンスに変換(④)、A個のSelf-Attention の計算を並行で行った後、reshape で各ヘッドの計算結果を結合してH次元ベクトルのシーケンスに戻します(⑤)。

以下、説明を省いた部分もありますが、Multi Head Self-Attention の構造になります。図中、丸数字で示した箇所が説明内容と意味的に相当する部分になります。

Multi Head Self-Attention

Transformer と Attention に関しては Ryobot氏の記事6が非常に分りやすく書かれているので興味のある方は一読してみて下さい。

では、次は BERT がどのような事前学習を行っているのか見ていきましょう。

4. BERTの事前学習

ここからは BERT の事前学習について説明します。事前学習の内容を押さえていれば、ファインチューニング時に入力シーケンスの形式やどのような学習をさせるかといった判断がしやすくなるでしょう。前章で 「BERT にはベクトルのシーケンスを入力する」と書きましたが、イメージとしては以下のようになります。

BERTへの入力イメージ

最上段の “input” の部分に着目してください。先頭に“[CLS]"、2つのセンテンスA, Bの末尾に”[SEP]“という特殊トークンを挿入して連結した構造が1入力シーケンスになります。

2行目以降の * Embedding ですが、BERT に "input” で示された入力シーケンスを投入した直後、トークンのIDが Token Embedding、センテンスA, Bの区分が Segment Embedding、シーケンス内の位置が Position Embedding と、それぞれ事前学習の過程で学習されるH次元の埋め込み表現に置き換えられ、それらを加算したベクトルのシーケンスが Transformer への入力(3.1節の図における Input Embedding)になります。

BERT はこの入力に対し、以下の二つの目的関数(Masked LM, Next Sentence Prediction)を合わせた事前学習を行います。

Masked LM

入力シーケンスから”[CLS]”、”[SEP]”を省いて無作為抽出した15%を以下のように置き換えた上で、置きかえられる前の語は何であったかを予測します。

  • 80% を “[MASK]” に差し替え
  • 10% を ランダムな語彙に差し替え
  • 10% はオリジナルのまま(ファインチューニング時は”[MASK]”が発生しない為、その差異を緩和する処理)

Next Sentence Prediction

学習サンプルの50%をセンテンスA、Bが連続したもの、50%を不連続なものとします。そして、先頭の“[CLS]”に対応する出力から連続/不連続を予測します。

つまり、BERT は事前学習の過程で、文章の穴埋め(=前後の文脈を加味した埋め込み表現の生成)をする能力と二つのシーケンスが連続したものであるか否か(=二つのシーケンスの関係性)を判断する能力を得ていることになります。

ここからは BERT をファインチューニングして独自のモデルを構築することを念頭に実装レベルで見ていきましょう。

5. BERT-JAPANESE

それでは、実際に事前学習やファインチューニングを行う際の学習データの構造や BERT の入出力を見ていきましょう。これらを押さえれば、独自モデルの構築する際の参考になるはずです。

BERT の実装としては yoheikikuta 氏が公開している bert-japanese 7 を対象とします。

Google の公式実装との主な違いはトークン化8の処理を SentencePiece 9 に置き換えていることです。 公式 BERT のトークン化には sub-word と呼ばれる単語よりも細かい単位が用いられています。 入力イメージの図で “playing” が “play” と “##ing” に分かれている部分が sub-word です。sub-word を用い単語を部品化することで、希少語を sub-word の組み合わせで表現できるなどの利点があります。

ただ公式 BERT の実装をそのまま日本語に適用すると、ほぼ文字単位に近しい分割になるなど日本語との相性が良くない為、SentencePiece に差し替えたとのことです。SentencePiece に関する詳しい説明は省きますが、sub-word 同様に希少語を単語よりも細かい単位で(というよりも単語という単位に拘らずに)分割する手法になっています。以下は SentencePiece とその他の分割手法との比較ですが、"足利義“ のところに単語に拘らない特徴が良く出ています。

SentencePiece

少し前置きが長くなりましたが、まずは事前学習時に使用する学習データの構造から見ていきましょう。

6. 事前学習の学習データ構造

bert-japanese では事前学習データを src/create_pretraining_data.py で生成します。出力される形式は tf.train.ExamplesTFRecord フォーマットにシリアライズしたもので、学習データの1件は以下のように入力データと正解ラベルが一体となった構造をしています。

事前学習データの構造

  • 最大シーケンス長=10、Masked LM での1サンプルあたりの最大予測数=6 とした場合のイメージです。
  • 入力シーケンス長、1サンプル当たりの最大予測数は共に固定長であり、データが最大長に満たない場合は 0 で埋めます(グレー網掛け部分)。
  • input_ids, masked_lm_ids のセルにはトークンの文字列表現を記載していますが、実際はトークンのID(整数)が入ります。
  • segment_ids は入力シーケンスのセンテンスA, センテンスBの範囲を 0/1 で示します。
  • input_mask は入力シーケンス中の有効な値がある(=パディングでない)範囲を示します。
  • t = 3 は分りにくいですが、Masked LM で tok_3 がランダムな語(tok_57)に置き換えられたケースです。

それでは次に、上記の入力データを流し込み、正解ラベルと突き合わせる BERT の入出力について見ていきましょう。

7. BERTの入出力

BERT は Google の公式実装7modeling.py 中の BertModel クラスで定義されており、BERTへの入出力は以下の通りです。BERT の入出力は固定長で論文や公開学習済みモデルでは 512 が使用されています。

BERTの入出力

BERTの入力における input_ids, input_mask, token_type_ids が学習データ構造の入力シーケンスに対応します。

出力は主に以下の二つを用います。

model.get_sequence_output()

BERT で変換された出力シーケンスです。Masked LM のラベルはこちらと突合せます。ファインチューニング時は入力シーケンスの各要素の品詞を判定したり、質問の答えに相当する箇所を判断したりする用途で使います。

model.get_pooled_output()

出力シーケンスの先頭、つまり”[CLS]“に対応するベクトルをさらに線形写像したもので、入力シーケンス全体として何らかの判定をしたいときに用います。Next Sentence Prediction のラベルとの突合せ、ファインチューニングでの文章の分類問題などでは、こちらを使います。

ここからは bert-japanese に同梱されている文書分類を題材にファインチューニングについて見てみましょう。

8. ファインチューニングによる文章分類

bert-japanese にはファインチューニングの題材として Livedoor News コーパス10で文章分類モデルを構築するコードが含まれています。Livedoor News コーパスは 9つのグループに分けられたニュース記事の集合となっており、ニュース記事を入力とし、どのグループに属する記事なのかを推論するモデルをファインチューニングによって作っています。

BERT の使い方のイメージとしては、以下のようになります。

文章分類のイメージ

事前学習ではセンテンスA / B の二つセットで入力していましたが、文章分類ではセンテンスAのみ投入し、”[CLS]“トークンに相当する出力(= model.get_pooled_output())に出力層を接続して分類します。

文章分類のコードは bert-japanese 中の src/run_classifier.py に記述されており、学習データの構造は以下のとおりです。

文章分類の学習データ構造

  • 最大シーケンス長=6 とした場合のイメージです。
  • 末尾の”[SEP]“の有無が直前のイメージ図と異なりますが、ソースコード上は”[SEP]“を付ける処理になっています。
  • is_real_example ですが、TPU で実行する場合はミニバッチサイズが固定である必要がある為、エポックの最終ミニバッチの件数が指定したミニバッチサイズの際にダミーサンプルで埋める必要があり、そのダミーであるか否かを識別する為のフラグになります。

さて、ここで少し寄り道です。ファインチューニングは比較的少量のラベル付きデータで可能とのことなので、学習サンプル数が分類精度に与える影響を確認してみました。

実験結果1

学習データ全件(4421件)で F1 = 0.96 だったものを、900件(単純計算でクラス毎に100件)まで落としても F1 = 0.94 が維持できています。クラス毎に100件であれば、少し頑張れば用意できそうなボリュームですね。

ここまで、BERT の概要と入出力および学習時に用意するべきデータの構造について説明しました。 ここからはもう少し複雑なラベルのついたデータセットでファインチューニングの実験をしてみたので、その結果を紹介します。

9. chABSA データセットでの実験

今回は単純な文書分類よりも複雑なタスクの例として、 chABSA データセット11を用いた観点感情分析の実験を行いました。

なお、本章の実験と次章の Tensorflow Serving へのデプロイに関しては、Tensorflow 1.13 を使用しています。

9.1 chABSA データセット

chABSA データセットは上場企業の有価証券報告書(2016年度)をベースに作成されたデータセットで、各文に対してネガティブ/ポジティブの感情分類だけでなく、「何が」ネガティブ/ポジティブなのかという観点を表す情報が含まれていることが特徴です。以下はデータセットのイメージです。製品の生産量について記載された文の内容に関し、「製品の生産量="product#amount"」という観点で、「ブナシメジ」と「マイタケ」はポジティブ、「エリンギ」はネガティブというラベルが付いています。

chABSA データセット

chABSA データセットには Slot1 ~ 3 の3つのタスクが定義されており、今回は Slot3 の実験を行いました。

Slot3 は上の例で言えば、文(なお、当連結会計年度の…となりました)、極性の対象(ブナシメジ)、観点("product#amount”)を入力として、その極性(positive/negative)を判定するタスクになります12。実験に用いたデータセットの内訳は以下のとおりです。また、Cross Validation は行っていません。

データセットの内訳

9.2 BERT への入力イメージ

chABSA データセットの文、極性の対象、観点をどのように BERT に入力するかが思案のしどころですが、今回は以下のような構成としました。 文をセンテンスAとし、センテンスBは極性の対象と日本語化した観点を「について」で連結したものを用いました。観点の日本語化は観点(“business#general"等)の英単語を固定の変換テーブルで日本語化した上で、「の」で連結しています。

Slot3タスクの入力イメージ

入力シーケンスの構造が決まれば後は分類問題ですので、実装コード的にはデータファイルの読み込みと入力シーケンス生成以外は、前述の Livedoor News コーパスの分類とほぼ同一になります。

9.3 実験結果

実験結果は以下のとおりです、全クラスの平均で F1 = 0.94 でした(Cross Validation をしてないので正確ではないかもしれませんが)。"neutral” で苦戦していますが、学習サンプル数が少ないので仕方ないというところでしょうか。

実験結果1

次は他の実験結果との比較です。"ours(target)“ は上記のモデルでセンテンスBを極性の対象だけにしたもの、"ours(category)"は観点だけにしたものです。"baseline” は chABSA データセットのベースラインモデルのスコア、"SemEval" は意味解析のワークショップである SemEval 2016 におけるレストランレビューの英語データセットでの最良スコアです。ただし、データセットの内容が異なる上に、"baseline", “SemEval” ともに極性の対象(target)を使用していないモデルなので、ご参考程度に考えて下さい13

実験結果2

次は精度に対する学習サンプル数の影響を見てみましょう。単純な文章分類よりも複雑なタスクだけに、Livedoor News コーパスの文章分類よりも学習データ件数削減の影響が強いようです。

実験結果3

それでは、ファインチューニングで独自モデルを構築する雰囲気が押さえられたところで、構築した独自モデルをアプリケーションから利用するポイントについて見ていきましょう。

10. 学習済みモデルのアプリケーションからの利用

BERT に限った話ではありませんが、Tensorflow の学習済みモデルをアプリケーションから利用する際は、Tensorflow Serving を使用します。

Tensorflow Serving は学習済みモデルのプロダクション環境利用の為に設計されており、C++で記述された高性能かつ柔軟性の高いモジュールです。学習済みモデルの複数のバージョンを同時にロード、並行で供給したり、新バージョン追加時のバージョン入れ替えの挙動をポリシーで指定することができ、ロードした学習済みモデルは gRPC もしくは Rest API コールにより推論を行えるため、Python 以外で記述されたアプリケーションからも問題なく利用することができます。

10.1 SavedModel へのエクスポート

BERT の学習済みモデルを Tensorflow Serving から利用するには、まず SavedModel と呼ばれる形式でエクスポートする必要があります。BERT は TPUEstimator を使用するコードになっているので、Tensorflow Serving での要求の受け口を定義すれば、export_savedmodel() メソッドで SavedModel を出力することができます。

bert-japanese で Livedoor News コーパスのファインチューニングを行い、 notebook/finetune-to-livedoor-corpus.ipynb を以下のセルまで動かしてください。

model_fn = model_fn_builder(
    bert_config=bert_config,
    num_labels=len(label_list),
    init_checkpoint=FLAGS.init_checkpoint,
    learning_rate=FLAGS.learning_rate,
    num_train_steps=FLAGS.num_train_steps,
    num_warmup_steps=FLAGS.num_warmup_steps,
    use_tpu=FLAGS.use_tpu,
    use_one_hot_embeddings=FLAGS.use_tpu)


estimator = tf.contrib.tpu.TPUEstimator(
    use_tpu=FLAGS.use_tpu,
    model_fn=model_fn,
    config=run_config,
    train_batch_size=FLAGS.train_batch_size,
    eval_batch_size=FLAGS.eval_batch_size,
    predict_batch_size=FLAGS.predict_batch_size)

ここで、以下のコードを動かし Tensorflow Serving にデプロイした際のリクエストの受け口を定義します。

def serving_input_fn():
    input_ids = tf.placeholder(
        tf.int32, [None, FLAGS.max_seq_length], name='input_ids')
    input_mask = tf.placeholder(
        tf.int32, [None, FLAGS.max_seq_length], name='input_mask')
    segment_ids = tf.placeholder(
        tf.int32, [None, FLAGS.max_seq_length], name='segment_ids')
    label_ids = tf.placeholder(
        tf.int32, [None], name='label_ids')
    input_fn = tf.estimator.export.build_raw_serving_input_receiver_fn({
        'input_ids': input_ids,
        'input_mask': input_mask,
        'segment_ids': segment_ids,
        'label_ids': label_ids,
    })()
    return input_fn

推論リクエストの受け口の定義に label_ids (正解ラベルのID) が入っているのはおかしいですが、これは bert-japanesesrc/run_classifier.py で定義されている model_fn_builder() 関数内で label_ids が指定されることが前提のコードになっている為です14

後は、以下のように export_savedmodel() を使用することで、saved_model ディレクトリにエクスポートされます。

estimator._export_to_tpu = False
estimator.export_savedmodel("./saved_model", serving_input_fn, checkpoint_path=FINETUNED_MODEL_PATH)

リクエストの受け口はserving_input_fn() で定義したので、レスポンスの形式も確認しておきましょう。 レスポンスの形式は、estimator の生成時に model_fn パラメータに渡した関数の記述によって決まります。 具体的には、src/run_classifier.pymodel_fn(features, labels, mode, params) で、この関数の modetf.estimator.ModeKeys.PREDICT を渡した場合の返値における predictions が相当します。

    else:
      output_spec = tf.contrib.tpu.TPUEstimatorSpec(
          mode=mode,
          predictions={"probabilities": probabilities},
          scaffold_fn=scaffold_fn)
    return output_spec

10.2 Tensorflow Serving の起動

Tensorflow Serving に先程の SavedModel を読み込ませて起動する訳ですが、 Docker を使用すると手軽です。 nvidia-docker の使える環境で以下のコマンドを実行してください。 -v に渡している /full/path/to/saved_model が先程のエクスポート先ディレクトリへのフルパスになります。

docker run --runtime=nvidia 
  -p 8501:8501 \ 
  -v /full/path/to/saved_model:/models/bert \
  -e MODEL_NAME=bert \
  -t \
  tensorflow/serving:1.13.0-gpu

10.3 REST API での呼び出し

ここからは、前述の docker run コマンドを実行したマシンのホスト名を dockerhost.somewhere とします。 tensorflow serving で公開されているモデルは https://dockerhost.somewhere:8501/v1/models/bert:predict に 以下のような JSON を POST することで実行できます。

{
  "instances": [
    {"input_ids"   : [4, 9, 24613, 13, 82, 3037, 0, ..., 0, 0], 
     "segment_ids" : [0, 0,     0,  0,  0,    0, 0, ..., 0, 0], 
     "input_mask"  : [1, 1,     1,  1,  1,    1, 0, ..., 0, 0],
     "label_ids"   : 0 
    }
  ]
}

ここで、inputs_ids, segment_ids, input_mask はそれぞれ長さ512のint配列です。 配列の内容は文章分類での学習データ構造そのものです。 label_ids は前述のとおりダミー項目なので値は無視されます。

このリクエストを作るには分類対象のテキストを BERT の学習時に使用した SentencePiece モデルで トークナイズし、シーケンス最大長に合わせて 0 パディングする必要があります15。 この辺りの前処理は SavedModel に入れたかったのですが SentencePiece の処理が無理そうだったので、 今回はあきらめました。

Tensorflow Serving からのレスポンスは以下の形式で返ってきます。

{
  "predictions": [
    [
      2.86528e-06,
      4.0434e-06,
      3.3289e-06,
      4.27152e-06,
      3.14218e-06,
      2.75092e-06,
      3.95716e-06,
      0.999972,
      3.33052e-06
    ]
  ]
}

上記の場合、8番目のクラスである確率がほぼ100%ということです。8番目のラベルが何かを決めているのは bert-japanesesrc/run_classifier.pyget_labels() です。この場合は、"sports-watch" ですね。

  def get_labels(self):
    """See base class."""
    return ['dokujo-tsushin', 'it-life-hack', 'kaden-channel', 'livedoor-homme', 'movie-enter', 'peachy', 'smax', 'sports-watch', 'topic-news']

さいごに

駆け足でしたが、 BERT の構造、事前学習/ファインチューニング時の学習データ構造について説明し、chABSA データセット での実験結果とTensorflow Serving を用いた公開方法について紹介しました。これで BERT を利用して独自モデルを構築しアプリケーションに組み込む際のイメージがつかめたかと思います。

BERT の事前学習には非常に多くの計算リソースが必要となりますが、bert-japanese や京都大学 黒岩・河原研究室16が BERT の事前学習済みモデルを公開してくださっているので、そちらを利用してファインチューニングを行ってみてはいかがでしょうか?


  1. 論文( https://arxiv.org/abs/1810.04805 )ではファインチューニングをしない手法("feature-base"と呼ばれます)でも良好な結果が得られるとされています。 

  2. https://arxiv.org/abs/1706.03762 

  3. Tensorflow の API の名称そのままですので、興味のある方はリファレンスを参照して見て下さい( https://www.tensorflow.org/api_docs )。 

  4. 枝分かれして+記号で合流する構造です。層数を深くした際の性能低下を抑える働きがあります。 

  5. 機械翻訳等で利用される Key と Query が異なる Attention は Source-Target Attention と呼ばれます。 

  6. https://deeplearning.hatenablog.com/entry/transformer 

  7. https://github.com/yoheikikuta/bert-japanese 

  8. トークン化とは、"本日は晴天なり" => “本日”, “は”, “晴天”, “なり” のように、自然言語の文章を単語(あるいはそれに近しい単位)に分割することです。 

  9. https://github.com/google/sentencepiece  

  10. https://www.rondhuit.com/download.html 

  11. https://github.com/chakki-works/chABSA-dataset 

  12. chABSA データセットの Wiki(https://github.com/chakki-works/chABSA-dataset/wiki) では Slot3 には極性の対象(target)を使用せず文と観点だけで極性を予測する旨の記述がありますが、せっかく細かい粒度でアノテーションが付いているので今回はtarget込みで実験をしています。 

  13. “SemEval” の数値は https://github.com/chakki-works/chABSA-dataset/wiki から引用しています。 “ours(category)” が “SemEval” に負けてしまっていますが、"product#amount" のようなラベルを日本語にしている分、無駄にタスクを難しくしてしまったかもしれません。 

  14. 本来であればエクスポートしたい形に合わせて、この辺りの関数も定義しなおすべきではあるのですが、説明を簡単にする為に serving_input_fn()側で現物合わせで対処しています。 

  15. bert-japanesesrc/run_classifier.pyconvert_single_example()を参考にして下さい。 

  16. https://nlp.ist.i.kyoto-u.ac.jp/index.php?BERT%E6%97%A5%E6%9C%AC%E8%AA%9EPretrained%E3%83%A2%E3%83%87%E3%83%AB