[PYTHON] [Traitement du langage 100 coups 2020] Chapitre 9: RNN, CNN

introduction

Version 2020 de 100 coups de traitement du langage, qui est célèbre comme une collection de problèmes de traitement du langage naturel, a été publié. Cet article résume les résultats de la résolution du «Chapitre 9: RNN, CNN» des chapitres 1 à 10 suivants.

Préparation préalable

Google Colaboratory est utilisé pour la réponse. Pour plus d'informations sur la configuration et l'utilisation de Google Colaboratory, consultez cet article. ** Puisque le GPU est utilisé dans ce chapitre, changez 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 des réponses suivantes est disponible sur github.

Chapitre 9: RNN, CNN

80. Conversion en numéro d'identification

Je souhaite attribuer un numéro d'identification unique aux mots des données d'entraînement construites à la question 51. Le mot qui apparaît le plus fréquemment dans les données d'entraînement est 1 '', le mot qui apparaît en deuxième est 2 '', etc., et le mot qui apparaît plus d'une fois dans les données d'entraînement est ID. Donnez-lui un numéro. Ensuite, implémentez une fonction qui renvoie une chaîne de numéros d'identification pour une chaîne de mots donnée. Cependant, tous les numéros d'identification des mots qui apparaissent moins de deux fois doivent être `` 0 ''.

Tout d'abord, après avoir téléchargé les données spécifiées, lisez-les comme une trame de données. Ensuite, il est divisé en données d'entraînement, données de vérification et données d'évaluation et sauvegardé. Jusqu'à présent, le processus est exactement le même que le problème 50 du Chapitre 6, il n'y a donc aucun problème pour lire les données qui y sont créées.

#Télécharger les données
!wget https://archive.ics.uci.edu/ml/machine-learning-databases/00359/NewsAggregatorDataset.zip
!unzip NewsAggregatorDataset.zip
#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
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'])

#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

Ensuite, créez un dictionnaire de mots. Les mots dans les données d'apprentissage sont comptés et le classement de fréquence (ID) est enregistré en utilisant celui qui apparaît plus d'une fois comme clé.

from collections import defaultdict
import string

#Agrégation de fréquence de mot
d = defaultdict(int)
table = str.maketrans(string.punctuation, ' '*len(string.punctuation))  #Un tableau qui remplace les symboles par des espaces
for text in train['TITLE']:
  for word in text.translate(table).split():
    d[word] += 1
d = sorted(d.items(), key=lambda x:x[1], reverse=True)

#Créer un dictionnaire d'ID de mot
word2id = {word: i + 1 for i, (word, cnt) in enumerate(d) if cnt > 1}  #Enregistrer les mots qui apparaissent plus d'une fois

print(f'Nombre d'identifiants: {len(set(word2id.values()))}\n')
print('---Top 20 des mots en fréquence---')
for key in list(word2id)[:20]:
    print(f'{key}: {word2id[key]}')

production


Nombre d'identifiants: 9405

---Top 20 des mots en fréquence---
to: 1
s: 2
in: 3
on: 4
UPDATE: 5
as: 6
US: 7
for: 8
The: 9
of: 10
1: 11
To: 12
2: 13
the: 14
and: 15
In: 16
Of: 17
a: 18
at: 19
A: 20

Enfin, définissez une fonction qui utilise un dictionnaire pour convertir une chaîne de mots donnée en une chaîne de numéros d'identification. À ce stade, suivez les instructions de la phrase de question et renvoyez `` 0 '' pour les mots qui ne sont pas dans le dictionnaire.

def tokenizer(text, word2id=word2id, unk=0):
  """Divisez le texte d'entrée avec des espaces et convertissez-le en colonne ID(S'il ne figure pas dans le dictionnaire, définissez le nombre spécifié par unk)"""
  table = str.maketrans(string.punctuation, ' '*len(string.punctuation))
  return [word2id.get(word, unk) for word in text.translate(table).split()]

Vérifiez la deuxième phrase.

#Vérification
text = train.iloc[1, train.columns.get_loc('TITLE')]
print(f'texte: {text}')
print(f'Colonne ID: {tokenizer(text)}')

production


texte: Amazon Plans to Fight FTC Over Mobile-App Purchases
Colonne ID: [169, 539, 1, 683, 1237, 82, 279, 1898, 4199]

81. Prédiction par RNN

Il y a une chaîne de mot $ \ boldsymbol {x} = (x_1, x_2, \ dots, x_T) $ représentée par un numéro d'identification. Cependant, $ T $ est la longueur de la chaîne de mots, et $ x_t \ in \ mathbb {R} ^ {V} $ est la notation unique du numéro d'identification du mot ($ V $ est le nombre total de mots). En utilisant un réseau neuronal récurrent (RNN), implémentez l'équation suivante comme modèle pour prédire la catégorie $ y $ à partir de la chaîne de mots $ \ boldsymbol {x} $.

\overrightarrow h_0 = 0,\ \overrightarrow h_t = {\rm \overrightarrow{RNN}}(\mathrm{emb}(x_t), \overrightarrow h_{t-1}), \ y = {\rm softmax}(W^{(yh)} \overrightarrow h_T + b^{(y)})


 > Cependant, $ \ mathrm {emb} (x) \ in \ mathbb {R} ^ {d_w} $ est l'incorporation de mots (une fonction qui convertit un mot de la notation one-hot en un vecteur de mot), $ \ overridearrow h_t \ in \ mathbb {R} ^ {d_h} $ est le vecteur d'état caché au temps $ t $, $ {\ rm \ overridearrow {RNN}} (x, h) $ provient de l'entrée $ x $ et de l'état caché $ h $ au temps précédent L'unité RNN qui calcule l'état suivant, $ W ^ {(yh)} \ in \ mathbb {R} ^ {L \ times d_h} $ est la matrice pour prédire la catégorie à partir du vecteur d'état caché, $ b ^ {(y) )} \ in \ mathbb {R} ^ {L} $ est un terme de biais ($ d_w, d_h, L $ sont respectivement le nombre de dimensions d'incorporation de mots, le nombre de dimensions de vecteur d'état masqué et le nombre d'étiquettes). L'unité RNN $ {\ rm \ overrightarrow {RNN}} (x, h) $ peut avoir différentes configurations, et l'équation suivante est un exemple typique.

