はじめに
業務にて自然言語処理に関わる事が多く、現在注目されているBERTに関して調べたのでまとめてみました。
※様々な記事から勉強させて頂きましたので、随時引用させて頂いております。
前提事項
下記前提を踏まえた上で、記載内容をご確認ください。
- あくまで「BERTを取り敢えず動かす」という事を目的として記載をしております。よって理解不足により、記載表現や内容に誤りがある可能性がございますので、その際はご指摘頂けると幸いです。
- BERTに関する概念的な説明は記載しておりません。下記「BERTを勉強する上での参考資料」に上手くまとまっておりますので、こちらをご参考ください。
BERTを勉強する上での参考資料
-
BERTの公式レポジトリ(+その翻訳が記載されているQiita)
-
BERTの概要、特徴など、概念的な話がまとめられている資料
Pretrained日本語BERTモデル
2019/9/12時点で調査したところ、下記の日本語学習済みモデルが確認できています。
-
モデル①:BERT日本語Pretrainedモデル - KUROHASHI-KAWAHARA LAB
- 入力テキスト: 日本語Wikipedia全て (約1,800万文, 半角を全角に正規化)
- 入力テキストにJuman++ (v2.0.0-rc2)で形態素解析を行い、さらにBPEを適用しsubwordに分割
- BERT_{BASE}と同じ設定 (12-layer, 768-hidden, 12-heads)
- 30 epoch (1GPUで1epochに約1日かかるのでpretrainingに約30日)
- 語彙数: 32,000 (形態素、subwordを含む)
- max_seq_length: 128
-
モデル②:BERT with SentencePiece を日本語 Wikipedia で学習してモデルを公開しました
- 日本語 Wikipedia で学習した BERT モデルを公開しました
- livedoor ニュースコーパスで finetuning して良い性能を発揮することも確認
- 二値分類タスクにおいてロジスティック回帰やLSTMよりも精度は高い結果が確認できたとの事です。
- モデル②の方はCookpadの研究開発部に在籍しているようであり、下記ブログも作成しております。こちらは様々な検証を行っており、大変勉強になる内容でした!
BERT with SentencePiece で日本語専用の pre-trained モデルを学習し、それを基にタスクを解く
-
モデル③:大規模日本語SNSコーパスによる文分散表現モデルの公開 : hottoSNS-BERTの配布
- 日本語twitterデータでの学習
-
モデル④:大規模日本語ビジネスニュースコーパスを学習したBERT事前学習済(MeCab利用)モデルの紹介
- 事前学習用データ:日本語ビジネスニュース記事(300万記事)
- トークナイザー:MeCab + NEologd
- 語彙数:32,000語(CLS、SEP、UNK等除く)
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を行いました。
ステップとしては下記の通りです。
- 学習データの用意
- bert/tokenization.pyに学習済みモデルと同様の形態素解析クラスを記載
- 2で記載したクラスを適用するように、bert/tokenization.pyを修正
- run_classifier.pyに独自で行う分類タスクを実行するためのクラスを記載
- 実行
例:発言小町の分類タスク
発言小町より取得したデータにて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モデルを元に、追加学習を行いました。実装手順としては下記の通りです。
- 学習データの準備
- bert/tokenization.pyに学習済みモデルと同様の形態素解析クラスを記載
- 2で記載したクラスを適用するように、bert/tokenization.pyを修正
- 学習データの加工
- 実行
また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も出てきており、技術進歩の早さについていくのに大変。。)
諸々苦戦したものの最新技術を動かす事が出来ると楽しいので、今後も自然言語処理関連の技術をキャッチアップし、手を動かしていこうと思いました!