【Python】TensorFlowでBERTモデルを作る方法|文章分類

Python
スポンサーリンク

今回はTensorFlowでBERTモデルを作り文章分類をする方法を解説します。

import tensorflow.keras.layers as L
import tensorflow.keras.models as M

from transformers import TFAutoModel
backbone = TFAutoModel.from_pretrained(MODEL_NAME)

def get_model(backbone, max_length, num_classes):
    input_ids = L.Input(
        shape = (max_length), dtype = tf.int32, name = "input_ids"
    )
    attention_mask = L.Input(
        shape = (max_length), dtype = tf.int32, name = "attention_mask"
    )
    token_type_ids = L.Input(
        shape = (max_length), dtype = tf.int32, name = "token_type_ids"
    )
    
    transformer_outputs = backbone(
        {"input_ids": input_ids,
         "attention_mask": attention_mask,
         "token_type_ids": token_type_ids}
    )
    pooler_output = transformer_outputs.pooler_output
    outputs = L.Dense(units = num_classes, activation = "softmax")(pooler_output)
    
    model = M.Model(
        inputs = [input_ids, attention_mask, token_type_ids],
        outputs = outputs
    )
    model.compile(
        optimizer = tf.keras.optimizers.Adam(learning_rate = 1e-5),
        loss = "categorical_crossentropy"
    )
    return model

tf.keras.backend.clear_session()

model = get_model(backbone, MAX_LENGTH, 20)
model.summary()

データ準備

from sklearn.datasets import fetch_20newsgroups
import pandas as pd

train_data = fetch_20newsgroups(subset = 'train')
valid_data = fetch_20newsgroups(subset = 'test')

train = pd.DataFrame({"text" : train_data["data"], "target" : train_data["target"]})
valid = pd.DataFrame({"text" : valid_data["data"], "target" : valid_data["target"]})

print(train.shape, valid.shape)
train.head()

fetch_20newsgroupsというデータセットを使います。

“train”が学習データで”valid”が検証データです。

データには文章が入っており、1~20までの分類クラスが振ってあります。

中身を見てみます。↓

print(train["text"].values[0])
print("Label:", train["target"].values[0])

こんな感じで文章とラベルが入っています。

ただ文章には改行やスペースが多いので、きれいにしましょう。

import re
def cleaning(text):
    text = re.sub("\n", " ", text) # 改行削除
    text = re.sub("[^A-Za-z0-9]", " ", text) # 記号削除
    text = re.sub("[' ']+", " ", text) # スペース統一
    return text.lower() # 小文字で出力

train["cleaned_text"] = train["text"].map(cleaning)
valid["cleaned_text"] = valid["text"].map(cleaning)

train.head()

こんな感じです。

Tokenizer

tokenizer

文章をそのままモデルに入れて計算することはできません。

なので、文章を数値化(ベクトル化)します。

そこで必須なのが、tokenzierです。

from transformers import AutoTokenizer
MODEL_NAME = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)

transformersからAutoTokenizerをインポートします。

tokenizerを呼び出すとき、使用するモデルの種類を指定します。

今回はベーシックな”bert-base”を使います。

“uncased”は小文字という意味です。

試しに1文だけ入れて変換してみましょう。↓

text = train["cleaned_text"].values[0]
print(text)

この文章を変換します。

encoded = tokenizer(text)
print(encoded.keys())
“input_ids”:文字をベクトル化した数値。
“attention_mask”:使用する文字の部分が1。パディングした不要な部分は0。
“token_type_ids”:2つ文章を入れた場合、1つ目が0、2つ目が1。
print(tokenizer.decode(encoded["input_ids"]))

decodeで逆変換できるので、input_idsを再度文章にしました。

こんな感じで数値になっていますが、tokenizerの中ではちゃんと元の文章として認識されています。

max_length

解説したように文章を数値に変換していくわけですが、当然文章が長いほど変換後の数値の数も多くなります。

しかしモデルに学習させる際には、入力するデータの長さを統一しなければなりません。

なので、一定の長さを規定して、
・長い文章⇒一定の長さまで切り取る
・短い文章⇒一定の長さまでパディングする
という調整が必要になります。
text = train["cleaned_text"].values[0]

encoded = tokenizer(text)
print(len(encoded["input_ids"]))
print(encoded["input_ids"][-10:])

encoded = tokenizer(text, max_length = 64, padding = "max_length", truncation = True)
print(len(encoded["input_ids"]))
print(encoded["input_ids"][-10:])

encoded = tokenizer(text, max_length = 1024, padding = "max_length", truncation = True)
print(len(encoded["input_ids"]))
print(encoded["input_ids"][-10:])
“max_length”:最大長さを指定
“padding”:max_lengthにすると指定した長さまで埋めてくれる
“truncation”:Trueにするとmax_lengthを超えた分は切り取る

もともとの長さは154です。

max_length = 64にすると超過するので、64個までで切り取られています。

max_length = 1024にすると不足しているので、0で穴埋めされています。

実際にはこの処理を全データを対象に行い、データの長さを統一します。

max_lengthの長さはいくつにしてもいいですが、
短すぎるとデータの情報量を落とすことになり、
逆に長すぎると計算時間が長くなります。

なので、まずは全データの長さを見てみましょう。

import numpy as np

lens = []
for text in train["cleaned_text"].values:
    encoded = tokenizer(text)
    lens.append(len(encoded["input_ids"]))

for i in [50, 75, 90, 95]:
    print(i, np.percentile(lens, i))