>```math
{\rm \overrightarrow{RNN}}(x,h) = g(W^{(hx)} x + W^{(hh)}h + b^{(h)})

Cependant, $ W ^ {(hx)} \ in \ mathbb {R} ^ {d_h \ times d_w}, W ^ {(hh)} \ in \ mathbb {R} ^ {d_h \ times d_h}, b ^ {(h)} \ in \ mathbb {R} ^ {d_h} $ est le paramètre de l'unité RNN, et $ g $ est la fonction d'activation (par exemple, $ \ tanh $ et ReLU).

Notez que ce problème n'entraîne pas les paramètres, il nécessite seulement de calculer $ y $ avec les paramètres initialisés aléatoirement. Les paramètres Hyper tels que le nombre de dimensions doivent être définis sur des valeurs appropriées telles que $ d_w = 300, d_h = 50 $ (il en va de même pour les problèmes suivants).

Avant d'entrer dans la réponse, organisons le flux de traitement du langage naturel à l'aide d'un réseau neuronal, en particulier dans la classification de texte. La classification de texte à l'aide d'un réseau neuronal comprend principalement les quatre étapes suivantes.

  1. Divisez une phrase en colonnes de jetons (par exemple des mots)
  2. Convertissez chaque jeton en vecteur
  3. Agréger les vecteurs de jetons en un seul comme vecteur de déclaration
  4. Classer les étiquettes avec le vecteur d'instruction comme entrée

Différentes méthodes peuvent être envisagées pour chaque processus, mais au Chapitre 8, par exemple,

  1. Divisez une phrase en colonnes de jetons (par exemple des mots) ⇒ ** Diviser par espace **
  2. Convertissez chaque jeton en vecteur ⇒ ** Convertir avec Word2Vec pré-appris **
  3. Agréger les vecteurs de jetons en un seul comme vecteur de déclaration ⇒ ** Vecteur de jeton moyen **
  4. Classer les étiquettes avec le vecteur d'instruction comme entrée ⇒ ** Classé par couche entièrement connectée **

J'ai implémenté le flux du n ° 4 et appris les paramètres du n ° 4 (en ciblant les documents japonais, au n ° 1 chapitre 4 E06014b146a18e97ca59) une analyse morphologique est requise).

D'autre part, dans ce chapitre,

  1. Divisez une phrase en colonnes de jetons (par exemple des mots) ⇒ Diviser par l'espace
  2. Convertissez chaque jeton en vecteur ⇒ ** Convertir avec une couche intégrée **
  3. Agréger les vecteurs de jetons en un seul comme vecteur de déclaration ⇒ ** Agrégat par RNN ou CNN **
  4. Classer les étiquettes avec le vecteur d'instruction comme entrée ⇒ Classé par couche entièrement connectée

Ensuite, nous apprendrons les paramètres du réseau qui relie les n ° 2 à 4. Comme dans le problème de ce chapitre, il arrive souvent que les jetons divisés soient convertis en ID correspondants pour plus de commodité, mais cela est inclus dans le processus n ° 1.

Maintenant, implémentons immédiatement le réseau de cette question. Utilisez nn.Embedding '' pour la couche incorporée. Compte tenu du mot ID, cette couche se convertit en un vecteur one-hot puis en un vecteur de la taille spécifiée (```emb_size```). La partie RNN suivante peut être réalisée par le processus de passage récursif à travers la couche entièrement connectée, mais elle peut être écrite simplement en utilisant nn.RNN ''. Enfin, connectez les couches entièrement connectées et vous avez terminé.

import torch
from torch import nn

class RNN(nn.Module):
  def __init__(self, vocab_size, emb_size, padding_idx, output_size, hidden_size):
    super().__init__()
    self.hidden_size = hidden_size
    self.emb = nn.Embedding(vocab_size, emb_size, padding_idx=padding_idx)
    self.rnn = nn.RNN(emb_size, hidden_size, nonlinearity='tanh', batch_first=True)
    self.fc = nn.Linear(hidden_size, output_size)
    
  def forward(self, x):
    self.batch_size = x.size()[0]
    hidden = self.init_hidden()  #Créer un vecteur nul pour h0
    emb = self.emb(x)
    # emb.size() = (batch_size, seq_len, emb_size)
    out, hidden = self.rnn(emb, hidden)
    # out.size() = (batch_size, seq_len, hidden_size)
    out = self.fc(out[:, -1, :])
    # out.size() = (batch_size, output_size)
    return out
    
  def init_hidden(self):
    hidden = torch.zeros(1, self.batch_size, self.hidden_size)
    return hidden

Ensuite, définissez la classe qui crée le Dataset '' comme dans le chapitre précédent. Cette fois, nous recevrons le texte et l'étiquette, convertirons le texte en ID avec le tokenizer '' spécifié, puis donnerons à chacun une fonction à afficher en type Tensor.

from torch.utils.data import Dataset

class CreateDataset(Dataset):
  def __init__(self, X, y, tokenizer):
    self.X = X
    self.y = y
    self.tokenizer = tokenizer

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

    return {
      'inputs': torch.tensor(inputs, dtype=torch.int64),
      'labels': torch.tensor(self.y[index], dtype=torch.int64)
    }

Créez un ensemble de données '' en utilisant ce qui précède. Pour tokenizer '', spécifiez la fonction définie dans la question précédente.

#Créer un vecteur d'étiquette
category_dict = {'b': 0, 't': 1, 'e':2, 'm':3}
y_train = train['CATEGORY'].map(lambda x: category_dict[x]).values
y_valid = valid['CATEGORY'].map(lambda x: category_dict[x]).values
y_test = test['CATEGORY'].map(lambda x: category_dict[x]).values

#Créer un jeu de données
dataset_train = CreateDataset(train['TITLE'], y_train, tokenizer)
dataset_valid = CreateDataset(valid['TITLE'], y_valid, tokenizer)
dataset_test = CreateDataset(test['TITLE'], y_test, tokenizer)

print(f'len(Dataset)Sortie de: {len(dataset_train)}')
print('Dataset[index]Sortie de:')
for var in dataset_train[1]:
  print(f'  {var}: {dataset_train[1][var]}')

production


len(Dataset)Sortie de: 10684
Dataset[index]Sortie de:
  inputs: tensor([ 169,  539,    1,  683, 1237,   82,  279, 1898, 4199])
  labels: 1

Puisque nous n'apprendrons pas dans cette question, donnez inputs de `` `Dataset``` au modèle et vérifiez la sortie telle qu'elle est après Softmax.

#Paramétrage
VOCAB_SIZE = len(set(word2id.values())) + 1  #Nombre d'ID de dictionnaire+ID de remplissage
EMB_SIZE = 300
PADDING_IDX = len(set(word2id.values()))
OUTPUT_SIZE = 4
HIDDEN_SIZE = 50

#Définition du modèle
model = RNN(VOCAB_SIZE, EMB_SIZE, PADDING_IDX, OUTPUT_SIZE, HIDDEN_SIZE)

#Obtenez les 10 premières valeurs prédites
for i in range(10):
  X = dataset_train[i]['inputs']
  print(torch.softmax(model(X.unsqueeze(0)), dim=-1))

production


tensor([[0.2667, 0.2074, 0.2974, 0.2285]], grad_fn=<SoftmaxBackward>)
tensor([[0.1660, 0.3465, 0.2154, 0.2720]], grad_fn=<SoftmaxBackward>)
tensor([[0.2133, 0.2987, 0.3097, 0.1783]], grad_fn=<SoftmaxBackward>)
tensor([[0.2512, 0.4107, 0.1825, 0.1556]], grad_fn=<SoftmaxBackward>)
tensor([[0.2784, 0.1307, 0.3715, 0.2194]], grad_fn=<SoftmaxBackward>)
tensor([[0.2625, 0.1569, 0.2339, 0.3466]], grad_fn=<SoftmaxBackward>)
tensor([[0.1331, 0.5129, 0.2220, 0.1319]], grad_fn=<SoftmaxBackward>)
tensor([[0.2404, 0.1314, 0.2023, 0.4260]], grad_fn=<SoftmaxBackward>)
tensor([[0.1162, 0.4576, 0.2588, 0.1674]], grad_fn=<SoftmaxBackward>)
tensor([[0.4685, 0.1414, 0.2633, 0.1268]], grad_fn=<SoftmaxBackward>)

82. Apprentissage par la méthode probabiliste de descente de gradient

Apprenez le modèle construit dans le problème 81 à l'aide de la méthode SGD (Stochastic Gradient Descent). Entraînez le modèle tout en affichant le taux de perte et de réponse correcte sur les données d'entraînement et le taux de perte et de réponse correcte sur les données d'évaluation, et terminez avec une norme appropriée (par exemple, 10 époques).

Comme dans le chapitre précédent, cela définit également une série de traitements pour l'apprentissage comme une fonction `` train_model ''.

from torch.utils.data import DataLoader
import time
from torch import optim

def calculate_loss_and_accuracy(model, dataset, device=None, criterion=None):
  """Calculer le taux de perte / réponse correcte"""
  dataloader = DataLoader(dataset, batch_size=1, shuffle=False)
  loss = 0.0
  total = 0
  correct = 0
  with torch.no_grad():
    for data in dataloader:
      #Spécification de l'appareil
      inputs = data['inputs'].to(device)
      labels = data['labels'].to(device)

      #Propagation vers l'avant
      outputs = model(inputs)

      #Calcul des pertes
      if criterion != None:
        loss += criterion(outputs, labels).item()

      #Calcul du taux de réponse correct
      pred = torch.argmax(outputs, dim=-1)
      total += len(inputs)
      correct += (pred == labels).sum().item()
      
  return loss / len(dataset), correct / total
  

def train_model(dataset_train, dataset_valid, batch_size, model, criterion, optimizer, num_epochs, collate_fn=None, 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, collate_fn=collate_fn)
  dataloader_valid = DataLoader(dataset_valid, batch_size=1, shuffle=False)

  #Paramètres du planificateur
  scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, num_epochs, eta_min=1e-5, last_epoch=-1)

  #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:
      #Initialiser le dégradé à zéro
      optimizer.zero_grad()

      #Propagation vers l'avant+Erreur de propagation de retour+Mise à jour du poids
      inputs = data['inputs'].to(device)
      labels = data['labels'].to(device)
      outputs = model.forward(inputs)
      loss = criterion(outputs, labels)
      loss.backward()
      optimizer.step()
    
    #Mettre en mode évaluation
    model.eval()

    #Calcul de la perte et taux de réponse correcte
    loss_train, acc_train = calculate_loss_and_accuracy(model, dataset_train, device, criterion=criterion)
    loss_valid, acc_valid = calculate_loss_and_accuracy(model, dataset_valid, device, criterion=criterion)
    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') 

    #L'apprentissage se termine si la perte des données de vérification ne diminue pas pendant 3 époques consécutives
    if epoch > 2 and log_valid[epoch - 3][0] <= log_valid[epoch - 2][0] <= log_valid[epoch - 1][0] <= log_valid[epoch][0]:
      break
      
    #Faites un pas vers le planificateur
    scheduler.step()

  return {'train': log_train, 'valid': log_valid}

De plus, définissez une fonction pour visualiser le journal.

import numpy as np
from matplotlib import pyplot as plt

def visualize_logs(log):
  fig, ax = plt.subplots(1, 2, figsize=(15, 5))
  ax[0].plot(np.array(log['train']).T[0], label='train')
  ax[0].plot(np.array(log['valid']).T[0], label='valid')
  ax[0].set_xlabel('epoch')
  ax[0].set_ylabel('loss')
  ax[0].legend()
  ax[1].plot(np.array(log['train']).T[1], label='train')
  ax[1].plot(np.array(log['valid']).T[1], label='valid')
  ax[1].set_xlabel('epoch')
  ax[1].set_ylabel('accuracy')
  ax[1].legend()
  plt.show()

Définissez les paramètres et entraînez le modèle.

#Paramétrage
VOCAB_SIZE = len(set(word2id.values())) + 1 
EMB_SIZE = 300
PADDING_IDX = len(set(word2id.values()))
OUTPUT_SIZE = 4
HIDDEN_SIZE = 50
LEARNING_RATE = 1e-3
BATCH_SIZE = 1
NUM_EPOCHS = 10

#Définition du modèle
model = RNN(VOCAB_SIZE, EMB_SIZE, PADDING_IDX, OUTPUT_SIZE, HIDDEN_SIZE)

#Définition de la fonction de perte
criterion = nn.CrossEntropyLoss()

#Définition de l'optimiseur
optimizer = torch.optim.SGD(model.parameters(), lr=LEARNING_RATE)

#Apprentissage de modèle
log = train_model(dataset_train, dataset_valid, BATCH_SIZE, model, criterion, optimizer, NUM_EPOCHS)

production


epoch: 1, loss_train: 1.0954, accuracy_train: 0.5356, loss_valid: 1.1334, accuracy_valid: 0.5015, 86.4033sec
epoch: 2, loss_train: 1.0040, accuracy_train: 0.6019, loss_valid: 1.0770, accuracy_valid: 0.5516, 85.2816sec
epoch: 3, loss_train: 0.8813, accuracy_train: 0.6689, loss_valid: 0.9793, accuracy_valid: 0.6287, 78.9026sec
epoch: 4, loss_train: 0.7384, accuracy_train: 0.7364, loss_valid: 0.8498, accuracy_valid: 0.7058, 78.4496sec
epoch: 5, loss_train: 0.6427, accuracy_train: 0.7696, loss_valid: 0.7878, accuracy_valid: 0.7253, 83.4453sec
epoch: 6, loss_train: 0.5730, accuracy_train: 0.7942, loss_valid: 0.7378, accuracy_valid: 0.7470, 79.6968sec
epoch: 7, loss_train: 0.5221, accuracy_train: 0.8064, loss_valid: 0.7058, accuracy_valid: 0.7530, 79.7377sec
epoch: 8, loss_train: 0.4924, accuracy_train: 0.8173, loss_valid: 0.7017, accuracy_valid: 0.7605, 78.2168sec
epoch: 9, loss_train: 0.4800, accuracy_train: 0.8234, loss_valid: 0.7014, accuracy_valid: 0.7575, 77.8689sec
epoch: 10, loss_train: 0.4706, accuracy_train: 0.8253, loss_valid: 0.6889, accuracy_valid: 0.7650, 79.4202sec
#Visualisation du journal
visualize_logs(log)

#Calcul du taux de réponse correcte
_, acc_train = calculate_loss_and_accuracy(model, dataset_train)
_, acc_test = calculate_loss_and_accuracy(model, dataset_test)
print(f'Taux de réponse correct (données d'apprentissage):{acc_train:.3f}')
print(f'Taux de réponse correct (données d'évaluation):{acc_test:.3f}')

82.png

production


Taux de réponse correct (données d'apprentissage): 0.825
Taux de réponse correct (données d'évaluation): 0.773

83. Mini-batch / apprentissage sur GPU

Modifiez le code du problème 82 afin de pouvoir apprendre en calculant la perte / gradient pour chaque cas $ B $ (choisissez la valeur de $ B $ de manière appropriée). Exécutez également l'apprentissage sur le GPU.

Actuellement, la longueur de la série diffère pour chaque phrase, mais il est nécessaire d'aligner la longueur de la série afin de les assembler sous forme de mini-lot. Par conséquent, nous définissons une nouvelle classe Padsequence '' qui a la fonction de remplissage en fonction de la longueur de séquence maximale de plusieurs instructions. En donnant cela à l'argument collate_fn de `` Dataloader, il est possible de réaliser le processus d'alignement de la longueur de la série à chaque fois qu'un mini-lot est extrait.

class Padsequence():
  """Rembourrage avec une longueur de série maximale à chaque fois qu'un mini lot est retiré du chargeur de données"""
  def __init__(self, padding_idx):
    self.padding_idx = padding_idx

  def __call__(self, batch):
    sorted_batch = sorted(batch, key=lambda x: x['inputs'].shape[0], reverse=True)
    sequences = [x['inputs'] for x in sorted_batch]
    sequences_padded = torch.nn.utils.rnn.pad_sequence(sequences, batch_first=True, padding_value=self.padding_idx)
    labels = torch.LongTensor([x['labels'] for x in sorted_batch])

    return {'inputs': sequences_padded, 'labels': labels}
#Paramétrage
VOCAB_SIZE = len(set(word2id.values())) + 1
EMB_SIZE = 300
PADDING_IDX = len(set(word2id.values()))
OUTPUT_SIZE = 4
HIDDEN_SIZE = 50
LEARNING_RATE = 5e-2
BATCH_SIZE = 32
NUM_EPOCHS = 10

#Définition du modèle
model = RNN(VOCAB_SIZE, EMB_SIZE, PADDING_IDX, OUTPUT_SIZE, HIDDEN_SIZE)

#Définition de la fonction de perte
criterion = nn.CrossEntropyLoss()

#Définition de l'optimiseur
optimizer = torch.optim.SGD(model.parameters(), lr=LEARNING_RATE)

#Spécification de l'appareil
device = torch.device('cuda')

#Apprentissage de modèle
log = train_model(dataset_train, dataset_valid, BATCH_SIZE, model, criterion, optimizer, NUM_EPOCHS, collate_fn=Padsequence(PADDING_IDX), device=device)

production


epoch: 1, loss_train: 1.2605, accuracy_train: 0.3890, loss_valid: 1.2479, accuracy_valid: 0.4162, 12.1096sec
epoch: 2, loss_train: 1.2492, accuracy_train: 0.4246, loss_valid: 1.2541, accuracy_valid: 0.4424, 12.0607sec
epoch: 3, loss_train: 1.2034, accuracy_train: 0.4795, loss_valid: 1.2220, accuracy_valid: 0.4686, 11.8881sec
epoch: 4, loss_train: 1.1325, accuracy_train: 0.5392, loss_valid: 1.1542, accuracy_valid: 0.5210, 12.2269sec
epoch: 5, loss_train: 1.0543, accuracy_train: 0.6214, loss_valid: 1.0623, accuracy_valid: 0.6175, 11.8767sec
epoch: 6, loss_train: 1.0381, accuracy_train: 0.6316, loss_valid: 1.0556, accuracy_valid: 0.6145, 11.9757sec
epoch: 7, loss_train: 1.0546, accuracy_train: 0.6165, loss_valid: 1.0806, accuracy_valid: 0.5913, 12.0352sec
epoch: 8, loss_train: 0.9924, accuracy_train: 0.6689, loss_valid: 1.0150, accuracy_valid: 0.6587, 11.9090sec
epoch: 9, loss_train: 1.0123, accuracy_train: 0.6517, loss_valid: 1.0482, accuracy_valid: 0.6310, 12.0953sec
epoch: 10, loss_train: 1.0036, accuracy_train: 0.6623, loss_valid: 1.0319, accuracy_valid: 0.6504, 11.9331sec
#Visualisation du journal
visualize_logs(log)

#Calcul du taux de réponse correcte
_, acc_train = calculate_loss_and_accuracy(model, dataset_train, device)
_, acc_test = calculate_loss_and_accuracy(model, dataset_test, device)
print(f'Taux de réponse correct (données d'apprentissage):{acc_train:.3f}')
print(f'Taux de réponse correct (données d'évaluation):{acc_test:.3f}')

83.png

production


Taux de réponse correct (données d'apprentissage): 0.662
Taux de réponse correct (données d'évaluation): 0.649

84. Introduction du vecteur de mot

Initialisez et apprenez le mot intégrant $ emb (x) $ avec un vecteur de mots pré-appris (par exemple, un vecteur de mots appris dans l'ensemble de données de Google Actualités (environ 100 milliards de mots)).

Téléchargez le vecteur de mots pré-appris comme dans le chapitre précédent.

#Télécharger le vecteur de mot appris
FILE_ID = "0B7XkCwpI5KDYNlNUTTlSS21pQmM"
FILE_NAME = "GoogleNews-vectors-negative300.bin.gz"
!wget --load-cookies /tmp/cookies.txt "https://docs.google.com/uc?export=download&confirm=$(wget --quiet --save-cookies /tmp/cookies.txt --keep-session-cookies --no-check-certificate 'https://docs.google.com/uc?export=download&id=$FILE_ID' -O- | sed -rn 's/.*confirm=([0-9A-Za-z_]+).*/\1\n/p')&id=$FILE_ID" -O $FILE_NAME && rm -rf /tmp/cookies.txt

Lorsque vous utilisez un vecteur de mots pré-appris comme modèle, il existe deux méthodes: l'une consiste à utiliser tous les mots (remplacez le dictionnaire), et l'autre consiste à utiliser le dictionnaire des données disponibles tel quel et à ne l'utiliser que comme valeur initiale de ces vecteurs de mots. Il y a. Cette fois, la dernière méthode est adoptée et le vecteur de mot correspondant au dictionnaire déjà créé est extrait.

from gensim.models import KeyedVectors

#Chargement du modèle entraîné
model = KeyedVectors.load_word2vec_format('./GoogleNews-vectors-negative300.bin.gz', binary=True)

#Obtenez un vecteur de mot appris
VOCAB_SIZE = len(set(word2id.values())) + 1
EMB_SIZE = 300
weights = np.zeros((VOCAB_SIZE, EMB_SIZE))
words_in_pretrained = 0
for i, word in enumerate(word2id.keys()):
  try:
    weights[i] = model[word]
    words_in_pretrained += 1
  except KeyError:
    weights[i] = np.random.normal(scale=0.4, size=(EMB_SIZE,))
weights = torch.from_numpy(weights.astype((np.float32)))

print(f'Nombre de mots utilisés dans le vecteur appris: {words_in_pretrained} / {VOCAB_SIZE}')
print(weights.size())

production


Nombre de mots utilisés dans le vecteur appris: 9174 / 9406
torch.Size([9406, 300])

Modifiez afin que la valeur initiale puisse être définie pour la couche intégrée du réseau. Ajoutez également des paramètres pour le bidirectionnel et le multicouche pour le problème suivant.

class RNN(nn.Module):
  def __init__(self, vocab_size, emb_size, padding_idx, output_size, hidden_size, num_layers, emb_weights=None, bidirectional=False):
    super().__init__()
    self.hidden_size = hidden_size
    self.num_layers = num_layers
    self.num_directions = bidirectional + 1  #Unidirectionnel: 1, bidirectionnel: 2
    if emb_weights != None:  #Incorporez le poids du calque incorporé si spécifié_Initialiser avec des poids
      self.emb = nn.Embedding.from_pretrained(emb_weights, padding_idx=padding_idx)
    else:
      self.emb = nn.Embedding(vocab_size, emb_size, padding_idx=padding_idx)
    self.rnn = nn.RNN(emb_size, hidden_size, num_layers, nonlinearity='tanh', bidirectional=bidirectional, batch_first=True)
    self.fc = nn.Linear(hidden_size * self.num_directions, output_size)
    
  def forward(self, x):
    self.batch_size = x.size()[0]
    hidden = self.init_hidden()  #Créer un vecteur nul pour h0
    emb = self.emb(x)
    # emb.size() = (batch_size, seq_len, emb_size)
    out, hidden = self.rnn(emb, hidden)
    # out.size() = (batch_size, seq_len, hidden_size * num_directions)
    out = self.fc(out[:, -1, :])
    # out.size() = (batch_size, output_size)
    return out
    
  def init_hidden(self):
    hidden = torch.zeros(self.num_layers * self.num_directions, self.batch_size, self.hidden_size)
    return hidden

Apprenez en spécifiant la valeur initiale de la couche incorporée.

#Paramétrage
VOCAB_SIZE = len(set(word2id.values())) + 1
EMB_SIZE = 300
PADDING_IDX = len(set(word2id.values()))
OUTPUT_SIZE = 4
HIDDEN_SIZE = 50
NUM_LAYERS = 1
LEARNING_RATE = 5e-2
BATCH_SIZE = 32
NUM_EPOCHS = 10

#Définition du modèle
model = RNN(VOCAB_SIZE, EMB_SIZE, PADDING_IDX, OUTPUT_SIZE, HIDDEN_SIZE, NUM_LAYERS, emb_weights=weights)

#Définition de la fonction de perte
criterion = nn.CrossEntropyLoss()

#Définition de l'optimiseur
optimizer = torch.optim.SGD(model.parameters(), lr=LEARNING_RATE)

#Spécification de l'appareil
device = torch.device('cuda')

#Apprentissage de modèle
log = train_model(dataset_train, dataset_valid, BATCH_SIZE, model, criterion, optimizer, NUM_EPOCHS, collate_fn=Padsequence(PADDING_IDX), device=device)

production


epoch: 1, loss_train: 1.1655, accuracy_train: 0.4270, loss_valid: 1.1839, accuracy_valid: 0.4244, 9.7483sec
epoch: 2, loss_train: 1.1555, accuracy_train: 0.4635, loss_valid: 1.1404, accuracy_valid: 0.4865, 9.7553sec
epoch: 3, loss_train: 1.0189, accuracy_train: 0.6263, loss_valid: 1.0551, accuracy_valid: 0.6085, 10.0445sec
epoch: 4, loss_train: 1.0377, accuracy_train: 0.6221, loss_valid: 1.0947, accuracy_valid: 0.5951, 10.1138sec
epoch: 5, loss_train: 1.0392, accuracy_train: 0.6082, loss_valid: 1.0776, accuracy_valid: 0.5921, 9.8540sec
epoch: 6, loss_train: 1.0447, accuracy_train: 0.6087, loss_valid: 1.1020, accuracy_valid: 0.5793, 9.8598sec
epoch: 7, loss_train: 0.9999, accuracy_train: 0.6270, loss_valid: 1.0519, accuracy_valid: 0.6108, 9.7565sec
epoch: 8, loss_train: 0.9539, accuracy_train: 0.6557, loss_valid: 1.0092, accuracy_valid: 0.6385, 9.7457sec
epoch: 9, loss_train: 0.9287, accuracy_train: 0.6674, loss_valid: 0.9806, accuracy_valid: 0.6430, 9.6464sec
epoch: 10, loss_train: 0.9456, accuracy_train: 0.6593, loss_valid: 1.0029, accuracy_valid: 0.6377, 9.6835sec
#Visualisation du journal
visualize_logs(log)

#Calcul du taux de réponse correcte
_, acc_train = calculate_loss_and_accuracy(model, dataset_train, device)
_, acc_test = calculate_loss_and_accuracy(model, dataset_test, device)
print(f'Taux de réponse correct (données d'apprentissage):{acc_train:.3f}')
print(f'Taux de réponse correct (données d'évaluation):{acc_test:.3f}')

84.png

production


Taux de réponse correct (données d'apprentissage): 0.659
Taux de réponse correct (données d'évaluation): 0.645

85. RNN bidirectionnel / multicouche

Encodez le texte d'entrée à l'aide des RNN avant et arrière et entraînez le modèle.

\overleftarrow h_{T+1} = 0, \ \overleftarrow h_t = {\rm \overleftarrow{RNN}}(\mathrm{emb}(x_t), \overleftarrow h_{t+1}), \ y = {\rm softmax}(W^{(yh)} [\overrightarrow h_T; \overleftarrow h_1] + b^{(y)})


 > Cependant, $ \ overrightarrow h_t \ in \ mathbb {R} ^ {d_h}, \ overleftarrow h_t \ in \ mathbb {R} ^ {d_h} $ sont les temps $ t obtenus par les RNN avant et arrière, respectivement. Le vecteur d'état caché de $, $ {\ rm \ overleftarrow {RNN}} (x, h) $ est l'unité RNN qui calcule l'état précédent à partir de l'entrée $ x $ et l'état caché $ h $ de la prochaine fois, $ W ^ {( yh)} \ in \ mathbb {R} ^ {L \ times 2d_h} $ est une matrice pour prédire les catégories à partir de vecteurs d'état cachés, $ b ^ {(y)} \ in \ mathbb {R} ^ {L} $ Est un terme de biais. De plus, $ [a; b] $ représente la concaténation des vecteurs $ a $ et $ b $.

 > De plus, expérimentez des RNN bidirectionnels multicouches.

 Définissez `` `` bidirectionnel```, qui est un argument qui spécifie la bidirectionnalité, sur `` `` True```, et définissez `` `` NUM_LAYERS ''` sur `` 2' 'pour exécuter l'apprentissage. ..

