LoginSignup
118
120

More than 3 years have passed since last update.

自然言語処理で注目のBERT ~取り敢えず動かしてみる編~

Last updated at Posted at 2019-09-12

はじめに

業務にて自然言語処理に関わる事が多く、現在注目されているBERTに関して調べたのでまとめてみました。
※様々な記事から勉強させて頂きましたので、随時引用させて頂いております。

前提事項

下記前提を踏まえた上で、記載内容をご確認ください。

  • あくまで「BERTを取り敢えず動かす」という事を目的として記載をしております。よって理解不足により、記載表現や内容に誤りがある可能性がございますので、その際はご指摘頂けると幸いです。
  • BERTに関する概念的な説明は記載しておりません。下記「BERTを勉強する上での参考資料」に上手くまとまっておりますので、こちらをご参考ください。

BERTを勉強する上での参考資料

Pretrained日本語BERTモデル

2019/9/12時点で調査したところ、下記の日本語学習済みモデルが確認できています。

Pretrainedモデルを活用した埋め込みベクトルの取得

ケース①:pytorchのライブラリ"pytorch_pretrained_bert"を活用

PYTORCHでBERTの日本語学習済みモデルを利用する - 文章埋め込み編

上記サイトではモデル①を活用した文章ベクトルの取得方法を記載しており、ソースコードも公開しております。
こちらはpytorchのライブラリである"pytorch_pretrained_bert"を活用し、"pytorch_model.bin"のモデルからベクトルを取得しています。

上記をソースコードを元に、単語ベクトルも取得する事ができました。

ただBERTの公式レポジトリによるfine-tuningを行うと、pytorch用のモデルが出力される訳ではないため、pytorch用のモデル生成をする必要があるのですが、その方法はイマイチ分かっていません。。

ケース②:BERTのリポジトリで公開されている"extract_features.py"を活用

BERTの日本語事前学習済みモデルでテキスト埋め込みをやってみる

上記サイトもモデル①を活用した文章ベクトルの取得方法を記載しており、ソースコードを公開しております。
こちらはBERTのリポジトリで公開されている"bert/extract_features.py"を活用し、ベクトルを取得しています。

fine-tuning

「分類タスク」におけるfine-tuning

BERTのレポジトリに格納されている"run_classifier.py"を活用し、分類タスクのfine-tuningを行いました。
ステップとしては下記の通りです。
1. 学習データの用意
2. bert/tokenization.pyに学習済みモデルと同様の形態素解析クラスを記載
3. 2で記載したクラスを適用するように、bert/tokenization.pyを修正
4. run_classifier.pyに独自で行う分類タスクを実行するためのクラスを記載
5. 実行

例:発言小町の分類タスク

発言小町より取得したデータにてfine-tuningを実装したので、例としてその手順を記載します。

1.学習データの用意

tsv形式にてtrain/dev/testの3つのファイルを作成します。
tsvは1行目にラベルとなる"text","label"が記載されており、2行目以降に各テキストとラベルを記載しています。
(発言小町のデータ取得方法などは割愛)

text    label
ここは女性の多い掲示板なので投稿させて頂きます。よろしくお願い申し上げ・・・  男性から発信するトピ
婚約破棄しようか悩んでいます。付き合い始めて約2ヶ月ですが、早く結婚を・・・  男性から発信するトピ

2. bert/tokenization.pyに学習済みモデルと同様の形態素解析クラスを記載

今回は京大が作成したモデル①を活用しているため、Juman++をクラスとして追加します。
修正方法は下記を参考に実施しました。

BERTの日本語事前学習済みモデルでテキスト埋め込みをやってみる

tokenization.pyの末尾に下記コードを記載します。

class JumanPPTokenizer(BasicTokenizer):
  def __init__(self):
    """Constructs a BasicTokenizer.
    """
    from pyknp import Juman

    self.do_lower_case = False
    self._jumanpp = Juman()

  def tokenize(self, text):
    """Tokenizes a piece of text."""
    text = convert_to_unicode(text.replace(' ', ''))
    text = self._clean_text(text)

    juman_result = self._jumanpp.analysis(text)
    split_tokens = []
    for mrph in juman_result.mrph_list():
      split_tokens.extend(self._run_split_on_punc(mrph.midasi))

    output_tokens = whitespace_tokenize(" ".join(split_tokens))
    print(split_tokens)
    return output_tokens

