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.)
Cette fois, c'est solide, mais nous allons créer un classificateur multi-classes en utilisant le corpus Livedoor que tout le monde aime.
Changez le runtime en TPU à partir de Runtime de Notebook-> Change Runtime Type. (La valeur par défaut est Aucun)
É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"
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.]]
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
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
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