```python
#Paramétrage
VOCAB_SIZE = len(set(word2id.values())) + 1
EMB_SIZE = 300
PADDING_IDX = len(set(word2id.values()))
OUTPUT_SIZE = 4
HIDDEN_SIZE = 50
NUM_LAYERS = 2
LEARNING_RATE = 5e-2
BATCH_SIZE = 32
NUM_EPOCHS = 10

#Définition du modèle
model = RNN(VOCAB_SIZE, EMB_SIZE, PADDING_IDX, OUTPUT_SIZE, HIDDEN_SIZE, NUM_LAYERS, emb_weights=weights, bidirectional=True)

#Définition de la fonction de perte
criterion = nn.CrossEntropyLoss()

#Définition de l'optimiseur
optimizer = torch.optim.SGD(model.parameters(), lr=LEARNING_RATE)

#Spécification de l'appareil
device = torch.device('cuda')

#Apprentissage de modèle
log = train_model(dataset_train, dataset_valid, BATCH_SIZE, model, criterion, optimizer, NUM_EPOCHS, collate_fn=Padsequence(PADDING_IDX), device=device)

production


epoch: 1, loss_train: 1.1731, accuracy_train: 0.4307, loss_valid: 1.1915, accuracy_valid: 0.4274, 19.3181sec
epoch: 2, loss_train: 1.0395, accuracy_train: 0.6116, loss_valid: 1.0555, accuracy_valid: 0.5996, 18.8118sec
epoch: 3, loss_train: 1.0529, accuracy_train: 0.5899, loss_valid: 1.0832, accuracy_valid: 0.5696, 18.9088sec
epoch: 4, loss_train: 0.9831, accuracy_train: 0.6351, loss_valid: 1.0144, accuracy_valid: 0.6235, 18.8913sec
epoch: 5, loss_train: 1.0622, accuracy_train: 0.5797, loss_valid: 1.1142, accuracy_valid: 0.5487, 19.0636sec
epoch: 6, loss_train: 1.0463, accuracy_train: 0.5741, loss_valid: 1.0972, accuracy_valid: 0.5367, 19.0612sec
epoch: 7, loss_train: 1.0056, accuracy_train: 0.6102, loss_valid: 1.0485, accuracy_valid: 0.5898, 19.0420sec
epoch: 8, loss_train: 0.9724, accuracy_train: 0.6294, loss_valid: 1.0278, accuracy_valid: 0.6093, 19.3077sec
epoch: 9, loss_train: 0.9469, accuracy_train: 0.6371, loss_valid: 0.9943, accuracy_valid: 0.6160, 19.2803sec
epoch: 10, loss_train: 0.9343, accuracy_train: 0.6451, loss_valid: 0.9867, accuracy_valid: 0.6235, 19.0755sec
#Visualisation du journal
visualize_logs(log)

#Calcul du taux de réponse correcte
_, acc_train = calculate_loss_and_accuracy(model, dataset_train, device)
_, acc_test = calculate_loss_and_accuracy(model, dataset_test, device)
print(f'Taux de réponse correct (données d'apprentissage):{acc_train:.3f}')
print(f'Taux de réponse correct (données d'évaluation):{acc_test:.3f}')

85.png

production


Taux de réponse correct (données d'apprentissage): 0.645
Taux de réponse correct (données d'évaluation): 0.634

86. Réseau neuronal convolutif (CNN)

Il y a une chaîne de mot $ \ boldsymbol x = (x_1, x_2, \ dots, x_T) $ représentée par un numéro d'identification. Cependant, $ T $ est la longueur de la chaîne de mots, et $ x_t \ in \ mathbb {R} ^ {V} $ est la notation unique du numéro d'identification du mot ($ V $ est le nombre total de mots). Implémentez un modèle qui prédit la catégorie $ y $ à partir de la chaîne de mots $ \ boldsymbol x $ en utilisant le réseau de neurones convolutifs (CNN).

Cependant, la configuration du réseau de neurones convolutifs est la suivante.

  • Nombre de dimensions d'incorporation de mots: $ d_w $

p_t = g(W^{(px)} [\mathrm{emb}(x_{t-1}); \mathrm{emb}(x_t); \mathrm{emb}(x_{t+1})] + b^{(p)}) $]


 > Cependant, $ W ^ {(px)} \ in \ mathbb {R} ^ {d_h \ times 3d_w}, b ^ {(p)} \ in \ mathbb {R} ^ {d_h} $ est un paramètre CNN. $ g $ est une fonction d'activation (par exemple $ \ tanh $ ou ReLU), et $ [a; b; c] $ est une concaténation de vecteurs $ a, b, c $. Le nombre de colonnes dans la matrice $ W ^ {(px)} $ est $ 3d_w $ car la conversion linéaire est effectuée sur les embeddings de mots concaténés de trois jetons.
 Dans la mise en commun de valeurs maximales, la valeur maximale à tout moment est prise pour chaque dimension du vecteur d'entités, et le vecteur d'entités $ c \ in \ mathbb {R} ^ {d_h} $ du document d'entrée est obtenu. Si $ c [i] $ représente la valeur de la $ i $ ème dimension du vecteur $ c $, la mise en commun de la valeur maximale est exprimée par l'équation suivante.

>```math
c[i] = \max_{1 \leq t \leq T} p_t[i]

