Help us understand the problem. What is going on with this article?

もっと簡単に Keras BERT でファインチューニングしてみる

もっと簡単に Keras BERT でファインチューニングしてみる

TL;DR

text-vectorianをバージョンアップし、BERT のファインチューニングで役に立つ機能を追加しました。

BERT のモデルやベンチマーク用のデータなどはKeras BERT でファインチューニングしてみるを参照してください。

事前準備

BERT モデルのダウンロード

BERT のモデルは別途準備する必要があります。 日本語 Wikipedia を元に学習した学習済みモデルは以下の方が提供されています。

以下のファイルをダウンロードしておきます。

  • wiki-ja.vocab
  • wiki-ja.model
  • model.ckpt-1400000.data-00000-of-00001
  • model.ckpt-1400000.index
  • model.ckpt-1400000.meta

text-vectorian のインストール

pip intall text-vectorian

バージョンは0.2.0以上であることを確認してください。

Keras BERT の Config

text-vectorianではKeras BERTを使用していますが、以下の設定をデフォルトで利用します。

{
    "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": 128,
    "max_seq_length": 128,
    "num_attention_heads": 12,
    "num_hidden_layers": 12,
    "type_vocab_size": 2,
    "vocab_size": 32000
}

max_position_embeddingsmax_seq_lengthについては、トークンが大きい場合はモデルの最大である 512 まで拡張することができます。
デフォルトとは異なる設定を使用する場合は、上記のJSONを任意のファイルとして保存し、SpBertVectorianにパラメータとして指定してください。
デフォルトのまま実行する場合は、config_filenameは必要ありません。

vectorizer_filename = f'{ROOT_DIR}/bert-japanese/model/model.ckpt-1400000'
tokenizer_filename = f'{ROOT_DIR}/bert-japanese/model/wiki-ja.model'
config_filename = f'{ROOT_DIR}/my-config.json'
vectorian = SpBertVectorian(
    tokenizer_filename=tokenizer_filename,
    vectorizer_filename=vectorizer_filename,
    config_filename=cofnig_filename
)

ソースコード

モジュールのロード

from text_vectorian import SpBertVectorian

# `model.ckpt-1400000` のように拡張子を付けないのがポイントです。
vectorizer_filename = f'{ROOT_DIR}/bert-japanese/model/model.ckpt-1400000'
tokenizer_filename = f'{ROOT_DIR}/bert-japanese/model/wiki-ja.model'
vectorian = SpBertVectorian(
    tokenizer_filename=tokenizer_filename,
    vectorizer_filename=vectorizer_filename
)

データロード用関数

import pandas as pd
import sentencepiece as spm
from keras import utils
from keras.preprocessing.sequence import pad_sequences
import logging
import numpy as np

def _load_labeldata(train_dir, test_dir):
    train_features_df = pd.read_csv(f'{train_dir}/features.csv')
    train_labels_df = pd.read_csv(f'{train_dir}/labels.csv')
    test_features_df = pd.read_csv(f'{test_dir}/features.csv')
    test_labels_df = pd.read_csv(f'{test_dir}/labels.csv')
    label2index = {k: i for i, k in enumerate(train_labels_df['label'].unique())}
    index2label = {i: k for i, k in enumerate(train_labels_df['label'].unique())}
    class_count = len(label2index)
    train_labels = utils.np_utils.to_categorical([label2index[label] for label in train_labels_df['label']], num_classes=class_count)
    test_label_indices = [label2index[label] for label in test_labels_df['label']]
    test_labels = utils.np_utils.to_categorical(test_label_indices, num_classes=class_count)

    train_features = []
    test_features = []

    for feature in train_features_df['feature']:
        train_features.append(vectorian.fit(feature, suppress_vectors=True).indices)
    train_segments = vectorian.get_segments()
    vectorian.reset()
    for feature in test_features_df['feature']:
        test_features.append(vectorian.fit(feature, suppress_vectors=True).indices)
    test_segments = vectorian.get_segments()

    print(f'Trainデータ数: {len(train_features_df)}, Testデータ数: {len(test_features_df)}, ラベル数: {class_count}')

    return {
        'class_count': class_count,
        'label2index': label2index,
        'index2label': index2label,
        'train_labels': train_labels,
        'test_labels': test_labels,
        'test_label_indices': test_label_indices,
        'train_features': np.array(train_features),
        'train_segments': np.array(train_segments),
        'test_features': np.array(test_features),
        'test_segments': np.array(test_segments)
    }

モデル準備関数

from keras import Model
from keras.layers import Dense
import keras

def _create_model(class_count, samples_len, batch_size, epochs):
    layers = vectorian.get_keras_layer(trainable=True)
    optimizer = vectorian.get_optimizer(samples_len=samples_len, batch_size=batch_size, epochs=epochs)

    output_tensor = keras.layers.Dense(class_count, activation='softmax')(layers['last'])
    model = keras.Model(layers['inputs'], output_tensor)
    model.compile(loss='categorical_crossentropy', optimizer=optimizer, metrics=['mae', 'mse', 'acc'])
    model.summary()

    return model

データのロードとモデルの準備

trains_dir = f'{ROOT_DIR}/word-or-character/data/trains'
tests_dir = f'{ROOT_DIR}/word-or-character/data/tests'

data = _load_labeldata(trains_dir, tests_dir)
samples_len = len(data['train_features'])
batch_size = 8
epochs = 10

model = _create_model(data['class_count'], samples_len, batch_size, epochs)

学習の実行

from keras.callbacks import EarlyStopping, ModelCheckpoint, TensorBoard
import pandas as pd

model_filename = 'bert.model'

history = model.fit([data['train_features'], data['train_segments']],
          data['train_labels'],
          epochs = epochs,
          batch_size = batch_size,
          validation_data=([data['test_features'], data['test_segments']], data['test_labels']),
          shuffle=False,
          verbose = 1,
          callbacks = [
              ModelCheckpoint(monitor='val_acc', mode='max', filepath=model_filename, save_best_only=True)
          ])
display(pd.DataFrame(history.history))

クラシフィケーションレポート

from sklearn.metrics import classification_report, confusion_matrix
from keras.models import load_model
from keras_bert import get_custom_objects

model = load_model(model_filename, custom_objects=get_custom_objects())

predicted_test_labels = model.predict([data['test_features'], data['test_segments']]).argmax(axis=1)
numeric_test_labels = np.array(data['test_labels']).argmax(axis=1)

report = classification_report(
        numeric_test_labels, predicted_test_labels, target_names=['グルメ', '携帯電話', '京都', 'スポーツ'], output_dict=True)

display(pd.DataFrame(report).T)

まとめ

Keras BERT でファインチューニングしてみるとほぼ同じ結果をえる事ができました。
text-vectorianを利用することで、BERT固有の前処理などを省略することができます。

ファインチューニングを行わずにベクトルだけ取得する場合

ファインチューニングが不要の場合は、以下の様に簡単にBERTによるベクトルだけ取得することが可能です。

from text_vectorian import SpBertVectorian

tokenizer_filename = '[モデルをダウンロードしたディレクトリ]/model/wiki-ja.model'
vectorizer_filename = '[モデルをダウンロードしたディレクトリ]/model/model.ckpt-1400000'
vectorian = SpBertVectorian(
    tokenizer_filename=tokenizer_filename,
    vectorizer_filename=vectorizer_filename,
)

text = 'これはテストです。'
vectors = vectorian.fit(text).vectors

参考文献

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away