(Part 1) tensorflow2でhuggingfaceのtransformersを使ってBERTを文書分類モデルに転移学習する
現在、NLPの分野でも転移学習やfine-tuningで高い精度がでる時代になっています。 おそらく最も名高いであろうBERTをはじめとして、競ってモデルが開発されています。
BERTは公式のtensorflow実装は公開されてありますが、画像分野の転移学習モデルに比べると不便さが際立ちます。 BERTに限らず、公式のtensorflow実装は難解で、tf.kerasの学習済みモデルに関してもほとんど画像のモデルしかないです。
ただし、pytorch用のライブラリにpytorch-transformersという有用なものがありまして、 BERT, GPT-2, RoBERTa, DistilBert, XLNetなどの多言語学習済みモデルが利用可能で、カスタマイズもしやすいということで有名でした。
このライブラリが名前をかえてtensorflow2に対応してくれました。
Transformers
🤗 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.
非常に対応しているモデルの量が多いです。また、インターフェースが整っており非常に利用しやすいです。
インストール
pipでインストール可能です。
$ pip install transformers
なお、tensorflow1.Xには対応していないので、バージョンが古い場合はpipで入れ直してください。
transfer leaning with BERT of huggingface transformers
こちら(ストックマーク?)で公開されている以下のような事前学習済みモデルを使いたいと思います。
事前学習用データ | 形態素解析 | 語彙数 |
---|---|---|
日本語ビジネスニュース記事(300万記事) | MeCab + NEologd | 32,000語(CLS、SEP、UNK等除く) |
このモデルを文書分類モデルに転移させて livedoor ニュースコーパスのカテゴリ分類を学習させてみます。 なお、使いやすさを確認する目的なので、前処理はさぼります。
全ソースコードはこちらから確認できます。colaboratoryで実装してあります。
[追記: 2019/12/15]
transformersの概要は掴めましたが、精度がいまいち上がらなかったので再挑戦しました。
→ (Part 2) tensorflow 2 でhugging faceのtransformers公式のBERT日本語学習済みモデルを文書分類モデルにfine-tuningする - メモ帳
前処理
流れは以下です。詳しくはソースコードを参照下さい。
- 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 | 事前学習 |
TFBertForMaskedLM | masked tokenの予測 |
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の更新は追っていきたいです。