Enfin, le vecteur caractéristique du document d'entrée $ c $ avec la matrice $ W ^ {(yc)} \ in \ mathbb {R} ^ {L \ times d_h} $ et le terme de biais $ b ^ {(y)} \ in Appliquez la transformation linéaire par \ mathbb {R} ^ {L} $ et la fonction softmax pour prédire la catégorie $ y $.

y = {\rm softmax}(W^{(yc)} c + b^{(y)})


 > Notez que ce problème n'entraîne pas le modèle, il nécessite seulement de calculer $ y $ avec une matrice de poids initialisée aléatoirement.

 Implémentez le réseau spécifié.
 En suivant la couche intégrée, calculez la convolution avec `` `` nn.Conv2d```. La valeur maximale est acquise dans le sens de la longueur de la série avec `` max_pool '', et les vecteurs sont agrégés en unités de phrase dans cette partie.

```python
from torch.nn import functional as F

class CNN(nn.Module):
  def __init__(self, vocab_size, emb_size, padding_idx, output_size, out_channels, kernel_heights, stride, padding, emb_weights=None):
    super().__init__()
    if emb_weights != None:  #Incorporez le poids du calque incorporé si spécifié_Initialiser avec des poids
      self.emb = nn.Embedding.from_pretrained(emb_weights, padding_idx=padding_idx)
    else:
      self.emb = nn.Embedding(vocab_size, emb_size, padding_idx=padding_idx)
    self.conv = nn.Conv2d(1, out_channels, (kernel_heights, emb_size), stride, (padding, 0))
    self.drop = nn.Dropout(0.3)
    self.fc = nn.Linear(out_channels, output_size)
    
  def forward(self, x):
    # x.size() = (batch_size, seq_len)
    emb = self.emb(x).unsqueeze(1)
    # emb.size() = (batch_size, 1, seq_len, emb_size)
    conv = self.conv(emb)
    # conv.size() = (batch_size, out_channels, seq_len, 1)
    act = F.relu(conv.squeeze(3))
    # act.size() = (batch_size, out_channels, seq_len)
    max_pool = F.max_pool1d(act, act.size()[2])
    # max_pool.size() = (batch_size, out_channels, 1) -> seq_Obtenez une valeur maximale dans le sens de la lentille
    out = self.fc(self.drop(max_pool.squeeze(2)))
    # out.size() = (batch_size, output_size)
    return out
