メモ帳

python, django, juliaを使った機械学習関連の技術記事

tensorflow2でhuggigfaceのtransformersを使ってBERTを文書分類モデルに転移学習する

現在、NLPの分野でも転移学習やfine-tuningで高い精度がでる時代になっています。 おそらく最も名高いであろうBERTをはじめとして、競ってモデルが開発されています。

BERTは公式のtensorflow実装は公開されてありますが、画像分野の転移学習モデルに比べると不便さが際立ちます。 BERTに限らず、公式のtensorflow実装は難解で、tf.kerasの学習済みモデルに関してもほとんど画像のモデルしかないです。

ただし、pytorch用のライブラリにpytorch-transformersという有用なものがありました。 BERT, GPT-2, RoBERTa, DistilBert, XLNetなどの多言語学習済みモデルが利用可能で、カスタマイズもしやすいということで有名でした。

このライブラリが名前をかえてtensorflow2に対応してくれました。

Transformers

f:id:atelier-0213:20191022214705p:plain
https://venturebeat.com/2019/09/26/hugging-face-launches-popular-transformers-nlp-library-for-tensorflow/

🤗 Transformers (formerly known as pytorch-transformers and pytorch-pretrained-bert) provides state-of-the-art general-purpose architectures (BERT, GPT-2, RoBERTa, XLM, DistilBert, XLNet, CTRL...) for Natural Language Understanding (NLU) and Natural Language Generation (NLG) with over 32+ pretrained models in 100+ languages and deep interoperability between TensorFlow 2.0 and PyTorch.

github.com

非常に対応しているモデルの量が多いです。また、インターフェースが整っており非常に利用しやすいです。

インストール

pipでインストール可能です。

$ pip install transformers

なお、tensorflow1.Xには対応していないので、バージョンが古い場合はpipで入れ直してください。

transfer leaning with BERT of huggingface transformers

こちら(ストックマーク?)で公開されている以下のような事前学習済みモデルを使いたいと思います。

事前学習用データ トークナイザー 語彙数
日本語ビジネスニュース記事(300万記事) MeCab + NEologd 32,000語(CLS、SEP、UNK等除く)

このモデルを文書分類モデルに転移させて livedoor ニュースコーパスのカテゴリ分類を学習させてみます。 なお、使いやすさを確認する目的なので、前処理はさぼります。



ソースコードこちらから確認できます。colaboratoryで実装してあります。

前処理

流れは以下です。詳しくはソースコードを参照下さい。

  • MeCabのインストール
  • livedoor news corpusを読み込み、以下の前処理を施す
  • datasetをtrainとvalidation用に分割
  • tensorflow.Datasetの形式に変換
  • vocabularyを指定してワードをIDに変換
  • GLUEのdatasetと同様な形式に変換してBERTの学習に使えるようにする

BERT用の前処理が少しまわりくどいんですが、以下のようにpandasなどで前処理した後でtensorflow.Dataset形式に変換し、BertTokenizerとglue_convert_examples_to_featuresという関数を使ってBertが読めるようにする必要があります。

import tensorflow as tf
from transformers import (
    BertTokenizer,
    glue_convert_examples_to_features,
)

data = {
    "train": tf.data.Dataset.from_tensor_slices({
        'idx': idx_train,
        'sentence1': df_train['body_wakati'].tolist(),
        'sentence2': ['', ] * len(df_train),
        'label': df_train['label'].tolist()
    }),
    "validation": tf.data.Dataset.from_tensor_slices({
        'idx': idx_val,
        'sentence1': df_val['body_wakati'].tolist(),
        'sentence2': ['', ] * len(df_val),
        'label': df_val['label'].tolist()
    })
}


# dataをBERTが読める形式に変換します
train_batch = 32
epochs = 5
val_batch = 64

# pretrained weightのダウンロード時に取得したvocab.txtファイルを指定します
tokenizer = BertTokenizer.from_pretrained(os.path.join(pretrained_dir, 'vocab.txt'))

# GLUE形式のtf.data.Dataset instanceに変換
train_dataset = glue_convert_examples_to_features(data['train'], tokenizer, label_list=labels, max_length=128, task='mrpc')
valid_dataset = glue_convert_examples_to_features(data['validation'], tokenizer, label_list=labels, max_length=128, task='mrpc')

Download pretrained model

公開して頂いている日本語BERTのpretrained modelをダウンロードします。

ただし、transformersで読み込める学習済みモデルの重みは

  • pytorch_model.bin: pytorch版
  • tf_model.h5: tensorflow2.X版

です。公開いただいているのは tensorflow 1.X系のcheckpoint形式かpytorchのbin形式の2種類なのでpytorchのpretrained modelを使います。

transormersが読み込む全てのファイルは以下の3つです。

  • config.json: パラメータやモデルの設定ファイル
  • pytorch_model.bin: 学習済みweight
  • vocab.txt: 学習に使用したワードID

上記pretrainedモデルを利用する場合は、bert_config.jsonというファイルをconfig.jsonという名前に変更しておく必要があります。

model定義

BERTの定義済みモデルは、transformers.TFBertModelクラスをベースにして

クラス タスク
TFBertForPreTraining 事前学習
TFBertForNextSentencePrediction 次センテンス予測
TFBertForSequenceClassification 文書分類
TFBertForQuestionAnswering 質問応答

など様々です。他にも色々ありますのでソースを参照して頂けたらと思います。

これらは基本的にTFBertModelの出力を出すところまでは同じ処理をしていて、

