[PYTHON] Jugement négatif / positif des phrases et visualisation des motifs par Transformer

1.Tout d'abord

Je lis ** "Developmental Deep Learning avec PyTorch" **. Cette fois, j'ai étudié Transformer au chapitre 7, donc j'aimerais produire mon propre résumé.

2. Qu'est-ce que Transformer?

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.

スクリーンショット 2020-07-28 10.04.07.png

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.

スクリーンショット 2020-07-28 13.55.03.png 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 スクリーンショット 2020-07-28 18.41.37.png

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

スクリーンショット 2020-07-28 18.30.55.png

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 スクリーンショット 2020-07-29 09.37.30.png

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.

3. Modèle à mettre en œuvre cette fois

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. スクリーンショット 2020-07-27 16.53.25.png

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.

4. Code du modèle

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 est inséré doit être 0 lorsqu'elle est multipliée par softmax, donc la partie correspondante est remplacée par moins l'infini (-1e9).

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.

5. Code complet et exécution

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

スクリーンショット 2020-07-29 10.46.38.png

De cette manière, le résultat du jugement et sa base sont affichés.

6. Essayez-le avec un ensemble de données japonais.

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 PyTorchJ'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

Jugement négatif / positif des phrases et visualisation des motifs par Transformer
Jugement négatif / positif des phrases par BERT et visualisation des motifs
Analyse des données financières par pandas et leur visualisation (1)
Comment visualiser les données par variable explicative et variable objective
Visualisation des données par préfecture
Visualisation de la matrice créée par numpy
Jugement du if par la notation d'inclusion de liste
Analyse négative / positive 1 Application de l'analyse de texte
Agrégation et visualisation des nombres accumulés
[Python] J'ai analysé le journal d'un homme au cours de sa première année de vie professionnelle et j'ai fait un jugement positif / négatif sur la vie professionnelle.
Visualisation de corrélation entre la quantité de caractéristiques et la variable objective
Création d'un classificateur négatif / positif à l'aide de BERT
Déterminez s'il s'agit de mon enfant à partir de l'image du chien Shiba par apprentissage profond (4) Visualisation par Grad-CAM et Grad-CAM guidé