#Paramétrage
VOCAB_SIZE = len(set(word2id.values())) + 1
EMB_SIZE = 300
PADDING_IDX = len(set(word2id.values()))
OUTPUT_SIZE = 4
OUT_CHANNELS = 100
KERNEL_HEIGHTS = 3
STRIDE = 1
PADDING = 1

#Définition du modèle
model = CNN(VOCAB_SIZE, EMB_SIZE, PADDING_IDX, OUTPUT_SIZE, OUT_CHANNELS, KERNEL_HEIGHTS, STRIDE, PADDING, emb_weights=weights)

#Obtenez les 10 premières valeurs prédites
for i in range(10):
  X = dataset_train[i]['inputs']
  print(torch.softmax(model(X.unsqueeze(0)), dim=-1))

production


tensor([[0.2607, 0.2267, 0.2121, 0.3006]], grad_fn=<SoftmaxBackward>)
tensor([[0.2349, 0.2660, 0.2462, 0.2529]], grad_fn=<SoftmaxBackward>)
tensor([[0.2305, 0.2649, 0.2099, 0.2948]], grad_fn=<SoftmaxBackward>)
tensor([[0.2569, 0.2409, 0.2418, 0.2604]], grad_fn=<SoftmaxBackward>)
tensor([[0.2610, 0.2149, 0.2355, 0.2886]], grad_fn=<SoftmaxBackward>)
tensor([[0.2627, 0.2363, 0.2388, 0.2622]], grad_fn=<SoftmaxBackward>)
tensor([[0.2694, 0.2434, 0.2224, 0.2648]], grad_fn=<SoftmaxBackward>)
tensor([[0.2423, 0.2465, 0.2365, 0.2747]], grad_fn=<SoftmaxBackward>)
tensor([[0.2591, 0.2695, 0.2468, 0.2246]], grad_fn=<SoftmaxBackward>)
tensor([[0.2794, 0.2465, 0.2234, 0.2507]], grad_fn=<SoftmaxBackward>)