percentileはパーセンタイル値を出してくれます。50は中央値です。

だいたい600あれば90%のデータを確保してくれますけど、計算を軽くしたいので256にしましょう。

MAX_LENGTH = 256

train_tokens = tokenizer.batch_encode_plus(
    train["text"].to_list(),
    padding = "max_length",
    max_length = MAX_LENGTH,
    truncation = True
)

valid_tokens = tokenizer.batch_encode_plus(
    valid["text"].to_list(),
    padding = "max_length",
    max_length = MAX_LENGTH,
    truncation = True
)

全データを一気に変換するにはbatch_encode_plusを使います。

これで全文章をMAX_LENGTHの長さで数値化できました。

データセット作成

トークン化まで終わったのでTensorFlowで学習するためのデータセットを作ります。

import tensorflow as tf
AUTO = tf.data.experimental.AUTOTUNE
BATCH_SIZE = 64

y_train = tf.one_hot(train["target"].values, depth = 20)
y_valid = tf.one_hot(valid["target"].values, depth = 20)

train_dataset = tf.data.Dataset.from_tensor_slices((
    {"input_ids": train_tokens["input_ids"],
     "attention_mask": train_tokens["attention_mask"],
     "token_type_ids": train_tokens["token_type_ids"]
    }, y_train
))

train_dataset = train_dataset.shuffle(1024 * 10)
train_dataset = train_dataset.batch(BATCH_SIZE)
train_dataset = train_dataset.prefetch(AUTO)

valid_dataset = tf.data.Dataset.from_tensor_slices((
    {"input_ids": valid_tokens["input_ids"],
     "attention_mask": valid_tokens["attention_mask"],
     "token_type_ids": valid_tokens["token_type_ids"]
    }, y_valid

))

valid_dataset = valid_dataset.batch(BATCH_SIZE * 2)
valid_dataset = valid_dataset.prefetch(AUTO)

(入力, 正解データ)の順で入れます。

正解データは20ラベルあるので、OneHotエンコーディングで20行にしておきます。

バッチサイズは計算環境次第なので、メモリエラーが出たら下げましょう。

モデル作成

細かいことは以下の記事を参照してください。

import tensorflow.keras.layers as L
import tensorflow.keras.models as M

from transformers import TFAutoModel
backbone = TFAutoModel.from_pretrained(MODEL_NAME)

def get_model(backbone, max_length, num_classes):
    input_ids = L.Input(
        shape = (max_length), dtype = tf.int32, name = "input_ids"
    )
    attention_mask = L.Input(
        shape = (max_length), dtype = tf.int32, name = "attention_mask"
    )
    token_type_ids = L.Input(
        shape = (max_length), dtype = tf.int32, name = "token_type_ids"
    )
    
    transformer_outputs = backbone(
        {"input_ids": input_ids,
         "attention_mask": attention_mask,
         "token_type_ids": token_type_ids}
    )
    pooler_output = transformer_outputs.pooler_output
    outputs = L.Dense(units = num_classes, activation = "softmax")(pooler_output)
    
    model = M.Model(
        inputs = [input_ids, attention_mask, token_type_ids],
        outputs = outputs
    )
    model.compile(
        optimizer = tf.keras.optimizers.Adam(learning_rate = 1e-5),
        loss = "categorical_crossentropy"
    )
    return model

tf.keras.backend.clear_session()

model = get_model(backbone, MAX_LENGTH, 20)
model.summary()
“input_ids”, “attention_mask”, “token_type_ids”
を受け取るInput層をそれぞれ作りましょう。

M.Modelの部分で、入力する3つのデータをリストで渡します。

BERTには複数の出力形態があり、今回はpooler_outputを使用します。

学習率は1e-5でかなり小さくしました。大きいと学習がうまくいきません。

最後の出力はクラス数の20にしてsoftmaxにしておきましょう。

学習と予測

checkpoint = tf.keras.callbacks.ModelCheckpoint(
    "best_weight.h5",
    monitor = "val_loss",
    direction = "min",
    save_best_only = True,
    save_weights_only = True
)

history = model.fit(
    train_dataset,
    validation_data = valid_dataset,
    epochs = 8,
    callbacks = [checkpoint]
)

fitで学習を実行します。

学習が進むにつれて過学習をおこすので、checkpointで最もlossの小さいステータスを保存します。

import matplotlib.pyplot as plt
plt.plot(history.history["loss"], label = "train")
plt.plot(history.history["val_loss"], label = "valid")
plt.legend()
plt.show()

from sklearn.metrics import accuracy_score, precision_score, recall_score

model.load_weights("best_weight.h5")
pred = model.predict(
    valid_dataset, verbose = 1
).argmax(axis = 1)
truth = y_valid.numpy().argmax(axis = 1)

accuracy = accuracy_score(truth, pred)
precision = precision_score(truth, pred, average = "macro")
recall = recall_score(truth, pred, average = "macro")
print("ACC:", accuracy.round(3), "PRE:", precision.round(3), "REC:", recall.round(3))

最良のステータスを読み込み、predictで予測します。

1~20の中で最も値の大きい列番号が予測されたラベルになります。

正解率、適合率、再現率を計算しました。

だいたい85%の精度がありますね!

まとめ

今回はTensorFlowでBERTを実装する方法を解説しました。

TensorFlowはTPUが使えるので計算が重いBERTもサクサク回せるので便利です。

コメント

タイトルとURLをコピーしました