3. 2で記載したクラスを適用するように、bert/tokenization.pyを修正

tokenization.py内のFullTokenizerを修正。元々BasicTokenizerを使っていた箇所をJumanPPTokenizerに変更する。

  def __init__(self, vocab_file, do_lower_case=True):
    self.vocab = load_vocab(vocab_file)
    self.inv_vocab = {v: k for k, v in self.vocab.items()}
    # jumanPPTokenizerを活用するように修正
    # self.basic_tokenizer = BasicTokenizer(do_lower_case=do_lower_case)
    self.jumanpp_tokenizer = JumanPPTokenizer()
    self.wordpiece_tokenizer = WordpieceTokenizer(vocab=self.vocab)

  def tokenize(self, text):
    split_tokens = []
    # for token in self.basic_tokenizer.tokenize(text):
    for token in self.jumanpp_tokenizer.tokenize(text):
      for sub_token in self.wordpiece_tokenizer.tokenize(token):
        split_tokens.append(sub_token)

4. run_classifier.pyに独自で行う分類タスクを実行するためのクラスを記載

run_classifier.pyに発言小町用のクラスを作成します。既存で記載されているMrpcProcessor等のクラスをコピペし、発言小町にて処理できるように修正しました。

class KomachiProcessor(DataProcessor):
  """Processor for the original data set """
  def get_train_examples(self, data_dir):
    """See base class."""
    return self._create_examples(
        self._read_tsv(os.path.join(data_dir, "train.tsv")), "train")

  def get_dev_examples(self, data_dir):
    """See base class."""
    return self._create_examples(
        self._read_tsv(os.path.join(data_dir, "dev.tsv")), "dev")

  def get_test_examples(self, data_dir):
    """See base class."""
    return self._create_examples(
        self._read_tsv(os.path.join(data_dir, "test.tsv")), "test")

  def get_labels(self):
    """See base class."""
    # 分類するlabelの定義(テスト処理を実施するため、適当なクラスを定義した)
    return ["男性から発信するトピ", "null"]

  def _create_examples(self, lines, set_type):
    """Creates examples for the training and dev sets."""
    examples = []
    print("lines = " + str(lines))
    for (i, line) in enumerate(lines):
      print(str(i) + "番目:" + str(line))
      if i == 0:
        continue
      guid = "%s-%s" % (set_type, i)
      # 入力となるテキストをtext_aに格納するように設定
      text_a = tokenization.convert_to_unicode(line[0])
      label = tokenization.convert_to_unicode(line[1])
      examples.append(
          InputExample(guid=guid, text_a=text_a, text_b=None, label=label))
    return examples

またrun_classifier.pyにprocessorsの設定があるため、上記作成したクラスを追加します。

processors = {
      "cola": ColaProcessor,
      "mnli": MnliProcessor,
      "mrpc": MrpcProcessor,
      "xnli": XnliProcessor,
      "komachi": KomachiProcessor, # 実行時に呼び出すtask_name : クラス名
  }

5. 実行

引数を設定し、run_classifier.pyを実行します。

# 任意のディレクトリのPATHを設定
export BERT_BASE_DIR=/Users/Desktop/Techcology/bert/Kyoto_Univ/Japanese_L-12_H-768_A-12_E-30_BPE

python run_classifier.py \
--task_name=komachi \
--do_train=true \
--do_eval=true \
--data_dir=./input_data/fine_tuning/komachi \
--vocab_file=$BERT_BASE_DIR/vocab.txt \
--bert_config_file=$BERT_BASE_DIR/bert_config.json \
--init_checkpoint=$BERT_BASE_DIR/bert_model.ckpt \
--max_seq_length=128 \
--train_batch_size=32 \
--learning_rate=2e-5 \
--num_train_epochs=3.0 \
--output_dir=./output_data/fine_tuning/komachi \
--do_lower_case False

--task_name:4.のprocessorsに設定をしたtask_nameを記載
--data_dir:train.tsv等の学習データが格納されているPATHを記載
--vocab_file、--bert_config_file、--init_checkpoint:事前学習モデルにて作成されたファイルが格納されているPATHを記載
--init_checkpoint:BERTモデルに読み込むチェックポイントファイルのPATHを記載(学習した重み付けなどが記載されている)
--output_dir:出力結果の格納先を記載

上記処理の実行が終わると、下記のような結果が出力されます。(テストにて実施をした結果のため、精度は低いです。。)

