Cet article est le 11ème jour du Calendrier de l'Avent Ibaraki 2019.
Implémentez Word2Vec avec Pytorch. Word2Vec Quand j'ai pensé à construire Word2Vec, de nombreux articles de gensim ont été touchés, mais comme il y avait peu d'articles qui implémentaient Word2Vec en utilisant Pytorch, j'ai décidé de le publier. Puisqu'il existe de nombreux articles qui expliquent Word2Vec, je vais l'expliquer brièvement.
Skip-gram
skip-gram maximise la probabilité de sortie de la séquence de mots environnante $ {, w (t-1), w (t + 1)} $ lorsque le mot d'entrée $ w (t) $ est donné. Par conséquent, la variable objective à minimiser est la suivante.
\begin{align}
E
&= -log p( w_{t-1},w_{t+1} | w_{t} ) \\
&= -log p(w_{t-1},w_{t})*p(w_{t+1},w_{t}) \\
&= -log \prod_{i}\frac{exp(p(w_{i},w_{t}))}{\sum_{j}exp(p(w_{j},w_{t}))}
\end{align}
Ici, la molécule est un mot pour la taille de la fenêtre, mais le dénominateur doit calculer le nombre total de mots. Puisque ce n'est pas possible, nous l'approcherons avec un échantillonnage négatif.
Les mots sortis par échantillonnage négatif sont déterminés par la fréquence d'apparition des mots comme indiqué dans la référence [1]. Le programme ressemble à ceci:
def sample_negative(sample_size):
prob = {}
word2cnt = dict(Counter(list(itertools.chain.from_iterable(corpus))))
pow_sum = sum([v**0.75 for v in word2cnt.values()])
for word in word2cnt:
prob[word] = word_counts[word]**0.75 / pow_sum
words = np.array(list(word2cnt.keys()))
while True:
word_list = []
sampled_index = np.array(multinomial(sample_size, list(prob.values())))
for index, count in enumerate(sampled_index):
for _ in range(count):
word_list.append(words[index])
yield word_list
Il existe également un moyen d'exprimer un mot avec Onehot pour saisir un mot, mais cela augmenterait la dimension du nombre de mots.Après l'avoir converti en vecteur de mot à l'aide du calque d'incorporation, appliquez-le à Encoder et Decoder. L'évaluation prend le produit interne des vecteurs de mots et le produit avec la fonction log sigmoïde. La formule de calcul est la suivante.
L= \sum_{i} log \sigma({v'}_{w_{i}}^{T}v_{w_{I}})+\sum_{i}log \sigma(-{v'}_{w_{i}}^{T}v_{w_{I}})
class SkipGram(nn.Module):
def __init__(self, V, H):
super(SkipGram, self).__init__()
self.encode_embed = nn.Embedding(V, H)
self.decode_embed = nn.Embedding(V, H)
self.encode_embed.weight.data.uniform_(-0.5/H, 0.5/H)
self.decode_embed.weight.data.uniform_(0.0, 0.0)
def forward(self, contexts, center, neg_target):
embed_ctx = self.encode_embed(contexts)
embed_center = self.decode_embed(center)
neg_embed_center= self.encode_embed(neg_target)
#produit intérieur
##Exemple positif
score = torch.matmul(embed_ctx, torch.t(embed_center))
score = torch.sum(score, dim=2).view(1, -1)
log_target = F.logsigmoid(score)
##Exemple négatif
neg_score = torch.matmul(embed_ctx, torch.t(neg_embed_center))
neg_score = -torch.sum(neg_score, dim=2).view(1, -1)
log_neg_target = F.logsigmoid(neg_score)
return -1 * (torch.mean(log_target) + torch.mean(log_neg_target))
Il semble qu'il soit courant de séparer l'incorporation de l'encodeur et du décodeur. Puisqu'il s'agit d'un problème de maximisation, il est multiplié par un moins.
La précision n'est pas bonne dans son ensemble et il est nécessaire de définir le planificateur et le taux d'apprentissage de manière appropriée.
Je n'ai pas organisé le code, je publierai donc tout le code après l'avoir organisé.
[1] Distributed Representations of Words and Phrases and their Compositionality [2] word2vec Parameter Learning Explained
Recommended Posts