87. Apprentissage de CNN par la méthode probabiliste de descente de gradient

Apprenez le modèle construit dans le problème 86 à l'aide de la méthode SGD (Stochastic Gradient Descent) Entraînez le modèle tout en affichant le taux de perte et de réponse correcte sur les données d'entraînement et le taux de perte et de réponse correcte sur les données d'évaluation, et terminez avec une norme appropriée (par exemple, 10 époques).

#Paramétrage
VOCAB_SIZE = len(set(word2id.values())) + 1
EMB_SIZE = 300
PADDING_IDX = len(set(word2id.values()))
OUTPUT_SIZE = 4
OUT_CHANNELS = 100
KERNEL_HEIGHTS = 3
STRIDE = 1
PADDING = 1
LEARNING_RATE = 5e-2
BATCH_SIZE = 64
NUM_EPOCHS = 10

#Définition du modèle
model = CNN(VOCAB_SIZE, EMB_SIZE, PADDING_IDX, OUTPUT_SIZE, OUT_CHANNELS, KERNEL_HEIGHTS, STRIDE, PADDING, emb_weights=weights)

#Définition de la fonction de perte
criterion = nn.CrossEntropyLoss()

#Définition de l'optimiseur
optimizer = torch.optim.SGD(model.parameters(), lr=LEARNING_RATE)

#Spécification de l'appareil
device = torch.device('cuda')

#Apprentissage de modèle
log = train_model(dataset_train, dataset_valid, BATCH_SIZE, model, criterion, optimizer, NUM_EPOCHS, collate_fn=Padsequence(PADDING_IDX), device=device)

production


