In Sequence to Sequence (Seq2Seq), qui est une sorte de modèle EncoderDecoder, Un modèle d'attention sera présenté, et ses résultats de mise en œuvre et de vérification seront expliqués.
Dernière fois http://qiita.com/kenchin110100/items/b34f5106d5a211f4c004 J'ai implémenté le modèle Sequence to Sequence (Seq2Seq) avec Chainer, Cette fois, j'ai ajouté Attention Model à ce modèle.
Dans ce qui suit, nous expliquerons le modèle d'attention, sa méthode de mise en œuvre et les résultats de la vérification.
Attention Model
En utilisant un réseau RNN tel que LSTM, les données de série telles que les instructions peuvent être converties en vecteurs de caractéristiques.
Cependant, les données initialement entrées sont moins susceptibles d'être reflétées dans le vecteur de caractéristiques de sortie final.
En d'autres termes, la phrase «Maman s'est maquillée, a mis une jupe et est sortie en ville» et la phrase «Papa s'est maquillé, a mis une jupe et est allé en ville» est devenue presque le même vecteur de caractéristiques. Cela signifie que cela finira.
Le modèle d'attention est un mécanisme qui considère correctement les données saisies au début.
Sequence to Sequence with Attention Model
La figure ci-dessous montre le flux de calcul du modèle Seq2Seq implémenté la dernière fois.
Sequence to Sequence |
---|
(Le chiffre est légèrement différent du précédent)
La partie bleue est le codeur qui vectorise la parole et la partie rouge est le décodeur qui émet la réponse du vecteur.
Si vous ajoutez le modèle Attention à cela, il ressemblera à la figure ci-dessous.
Sequence to Sequence with Attention model |
---|
Ce sera un peu compliqué, mais l'endroit où [At] est écrit dans la figure est le modèle d'attention.
Du côté de l'encodeur, le vecteur intermédiaire qui est émis à chaque fois est stocké dans le modèle d'attention.
Du côté du décodeur, le vecteur intermédiaire précédent est entré dans le modèle d'attention. Sur la base du vecteur d'entrée, Attention Model prend la moyenne pondérée de l'entrée de vecteur intermédiaire côté encodeur et retourne.
En entrant la moyenne pondérée du vecteur intermédiaire de l'encodeur dans le décodeur, le modèle d'attention permet de prêter attention au mot avant, au mot après et n'importe où.
Il existe deux principaux types de modèles d'attention, appelés Attention globale et Attention locale.
Dans ce qui suit, nous expliquerons l'attention globale et l'attention locale.
Global Attention
Le document suivant propose une attention mondiale.
Bahdanau, Dzmitry, Kyunghyun Cho, and Yoshua Bengio. "Neural machine translation by jointly learning to align and translate." arXiv preprint arXiv:1409.0473 (2014).
À l'origine, il était utilisé dans la traduction automatique.
Le matériel qui explique Global Attention est https://www.slideshare.net/yutakikuchi927/deep-learning-nlp-attention Est facile à comprendre.
Ce sera en anglais, https://talbaumel.github.io/attention/ Est également facile à comprendre.
Le mécanisme de prise de la moyenne pondérée de l'entrée vectorielle intermédiaire du côté du codeur est illustré ci-dessous.
Global Attention |
---|
La figure considère un état dans lequel trois vecteurs [vecteur intermédiaire 1], [vecteur intermédiaire 2] et [vecteur intermédiaire 3] sont entrés du côté du codeur.
Dans la figure, [eh] et [hh] sont des couches de couplage linéaire qui sortent le vecteur de la taille de la couche cachée à partir du vecteur de la taille de la couche cachée, [+] est l'addition des vecteurs et [×] est la multiplication de chaque élément du vecteur. Je suis.
[tanh] est une tangente hyperbolique qui transforme les éléments d'un vecteur de -1 en 1.
[hw] est une couche de couplage linéaire qui génère un scalaire de taille 1 à partir de la taille de la couche cachée.
[soft max] est une fonction SoftMax qui normalise les valeurs entrées afin que la somme soit 1.
La valeur calculée par [Soft max] est utilisée comme poids de la moyenne pondérée, et le résultat de la prise de la moyenne pondérée du vecteur intermédiaire est sorti.
C'est ainsi que fonctionne Global Attention.
Local Attention
Les articles suivants ont proposé une attention locale
Luong, Minh-Thang, Hieu Pham, and Christopher D. Manning. "Effective approaches to attention-based neural machine translation." arXiv preprint arXiv:1508.04025 (2015).
Il s'agit également d'un document de traduction automatique.
Le matériel de référence est https://www.slideshare.net/yutakikuchi927/deep-learning-nlp-attention est.
Voici un organigramme de calcul de Local Attention.
Local Attention |
---|
Plus de réseaux ont été ajoutés à Global Attention.
La principale différence est le réseau de droite.
[ht] est une jointure linéaire qui génère le vecteur de taille de couche cachée à partir du vecteur de taille de couche cachée, et [tanh] fonctionne comme précédemment, en mettant à l'échelle les éléments du vecteur de -1 à 1.
[tw] est une couche couplée linéairement qui transforme un vecteur de taille de couche cachée en scalaire, et [sigmoïde] est une fonction sigmoïde qui met à l'échelle la valeur d'entrée de 0 à 1. Par conséquent, le vecteur entré jusqu'à présent sera un scalaire compris entre 0 et 1.
Ensuite, j'expliquerai ce que vous faites avec [ga] dans la figure. Le calcul de ga est le suivant.
output = \exp\bigl(-\frac{(s - input * Len(S))^2}{\sigma^2}\bigl)
Où $ input $ est un scalaire mis à l'échelle de 0 à 1, $ Len (S) $ est le nombre de vecteurs intermédiaires entrés par le codeur, et $ s $ est l'ordre des vecteurs de couche intermédiaire ([vecteur intermédiaire 1]]. Si c'est 1, cela représente 2) si c'est [vecteur intermédiaire 2].
Si la valeur sortie par la fonction sigmoïde est 0,1, [ga] sera une grande valeur lorsque le vecteur intermédiaire est 1, et une petite valeur lorsque le vecteur intermédiaire est 3.
En multipliant cette sortie par le poids calculé par Global Attention, il est possible de se concentrer davantage sur un vecteur intermédiaire spécifique.
Je l'ai implémenté avec le chainer comme avant. La partie Encoder est la même que pour Seq2Seq.
Le code auquel j'ai fait référence provient de l'ODA. Merci beaucoup. https://github.com/odashi/chainer_examples
Attention
J'ai implémenté Global Attention. Le code est comme suit
attention.py
class Attention(Chain):
def __init__(self, hidden_size, flag_gpu):
"""
Instanciation de l'attention
:param hidden_size:Taille du calque masqué
:param flag_gpu:Utiliser ou non le GPU
"""
super(Attention, self).__init__(
#Une couche de couplage linéaire qui transforme le vecteur intermédiaire de l'encodeur avant en un vecteur de taille de couche caché
fh=links.Linear(hidden_size, hidden_size),
#Une couche de couplage linéaire qui transforme le vecteur intermédiaire de l'encodeur inverse en un vecteur de taille de couche caché
bh=links.Linear(hidden_size, hidden_size),
#Une couche de couplage linéaire qui transforme le vecteur intermédiaire du décodeur en un vecteur de taille de couche caché
hh=links.Linear(hidden_size, hidden_size),
#Couche de couplage linéaire pour convertir des vecteurs de taille de couche cachés en scalaires
hw=links.Linear(hidden_size, 1),
)
#Souvenez-vous de la taille du calque caché
self.hidden_size = hidden_size
#Utilisez numpy lorsque vous n'utilisez pas cupy lors de l'utilisation de GPU
if flag_gpu:
self.ARR = cuda.cupy
else:
self.ARR = np
def __call__(self, fs, bs, h):
"""
Calcul de l'attention
:param fs:Une liste de vecteurs intermédiaires d'encodeur avant
:param bs:Liste des vecteurs intermédiaires du codeur inverse
:param h:Sortie vectorielle intermédiaire par le décodeur
:return:Moyenne pondérée du vecteur intermédiaire du codeur direct et moyenne pondérée du vecteur intermédiaire du codeur inverse
"""
#Rappelez-vous la taille du mini lot
batch_size = h.data.shape[0]
#Initialisation de la liste pour enregistrer les poids
ws = []
#Initialisez la valeur pour calculer la valeur totale du poids
sum_w = Variable(self.ARR.zeros((batch_size, 1), dtype='float32'))
#Calcul du poids à l'aide du vecteur intermédiaire de l'encodeur et du vecteur intermédiaire du décodeur
for f, b in zip(fs, bs):
#Calcul du poids à l'aide du vecteur intermédiaire du codeur direct, du vecteur intermédiaire du codeur inverse, du vecteur intermédiaire du décodeur
w = functions.tanh(self.fh(f)+self.bh(b)+self.hh(h))
#Normaliser à l'aide de la fonction softmax
w = functions.exp(self.hw(w))
#Enregistrez le poids calculé
ws.append(w)
sum_w += w
#Initialisation du vecteur moyen pondéré en sortie
att_f = Variable(self.ARR.zeros((batch_size, self.hidden_size), dtype='float32'))
att_b = Variable(self.ARR.zeros((batch_size, self.hidden_size), dtype='float32'))
for f, b, w in zip(fs, bs, ws):
#Normalisé pour que la somme des poids soit 1.
w /= sum_w
#poids*Ajouter le vecteur intermédiaire de l'encodeur au vecteur de sortie
att_f += functions.reshape(functions.batch_matmul(f, w), (batch_size, self.hidden_size))
att_b += functions.reshape(functions.batch_matmul(b, w), (batch_size, self.hidden_size))
return att_f, att_b
Dans l'explication, un seul encodeur a été utilisé, mais en fait, il est courant d'utiliser deux types d'encodeur, encodeur avant et encodeur inverse dans Attention Model.
Ainsi, lors du calcul de Attention, nous passons deux listes de vecteurs intermédiaires calculés par le codeur avant et une liste de vecteurs intermédiaires calculés par le codeur inverse.
Decoder
Contrairement au cas de Seq2Seq, les valeurs entrées par Decoder sont trois, le vecteur de mot, le vecteur intermédiaire calculé par Decoder, et la moyenne pondérée du vecteur intermédiaire de Encoder. Je réécris donc l'implémentation de Decoder.
att_decoder.py
class Att_LSTM_Decoder(Chain):
def __init__(self, vocab_size, embed_size, hidden_size):
"""
Instanciation du décodeur pour le modèle Attention
:param vocab_size:Nombre de vocabulaire
:param embed_size:Taille du vecteur Word
:param hidden_size:Taille du calque masqué
"""
super(Att_LSTM_Decoder, self).__init__(
#Couche pour convertir des mots en vecteurs de mots
ye=links.EmbedID(vocab_size, embed_size, ignore_label=-1),
#Un calque qui transforme un vecteur de mot en un vecteur quatre fois plus grand que le calque masqué
eh=links.Linear(embed_size, 4 * hidden_size),
#Un calque qui transforme le vecteur intermédiaire du décodeur en un vecteur quatre fois plus grand que le calque caché
hh=links.Linear(hidden_size, 4 * hidden_size),
#Un calque qui transforme la moyenne pondérée du vecteur intermédiaire de l'encodeur avant en un vecteur quatre fois la taille du calque caché
fh=links.Linear(hidden_size, 4 * hidden_size),
#Un calque qui transforme la moyenne pondérée du vecteur intermédiaire de l'encodeur avant en un vecteur quatre fois la taille du calque caché
bh=links.Linear(hidden_size, 4 * hidden_size),
#Un calque qui convertit un vecteur de taille de calque masqué en la taille d'un vecteur de mot
he=links.Linear(hidden_size, embed_size),
#Couche pour convertir le vecteur de mot en vecteur de taille de vocabulaire
ey=links.Linear(embed_size, vocab_size)
)
def __call__(self, y, c, h, f, b):
"""
Calcul du décodeur
:param y:Mots à entrer dans le décodeur
:param c:Mémoire interne
:param h:Vecteur intermédiaire décodeur
:param f:Moyenne pondérée du codeur direct calculé par le modèle Attention
:param b:Moyenne pondérée du codeur inversé calculée par Attention Model
:return:Dictionnaire de la taille du vocabulaire, mémoire interne mise à jour, vecteur intermédiaire mis à jour
"""
#Convertir des mots en vecteurs de mots
e = functions.tanh(self.ye(y))
#LSTM avec vecteur de mots, vecteur intermédiaire du décodeur, Attention du codeur avant, Attention du codeur inverse
c, h = functions.lstm(c, self.eh(e) + self.hh(h) + self.fh(f) + self.bh(b))
#Convertir la sortie vectorielle intermédiaire de LSTM en vecteur de taille lexicale
t = self.ey(functions.tanh(self.he(h)))
return t, c, h
L'utilisation d'un vecteur quatre fois la taille du calque caché est la même raison que j'ai expliquée la dernière fois.
Nous avons ajouté des couches [fh] et [bh] pour utiliser la moyenne pondérée des vecteurs intermédiaires de l'encodeur calculé par Attention, mais sinon, ce sont les mêmes.
Seq2Seq with Attention
Le modèle qui combine Encodeur, Décodeur et Attention est le suivant.
att_seq2seq.py
class Att_Seq2Seq(Chain):
def __init__(self, vocab_size, embed_size, hidden_size, batch_size, flag_gpu=True):
"""
Seq2Seq +Instanciation de l'attention
:param vocab_size:Nombre de taille de vocabulaire
:param embed_size:Taille du vecteur Word
:param hidden_size:Taille du calque masqué
:param batch_size:Mini taille de lot
:param flag_gpu:Utiliser ou non le GPU
"""
super(Att_Seq2Seq, self).__init__(
#Encodeur avant
f_encoder = LSTM_Encoder(vocab_size, embed_size, hidden_size),
#Encodeur inversé
b_encoder = LSTM_Encoder(vocab_size, embed_size, hidden_size),
# Attention Model
attention = Attention(hidden_size, flag_gpu),
# Decoder
decoder = Att_LSTM_Decoder(vocab_size, embed_size, hidden_size)
)
self.vocab_size = vocab_size
self.embed_size = embed_size
self.hidden_size = hidden_size
self.batch_size = batch_size
#Cupy lors de l'utilisation de GPU, numpy lorsque vous ne l'utilisez pas
if flag_gpu:
self.ARR = cuda.cupy
else:
self.ARR = np
#Initialisez la liste pour stocker le vecteur intermédiaire de l'encodeur avant et le vecteur intermédiaire de l'encodeur inverse
self.fs = []
self.bs = []
def encode(self, words):
"""
Calcul du codeur
:param words:Une liste enregistrée de mots à utiliser dans votre saisie
:return:
"""
#Mémoire interne, initialisation du vecteur intermédiaire
c = Variable(self.ARR.zeros((self.batch_size, self.hidden_size), dtype='float32'))
h = Variable(self.ARR.zeros((self.batch_size, self.hidden_size), dtype='float32'))
#Tout d'abord, calculez l'encodeur avant
for w in words:
c, h = self.f_encoder(w, c, h)
#Enregistrer le vecteur intermédiaire calculé
self.fs.append(h)
#Mémoire interne, initialisation du vecteur intermédiaire
c = Variable(self.ARR.zeros((self.batch_size, self.hidden_size), dtype='float32'))
h = Variable(self.ARR.zeros((self.batch_size, self.hidden_size), dtype='float32'))
#Calcul du codeur inversé
for w in reversed(words):
c, h = self.b_encoder(w, c, h)
#Enregistrer le vecteur intermédiaire calculé
self.bs.insert(0, h)
#Mémoire interne, initialisation du vecteur intermédiaire
self.c = Variable(self.ARR.zeros((self.batch_size, self.hidden_size), dtype='float32'))
self.h = Variable(self.ARR.zeros((self.batch_size, self.hidden_size), dtype='float32'))
def decode(self, w):
"""
Calcul du décodeur
:param w:Mots à saisir avec Decoder
:return:Mot prédictif
"""
#Calculez la moyenne pondérée de la couche intermédiaire de l'encodeur à l'aide du modèle Attention
att_f, att_b = self.attention(self.fs, self.bs, self.h)
#Utilisation du vecteur intermédiaire de Decoder, attention avant, attention inverse
#Calcul du prochain vecteur intermédiaire, mémoire interne, mot prédit
t, self.c, self.h = self.decoder(w, self.c, self.h, att_f, att_b)
return t
def reset(self):
"""
Initialiser les variables d'instance
:return:
"""
#Mémoire interne, initialisation du vecteur intermédiaire
self.c = Variable(self.ARR.zeros((self.batch_size, self.hidden_size), dtype='float32'))
self.h = Variable(self.ARR.zeros((self.batch_size, self.hidden_size), dtype='float32'))
#Initialisation de la liste qui enregistre le vecteur intermédiaire de l'encodeur
self.fs = []
self.bs = []
#Initialisation du gradient
self.zerograds()
Il utilise un total de trois LSTM: encodeur direct, encodeur inverse et décodeur.
Le calcul avant et le calcul du train sont les mêmes que pour Seq2Seq.
Le code créé est https://github.com/kenchin110100/machine_learning/blob/master/sampleAttSeq2Seq.py C'est dedans.
J'ai utilisé le corpus des échecs de dialogue comme je l'ai fait la dernière fois. https://sites.google.com/site/dialoguebreakdowndetection/chat-dialogue-corpus
Il existe 4 types d'énoncés comme avant
Voyons la réponse pour chaque époque.
Première 1 époque
Parlant:Bonjour=>réponse: ['alors', 'est', 'Hey', '</s>']
Parlant:Comment ça va?=>réponse: ['Oui', '、', 'quoi', 'À', 'Vous voyez', 'main', 'Masu', 'Ou', '?', '</s>']
Parlant:j'ai faim=>réponse: ['Oui', '</s>']
Parlant:Il fait chaud aujourd'hui=>réponse: ['Oui', '、', 'quoi', 'À', 'Vous voyez', 'main', 'Masu', 'Ou', '?', '</s>']
Avez-vous eu un regard si méchant ...
3Epoch
Parlant:Bonjour=>réponse: ['Bonjour.', '</s>']
Parlant:Comment ça va?=>réponse: ['alors', 'est', '</s>']
Parlant:j'ai faim=>réponse: ['coup de chaleur', 'À', 'Qi', 'À', 'Attacher', 'main', 'Masu', 'Ou', '?', '</s>']
Parlant:Il fait chaud aujourd'hui=>réponse: ['coup de chaleur', 'À', 'Qi', 'À', 'Attacher', 'Absent', 'Hmm', 'est', 'Ou', '?', '</s>']
5Epoch
Parlant:Bonjour=>réponse: ['Je vous remercie', '</s>']
Parlant:Comment ça va?=>réponse: ['pastèque', 'Est', 'Comme', 'est', 'Hey', '</s>']
Parlant:j'ai faim=>réponse: ['coup de chaleur', 'À', 'Qi', 'À', 'Attacher', 'main', 'Vouloir', 'est', 'Ou', '?', '</s>']
Parlant:Il fait chaud aujourd'hui=>réponse: ['coup de chaleur', 'À', 'Qi', 'À', 'Attacher', 'main', 'Vouloir', 'est', 'Ou', '?', '</s>']
Je connais déjà le coup de chaleur ...
7Epoch
Parlant:Bonjour=>réponse: ['Bonsoir', '</s>']
Parlant:Comment ça va?=>réponse: ['pastèque', 'Est', 'Je t'aime', 'est', 'Hey', '</s>']
Parlant:j'ai faim=>réponse: ['Bai', 'Bai', '</s>']
Parlant:Il fait chaud aujourd'hui=>réponse: ['coup de chaleur', 'À', 'Qi', 'À', 'Attacher', 'Absent', 'Hmm', 'est', 'Ou', '?', '</s>']
Bonjour => Bonsoir, c'est terrible ...
J'ai l'impression que la précision est pire que celle de Seq2Seq ...
Comme pour Seq2Seq, je n'ai pas été en mesure de bien répondre à la question "Comment ça va?" Peut-être que le mot «ton» n'a pas été utilisé dans le corpus.
Puisque l'original est un corpus d'échec de dialogue, le résultat de l'échec est renvoyé, c'est-à-dire qu'il se peut que vous appreniez bien avec cela ...
La transition de la valeur de perte totale et la comparaison du temps de calcul sont résumées à la fin.
J'ai calculé le modèle Seq2Seq + Attention en utilisant le chainer.
J'ai senti que le temps de calcul était extrêmement long par rapport au cas de Seq2Seq seul. La comparaison de cette zone sera ...
La prochaine fois, j'implémenterai CopyNet ... Je veux le faire.
Recommended Posts