Dans cet article, nous allons suivre le processus de mise au point d'un modèle BERT pré-formé à travers la tâche de catégoriser les en-têtes d'articles de presse en anglais. Dans le cas du japonais, contrairement à l'anglais, une analyse morphologique est nécessaire, mais le flux global est le même que le contenu de cet article.
De plus, cette implémentation est également la réponse à la question 89 de 100 language processing knock 2020 version. Pour obtenir des exemples de réponses à d'autres questions, consultez [Knock 100 Language Processing 2020] Résumé des exemples de réponses en Python.
Google Colaboratory est utilisé pour la mise en œuvre. Pour plus d'informations sur la configuration et l'utilisation de Google Colaboratory, consultez [cet article](https: // cpp-fu learning.com/python_colaboratory/). ** Si vous souhaitez utiliser le GPU pour la reproduction, veuillez changer l'accélérateur matériel sur "GPU" à partir de "Runtime" -> "Changer le type d'exécution" et enregistrez-le à l'avance. ** ** Le cahier contenant les résultats d'exécution est disponible sur github.
Les en-têtes des articles de presse utilisant les données publiques News Aggregator Data Set sont "Business", "Science and Technology" et "Entertainment". Nous mettrons en œuvre un modèle de classification des documents par BERT pour les tâches qui entrent dans la catégorie «santé».
Tout d'abord, téléchargez les données cibles.
!wget https://archive.ics.uci.edu/ml/machine-learning-databases/00359/NewsAggregatorDataset.zip
!unzip NewsAggregatorDataset.zip
#Vérifiez le nombre de lignes
!wc -l ./newsCorpora.csv
production
422937 ./newsCorpora.csv
#Vérifiez les 10 premières lignes
!head -10 ./newsCorpora.csv
production
1 Fed official says weak data caused by weather, should not slow taper http://www.latimes.com/business/money/la-fi-mo-federal-reserve-plosser-stimulus-economy-20140310,0,1312750.story\?track=rss Los Angeles Times b ddUyU0VZz0BRneMioxUPQVP6sIxvM www.latimes.com 1394470370698
2 Fed's Charles Plosser sees high bar for change in pace of tapering http://www.livemint.com/Politics/H2EvwJSK2VE6OF7iK1g3PP/Feds-Charles-Plosser-sees-high-bar-for-change-in-pace-of-ta.html Livemint b ddUyU0VZz0BRneMioxUPQVP6sIxvM www.livemint.com 1394470371207
3 US open: Stocks fall after Fed official hints at accelerated tapering http://www.ifamagazine.com/news/us-open-stocks-fall-after-fed-official-hints-at-accelerated-tapering-294436 IFA Magazine b ddUyU0VZz0BRneMioxUPQVP6sIxvM www.ifamagazine.com 1394470371550
4 Fed risks falling 'behind the curve', Charles Plosser says http://www.ifamagazine.com/news/fed-risks-falling-behind-the-curve-charles-plosser-says-294430 IFA Magazine b ddUyU0VZz0BRneMioxUPQVP6sIxvM www.ifamagazine.com 1394470371793
5 Fed's Plosser: Nasty Weather Has Curbed Job Growth http://www.moneynews.com/Economy/federal-reserve-charles-plosser-weather-job-growth/2014/03/10/id/557011 Moneynews b ddUyU0VZz0BRneMioxUPQVP6sIxvM www.moneynews.com 1394470372027
6 Plosser: Fed May Have to Accelerate Tapering Pace http://www.nasdaq.com/article/plosser-fed-may-have-to-accelerate-tapering-pace-20140310-00371 NASDAQ b ddUyU0VZz0BRneMioxUPQVP6sIxvM www.nasdaq.com 1394470372212
7 Fed's Plosser: Taper pace may be too slow http://www.marketwatch.com/story/feds-plosser-taper-pace-may-be-too-slow-2014-03-10\?reflink=MW_news_stmp MarketWatch b ddUyU0VZz0BRneMioxUPQVP6sIxvM www.marketwatch.com 1394470372405
8 Fed's Plosser expects US unemployment to fall to 6.2% by the end of 2014 http://www.fxstreet.com/news/forex-news/article.aspx\?storyid=23285020-b1b5-47ed-a8c4-96124bb91a39 FXstreet.com b ddUyU0VZz0BRneMioxUPQVP6sIxvM www.fxstreet.com 1394470372615
9 US jobs growth last month hit by weather:Fed President Charles Plosser http://economictimes.indiatimes.com/news/international/business/us-jobs-growth-last-month-hit-by-weatherfed-president-charles-plosser/articleshow/31788000.cms Economic Times b ddUyU0VZz0BRneMioxUPQVP6sIxvM economictimes.indiatimes.com 1394470372792
10 ECB unlikely to end sterilisation of SMP purchases - traders http://www.iii.co.uk/news-opinion/reuters/news/152615 Interactive Investor b dPhGU51DcrolUIMxbRm0InaHGA2XM www.iii.co.uk 1394470501265
#Remplacement des guillemets doubles par des guillemets simples pour éviter les erreurs lors de la lecture
!sed -e 's/"/'\''/g' ./newsCorpora.csv > ./newsCorpora_re.csv
Ensuite, lisez-le comme un bloc de données, extrayez uniquement les cas où la source d'informations (PUBLISHER) est Reuters, Huffington Post, Businessweek, Contactmusic.com, Daily Mail, puis divisez-le en données de formation, données de vérification et données d'évaluation.
import pandas as pd
from sklearn.model_selection import train_test_split
#Lire les données
df = pd.read_csv('./newsCorpora_re.csv', header=None, sep='\t', names=['ID', 'TITLE', 'URL', 'PUBLISHER', 'CATEGORY', 'STORY', 'HOSTNAME', 'TIMESTAMP'])
#Extraction de données
df = df.loc[df['PUBLISHER'].isin(['Reuters', 'Huffington Post', 'Businessweek', 'Contactmusic.com', 'Daily Mail']), ['TITLE', 'CATEGORY']]
#Répartition des données
train, valid_test = train_test_split(df, test_size=0.2, shuffle=True, random_state=123, stratify=df['CATEGORY'])
valid, test = train_test_split(valid_test, test_size=0.5, shuffle=True, random_state=123, stratify=valid_test['CATEGORY'])
train.reset_index(drop=True, inplace=True)
valid.reset_index(drop=True, inplace=True)
test.reset_index(drop=True, inplace=True)
print(train.head())
production
TITLE CATEGORY
0 REFILE-UPDATE 1-European car sales up for sixt... b
1 Amazon Plans to Fight FTC Over Mobile-App Purc... t
2 Kids Still Get Codeine In Emergency Rooms Desp... m
3 What On Earth Happened Between Solange And Jay... e
4 NATO Missile Defense Is Flight Tested Over Hawaii b
#Confirmation du nombre de cas
print('[Données d'apprentissage]')
print(train['CATEGORY'].value_counts())
print('[Données de vérification]')
print(valid['CATEGORY'].value_counts())
print('[Données d'évaluation]')
print(test['CATEGORY'].value_counts())
production
[Données d'apprentissage]
b 4501
e 4235
t 1220
m 728
Name: CATEGORY, dtype: int64
[Données de vérification]
b 563
e 529
t 153
m 91
Name: CATEGORY, dtype: int64
[Données d'évaluation]
b 563
e 530
t 152
m 91
Name: CATEGORY, dtype: int64
(b: affaires, e: divertissement, t: science et technologie, m: santé)
Installez la bibliothèque
transformers '' pour utiliser le modèle BERT. Grâce aux `` transformateurs '', de nombreux modèles pré-entraînés en plus de BERT peuvent être utilisés très facilement avec un code court.
!pip install transformers
Importez les bibliothèques nécessaires pour entraîner et évaluer votre modèle.
import numpy as np
import transformers
import torch
from torch.utils.data import Dataset, DataLoader
from transformers import BertTokenizer, BertModel
from torch import optim
from torch import cuda
import time
from matplotlib import pyplot as plt
Ensuite, façonnez les données sous une forme qui peut être entrée dans le modèle.
Tout d'abord, définissez une classe pour créer un ensemble de données '' qui contient le vecteur de fonction et le vecteur d'étiquette ensemble, ce qui est souvent utilisé dans PyTorch. En passant
tokenizer '' à cette classe, il est possible de prétraiter le texte d'entrée, de le remplir à la longueur de série la plus longue spécifiée, puis de le convertir en un identifiant de mot.
Cependant, le tokenizer '' lui-même, où tout le traitement est écrit pour BERT, sera obtenu plus tard via
tranformers '', donc ce dont vous avez besoin dans la classe est tokenizer
. Seul le processus de passage à ʻet le processus de réception du résultat.
#Définition de l'ensemble de données
class CreateDataset(Dataset):
def __init__(self, X, y, tokenizer, max_len):
self.X = X
self.y = y
self.tokenizer = tokenizer
self.max_len = max_len
def __len__(self): # len(Dataset)Spécifiez la valeur à renvoyer avec
return len(self.y)
def __getitem__(self, index): # Dataset[index]Spécifiez la valeur à renvoyer avec
text = self.X[index]
inputs = self.tokenizer.encode_plus(
text,
add_special_tokens=True,
max_length=self.max_len,
pad_to_max_length=True
)
ids = inputs['input_ids']
mask = inputs['attention_mask']
return {
'ids': torch.LongTensor(ids),
'mask': torch.LongTensor(mask),
'labels': torch.Tensor(self.y[index])
}
Créez un `` ensemble de données '' en utilisant ce qui précède. De plus, BERT qui peut être utilisé comme modèle pré-entraîné en version anglaise est LARGE, qui est une configuration visant la plus grande précision, BASE avec moins de paramètres, et 4 de chacun d'entre eux, uniquement en minuscules (Uncased) et en cas mixte (Cased) Il y a un modèle. Cette fois, nous utiliserons BASE's Uncased, que vous pouvez facilement essayer.
#Corriger l'étiquette un-Chaud
y_train = pd.get_dummies(train, columns=['CATEGORY'])[['CATEGORY_b', 'CATEGORY_e', 'CATEGORY_t', 'CATEGORY_m']].values
y_valid = pd.get_dummies(valid, columns=['CATEGORY'])[['CATEGORY_b', 'CATEGORY_e', 'CATEGORY_t', 'CATEGORY_m']].values
y_test = pd.get_dummies(test, columns=['CATEGORY'])[['CATEGORY_b', 'CATEGORY_e', 'CATEGORY_t', 'CATEGORY_m']].values
#Créer un jeu de données
max_len = 20
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
dataset_train = CreateDataset(train['TITLE'], y_train, tokenizer, max_len)
dataset_valid = CreateDataset(valid['TITLE'], y_valid, tokenizer, max_len)
dataset_test = CreateDataset(test['TITLE'], y_test, tokenizer, max_len)
for var in dataset_train[0]:
print(f'{var}: {dataset_train[0][var]}')
production
ids: tensor([ 101, 25416, 9463, 1011, 10651, 1015, 1011, 2647, 2482, 4341,
2039, 2005, 4369, 3204, 2004, 18730, 8980, 102, 0, 0])
mask: tensor([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0])
labels: tensor([1., 0., 0., 0.])
Les informations de la première phrase sont sorties.
Vous pouvez voir que la chaîne d'entrée a été convertie en une série d'ID en tant que ids
. Dans BERT, des délimiteurs spéciaux [CLS] et [SEP] sont insérés au début et à la fin de la phrase originale pendant le processus de conversion, ils sont donc également 101 '' et
102 ''. Inclus dans la série comme `.
0 représente le remplissage. Les étiquettes de réponse correctes sont également conservées dans un format unique en tant que `` étiquettes ''. Aussi, gardez le `` masque
qui représente la position de remplissage afin qu'il puisse être passé au modèle avec les `` aides``` pendant l'entraînement.
Ensuite, définissez le réseau.
transfomers
En utilisant, toute la partie bertbertmodel
Il peut être exprimé avec. Ensuite, pour faciliter la tâche de classification, définissez un abandon qui reçoit le vecteur de sortie de bert et une couche entièrement connectée, et vous avez terminé.
#Définition du modèle de classification BERT
class BERTClass(torch.nn.Module):
def __init__(self, drop_rate, otuput_size):
super().__init__()
self.bert = BertModel.from_pretrained('bert-base-uncased')
self.drop = torch.nn.Dropout(drop_rate)
self.fc = torch.nn.Linear(768, otuput_size) #Spécifiez 768 dimensions en fonction de la sortie de BERT
def forward(self, ids, mask):
_, out = self.bert(ids, attention_mask=mask)
out = self.fc(self.drop(out))
return out
Maintenant que vous avez un réseau avec Dataset '', il est temps de créer votre boucle d'apprentissage habituelle. Ici, une série de flux est définie comme une fonction
train_model ''. Pour connaître la signification des composants qui apparaissent, consultez le flux du problème dans l'article [Language Processing 100 Knock 2020] Chapitre 8: Neural Net. Veuillez vous référer à l'explication avec elle.
def calculate_loss_and_accuracy(model, criterion, loader, device):
"""Calculer le taux de perte / réponse correcte"""
model.eval()
loss = 0.0
total = 0
correct = 0
with torch.no_grad():
for data in loader:
#Spécification de l'appareil
ids = data['ids'].to(device)
mask = data['mask'].to(device)
labels = data['labels'].to(device)
#Propagation vers l'avant
outputs = model.forward(ids, mask)
#Calcul des pertes
loss += criterion(outputs, labels).item()
#Calcul du taux de réponse correct
pred = torch.argmax(outputs, dim=-1).cpu().numpy() #Tableau d'étiquettes prévu pour la longueur de la taille du lot
labels = torch.argmax(labels, dim=-1).cpu().numpy() #Tableau d'étiquettes correct de la taille du lot
total += len(labels)
correct += (pred == labels).sum().item()
return loss / len(loader), correct / total
def train_model(dataset_train, dataset_valid, batch_size, model, criterion, optimizer, num_epochs, device=None):
"""Exécute la formation du modèle et renvoie un journal du taux de perte / réponse correcte"""
#Spécification de l'appareil
model.to(device)
#Créer un chargeur de données
dataloader_train = DataLoader(dataset_train, batch_size=batch_size, shuffle=True)
dataloader_valid = DataLoader(dataset_valid, batch_size=len(dataset_valid), shuffle=False)
#Apprentissage
log_train = []
log_valid = []
for epoch in range(num_epochs):
#Heure de début d'enregistrement
s_time = time.time()
#Mettre en mode entraînement
model.train()
for data in dataloader_train:
#Spécification de l'appareil
ids = data['ids'].to(device)
mask = data['mask'].to(device)
labels = data['labels'].to(device)
#Initialiser le dégradé à zéro
optimizer.zero_grad()
#Propagation vers l'avant+Erreur de propagation de retour+Mise à jour du poids
outputs = model.forward(ids, mask)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
#Calcul de la perte et taux de réponse correcte
loss_train, acc_train = calculate_loss_and_accuracy(model, criterion, dataloader_train, device)
loss_valid, acc_valid = calculate_loss_and_accuracy(model, criterion, dataloader_valid, device)
log_train.append([loss_train, acc_train])
log_valid.append([loss_valid, acc_valid])
#Enregistrer le point de contrôle
torch.save({'epoch': epoch, 'model_state_dict': model.state_dict(), 'optimizer_state_dict': optimizer.state_dict()}, f'checkpoint{epoch + 1}.pt')
#Enregistrer l'heure de fin
e_time = time.time()
#Journal de sortie
print(f'epoch: {epoch + 1}, loss_train: {loss_train:.4f}, accuracy_train: {acc_train:.4f}, loss_valid: {loss_valid:.4f}, accuracy_valid: {acc_valid:.4f}, {(e_time - s_time):.4f}sec')
return {'train': log_train, 'valid': log_valid}
Définissez les paramètres et effectuez un réglage fin.
#Paramétrage
DROP_RATE = 0.4
OUTPUT_SIZE = 4
BATCH_SIZE = 32
NUM_EPOCHS = 4
LEARNING_RATE = 2e-5
#Définition du modèle
model = BERTClass(DROP_RATE, OUTPUT_SIZE)
#Définition de la fonction de perte
criterion = torch.nn.BCEWithLogitsLoss()
#Définition de l'optimiseur
optimizer = torch.optim.AdamW(params=model.parameters(), lr=LEARNING_RATE)
#Spécification de l'appareil
device = 'cuda' if cuda.is_available() else 'cpu'
#Apprentissage de modèle
log = train_model(dataset_train, dataset_valid, BATCH_SIZE, model, criterion, optimizer, NUM_EPOCHS, device=device)
production
epoch: 1, loss_train: 0.0859, accuracy_train: 0.9516, loss_valid: 0.1142, accuracy_valid: 0.9229, 49.9137sec
epoch: 2, loss_train: 0.0448, accuracy_train: 0.9766, loss_valid: 0.1046, accuracy_valid: 0.9259, 49.7376sec
epoch: 3, loss_train: 0.0316, accuracy_train: 0.9831, loss_valid: 0.1082, accuracy_valid: 0.9266, 49.5454sec
epoch: 4, loss_train: 0.0170, accuracy_train: 0.9932, loss_valid: 0.1179, accuracy_valid: 0.9289, 49.4525sec
Vérifiez le résultat.
#Visualisation du journal
x_axis = [x for x in range(1, len(log['train']) + 1)]
fig, ax = plt.subplots(1, 2, figsize=(15, 5))
ax[0].plot(x_axis, np.array(log['train']).T[0], label='train')
ax[0].plot(x_axis, np.array(log['valid']).T[0], label='valid')
ax[0].set_xlabel('epoch')
ax[0].set_ylabel('loss')
ax[0].legend()
ax[1].plot(x_axis, np.array(log['train']).T[1], label='train')
ax[1].plot(x_axis, np.array(log['valid']).T[1], label='valid')
ax[1].set_xlabel('epoch')
ax[1].set_ylabel('accuracy')
ax[1].legend()
plt.show()
#Calcul du taux de réponse correcte
def calculate_accuracy(model, dataset, device):
#Créer un chargeur de données
loader = DataLoader(dataset, batch_size=len(dataset), shuffle=False)
model.eval()
total = 0
correct = 0
with torch.no_grad():
for data in loader:
#Spécification de l'appareil
ids = data['ids'].to(device)
mask = data['mask'].to(device)
labels = data['labels'].to(device)
#Propagation vers l'avant+Obtenez la valeur prévue+Compter le nombre de bonnes réponses
outputs = model.forward(ids, mask)
pred = torch.argmax(outputs, dim=-1).cpu().numpy()
labels = torch.argmax(labels, dim=-1).cpu().numpy()
total += len(labels)
correct += (pred == labels).sum().item()
return correct / total
print(f'Taux de réponse correct (données d'apprentissage):{calculate_accuracy(model, dataset_train, device):.3f}')
print(f'Taux de réponse correct (données de vérification):{calculate_accuracy(model, dataset_valid, device):.3f}')
print(f'Taux de réponse correct (données d'évaluation):{calculate_accuracy(model, dataset_test, device):.3f}')
production
Taux de réponse correct (données d'apprentissage): 0.993
Taux de réponse correct (données de vérification): 0.929
Taux de réponse correct (données d'évaluation): 0.948
Le taux de réponse correcte était d'environ 95% dans les données d'évaluation.
Normalement, je pense que dans de nombreux cas, des paramètres tels que la fixation ou non des poids pour chaque couche de BERT et le taux d'apprentissage sont ajustés tout en vérifiant l'exactitude des données de vérification. Cette fois, les paramètres ont été fixés, mais la précision était relativement élevée et le résultat a montré la force du pré-apprentissage.
transformers BERT (officiel) BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding, Devlin, J. et al. (2018) (Article original) [Language processing 100 knock 2020] Résumé des exemples de réponses par Python