Python
自然言語処理
機械学習
TensorFlow
bert

BERTを用いて日本語ツイートの感情分析を試す

BERTとは言語表現モデルで、GLUEデータセットなどに対して(ファインチューニングして)最近SOTAを達成したものです。今回は、日本語ツイートの感情分析を試し、その後四苦八苦して色々試したことを書きます。

BERTの基本

  1. 事前訓練
  2. ファインチューニング

以上。

追記 2018/11/09 20:57

この記事はどちらかというと理論家ではなくエンジニア向けの説明なので、「BERTは双方向Transformer」とか「そのTransformerとはなんだ」とか、「事前訓練ではどんなタスクを解いているのか」とか、そういうことを知りたければ他の記事を参考にしてください。

BERTを単に「使うだけ」の方は、事前訓練の方法とファインチューニングの方法さえわかっていればそれなりに使えるかと思います。今後「BERTが簡単に使えるライブラリ」みたいなものが出てくるとしたら、その2つさえおさえておけば良いと思います。

BERTのダウンロード

git clone https://github.com/google-research/bert
wget https://storage.googleapis.com/bert_models/2018_11_03/multilingual_L-12_H-768_A-12.zip
unzip multilingual*

ツイート感情分析データの用意

以下の記事で用いた方法でデータを取得します。
https://qiita.com/sugiyamath/items/7cabef39390c4a07e4d8

取得済みのデータは以下。
https://github.com/sugiyamath/bert/tree/master/JAS

run_classifier.pyを修正

以下を追加します。

class JasProcessor(DataProcessor):

  def read_tsv(self, path):
    df = pd.read_csv(path, sep="\t")
    return [(str(text), str(label)) for text,label in zip(df['text'], df['label'])]


  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."""
    return ["0", "1", "2", "3", "4", "5"]

  def _create_examples(self, lines, set_type):
    """Creates examples for the training and dev sets."""
    examples = []
    for (i, line) in enumerate(lines):
      guid = "%s-%s" % (set_type, i)
      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

さらにmain関数内の以下部分を修正します。

  processors = {
      "cola": ColaProcessor,
      "mnli": MnliProcessor,
      "mrpc": MrpcProcessor,
      "xnli": XnliProcessor,
      "jas": JasProcessor
  }

実行

JASをGLUEデータセットのディレクトリの中につっこみます。

mv JAS glue_data

ついで、以下コマンドを実行します。(exportの部分は修正してください。)

export BERT_BASE_DIR=/path/to/bert/multilingual_L-12_H-768_A-12
export GLUE_DIR=/path/to/glue_data

python run_classifier.py \
  --task_name=JAS \
  --do_train=true \
  --do_eval=true \
  --data_dir=$GLUE_DIR/JAS \
  --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=/tmp/jas_output/

結果は以下です。

eval_accuracy = 0.363
eval_loss = 1.5512451
global_step = 937
loss = 1.5512451

その後の四苦八苦 (この先は読む価値なし)

日本語のボキャブラリが少ないのが原因かと思いましたが、WordPieceという手法をわざわざ使っているので、語の基本要素のようなものをネットワーク内で組み合せることがキモの一つなのかなーとも思います。

ともあれ、MeCabのボキャブラリーに合わせてコードを修正したらどうなるか試したわけです。

修正したtokenizerは以下です。
https://github.com/sugiyamath/bert/blob/master/tokenization.py

修正部分は

  1. テキストの正規化をしないようにする。(濁点が消えないように)
  2. 中国文字を認識しないようにする。(文字ベースにならないように)

で、wikipediaから作成したvocab.txtは以下です。
https://github.com/sugiyamath/bert/blob/master/jamodel/vocab.txt

最後に、以下スクリプトを実行。

#!/bin/bash

export BERT_BASE_DIR=/root/work/bert/jamodel
FILE=/root/work/tf_examples.tfrecord


if [ -f $FILE ]; then
    echo "FILE EXISTS"
else
    python create_pretraining_data.py \
       --input_file=/root/work/data/splitted_1.txt \
       --output_file=/root/work/tf_examples.tfrecord \
       --vocab_file=$BERT_BASE_DIR/vocab.txt \
       --do_lower_case=True \
       --max_seq_length=128 \
       --max_predictions_per_seq=20 \
       --masked_lm_prob=0.15 \
       --random_seed=12345 \
       --dupe_factor=5
fi


python run_pretraining.py \
       --input_file=/root/work/tf_examples.tfrecord \
       --output_dir=/root/work/pretraining_output \
       --do_train=True \
       --do_eval=True \
       --bert_config_file=$BERT_BASE_DIR/bert_config.json \
       --train_batch_size=32 \
       --max_seq_length=128 \
       --max_predictions_per_seq=20 \
       --num_train_steps=20 \
       --num_warmup_steps=10 \
       --learning_rate=2e-5

結果は... run_pretraining.pyでOOMエラー。理由は、ボキャブラリーのサイズがでかすぎてネットワークが巨大になるので。

ちなみに、bert_configは以下です。

{
  "attention_probs_dropout_prob": 0.1,
  "hidden_act": "gelu",
  "hidden_dropout_prob": 0.1,
  "hidden_size": 768,
  "initializer_range": 0.02,
  "intermediate_size": 3072,
  "max_position_embeddings": 512,
  "num_attention_heads": 6,
  "num_hidden_layers": 6,
  "type_vocab_size": 2,
  "vocab_size": 1710509
}

ちなみに、splitted_1.txtはこちらを参考にしていますが、create_pretraining_data.pyの実行は正常に完了しています。

参考

[0] https://qiita.com/Kosuke-Szk/items/4b74b5cce84f423b7125
[1] https://github.com/google-research/bert