Je lis ** "Developmental Deep Learning avec PyTorch" **. Cette fois, j'ai étudié Transformer au chapitre 7, donc j'aimerais produire mon propre résumé.
En 2017, un article d'époque ** "Attention All You Need" ** a été publié dans le domaine du traitement du langage naturel. Le modèle proposé était ** Transformer **, qui a réalisé SoTA avec ** Attention ** uniquement, sans utiliser aucun des RNN précédemment courants dans les tâches de traduction.
Depuis lors, les modèles basés sur ce Transformer, tels que ** BERT, XLNet et ALBERT **, ont dominé le domaine du traitement du langage naturel, et il a fini par s'appeler Transformer pour le traitement du langage naturel.
Voici un diagramme modèle du Transformer qui effectue la tâche de traduction. Par exemple, en considérant la traduction japonais-anglais, le ** Encodeur ** sur le côté gauche apprend l'attention de chaque mot dans la phrase japonaise, et le ** Décodeur ** sur le côté droit apprend l'attention de chaque mot dans la phrase anglaise tout en se référant à cette information. est. Maintenant, laissez-moi vous expliquer cinq fonctionnalités.
1) Psitional Encoding Le principal objectif de Transformer est d'utiliser le GPU et d'augmenter considérablement la vitesse de traitement en traitant tous les mots ** en parallèle ** pour chaque phrase, au lieu de traiter les mots un par un comme RNN. .. Par conséquent, le ** codage positionnel ** ajoute des informations d'ordre des mots à chaque mot pour éviter que les informations d'ordre des mots ne soient perdues en raison du traitement parallèle.
2) Scaled dot-product Attention C'est le cœur de Transformer, je vais donc l'expliquer un peu plus attentivement. Pour le calcul de l'attention, ** Requête ** (vecteur de mots pour lequel l'attention est calculée), ** Clé ** (collection de vecteurs de mots utilisés pour le calcul de pertinence), ** Valeur ** (vecteur utilisé pour le calcul de la somme pondérée) 3) apparaîtra.
J'expliquerai comment calculer l'Attention de "Je" lorsque la phrase est composée de 5 mots, "I", "Ha", "Cat", "De" et "Aru".
Puisque le degré de pertinence peut être calculé par le produit interne des vecteurs, le produit interne du vecteur "I" ** Query ** et de la matrice transposée ** $ Key ^ T $ ** des cinq vecteurs de mot est pris. Ensuite, en divisant par $ {\ sqrt {d_k}} $ puis en multipliant par Softmax, vous pouvez trouver le poids (** Attention Weight **) qui indique quel mot est lié à "I" et combien.
La raison de la division par $ {\ sqrt {d_k}} $ est que s'il y a une valeur trop grande dans le calcul interne du produit, lorsque Softmax est multiplié, les autres valeurs peuvent devenir 0.
Ensuite, par ** produit interne ** de ** Poids d'attention ** et une matrice de cinq vecteurs de mots ** Valeur **, la composante vectorielle des mots étroitement liés à «I» est dominante * * Le vecteur de contexte peut être calculé. C'est le calcul de l'attention du «je».
À propos, Trindformer peut effectuer des calculs parallèles et peut calculer toutes les requêtes à la fois, donc
De cette façon, le calcul de l'attention de toutes les requêtes est effectué en une seule fois. Ce calcul est exprimé par la formule suivante dans l'article.
Attention(Q, K, V)=softmax(\frac{QK^T}{\sqrt{d_k}})V
3) Multi-Head Attention
Entrée vers le produit scalaire mis à l'échelle Attention ** Query, Key, Value ** a une structure dans laquelle la sortie de l'étape précédente entre via chaque couche entièrement connectée. En d'autres termes, la sortie de l'étape précédente est multipliée par les poids respectivement $ W_q, W_k et W_v $. À ce stade, plutôt que d'avoir une grande requête, clé, ensemble de valeurs (appelé une tête), ont plusieurs petites têtes de requête, clé, valeur, et chaque tête a une expression latente $ W_q, W_k, ** Multi-Head Attention ** montre que les performances sont améliorées en calculant W_v $ et en en faisant un à la fin.
4) Musked Multi-Head Attention
L'attention côté décodeur est également calculée en parallèle, mais lors du calcul de l'Attention de "I", si "suis", "a", "chat" sont inclus dans la cible de calcul, le mot à prédire sera mis en boîte. Alors, masquez le mot précédent dans la clé pour le rendre invisible. L'attention multi-têtes avec cette fonction est appelée ** Attention multi-têtes musquée **.
5) Position-wise Feed-Forward Networks Il s'agit d'une unité qui convertit la quantité d'entités de la sortie de la couche Attention avec deux couches entièrement connectées. L'entrée est (nombre de mots, nombre de dimensions incorporées des mots), et le produit de ceci et des poids des deux couches entièrement connectées est la sortie (nombre de mots, nombre de dimensions incorporées des mots). Nous l'avons nommé ** Position -wise ** car il semble qu'il y ait un réseau neuronal indépendant pour chaque mot.
Cette fois, nous allons implémenter un modèle qui peut résoudre la tâche de classification en apprenant l'attention de chaque mot de la phrase ** en utilisant uniquement l'encodeur sur le côté gauche du modèle de traduction Transformer. De plus, en donnant la priorité à la clarté, c'est une attention à une seule tête, pas une attention à plusieurs têtes.
L'ensemble de données utilisé est ** IMDb ** (Internet Movie Dataset), qui résume si le contenu de la critique du film (en anglais) est positif ou négatif.
En entraînant le modèle, lorsque vous entrez une critique d'un film, ** déterminez si la critique est positive ou négative **, et à partir de l'attention mutuelle des mots de la critique ** indiquez clairement le mot sur lequel la décision était basée * * Laisse moi faire.
Ensuite, je voudrais mettre en œuvre dans l'ordre à partir de l'entrée.
class Embedder(nn.Module):
'''Convertit le mot indiqué par id en vecteur'''
def __init__(self, text_embedding_vectors):
super(Embedder, self).__init__()
self.embeddings = nn.Embedding.from_pretrained(
embeddings=text_embedding_vectors, freeze=True)
# freeze=True ne sera pas mis à jour et ne changera pas dans la propagation arrière
def forward(self, x):
x_vec = self.embeddings(x)
return x_vec
C'est la partie qui convertit le mot ID en un vecteur intégré à l'aide de l'unité nn.Embedding de Pytorch.
class PositionalEncoder(nn.Module):
'''Ajouter des informations vectorielles indiquant la position du mot saisi'''
def __init__(self, d_model=300, max_seq_len=256):
super().__init__()
self.d_model = d_model #Nombre de dimensions du vecteur de mot
#Créer une table de valeurs comme pe qui est uniquement déterminée par l'ordre des mots (pos) et la position de la dimension du vecteur incorporé (i)
pe = torch.zeros(max_seq_len, d_model)
#Envoyer au GPU si le GPU est disponible
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
pe = pe.to(device)
for pos in range(max_seq_len):
for i in range(0, d_model, 2):
pe[pos, i] = math.sin(pos / (10000 ** ((2 * i)/d_model)))
pe[pos, i + 1] = math.cos(pos / (10000 ** ((2 * i)/d_model)))
#Ajouter la dimension mini-lot au début du tableau pe
self.pe = pe.unsqueeze(0)
#Évitez de calculer le gradient
self.pe.requires_grad = False
def forward(self, x):
#Ajouter l'entrée x et le codage positif
#x est plus petit que pe, alors agrandissez-le
ret = math.sqrt(self.d_model)*x + self.pe
return ret
Il s'agit de la partie codeur positionnel.
class Attention(nn.Module):
'''Le transformateur est vraiment un multi-tête Attention,
La priorité est donnée à la clarté et mise en œuvre avec une seule attention'''
def __init__(self, d_model=300):
super().__init__()
#SAGAN a utilisé 1dConv, mais cette fois, la quantité d'entités est convertie dans la couche entièrement connectée.
self.q_linear = nn.Linear(d_model, d_model)
self.v_linear = nn.Linear(d_model, d_model)
self.k_linear = nn.Linear(d_model, d_model)
#Couche entièrement connectée utilisée pour la sortie
self.out = nn.Linear(d_model, d_model)
#Attention, variable d'ajustement de la taille
self.d_k = d_model
def forward(self, q, k, v, mask):
#Convertir des entités en couches entièrement connectées
k = self.k_linear(k)
q = self.q_linear(q)
v = self.v_linear(v)
#Calculez la valeur de Attention
#Si vous ajoutez chaque valeur, elle sera trop grande, donc root(d_k)Divisez et ajustez
weights = torch.matmul(q, k.transpose(1, 2)) / math.sqrt(self.d_k)
#Calculer le masque ici
mask = mask.unsqueeze(1)
weights = weights.masked_fill(mask == 0, -1e9)
#Standardisez avec softmax
normlized_weights = F.softmax(weights, dim=-1)
#Multiplier l'attention par la valeur
output = torch.matmul(normlized_weights, v)
#Convertir des entités en couches entièrement connectées
output = self.out(output)
return output, normlized_weights
C'est la partie Attention. Dans le calcul du masque ici, la partie où les données de texte sont courtes et
class FeedForward(nn.Module):
def __init__(self, d_model, d_ff=1024, dropout=0.1):
'''C'est une unité qui convertit simplement la quantité d'entités de la couche Attention avec deux couches entièrement connectées.'''
super().__init__()
self.linear_1 = nn.Linear(d_model, d_ff)
self.dropout = nn.Dropout(dropout)
self.linear_2 = nn.Linear(d_ff, d_model)
def forward(self, x):
x = self.linear_1(x)
x = self.dropout(F.relu(x))
x = self.linear_2(x)
return x
C'est la partie Feed Forward. Il s'agit d'une simple couche entièrement connectée à deux couches.
class TransformerBlock(nn.Module):
def __init__(self, d_model, dropout=0.1):
super().__init__()
#Calque de normalisation des calques
# https://pytorch.org/docs/stable/nn.html?highlight=layernorm
self.norm_1 = nn.LayerNorm(d_model)
self.norm_2 = nn.LayerNorm(d_model)
#Couche d'attention
self.attn = Attention(d_model)
#Deux couches entièrement connectées après Attention
self.ff = FeedForward(d_model)
# Dropout
self.dropout_1 = nn.Dropout(dropout)
self.dropout_2 = nn.Dropout(dropout)
def forward(self, x, mask):
#Normalisation et attention
x_normlized = self.norm_1(x)
output, normlized_weights = self.attn(
x_normlized, x_normlized, x_normlized, mask)
x2 = x + self.dropout_1(output)
#Normalisation et couche entièrement connectée
x_normlized2 = self.norm_2(x2)
output = x2 + self.dropout_2(self.ff(x_normlized2))
return output, normlized_weights
C'est la partie qui crée un bloc de transformateur en combinant Attention et Feed Foward. Les deux sont multipliés par ** Normalisation de couche ** et ** Suppression **, ainsi que ** liaison de résidu ** similaire à ResNet.
class ClassificationHead(nn.Module):
'''Transformer_Utiliser la sortie de bloc et enfin classer'''
def __init__(self, d_model=300, output_dim=2):
super().__init__()
#Couche entièrement connectée
self.linear = nn.Linear(d_model, output_dim) # output_dim est deux positif et négatif
#Traitement d'initialisation du poids
nn.init.normal_(self.linear.weight, std=0.02)
nn.init.normal_(self.linear.bias, 0)
def forward(self, x):
x0 = x[:, 0, :] #Extraire la quantité de caractéristiques (300 dimensions) du premier mot de chaque phrase de chaque mini-lot
out = self.linear(x0)
return out
Enfin, c'est la partie qui porte un jugement négatif / positif. En classant en utilisant les caractéristiques du premier mot de chaque phrase et en rétropropageant la perte à apprendre, les caractéristiques du premier mot deviennent naturellement les caractéristiques qui jugent le négatif / positif de la phrase.
class TransformerClassification(nn.Module):
'''Classifier avec Transformer'''
def __init__(self, text_embedding_vectors, d_model=300, max_seq_len=256, output_dim=2):
super().__init__()
#Construction de modèles
self.net1 = Embedder(text_embedding_vectors)
self.net2 = PositionalEncoder(d_model=d_model, max_seq_len=max_seq_len)
self.net3_1 = TransformerBlock(d_model=d_model)
self.net3_2 = TransformerBlock(d_model=d_model)
self.net4 = ClassificationHead(output_dim=output_dim, d_model=d_model)
def forward(self, x, mask):
x1 = self.net1(x) #Les mots en vecteurs
x2 = self.net2(x1) #Ajouter des informations de position
x3_1, normlized_weights_1 = self.net3_1(
x2, mask) # Self-Convertissez des fonctionnalités avec attention
x3_2, normlized_weights_2 = self.net3_2(
x3_1, mask) # Self-Convertissez des fonctionnalités avec attention
x4 = self.net4(x3_2) #Classification 0 utilisant le 0ème mot de la sortie finale-Sortie 1 scalaire
return x4, normlized_weights_1, normlized_weights_2
C'est la partie qui construit finalement le modèle entier en utilisant les classes définies jusqu'à présent.
L'ensemble du code a été créé dans Google Colab et publié sur Github, donc si vous voulez l'essayer vous-même, ce [** "lien" **](https://github.com/cedro3/Transformer/blob/master/ Vous pouvez le déplacer en cliquant sur transformer_en_run.ipynb) et en cliquant sur le bouton "Colab on Web" en haut de la feuille affichée.
Quand tu cours
De cette manière, le résultat du jugement et sa base sont affichés.
Lorsque je naviguais sur divers sites Web, il y avait un exemple d'extraction de phrases du rapport sur les valeurs mobilières d'une société cotée japonaise appelée ** chABSA-dataset **, faisant un jugement négatif / positif et affichant la base de jugement, donc je l'ai également résumé sur Google Colab. Vu. Si vous voulez l'essayer vous-même, cliquez sur ce ** "lien" ** et il sera en haut de la feuille affichée. Vous pouvez le déplacer en cliquant sur le bouton "Colab on Web".
(référence) ・ Apprenez en créant! Apprentissage en profondeur par PyTorch ・ J'ai créé une application d'analyse négative / positive avec apprentissage en profondeur (python) [Partie 1] ・ Commentaire d'article Attention Is All You Need (Transformer)
Recommended Posts