[PYTHON] [TPU] [Transformers] Faites du BERT à une vitesse explosive

J'ai pu construire un modèle BERT très facilement en utilisant TPU + Transformers dans le concours auquel j'ai participé, je vais donc le partager.

Quoi qu'il en soit, nous donnerons la priorité à la vitesse et créerons BERT, qui est une tendance récente de la PNL.

BERT Il s'agit d'un modèle de langage à usage général conçu par Google. Cette fois, nous utiliserons une recette qui permet de gagner du temps en utilisant Distil BERT, qui est un modèle de distillation plus léger et plus rapide que BERT. Le modèle Pretrain utilise modèle japonais publié par Bandai Namco Research Institute.

TPU TPU (Tensor Processing Unit) est un processeur développé par Google spécialisé dans les calculs autour de l'apprentissage automatique. Il semble que le calcul matriciel puisse être effectué plus rapidement que les processeurs à usage général en remplaçant le circuit arithmétique de 32 bits à 8 ou 16 bits, ou en passant des valeurs entre des circuits arithmétiques sans lecture ni écriture de mémoire. Récemment, il existe des TPU pour Edge qui peuvent être utilisés avec GCP et peuvent être installés dans Raspberry pi.

Cette fois, nous utiliserons TPU sur Google Colab pour faire exploser le temps d'apprentissage.

Transformers Il s'agit d'un cadre d'apprentissage en profondeur fourni par Hugging Face, spécialisé dans les modèles Transformer. Vous pouvez facilement charger les modèles Tokenizer et Pretrain requis pour créer des modèles Transformer à partir de ceux publiés sur Hugging Face's HP. (Bien sûr, vous pouvez également vous référer au modèle enregistré localement.) Au début du développement, il n'était compatible qu'avec Pytorch, mais maintenant il est également compatible avec Tensorflow. Cette fois, nous allons charger et construire un modèle pour Tensorflow (Keras) en tenant compte de la simplicité d'I / F et de la facilité d'utilisation du TPU. (Lorsque j'utilise TPU avec Pytorch, j'utilise Tensorflow, qui est relativement facile à utiliser car il est gênant d'utiliser XLA et d'écrire du multi-traitement.)

Construire

Cette fois, c'est solide, mais nous allons créer un classificateur multi-classes en utilisant le corpus Livedoor que tout le monde aime.

Préparation

Changez le runtime en TPU à partir de Runtime de Notebook-> Change Runtime Type. (La valeur par défaut est Aucun)

tpu.png

Étant donné que les transfomers ne sont pas inclus dans l'environnement Colab, je vais le déposer avec pip. De plus, étant donné que le Tokenizer utilisé cette fois-ci utilise Mecab, installez-le également.

!pip install transformers
!apt install aptitude
!aptitude install mecab libmecab-dev mecab-ipadic-utf8 git make curl xz-utils file -y
!pip install mecab-python3==0.7
from google.colab import drive
drive.mount('/gdrive')
%cd "/gdrive/My Drive/workspace/python/bakusoku"

Prétraitement des données

