この記事では、Google ColaboratoryのGPUを使用してBERTを動かし、日本語文章の多値分類を行います。
前回の記事の続きです。重なる部分が多いと思います。
Google Colaboratory上でBERTを使い、日本語文章の二値分類を行う
学習済みモデルは、GoogleDriveに保存し、GoogleColaboratoryにGoogleDriveをマウントして使用します。
*注意
GoogleDriveに学習済みモデルとデータセットを保存するため、1.6GB以上の空きが必要です。
一度だけ試す場合は、GoogleDriveとGoogleColaboratory、を連携せず、GoogleColaboratoryのGPUインスタンス上にモデルやデータセットを保存する方が良いかもしれません。
BERTを使用した日本語文章の多値分類
ここでは、BERTとBERT日本語学習済みモデル、livedoor newsコーパスを使用して、GoogleColaboratory上で学習済みモデルのfine-tuningと文章の多値分類を行います。
分類は記事のタイトルを使用して行います。
fine-tuning、分類はBERTの公式が公開しているrun_classifier.py
のコードを一部変更して行います。
基本的な流れは前回作成した記事と同じですので、詳しい方法を知りたい方は下記の記事をご確認ください。
Google Colaboratory上でBERTを使い、日本語文章の二値分類を行う
それでは、早速実行していきましょう。
用意するもの
- Googleアカウント
多値分類の実施
今回は、livedoor newsコーパスの中から以下のジャンルを分類します。
- dokujo-tsushin
- it-life-hack
- kaden-channel
- livedoor-homme
- movie-enter
- peachy
- smax
- sports-watch
- topic-news
分類に使用する情報は、記事のタイトルです。
(データセットをBERT向けのフォーマットに変換の際に使用するプログラムを変更することで、本文を用いたfine-tuning、予測が可能になります。)
内容を保存したColaboratory notebookを私のGitHubに公開していますので、ソースコードの方が理解しやすい方はそちらをご覧ください。
Yuu94/bert-ja-maruchi-classification
本記事は以下のような流れをとります。
- 形態素解析器のインストール
- GoogleDriveをマウント
- データセット/BERT日本語学習済みモデルをGoogleDriveへ保存
- データセットをBERT向けのフォーマットに変換
- BERTのリポジトリをClone
- プログラムの改変
- trainデータを使用した、学習済みモデルのfine-tuning
- テストデータの予測
- 予測結果の検証
日本語学習済みモデルは 京都大学 黒橋・河原研究所が公開しているBERT日本語Pretrainedモデル
を、形態素解析器はJUMAN
を使用します。
最終的には、GoogleDrive上が以下のようなディレクトリ構成になります。
上手く読み込めない等の問題があれば、これを確認しながら作業してみてください。
マイドライブ/
└ bert/
└ livedoor_news
├ Japanese_L-12_H-768_A-12_E-30_BPE/ # 日本語学習済みモデル
├ tmp/ # 出力ファイル
├ livedoor_news_output_predic/
└ livedoor_news_output_fine/
├ bert/
├tokenization.py
└ run_classifier_livedoor.py
├ train.tsv
├ test.tsv
├ rand-all.tsv
├ dev.tsv
├ all.tsv
└ livedoor_news_multi.ipynb
形態素解析器のインストール
JUMANのインストール
今回の学習済みモデルは、 京都大学 黒橋・河原研究所が公開しているBERT日本語Pretrainedモデル
を使用します。
上記のモデルは、同研究室が公開している日本語形態素解析器システムJUMAN
を使用してモデルを学習させているため、同じ形態素解析器を使用します。
異なる形態素解析器を使用すると、学習済みモデルを作成した際の単語の最小単位と、新しくfine-tuningまたは入力データの単語の最小単位が異なり、新たに入力した単語が未知の単語扱いになるため、単語をベクトル化することができません。
学習済みモデルを使用する際、どの形態素解析器を使用すれば良いのかは、モデルを公開しているページに記述されていることが多いので、そこを確認する必要があります。
京都大学 黒橋・河原研究所が公開しているBERT日本語Pretrainedモデル
のページにも、どの形態素解析器を使用すれば良いのか記述がされているので、リンクを貼ります。
BERT日本語Pretrainedモデル - KUROHASHI-KAWAHARA LAB - 詳細
それでは、Colaboratory notebookを使用して、GPUマシンにJUMANをインストールします。
下記のコマンドをセルに貼り付け、実行していください。
!wget https://github.com/ku-nlp/jumanpp/releases/download/v2.0.0-rc2/jumanpp-2.0.0-rc2.tar.xz && \
tar xJvf jumanpp-2.0.0-rc2.tar.xz && \
rm jumanpp-2.0.0-rc2.tar.xz && \
cd jumanpp-2.0.0-rc2/ && \
mkdir bld && \
cd bld && \
cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr/local && \
make && \
sudo make install
以下のコマンドで、JUMANが問題なくインストールされているか確認します。
!jumanpp -v
Juman++ Version: 2.0.0-rc2
と表示されれば、インストール完了です。
TensorFlowのバージョン変更
本チュートリアルはTensorFlow 1系で動作するプログラムであるため、以下のコマンドを実行してTensorFlowのバージョンを1系に変更します。
デフォルトではTensorFlowは2系です。
%tensorflow_version 1.x
pyknpのインストール
JUMANをPythonで使用するため、pyknp
をpipインストールします。
pipはサーバーにインストール済みなので、以下のコマンドをセルに貼り付け、pyknp
をインストールします。
!pip install pyknp
GoogleDriveをマウント
学習済みモデルやデータセットを保存するGoogleDriveをマウントします。
以下のコードをセルに貼り付け、実行します。
from google.colab import drive
drive.mount('/content/drive')
次に作業ディレクトリの作成と移動を行います。
今回は、GoogleDrive直下にbert
ディレクトリを作成し、その中にlivedoor_news
ディレクトリを作成してその中で作業を行います。
そのため、以下のコマンドを使用して、ディレクトリを作成します。
(*別々のセルでコマンドを実行してください。)
!mkdir -p /content/drive/'My Drive'/bert/livedoor_news
cd /content/drive/'My Drive'/bert/livedoor_news
データセット/BERT日本語学習済みモデルをGoogleDriveへ保存
livedoor newsコーパスのダウンロード
下記のコマンドを使用して、データセットをダウンロードします。
import urllib.request
livedoor_news_url = "https://www.rondhuit.com/download/ldcc-20140209.tar.gz"
urllib.request.urlretrieve(livedoor_news_url, "ldcc-20140209.tar.gz")
BERT日本語学習済みモデル
*注意
学習済みモデルの容量1.6GBあるので、GoogleDriveの容量、ダウンロード時間にはご注意ください。
私の場合、ダウンロードは1時間以上かかった記憶がありますので、ダウンロードコマンドを実行したら、放置した方が良いと思います。
下記のコマンドをセルに貼り付け、実行し、モデルをダウンロードします。
kyoto_u_bert_url = "http://nlp.ist.i.kyoto-u.ac.jp/nl-resource/JapaneseBertPretrainedModel/Japanese_L-12_H-768_A-12_E-30_BPE.zip"
urllib.request.urlretrieve(kyoto_u_bert_url, "Japanese_L-12_H-768_A-12_E-30_BPE.zip")
ダウンロードが完了し、GoogleDriveのbert/livedoor_news
にJapanese_L-12_H-768_A-12_E-30_BPE.zip
が保存されていることを確認したら、下記のコマンドをColaboratory notebookで実行し、ZIPを解凍します。
!unzip Japanese_L-12_H-768_A-12_E-30_BPE.zip
問題がなければ、GoogleDrive上にbert/livedoor_news/Japanese_L-12_H-768_A-12_E-30_BPE
ディレクトリが作成されます。
データセットをBERT向けのフォーマットに変換
ダウンロードしたlivedoor newsコーパスを、BERTで扱いやすいようなフォーマットに変換します。
下記の記事を参考に作成しました。
BERT多言語モデルで日本語文章の二値分類を試す
次のプログラムをセルに貼り付け、実行します。
(実行後、GoogleDriveにマウントされるまで少し時間に差があります。)
import tarfile
import csv
import re
target_genre = [
"dokujo-tsushin",
"it-life-hack",
"kaden-channel",
"livedoor-homme",
"movie-enter",
"peachy",
"smax",
"sports-watch",
"topic-news"
]
fname_list = [[] for i in range(len(target_genre))]
tsv_fname = "all.tsv"
brackets_tail = re.compile('【[^】]*】$')
brackets_head = re.compile('^【[^】]*】')
def remove_brackets(inp):
output = re.sub(brackets_head, '',re.sub(brackets_tail, '', inp))
return output
def read_title(f):
next(f)
next(f)
title = next(f)
title = remove_brackets(title.decode('utf-8'))
return title[:-1]
with tarfile.open("ldcc-20140209.tar.gz") as tf:
for ti in tf:
if "LICENSE.txt" in ti.name:
continue
elif "CHANGES.txt" in ti.name:
continue
elif "README.txt" in ti.name:
continue
else:
for i, t in enumerate(target_genre):
if target_genre[i] in ti.name and ti.name.endswith(".txt"):
fname_list[i].append(ti.name)
continue
with open(tsv_fname, "w") as wf:
writer = csv.writer(wf, delimiter='\t')
for i, fcategory in enumerate(fname_list):
for name in fcategory:
f = tf.extractfile(name)
title = read_title(f)
row = [target_genre[i], i, '', title]
writer.writerow(row)
bert/livedoor_news/
にall.tsv
の作成が確認できたら、次のコードを実行して、学習用/テスト用のtsvファイルに分割します。
import random
random.seed(100)
with open("all.tsv", 'r') as f, open("rand-all.tsv", "w") as wf:
lines = f.readlines()
random.shuffle(lines)
for line in lines:
wf.write(line)
random.seed(101)
train_fname, dev_fname, test_fname = ["train.tsv", "dev.tsv", "test.tsv"]
with open("rand-all.tsv") as f, open(train_fname, "w") as tf, open(dev_fname, "w") as df, open(test_fname, "w") as ef:
ef.write("class\tsentence\n")
for line in f:
v = random.randint(0, 9)
if v == 8:
df.write(line)
elif v == 9:
ef.write(line)
else:
tf.write(line)
BERTのリポジトリをClone
BERTの公式リポジトリを以下のコマンドをセルにコピー/実行してクローンします。
!git clone https://github.com/google-research/bert.git
プログラムの改変
ここでは、BERTのチュートリアルにある文章の等価性を評価するタスク(MRPC)のプログラムを改変し、文章の多値分類を行います。
そのため、Colaboratory notebookからGoogleDriveにCloneしたrun_classifier.py
とtokenization.py
をローカルで編集します。
今回は、元のプログラムを残したいので、run_classifier.py
をローカルでコピーし、run_classifier_livedoor.py
と名前を変更して中身を改変することにします。
tokenization.py
に関しては、名前が変わるとプログラムを呼び出す部分も変更する必要があり(少々面倒なので)このままの名前で編集します。
下記の記事を参考に、改変を行いました。
自然言語処理で注目のBERT ~取り敢えず動かしてみる編~
run_classifier_livedoor.pyの変更
変更する場所は以下の2点です。
- LivedoorProcessorクラスの追加
- 引数からLivedoorProcessorを呼び出せるようprocessorsに設定
LivedoorProcessorクラスの追加
既に作成されているColaProcessor
クラスの下に、新しいクラスLivedoorProcessor
を作成します。
class LivedoorProcessor(DataProcessor):
"""Processor for the MRPC data set (GLUE version)."""
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", "6", "7", "8"]
def _create_examples(self, lines, set_type):
"""Creates examples for the training and dev sets."""
examples = []
for (i, line) in enumerate(lines):
if i == 0:
continue
guid = "%s-%s" % (set_type, i)
text_a = tokenization.convert_to_unicode(line[3])
if set_type == "test":
label = "0"
else:
label = tokenization.convert_to_unicode(line[1])
examples.append(
InputExample(guid=guid, text_a=text_a, text_b=None, label=label))
return examples
プログラムのベースはMrpcProcessor
クラスですが、2つの文章(text_a, text_b)を比較する部分を一つの文章(text_a)に変更しています。
また、今回はたち分類ですので、def get_labels
の部分で9つのラベルを使用する様に変更しています。
processorsの設定
次に、main関数
の中にあるprocessors
に先ほど追加したクラスの情報を追記します。
processors = {
"cola": ColaProcessor,
"mnli": MnliProcessor,
"mrpc": MrpcProcessor,
"xnli": XnliProcessor,
"livedoor": LivedoorProcessor, # 追記
}
tokenization.pyの変更
今回は、形態素解析器にJUMANを使用している為、tokenization.py
にJUMANを使用して形態素解析を行うのプログラムを記述します。
FullTokenizer
クラスの__init__
とtokenize
部位分を変更します。
class FullTokenizer(object):
"""Runs end-to-end tokenziation."""
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()}
# Jumanを使用する様に変更
# 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 = []
# Jumanを使用する様に変更
# 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)
return split_tokens
これで、形態素解析を行う際にJUMANが使用されます。
trainデータを使用した、学習済みモデルのfine-tuning
改変したプログラムを使用して、fine-tuningを行います。
以下のコマンドを実行してfine-tuningを行います。
!python bert/run_classifier_livedoor.py \
--task_name=livedoor \
--do_train=true \
--do_eval=true \
--data_dir=./ \
--vocab_file=./Japanese_L-12_H-768_A-12_E-30_BPE/vocab.txt \
--bert_config_file=./Japanese_L-12_H-768_A-12_E-30_BPE/bert_config.json \
--init_checkpoint=./Japanese_L-12_H-768_A-12_E-30_BPE/bert_model.ckpt \
--max_seq_length=128 \
--train_batch_size=32 \
--learning_rate=2e-5 \
--num_train_epochs=3.0 \
--output_dir=./tmp/livedoor_news_output_fine \
--do_lower_case False
問題なければ、以下の様に結果が出力されます。
INFO:tensorflow:***** Eval results *****
I0113 04:51:41.729829 140011245029248 run_classifier_livedoor.py:963] ***** Eval results *****
INFO:tensorflow: eval_accuracy = 0.875
I0113 04:51:41.730009 140011245029248 run_classifier_livedoor.py:965] eval_accuracy = 0.875
INFO:tensorflow: eval_loss = 0.40959355
I0113 04:51:41.734117 140011245029248 run_classifier_livedoor.py:965] eval_loss = 0.40959355
INFO:tensorflow: global_step = 131
I0113 04:51:41.734362 140011245029248 run_classifier_livedoor.py:965] global_step = 131
INFO:tensorflow: loss = 0.40959355
I0113 04:51:41.734567 140011245029248 run_classifier_livedoor.py:965] loss = 0.40959355
テストデータの予測
fine-tuningを行なったモデルを使用して、test.tsvを多値分類します。
以下のコマンドを使用して、テストデータの予測を行います。
!python bert/run_classifier_livedoor.py \
--task_name=livedoor \
--do_predict=true \
--data_dir=./ \
--vocab_file=./Japanese_L-12_H-768_A-12_E-30_BPE/vocab.txt \
--bert_config_file=./Japanese_L-12_H-768_A-12_E-30_BPE/bert_config.json \
--init_checkpoint=./tmp/livedoor_news_output_fine \
--max_seq_length=128 \
--output_dir=tmp/livedoor_news_output_predic/
問題がなければ、GoogleDriveのtmp/livedoor_news_output_predic/
にtest_results.tsv
が出力されます。
(出力結果がマウントされるのにし少し時間がかかります。)
予測結果の検証
text.tsvには、既に正解のカラムがありますので、BERTを使用して予測した値と正解の値を比較し、正解率を求めます。
下記のコードをセルにコピーして実行します。
import csv
import numpy as np
with open("./test.tsv") as f, open("tmp/livedoor_news_output_predic/test_results.tsv") as rf:
test = csv.reader(f, delimiter = '\t')
test_result = csv.reader(rf, delimiter = '\t')
# 正解データの抽出
next(test)
test_list = [int(row[1]) for row in test ]
# 予測結果を抽出
result_list = []
for result in test_result:
max_index = np.argmax(result)
result_list.append(max_index)
# 分類した予測結果(カテゴリNo)を出力
with open('tmp/livedoor_news_output_predic/test_results.csv', 'w') as of:
writer = csv.writer(of)
for row in result_list:
writer.writerow([row])
test_count = len(test_list)
result_correct_answer_list = [result for test, result in zip(test_list, result_list) if test == result]
result_correct_answer_count = len(result_correct_answer_list)
print("正解率: ", result_correct_answer_count / test_count)
* 今回は、分類結果をtmp/livedoor_news_output_predic/test_results.csv
に出力するプログラムを追加しました。多値分類ができていることを確認してみてください。
特に何もせず高い正解率が出ていると思います。
最後に
BERTで日本語の処理を行う手順をさらっとですが、ここで紹介しました。
BERTのチュートリアルから日本語の二値分類、そして多値分類と一通り紹介することができたので、次はBERTを理解したり何かに活用できるようにしたいと思います。
最後までお付き合いありがとうございました。