[PyTorch] Introduction à la classification de documents à l'aide de BERT

introduction

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.

Préparation préalable

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.

Classification des documents par BERT

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é».

Lire les données

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é)

Se préparer à l'apprentissage

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. transfomersEn utilisant, toute la partie bertbertmodelIl 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

Apprendre le modèle de classification BERT

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()

89.png

#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.

référence

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

Recommended Posts

[PyTorch] Introduction à la classification de documents à l'aide de BERT
[PyTorch] Introduction à la classification des documents japonais à l'aide de BERT
Introduction à Lightning Pytorch
Introduction à PyTorch (1) Différenciation automatique
Introduction à discord.py (3) Utilisation de la voix
[Détails (?)] Introduction au pytorch ~ CNN de CIFAR10 ~
Introduction à la simulation d'événements discrets à l'aide de Python # 1
Classification des documents avec texte toch de PyTorch
[Introduction à Pytorch] J'ai joué avec sinGAN ♬
Créez rapidement des données de classification de documents à l'aide de NLTK
Introduction à la simulation d'événements discrets à l'aide de Python # 2
Introduction à Tornado (3): Développement à l'aide de modèles [Pratique]
J'ai essayé de comparer la précision de la classification des phrases BERT japonaises et japonaises Distil BERT avec PyTorch et introduction de la technique d'amélioration de la précision BERT
Introduction à MQTT (Introduction)
Introduction à Scrapy (1)
[Super introduction à l'apprentissage automatique] Découvrez les didacticiels Pytorch
Introduction à Scrapy (3)
Introduction à Tkinter 1: Introduction
pytorch super introduction
Introduction à PyQt
Introduction à Scrapy (2)
[PyTorch] Comment utiliser BERT - Réglage fin des modèles pré-entraînés japonais pour résoudre les problèmes de classification
[Linux] Introduction à Linux
[Super introduction à l'apprentissage automatique] Découvrez les didacticiels Pytorch
Introduction à Scrapy (4)
Introduction à discord.py (2)
Introduction à Tornado (2): Introduction au développement à l'aide de modèles - Génération de pages dynamiques -
J'ai essayé d'implémenter la classification des phrases et la visualisation de l'attention par le japonais BERT avec PyTorch
Introduction à Scapy ② (transmission ICMP, HTTP (TCP) avec Scapy)
[Introduction à Pytorch] J'ai essayé de catégoriser Cifar10 avec VGG16 ♬
[Introduction à Python] Comment arrêter la boucle en utilisant break?
[Introduction à cx_Oracle] (13e) Connexion utilisant le pool de connexions (côté client)
[Introduction à Python] Comment écrire des instructions répétitives à l'aide d'instructions for
[Livre technique] Introduction à l'analyse de données avec Python -1 Chapitre Introduction-
Premiers pas avec le Web Scraping
Introduction aux baies non paramétriques
Introduction à EV3 / MicroPython
Introduction au langage Python
Introduction à la reconnaissance d'image TensorFlow
Introduction à OpenCV (python) - (2)
[Pytorch] numpy à tenseur
Introduction à PyQt4 Partie 1
Introduction à l'injection de dépendances
Introduction à Private Chainer
Introduction à PyTorch (environnement virtuel)
PyTorch Super Introduction Principes de base de PyTorch
Introduction à l'apprentissage automatique
[Introduction à Python] Comment écrire des branches conditionnelles en utilisant des instructions if
Jour 67 [Introduction à Kaggle] Avez-vous essayé d'utiliser Random Forest?
Introduction à la modélisation bayésienne à l'aide de la traduction japonaise pymc3 de modélisation bayésienne en Python (chapitre 0-2)
[Introduction à Pytorch] Je souhaite générer des phrases dans des articles de presse
[Python] Introduction à la création de graphiques à l'aide de données de virus corona [Pour les débutants]
Essayez d'implémenter la régression linéaire à l'aide de Pytorch avec Google Colaboratory