LoginSignup
39
45

More than 3 years have passed since last update.

Google Colaboratory上でBERTを使い、日本語文章の二値分類を行う

Last updated at Posted at 2020-01-13

この記事では、Google ColaboratoryのGPUを使用してBERTを動かし、日本語文章の二値分類を行います。

前回の記事の続きです。
詳細な説明は下記の記事で行なっていますので、初めての方に関しては、そちらを参考に進めてください。
Google Colaboratory(GPU)上でBERTのチュートリアルを行う

学習済みモデルは、GoogleDriveに保存し、GoogleColaboratoryにGoogleDriveをマウントして使用します。

*注意
GoogleDriveに学習済みモデルとデータセットを保存するため、1.6GB以上の空きが必要です。
一度だけ試す場合は、GoogleDriveとGoogleColaboratory、を連携せず、GoogleColaboratoryのGPUインスタンス上にモデルやデータセットを保存する方が良いかもしれません。

*宣伝
新しく、BERTを用いて日本語文章の多値分類を行う記事を書きました。
次のステップとしてご利用ください。
BERTを用いて、日本語文章の多値分類を行う

BERTを使用した日本語文章の二値分類

ここでは、BERTとBERT日本語学習済みモデル、livedoor newsコーパスを使用して、GoogleColaboratory上で学習済みモデルのfine-tuningと文章の二値分類を行います。
分類は記事のタイトルを使用して行います。

fine-tuning、分類はBERTの公式が公開しているrun_classifier.pyのコードを一部変更して行います。

基本的な流れは前回作成した記事と同じですので、詳しい方法を知りたい方は下記の記事をご確認ください。
Google Colaboratory(GPU)上でBERTのチュートリアルを行う

それでは、早速実行していきましょう。

用意するもの

  • Googleアカウント

二値分類の実施

今回は、livedoor newsコーパスの中から"it-life-hack"と"kaden-channel"を分類していきます。
分類に使用する情報は、記事のタイトルです。
(データセットをBERT向けのフォーマットに変換の際に使用するプログラムを変更することで、本文を用いたfine-tuning、予測が可能になります。)

内容を保存したColaboratory notebookを私のGitHubに公開していますので、ソースコードの方が理解しやすい方はそちらをご覧ください。

Yuu94/bert-ja-two-classification

本記事は以下のような流れをとります。

  1. 形態素解析器のインストール
  2. GoogleDriveをマウント
  3. データセット/BERT日本語学習済みモデルをGoogleDriveへ保存
  4. データセットをBERT向けのフォーマットに変換
  5. BERTのリポジトリをClone
  6. プログラムの改変
  7. trainデータを使用した、学習済みモデルのfine-tuning
  8. テストデータの予測
  9. 予測結果の検証

日本語学習済みモデルは 京都大学 黒橋・河原研究所が公開している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_create_tsv.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_newsJapanese_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 = ["it-life-hack", "kaden-channel"]

zero_fnames = []
one_fnames = []
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))

    # 全角スペースを除去
    # output = output.replace(" ", " ")

    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
        if target_genre[0] in ti.name and ti.name.endswith(".txt"):
            zero_fnames.append(ti.name)
            continue
        if target_genre[1] in ti.name and ti.name.endswith(".txt"):
            one_fnames.append(ti.name)
    with open(tsv_fname, "w") as wf:
        writer = csv.writer(wf, delimiter='\t')
        for name in zero_fnames:
            f = tf.extractfile(name)
            title = read_title(f)
            row = [target_genre[0], 0, '', title]
            writer.writerow(row)
        for name in one_fnames:
            f = tf.extractfile(name)
            title = read_title(f)
            row = [target_genre[1], 1, '', 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.pytokenization.pyをローカルで編集します。
今回は、元のプログラムを残したいので、run_classifier.pyをローカルでコピーし、run_classifier_livedoor.pyと名前を変更して中身を改変することにします。
tokenization.pyに関しては、名前が変わるとプログラムを呼び出す部分も変更する必要があり(少々面倒なので)このままの名前で編集します。

下記の記事を参考に、改変を行いました。
自然言語処理で注目のBERT ~取り敢えず動かしてみる編~

run_classifier_livedoor.pyの変更

変更する場所は以下の2点です。

  1. LivedoorProcessorクラスの追加
  2. 引数からLivedoorProcessorを呼び出せるようprocessorsに設定

LivedoorProcessorクラスの追加

既に作成されているColaProcessorクラスの下に、新しいクラスLivedoorProcessorを作成します。

run_classifier_livedoor.py
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"]

  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)に変更しています。

processorsの設定

次に、main関数の中にあるprocessorsに先ほど追加したクラスの情報を追記します。

run_classifier_livedoor.py
  processors = {
      "cola": ColaProcessor,
      "mnli": MnliProcessor,
      "mrpc": MrpcProcessor,
      "xnli": XnliProcessor,
      "livedoor": LivedoorProcessor, # 追記
  }

tokenization.pyの変更

今回は、形態素解析器にJUMANを使用している為、tokenization.pyにJUMANを使用して形態素解析を行うのプログラムを記述します。
FullTokenizerクラスの__init__tokenize部位分を変更します。

tokenization.py
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

accuracy0.88loss0.41なので、良い結果だと思います。

テストデータの予測

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


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 = [0 if row[0] > row[1] else 1 for row in test_result ]

  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)

私の手元では、正解率: 0.8874172185430463が出力されました。

特に何もせず高い正解率が出ていると思います。

最後に

BERTで日本語の処理を行う手順をさらっとですが、ここで紹介しました。
次はできれば多項分類ができればと思っています。

39
45
13

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
39
45