epoch: 1, loss_train: 1.0671, accuracy_train: 0.5543, loss_valid: 1.0744, accuracy_valid: 0.5726, 12.9214sec
epoch: 2, loss_train: 0.9891, accuracy_train: 0.6594, loss_valid: 1.0148, accuracy_valid: 0.6452, 12.6483sec
epoch: 3, loss_train: 0.9098, accuracy_train: 0.6928, loss_valid: 0.9470, accuracy_valid: 0.6729, 12.7305sec
epoch: 4, loss_train: 0.8481, accuracy_train: 0.7139, loss_valid: 0.8956, accuracy_valid: 0.7028, 12.7967sec
epoch: 5, loss_train: 0.8055, accuracy_train: 0.7250, loss_valid: 0.8634, accuracy_valid: 0.7096, 12.6543sec
epoch: 6, loss_train: 0.7728, accuracy_train: 0.7361, loss_valid: 0.8425, accuracy_valid: 0.7141, 12.7423sec
epoch: 7, loss_train: 0.7527, accuracy_train: 0.7396, loss_valid: 0.8307, accuracy_valid: 0.7216, 12.6718sec
epoch: 8, loss_train: 0.7403, accuracy_train: 0.7432, loss_valid: 0.8227, accuracy_valid: 0.7246, 12.5854sec
epoch: 9, loss_train: 0.7346, accuracy_train: 0.7447, loss_valid: 0.8177, accuracy_valid: 0.7216, 12.4846sec
epoch: 10, loss_train: 0.7331, accuracy_train: 0.7448, loss_valid: 0.8167, accuracy_valid: 0.7231, 12.7443sec
#Visualisation du journal
visualize_logs(log)

#Calcul du taux de réponse correcte
_, acc_train = calculate_loss_and_accuracy(model, dataset_train, device)
_, acc_test = calculate_loss_and_accuracy(model, dataset_test, device)
print(f'Taux de réponse correct (données d'apprentissage):{acc_train:.3f}')
print(f'Taux de réponse correct (données d'évaluation):{acc_test:.3f}')

87.png

production