TFBertModel + (タスクに応じた処理 or Denseなど)

のような流れをとっています。
単なるtf.keras layerの連結ではないため、間に追加でlayerをはさんだり、入れ替えたりできません。 なので、transfer learningやfine-tuningをするさいはTFBertModelをimportしてfunctional APIでtf.keras layerを追加してく形をとるのがいいと思います。

pytorch版のpretrained weightをtf2でload

from transformers import TFBertModel

pretrained_dir = './pretrained' # ダウンロード先のディレクトリを指定
bert = TFBertModel.from_pretrained(pretrained_dir, from_pt=True)

transfer learningモデルの定義

BertModelに3層のFC層を足して、FC層のみ学習するようにします。

def make_model(bert, num_classes, bert_frozen=True):
    # bertモデルはリストになっているので、取り出す
    # 層をfreeze(学習させないように)する
    bert.layers[0].trainable = not bert_frozen

    # input
    input_ids = Input(shape=(128, ), dtype='int32', name='input_ids')
    attention_mask = Input(shape=(128, ), dtype='int32', name='attention_mask')
    token_type_ids = Input(shape=(128, ), dtype='int32', name='token_type_ids')
    inputs = [input_ids, attention_mask, token_type_ids]

    # bert
    x = bert.layers[0](inputs)
    # x: sequence_output, pooled_output
    # 2種類の出力がある。

    # TFBertForSequenceClassificationにならってpooled_outputのみ使用
    out = x[1]

    # fc layer(add layers for transfer learning)
    out = Dropout(0.1)(out)
    out = Dense(64, activation='relu')(out)
    out = Dropout(0.1)(out)
    out = Dense(64, activation='relu')(out)
    out = Dropout(0.1)(out)
    out = Dense(num_classes, activation='softmax')(out)
    return Model(inputs=inputs, outputs=out)

model = make_model(bert, num_classes)
model.summary()

サマリーを確認すると、以下の様につながっていて、bertの部分は学習しないように指定できていることがわかります。

Model: "model"
__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
input_ids (InputLayer)          [(None, 128)]        0                                            
__________________________________________________________________________________________________
attention_mask (InputLayer)     [(None, 128)]        0                                            
__________________________________________________________________________________________________
token_type_ids (InputLayer)     [(None, 128)]        0                                            
__________________________________________________________________________________________________
bert (TFBertMainLayer)          ((None, 128, 768), ( 110621184   input_ids[0][0]                  
                                                                 attention_mask[0][0]             
                                                                 token_type_ids[0][0]             
__________________________________________________________________________________________________
dropout_37 (Dropout)            (None, 768)          0           bert[0][1]                       
__________________________________________________________________________________________________
dense (Dense)                   (None, 64)           49216       dropout_37[0][0]                 
__________________________________________________________________________________________________
dropout_38 (Dropout)            (None, 64)           0           dense[0][0]                      
__________________________________________________________________________________________________
dense_1 (Dense)                 (None, 64)           4160        dropout_38[0][0]                 
__________________________________________________________________________________________________
dropout_39 (Dropout)            (None, 64)           0           dense_1[0][0]                    
__________________________________________________________________________________________________
dense_2 (Dense)                 (None, 9)            585         dropout_39[0][0]                 
==================================================================================================
Total params: 110,675,145
Trainable params: 53,961
Non-trainable params: 110,621,184
__________________________________________________________________________________________________

コンパイル

以下、通常のkerasの流れと同様です。

from tensorflow import keras
from tensorflow.keras import optimizers, losses, metrics
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, Dense, Dropout

optimizer = optimizers.Adam(learning_rate=3e-5, epsilon=1e-08, clipnorm=1.0)
loss = losses.SparseCategoricalCrossentropy(from_logits=True)
metric = metrics.SparseCategoricalAccuracy('accuracy')
model.compile(optimizer=optimizer, loss=loss, metrics=[metric])

学習

history = model.fit(train_dataset, epochs=epochs,
                    validation_data=valid_dataset)

確かに学習が行われます。以下は、colaboratoryのGPUを使用して学習した結果です。

Epoch 1/5
173/173 [==============================] - 154s 887ms/step - loss: 2.1949 - accuracy: 0.1357 - val_loss: 0.0000e+00 - val_accuracy: 0.0000e+00
Epoch 2/5
173/173 [==============================] - 132s 763ms/step - loss: 2.1914 - accuracy: 0.1347 - val_loss: 2.1884 - val_accuracy: 0.1564
Epoch 3/5
173/173 [==============================] - 132s 761ms/step - loss: 2.1868 - accuracy: 0.1446 - val_loss: 2.1848 - val_accuracy: 0.1748
Epoch 4/5
173/173 [==============================] - 132s 761ms/step - loss: 2.1828 - accuracy: 0.1589 - val_loss: 2.1797 - val_accuracy: 0.1982
Epoch 5/5
173/173 [==============================] - 132s 762ms/step - loss: 2.1802 - accuracy: 0.1652 - val_loss: 2.1741 - val_accuracy: 0.2172

以上になります!もっと細かくfreezeさせるやり方など深めていきたいです! 興味のある方はぜひ!

備考

tensorflow2.0の公式NLPモデルは少しずつ増えていっています。既にBERT, XLNetは利用可能です。まだまだ様子見といった感じですが、状況によってはhuggingfaceのライブラリに頼る必要がなくなるかもしれませんのでtensorflowのmodelsの更新は追っていきたいです。