***** Eval results *****
eval_accuracy = 0.5
eval_loss = 0.6836934
global_step = 1
loss = 0.6836934

参考URL

BERTを動かしてKaggle過去コンペ(Quora Question Pairs)にSubmit

Pretrainモデルの追加学習

分類タスクとは異なり、Pretrainモデルを元に、追加学習を行いました。実装手順としては下記の通りです。
1. 学習データの準備
2. bert/tokenization.pyに学習済みモデルと同様の形態素解析クラスを記載
3. 2で記載したクラスを適用するように、bert/tokenization.pyを修正
4. 学習データの加工
5. 実行

また2,3の手順に関しては、上記「分類タスク」の発言小町の例と同様のステップのため、省略します。

例:発言小町のデータを活用した追加学習

1. 学習データの準備

自然文が記載された.txt形式のファイルを用意します。中身は1行毎に改行されているものとし、ドキュメント毎に空行を挟みます。

ここは女性の多い掲示板なので投稿させて頂きます。よろしくお願い申し上げ・・・  
婚約破棄しようか悩んでいます。付き合い始めて約2ヶ月ですが、早く結婚を・・・  
(空行)
息子が引きこもって3年になります。推薦入学した大学もほとんど通わずに中・・・

4. 学習データの加工

1にて用意されたtxtファイルを学習用の入力形式(tfrecord)に加工します。コマンドは下記の通りです。

python create_pretraining_data.py \
--input_file=./input_data/fine_tuning/sample_text.txt \
--output_file=./input_data/fine_tuning/tf_examples.tfrecord \
--vocab_file=$BERT_BASE_DIR/vocab.txt \
--do_lower_case=False \
--max_seq_length=128 \
--max_predictions_per_seq=20 \
--masked_lm_prob=0.15 \
--random_seed=12345 \
--dupe_factor=5

iunput_file、output_fileは任意のディレクトリを設定。vocab_fileは既存で作成されているvocab.txtが存在するディレクトリを指定。

5. 実行

4にて加工されたtfrecordファイルを入力とし、モデル学習を実行します。コマンドは下記の通りです。
※最初から学習を行いたい場合には、init_checkpointをインクルードしないようです
※デモ実施のため、num_train_stepは5と設定していますが、本来は10000ステップ以上が理想との事
※run_pretraining.pyに渡されるmax_seq_lengthとmax_predictions_per_seqパラメータは、create_pretraining_data.pyに渡される値と同じにする

python run_pretraining.py \
--input_file=./input_data/fine_tuning/tf_examples.tfrecord \
--output_dir=./output_data/fine_tuning/\
--do_train=True \
--do_eval=True \
--bert_config_file=$BERT_BASE_DIR/bert_config.json \
--init_checkpoint=$BERT_BASE_DIR/bert_model.ckpt \
--train_batch_size=32 \ 
--max_seq_length=128 \
--max_predictions_per_seq=20 \
--num_train_steps=5 \
--num_warmup_steps=10 \
--learning_rate=2e-5

上記実行後、モデルの精度結果が出力され、設定したoutput_dirにモデルが出力されます。

***** Eval results *****
global_step = 5
loss = 1.1846145
masked_lm_accuracy = 0.90485466
masked_lm_loss = 0.54624075
next_sentence_accuracy = 0.76875
next_sentence_loss = 0.6385179

また上記のフローをデータを変えてLivedoorニュース約7,000ドキュメントでモデル作成を行ったところ、学習データ加工に8時間(mecab利用)、モデル生成に3時間(10,000epoch、GPU使用)でした。実務利用においては現実的な処理時間で終わる感じでした。

調査した所感

・やっぱBERTむずい
(取り敢えず動かす事は出来ているが、Attentionや学習方法など、中身の構造を理解していないと、自信を持って利用できている感じはない)
・Fine-tuningに可能性がありそう
(文脈を考慮した単語ベクトルが取得できるため、あとは如何様にも処理する事ができ、汎用的なモデルという事を理解し、様々なタスクに活かす事が出来そうな感じはある)
・自然言語処理の流れが早すぎ問題
(約半年前にBERTが発表され注目を浴びたものの、XLNetやRoBERTaも出てきており、技術進歩の早さについていくのに大変。。)

諸々苦戦したものの最新技術を動かす事が出来ると楽しいので、今後も自然言語処理関連の技術をキャッチアップし、手を動かしていこうと思いました!

118
120
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
118
120