Cette fois, j'utiliserai AutoTokenizer. Si vous spécifiez un modèle Pretrain, le Tokenizer adapté à ce modèle sera chargé automatiquement. (Dans ce cas, nous chargeons une instance de BertJapaneseTokenizer.) Vous pouvez tokenize l'instruction avec la méthode tokenize. Ce Tokenizer est divisé en unités Word Piece. (Il existe d'autres méthodes de division telles que le fragment de phrase.)

from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("cl-tohoku/bert-base-japanese-whole-word-masking")

print(tokenizer.tokenize('Faites BERT à une vitesse explosive'))
# ['LOL', '##Vite', 'alors', 'BE', '##R', '##T', 'À', 'créer']

Puisqu'il est nécessaire de convertir la phrase en une séquence d'identifiants de mot pour l'apprentissage, elle est convertie par la méthode suivante.

import numpy as np

def encode_texts(texts, tokenizer, maxlen=512):
    enc_di = tokenizer.batch_encode_plus(
        texts, 
        return_attention_masks=False, 
        return_token_type_ids=False,
        pad_to_max_length=True,
        max_length=maxlen
    )
    return np.array(enc_di['input_ids'])
x_train = encode_texts(train_df['text'].values, tokenizer)
x_valid = encode_texts(valid_df['text'].values, tokenizer)
x_test = encode_texts(test_df['text'].values, tokenizer)
print(x_train)
# [[    2   281   306 ...  2478     9     3]
#  [    2  1519     7 ...    15    16     3]
#  [    2 11634  3217 ...  2478     7     3]
#  ...
#  [    2  6093 16562 ...     0     0     0]
#  [    2   885  2149 ...     0     0     0]
#  [    2  5563  2037 ...     0     0     0]]

L'étiquette de réponse correcte est également encodée à chaud.

from tensorflow.keras.utils import to_categorical
y_train = to_categorical(train_df['label'].values)
y_valid = to_categorical(valid_df['label'].values)
print(y_train)
# [[1. 0. 0. ... 0. 0. 0.]
#  [1. 0. 0. ... 0. 0. 0.]
#  [1. 0. 0. ... 0. 0. 0.]
#  ...
#  [0. 0. 0. ... 0. 0. 1.]
#  [0. 0. 0. ... 0. 0. 1.]
#  [0. 0. 0. ... 0. 0. 1.]]

Se préparer à utiliser TPU

Contrairement au runtime GPU qui peut être utilisé simplement par commutation, le runtime TPU nécessite l'écriture du code suivant. C'est presque un niveau magique, mais il est plus rapide de changer la taille du lot en fonction du nombre de cœurs TPU.

import tensorflow as tf

try:
    tpu = tf.distribute.cluster_resolver.TPUClusterResolver()
    print('Running on TPU ', tpu.master())
except ValueError:
    tpu = None

if tpu:
    tf.config.experimental_connect_to_cluster(tpu)
    tf.tpu.experimental.initialize_tpu_system(tpu)
    strategy = tf.distribute.experimental.TPUStrategy(tpu)
else:
    strategy = tf.distribute.get_strategy()

num_replicas =  strategy.num_replicas_in_sync
print("REPLICAS: ", num_replicas)
# REPLICAS:  8
BATCH_SIZE = 16 * num_replicas # 128

Construction de modèles

Cette fois, nous allons créer un classificateur multi-classes en utilisant Distil BERT comme encodeur. De la sortie du codeur, celle correspondant au premier jeton (un jeton spécial indiquant le début de la phrase [CLS]) est reliée à la HEAD (couche de sortie par Softmax). Je pense qu'il est également possible de connecter après Global Pooling toute la sortie de l'encodeur.

from tensorflow.keras.layers import Dense, Input
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.models import Model

def build_model(transformer, num_cls=1, max_len=512):
    input_word_ids = Input(shape=(max_len,), dtype=tf.int32, name="input_word_ids")
    sequence_output = transformer(input_word_ids)[0]
    cls_token = sequence_output[:, 0, :]
    out = Dense(num_cls, activation='softmax')(cls_token)
    
    model = Model(inputs=input_word_ids, outputs=out)
    model.compile(Adam(lr=2e-4), loss='categorical_crossentropy', metrics=['accuracy']) # lr = 5e-5 * 4
    
    return model

Chargez le modèle Pretrain publié sur Hugging Face HP et créez le modèle ci-dessus. Comme le Tokenizer, il utilise TFAutoModel pour charger le modèle de pré-entraînement sur le TPU. (Dans ce cas, nous chargeons une instance de TFDistilBertModel.)

from transformers import TFAutoModel
with strategy.scope():
    transformer_layer = (TFAutoModel.from_pretrained('bandainamco-mirai/distilbert-base-japanese', from_pt=True))
    model = build_model(transformer_layer, num_cls=9, max_len=512)
model.summary()
# Model: "model_1"
# _________________________________________________________________
# Layer (type)                 Output Shape              Param #   
# =================================================================
# input_word_ids (InputLayer)  [(None, 512)]             0         
# _________________________________________________________________
# tf_distil_bert_model_1 (TFDi ((None, 512, 768), ((None 67497984  
# _________________________________________________________________
# tf_op_layer_strided_slice_1  [(None, 768)]             0         
# _________________________________________________________________
# dense_1 (Dense)              (None, 9)                 6921      
# =================================================================
# Total params: 67,504,905
# Trainable params: 67,504,905
# Non-trainable params: 0
# _________________________________________________________________

fine-tuning Entraînons le modèle que nous avons créé plus tôt. Dans l'exemple ci-dessous, 5 500 cas d'apprentissage seront terminés en environ 70 secondes avec 4 époques.

AUTO = tf.data.experimental.AUTOTUNE

train_dataset = (
    tf.data.Dataset
    .from_tensor_slices((x_train, y_train))
    .repeat()
    .shuffle(2048)
    .batch(BATCH_SIZE)
    .prefetch(AUTO)
)

valid_dataset = (
    tf.data.Dataset
    .from_tensor_slices((x_valid, y_valid))
    .batch(BATCH_SIZE)
    .cache()
    .prefetch(AUTO)
)

test_dataset = (
    tf.data.Dataset
    .from_tensor_slices(x_test)
    .batch(BATCH_SIZE)
)

n_steps = x_train.shape[0] // BATCH_SIZE
train_history = model.fit(
    train_dataset,
    steps_per_epoch=n_steps,
    validation_data=valid_dataset,
    epochs=4
)
# Epoch 1/4
# 43/43 [==============================] - 31s 715ms/step - accuracy: 0.2473 - loss: 2.0548 - val_accuracy: 0.3355 - val_loss: 1.9584
# Epoch 2/4
# 43/43 [==============================] - 13s 308ms/step - accuracy: 0.6726 - loss: 1.4064 - val_accuracy: 0.6612 - val_loss: 1.1878
# Epoch 3/4
# 43/43 [==============================] - 13s 309ms/step - accuracy: 0.8803 - loss: 0.7522 - val_accuracy: 0.7877 - val_loss: 0.8257
# Epoch 4/4
# 43/43 [==============================] - 13s 309ms/step - accuracy: 0.9304 - loss: 0.4401 - val_accuracy: 0.8181 - val_loss: 0.6747

Évaluation

La précision est de 81% ... Le résultat est bimyo. Je pense que la précision s'améliorera si vous concevez un peu plus de nettoyage des données et la structure du modèle après Encoder.

from sklearn.metrics import classification_report

test_df['predict'] = model.predict(test_dataset, verbose=1).argmax(axis=1)
print(classification_report(test_df['label'], test_df['predict'], target_names=target_names))
# 12/12 [==============================] - 11s 890ms/step
#                 precision    recall  f1-score   support
# 
# dokujo-tsushin       0.73      0.95      0.83       174
#   it-life-hack       0.66      0.91      0.76       174
#  kaden-channel       0.79      0.47      0.59       173
# livedoor-homme       0.91      0.31      0.47       102
#    movie-enter       0.81      0.96      0.88       174
#         peachy       0.81      0.71      0.76       169
#           smax       0.91      0.97      0.94       174
#   sports-watch       0.88      1.00      0.94       180
#     topic-news       0.91      0.75      0.83       154
# 
#       accuracy                           0.81      1474
#      macro avg       0.83      0.78      0.78      1474
#   weighted avg       0.82      0.81      0.79      1474

Recommended Posts

[TPU] [Transformers] Faites du BERT à une vitesse explosive
Créez un robot de notification de pluie pour Hangouts Chat à une vitesse explosive
Modèle Python qui effectue une analyse des journaux à une vitesse explosive
Créez des projets d'apprentissage automatique à une vitesse explosive à l'aide de modèles
Implémentez l'API à une vitesse explosive en utilisant Django REST Framework
Calculer le noyau gaussien à une vitesse explosive même avec python
Essayez de résoudre le Sudoku à une vitesse explosive en utilisant Numpy
Créez un serveur Web API à une vitesse explosive en utilisant HUG
Essayez l'analyse de corrélation multivariée à l'aide du lasso graphique à une vitesse explosive