Dieser Artikel ist der 11. Tagesartikel von Ibaradai Adventskalender 2019.
Implementieren Sie Word2Vec mit Pytorch. Word2Vec Als ich darüber nachdachte, Word2Vec zu erstellen, wurden viele Artikel über Gensim getroffen, aber da es nur wenige Artikel gab, die Word2Vec mit Pytorch implementierten, entschied ich mich, es zu veröffentlichen. Da es viele Artikel gibt, die Word2Vec erklären, werde ich es kurz erklären.
Skip-gram
Überspring-Gramm maximiert die Ausgabewahrscheinlichkeit der umgebenden Wortfolge $ {, w (t-1), w (t + 1)} $, wenn das Eingabewort $ w (t) $ gegeben ist. Daher ist die zu minimierende Zielvariable wie folgt.
\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}
Hier ist das Molekül ein Wort für die Fenstergröße, aber der Nenner muss die Gesamtzahl der Wörter berechnen. Da dies nicht möglich ist, werden wir es mit einer negativen Stichprobe approximieren.
Die durch negative Abtastung ausgegebenen Wörter werden durch die Häufigkeit des Auftretens der Wörter bestimmt, wie in Referenz [1] gezeigt. Das Programm sieht folgendermaßen aus:
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
Es gibt auch eine Möglichkeit, ein Wort mit Onehot zum Eingeben eines Wortes auszudrücken. Dies würde jedoch die Dimension um die Anzahl der Wörter erhöhen. Wenden Sie es nach dem Konvertieren in einen Wortvektor mithilfe der Einbettungsschicht auf Encoder und Decoder an. Die Auswertung nimmt das innere Produkt von Wortvektoren und gibt es mit der Log-Sigmoid-Funktion aus. Die Berechnungsformel lautet wie folgt.
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)
#Innenprodukt
##Positives Beispiel
score = torch.matmul(embed_ctx, torch.t(embed_center))
score = torch.sum(score, dim=2).view(1, -1)
log_target = F.logsigmoid(score)
##Negatives Beispiel
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))
Es scheint üblich zu sein, die Einbettung von Encoder und Decoder zu trennen. Da es sich um ein Maximierungsproblem handelt, wird es mit einem Minus multipliziert.
Die Genauigkeit ist insgesamt nicht gut, und es ist erforderlich, den Scheduler und die Lernrate entsprechend einzustellen.
Ich habe den Code nicht organisiert, daher werde ich den gesamten Code nach der Organisation veröffentlichen.
[1] Distributed Representations of Words and Phrases and their Compositionality [2] word2vec Parameter Learning Explained
Recommended Posts