Taux de réponse correct (données d'apprentissage): 0.745
Taux de réponse correct (données d'évaluation): 0.719

88. Réglage des paramètres

Construire un classificateur de catégories performant en modifiant le code des questions 85 et 87 et en ajustant la forme et les hyperparamètres du réseau de neurones.

Cette fois, je vais essayer un réseau qui simplifie TextCNN proposé dans Convolutional Neural Networks for Sentence Classification. Dans la question précédente, CNN n'a appris que des filtres d'une largeur de 3, mais ce réseau utilise des filtres de trois largeurs de 2, 3 et 4.

from torch.nn import functional as F

class textCNN(nn.Module):
  def __init__(self, vocab_size, emb_size, padding_idx, output_size, out_channels, conv_params, drop_rate, emb_weights=None):
    super().__init__()
    if emb_weights != None:  #Incorporez le poids du calque incorporé si spécifié_Initialiser avec des poids
      self.emb = nn.Embedding.from_pretrained(emb_weights, padding_idx=padding_idx)
    else:
      self.emb = nn.Embedding(vocab_size, emb_size, padding_idx=padding_idx)
    self.convs = nn.ModuleList([nn.Conv2d(1, out_channels, (kernel_height, emb_size), padding=(padding, 0)) for kernel_height, padding in conv_params])
    self.drop = nn.Dropout(drop_rate)
    self.fc = nn.Linear(len(conv_params) * out_channels, output_size)
    
  def forward(self, x):
    # x.size() = (batch_size, seq_len)
    emb = self.emb(x).unsqueeze(1)
    # emb.size() = (batch_size, 1, seq_len, emb_size)
    conv = [F.relu(conv(emb)).squeeze(3) for i, conv in enumerate(self.convs)]
    # conv[i].size() = (batch_size, out_channels, seq_len + padding * 2 - kernel_height + 1)
    max_pool = [F.max_pool1d(i, i.size(2)) for i in conv]
    # max_pool[i].size() = (batch_size, out_channels, 1) -> seq_Obtenez une valeur maximale dans le sens de la lentille
    max_pool_cat = torch.cat(max_pool, 1)
    # max_pool_cat.size() = (batch_size, len(conv_params) * out_channels, 1)  ->Combinez les résultats par filtre
    out = self.fc(self.drop(max_pool_cat.squeeze(2)))
    # out.size() = (batch_size, output_size)
    return out

Utilisez également optuna pour le réglage des paramètres comme dans Chapitre 6.

!pip install optuna
import optuna

def objective(trial):
  #Ensemble de paramètres à régler
  emb_size = int(trial.suggest_discrete_uniform('emb_size', 100, 400, 100))
  out_channels = int(trial.suggest_discrete_uniform('out_channels', 50, 200, 50))
  drop_rate = trial.suggest_discrete_uniform('drop_rate', 0.0, 0.5, 0.1)
  learning_rate = trial.suggest_loguniform('learning_rate', 5e-4, 5e-2)
  momentum = trial.suggest_discrete_uniform('momentum', 0.5, 0.9, 0.1)
  batch_size = int(trial.suggest_discrete_uniform('batch_size', 16, 128, 16))

  #Réglages des paramètres fixes
  VOCAB_SIZE = len(set(word2id.values())) + 1
  PADDING_IDX = len(set(word2id.values()))
  OUTPUT_SIZE = 4
  CONV_PARAMS = [[2, 0], [3, 1], [4, 2]]
  NUM_EPOCHS = 30

  #Définition du modèle
  model = textCNN(VOCAB_SIZE, EMB_SIZE, PADDING_IDX, OUTPUT_SIZE, out_channels, CONV_PARAMS, drop_rate, emb_weights=weights)

  #Définition de la fonction de perte
  criterion = nn.CrossEntropyLoss()

  #Définition de l'optimiseur
  optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate, momentum=momentum)

  #Spécification de l'appareil
  device = torch.device('cuda')

  #Apprentissage de modèle
  log = train_model(dataset_train, dataset_valid, batch_size, model, criterion, optimizer, NUM_EPOCHS, collate_fn=Padsequence(PADDING_IDX), device=device)

  #Calcul des pertes
  loss_valid, _ = calculate_loss_and_accuracy(model, dataset_valid, device, criterion=criterion) 

  return loss_valid 

Effectuez une recherche de paramètres.

#optimisation
study = optuna.create_study()
study.optimize(objective, timeout=7200)

#Voir les résultats
print('Best trial:')
trial = study.best_trial
print('  Value: {:.3f}'.format(trial.value))
print('  Params: ')
for key, value in trial.params.items():
  print('    {}: {}'.format(key, value))

production


Best trial:
  Value: 0.469
  Params: 
    emb_size: 300.0
    out_channels: 100.0
    drop_rate: 0.4
    learning_rate: 0.013345934577557608
    momentum: 0.8
    batch_size: 32.0

Entraînez le modèle avec les paramètres recherchés.

#Paramétrage
VOCAB_SIZE = len(set(word2id.values())) + 1
EMB_SIZE = int(trial.params['emb_size'])
PADDING_IDX = len(set(word2id.values()))
OUTPUT_SIZE = 4
OUT_CHANNELS = int(trial.params['out_channels'])
CONV_PARAMS = [[2, 0], [3, 1], [4, 2]]
DROP_RATE = trial.params['drop_rate']
LEARNING_RATE = trial.params['learning_rate']
BATCH_SIZE = int(trial.params['batch_size'])
NUM_EPOCHS = 30

#Définition du modèle
model = textCNN(VOCAB_SIZE, EMB_SIZE, PADDING_IDX, OUTPUT_SIZE, OUT_CHANNELS, CONV_PARAMS, DROP_RATE, emb_weights=weights)
print(model)

#Définition de la fonction de perte
criterion = nn.CrossEntropyLoss()

#Définition de l'optimiseur
optimizer = torch.optim.SGD(model.parameters(), lr=LEARNING_RATE, momentum=0.9)

#Spécification de l'appareil
device = torch.device('cuda')

#Apprentissage de modèle
log = train_model(dataset_train, dataset_valid, BATCH_SIZE, model, criterion, optimizer, NUM_EPOCHS, collate_fn=Padsequence(PADDING_IDX), device=device)

production


textCNN(
  (emb): Embedding(9406, 300, padding_idx=9405)
  (convs): ModuleList(
    (0): Conv2d(1, 100, kernel_size=(2, 300), stride=(1, 1))
    (1): Conv2d(1, 100, kernel_size=(3, 300), stride=(1, 1), padding=(1, 0))
    (2): Conv2d(1, 100, kernel_size=(4, 300), stride=(1, 1), padding=(2, 0))
  )
  (drop): Dropout(p=0.4, inplace=False)
  (fc): Linear(in_features=300, out_features=4, bias=True)
)
epoch: 1, loss_train: 0.7908, accuracy_train: 0.7239, loss_valid: 0.8660, accuracy_valid: 0.6901, 12.2279sec
epoch: 2, loss_train: 0.5800, accuracy_train: 0.7944, loss_valid: 0.7384, accuracy_valid: 0.7485, 12.1637sec
epoch: 3, loss_train: 0.3951, accuracy_train: 0.8738, loss_valid: 0.6189, accuracy_valid: 0.7919, 12.1612sec
epoch: 4, loss_train: 0.2713, accuracy_train: 0.9217, loss_valid: 0.5499, accuracy_valid: 0.8136, 12.1877sec
epoch: 5, loss_train: 0.1913, accuracy_train: 0.9593, loss_valid: 0.5176, accuracy_valid: 0.8293, 12.1722sec
epoch: 6, loss_train: 0.1322, accuracy_train: 0.9749, loss_valid: 0.5042, accuracy_valid: 0.8234, 12.4483sec
epoch: 7, loss_train: 0.1033, accuracy_train: 0.9807, loss_valid: 0.4922, accuracy_valid: 0.8323, 12.1556sec
epoch: 8, loss_train: 0.0723, accuracy_train: 0.9943, loss_valid: 0.4900, accuracy_valid: 0.8308, 12.0309sec
epoch: 9, loss_train: 0.0537, accuracy_train: 0.9966, loss_valid: 0.4903, accuracy_valid: 0.8346, 11.9471sec
epoch: 10, loss_train: 0.0414, accuracy_train: 0.9966, loss_valid: 0.4801, accuracy_valid: 0.8421, 11.9275sec
epoch: 11, loss_train: 0.0366, accuracy_train: 0.9978, loss_valid: 0.4943, accuracy_valid: 0.8406, 11.9691sec
epoch: 12, loss_train: 0.0292, accuracy_train: 0.9983, loss_valid: 0.4839, accuracy_valid: 0.8436, 11.9665sec
epoch: 13, loss_train: 0.0271, accuracy_train: 0.9982, loss_valid: 0.5042, accuracy_valid: 0.8421, 11.9634sec
epoch: 14, loss_train: 0.0222, accuracy_train: 0.9986, loss_valid: 0.4912, accuracy_valid: 0.8458, 11.9298sec
epoch: 15, loss_train: 0.0194, accuracy_train: 0.9988, loss_valid: 0.4925, accuracy_valid: 0.8436, 11.9375sec
epoch: 16, loss_train: 0.0176, accuracy_train: 0.9988, loss_valid: 0.5074, accuracy_valid: 0.8451, 11.9333sec
epoch: 17, loss_train: 0.0163, accuracy_train: 0.9991, loss_valid: 0.5124, accuracy_valid: 0.8436, 11.9137sec
#Visualisation du journal
visualize_logs(log)

#Calcul du taux de réponse correcte
_, acc_train = calculate_loss_and_accuracy(model, dataset_train, device)
_, acc_test = calculate_loss_and_accuracy(model, dataset_test, device)
print(f'Taux de réponse correct (données d'apprentissage):{acc_train:.3f}')
print(f'Taux de réponse correct (données d'évaluation):{acc_test:.3f}')

88.png

production


Taux de réponse correct (données d'apprentissage): 0.999
Taux de réponse correct (données d'évaluation): 0.851

89. Transférer l'apprentissage à partir d'un modèle de langage pré-formé

Construisez un modèle qui classe les titres des articles de presse en catégories, à partir d'un modèle de langage pré-formé (par exemple BERT).

[PyTorch] Introduction à la classification de documents à l'aide de BERT est découpé dans un autre article. Ici, seul le résultat du taux de réponse correct est affiché.

Taux de réponse correct (données d'apprentissage): 0.993
Taux de réponse correct (données d'évaluation): 0.948

en conclusion

Traitement du langage 100 coups sont conçus pour que vous puissiez apprendre non seulement le traitement du langage naturel lui-même, mais également le traitement des données de base et l'apprentissage automatique général. Même ceux qui étudient l'apprentissage automatique dans des cours en ligne pourront pratiquer de très bons résultats, alors essayez-le.

Pour répondre aux 100 questions

Recommended Posts

[Traitement du langage 100 coups 2020] Chapitre 9: RNN, CNN
100 Language Processing Knock 2020 Chapitre 9: RNN, CNN
100 coups de traitement du langage ~ Chapitre 1
Le traitement de 100 langues frappe le chapitre 2 (10 ~ 19)
100 coups de traitement linguistique (2020): 40
100 coups de traitement linguistique (2020): 32
100 coups de traitement linguistique (2020): 35
[Traitement du langage 100 coups 2020] Chapitre 3: Expressions régulières
100 coups de traitement linguistique (2020): 47
100 coups de traitement linguistique (2020): 39
100 coups de traitement linguistique (2020): 22
100 coups de traitement linguistique (2020): 26
100 coups de traitement linguistique (2020): 34
100 coups de traitement du langage 2020: Chapitre 4 (analyse morphologique)
[Traitement du langage 100 coups 2020] Chapitre 5: Analyse des dépendances
100 coups de traitement linguistique (2020): 42
100 coups de traitement linguistique (2020): 29
100 coups de traitement linguistique (2020): 49
Le traitement de 100 langues frappe 06 ~ 09
100 coups de traitement linguistique (2020): 43
100 coups de traitement linguistique (2020): 24
[Traitement du langage 100 coups 2020] Chapitre 1: Mouvement préparatoire
100 coups de traitement linguistique (2020): 45
100 coups de traitement linguistique (2020): 10-19
[Traitement du langage 100 coups 2020] Chapitre 7: Vecteur Word
100 coups de traitement linguistique (2020): 00-09
100 Language Processing Knock 2020: Chapitre 3 (expression régulière)
100 coups de traitement linguistique (2020): 31
[Traitement du langage 100 coups 2020] Chapitre 8: Réseau neuronal
100 coups de traitement linguistique (2020): 48
[Traitement du langage 100 coups 2020] Chapitre 2: Commandes UNIX
100 coups de traitement linguistique (2020): 44
100 coups de traitement linguistique (2020): 41
100 coups de traitement linguistique (2020): 37
100 coups de traitement linguistique (2020): 25
100 coups de traitement linguistique (2020): 23
100 coups de traitement linguistique (2020): 33
100 coups de traitement linguistique (2020): 20
100 coups de traitement linguistique (2020): 27
[Traitement du langage 100 coups 2020] Chapitre 4: Analyse morphologique
100 coups de traitement linguistique (2020): 46
100 coups de traitement linguistique (2020): 21
100 coups de traitement linguistique (2020): 36
Traitement du langage 100 coups Chapitre 4: Analyse morphologique 31. Verbes
100 coups de traitement du langage amateur: 41
100 coups de traitement du langage amateur: 71
100 coups de traitement du langage amateur: 56
100 coups de traitement du langage amateur: 24
100 coups de traitement du langage amateur: 50
100 coups de traitement du langage amateur: 59
100 coups de traitement du langage amateur: 62
100 coups de traitement du langage amateur: 60
100 Language Processing Knock 2020 Chapitre 1
100 coups de traitement du langage amateur: 92
100 coups de langue amateur: 30
100 coups de langue amateur: 06
100 coups de traitement du langage amateur: 84
100 coups de traitement du langage amateur: 81
100 coups de langue amateur: 33
100 coups de traitement du langage amateur: 46
100 coups de traitement du langage amateur: 88