[PYTHON] GloVe: Prototyp der Worteinbettung durch Glovalvektoren zur Wortrepräsentation

Einführung

Ein Memorandum über den Prozess der Erstellung von Word Embedding (Embedding Matrix) für die Verarbeitung natürlicher Sprache unter Verwendung des GloVe-Algorithmus.

Zweck

Erstellt, weil Word Embedding erforderlich ist, um einen generativen Chatbot zu erstellen. Transfer Learning, bei dem geschulte Materialien verwendet werden, wird allgemein als praktisch angesehen. Dieses Mal habe ich es mir zur Aufgabe gemacht, mein Verständnis der "eingebetteten Matrix" zu vertiefen, die "analog" ermöglicht, indem ich die Beziehungen zwischen Wörtern in einem linearen Raum vektorisiere.

Umgebung

Datenvorverarbeitung

Ausbildung

Referenzinformationen

GloVe Papers and Commentary Page (Stanford University)

Referenzierter GloVe Python-Code (GitHub)

Verwendeter Python-Code (GitHub)

Japanische Textdaten und Vorverarbeitung

Japanische Textdaten

Verwenden Sie den Wikipedia-Datendump als japanische Textdaten. Die Gesamtgröße beträgt ca. 13GB. So wie es ist, werden XML-Tags usw. angehängt. Verwenden Sie also "WikiExtractor", um den Teil zu extrahieren, den Sie für das Training verwenden möchten.

Japanische Division

Verwenden Sie das morphologische Analysetool MeCab, um japanischen Text in Bedeutungseinheiten zu schreiben.

Herausforderungen konfrontiert

Riesige Datenmenge und RAM-Limit

Zunächst habe ich versucht, nur 100 MB von Wiki Extractor zu verwenden, um eine Testversion zu erstellen.

Jede Zeile enthält eine Reihe von Sätzen. Als ich 100 MB vorverarbeitete Daten übergeben habe, habe ich das RAM-Limit erreicht und die Sitzung ist abgestürzt.

Mit etwa 12 GB RAM in Colab war der starke Anstieg des Speicherdrucks auf die Verwendung einer Matrix zurückzuführen, die Informationen zur Häufigkeit des gleichzeitigen Auftretens von Wort zu Wort enthielt. Die folgenden drei Stellen werden hauptsächlich verwendet.

  1. Beim Erstellen einer Matrix
  2. Bei der Berechnung der Häufigkeit des gleichzeitigen Auftretens
  3. Während des Parametertrainings

Es war zu groß, um es zu handhaben, da es einfach ein Array von Vokabeln x Vokabeln (218.438 x 218.438) verwendet.

Die Methode stattdessen genommen

Die Schulung wurde durchgeführt, indem der Text mit etwa 250.000 Zeilen unterteilt wurde. Nach jedem Datensatztraining wurden nur die Parameter gespeichert und die Matrix, für die die Häufigkeit des gemeinsamen Auftretens berechnet wurde, wurde zurückgesetzt (initialisiert), ohne vererbt zu werden. Auf diese Weise können Sie Ihre Parameter im gesamten Datensatz trainieren und gleichzeitig im verfügbaren Speicher belassen.

Problem

Der Vorteil von GloVe besteht darin, dass statistische Informationen wie die Häufigkeit des gleichzeitigen Auftretens von Wörtern zum Lernen verwendet werden. Daher wird angenommen, dass die Genauigkeit der Wortanalogie-Aufgabe verringert wird, da sie nicht maximal genutzt wird. Zu.

Code

Erstellen Sie ein Wörterbuch aus den gesamten Textdaten

Die Trainingsdaten für jede Sitzung sind aufgeteilt, aber die Parameter und das Wörterbuch müssen im gesamten Datensatz gemeinsam genutzt werden, damit sie zuerst generiert werden.

build_vocab.py


from collections import Counter

with open('preprocessed.txt') as f:
  preprocessed_text = f.readlines()

corpus = []
for line in preprocessed_text:
  line_lower = line.lower()
  corpus.append(line_lower)

vocab = Counter()
for line in corpus:
  tokens = line.strip().split()
  vocab.update(tokens)

vocab = {word: (i, freq) for i, (word, freq) in enumerate(vocab.items())}
id2word = dict((i, word) for word, (i, _) in vocab.items())

Counter () ist eine Container-Datentypklasse, die Elemente beim Zählen hinzufügt. Dadurch wird ein Wörterbuch erstellt, das Informationen darüber enthält, wie oft ein Wort erscheint.

Erstellen Sie von dort aus ein (Token-) Wörterbuch, das Wörtern Zahlen zuweist, und ein Wörterbuch, das Schlüssel und Wert ersetzt.

Aufteilung der Trainingsdaten

split_corpus.py


import math 

split_size = math.floor(len(corpus)/25)

start = split_size - 1000
end = split_size * 2

split_corpus = corpus[start:end]

print('\nLength of split_corpus: ', len(split_corpus))
#Über 11.000 Zeilen

Die Anzahl der für das Training verwendeten Textzeilen beträgt ca. 250.000 und ist leicht verständlich in 25 Zeilen unterteilt, damit sie ohne Absturz verarbeitet werden können.

Zum Zeitpunkt des Trainings für jeden Datensatz wird angenommen, dass die Informationen zur Häufigkeit des gleichzeitigen Auftretens in der Nähe der Unterbrechung geringer sind als sie sein sollten. Daher wird der Beginn des Index auf 1000 Zeilen ab der Unterbrechung festgelegt, um zusammenhängende Daten in der Nähe der Unterbrechung zu erstellen.

Erzeugung einer Koexistenzmatrix und Berechnung der Koexistenzhäufigkeit

build_cooccur.py


from scipy import sparse

window_size = 10
min_count = None
vocab_size = len(vocab)

cooccurrences = sparse.lil_matrix((vocab_size, vocab_size), dtype=np.float64)

for i, line in enumerate(split_corpus):
  if i % 1000 == 0:
    logger.info('Building cooccurrence matrix: on line %i', i)

  tokens = line.strip().split()
  token_ids = [vocab[word][0] for word in tokens]

  for center_i, center_id in enumerate(token_ids):
    context_ids = token_ids[max(0, center_i - window_size):center_i]
    contexts_len = len(context_ids)

    for left_i, left_id in enumerate(context_ids):
      distance = contexts_len - left_i 
      increment = 1.0 / float(distance)

      cooccurrences[center_id, left_id] += increment
      cooccurrences[left_id, center_id] += increment


for i, (row, data) in enumerate(zip(cooccurrences.row, cooccurrences.data)):
  if i % 50000 == 0:
    logger.info('yield cooccurrence matrix: on line %i', i)

  if min_count is not None and vocab[id2word[i]][1] < min_count:
    continue

  for data_idx, j in enumerate(row):
    if min_count is not None and vocab[id2word[j]][1] < min_count:
      continue

    yield i, j, data[data_idx]

#Der Rückgabewert ist functools.Mit Wraps dekorieren und als Liste zurückgeben.

Um Speicherplatz zu sparen, erstellen Sie ein Cooccurrences mit sparse.lil_matrix von scipy, das über einen Mechanismus verfügt, der nur die Werte von Nicht-Null-Elementen enthält. Wenn ich versuche, mit Numpy eine Matrix zu erstellen, übt dies nur Druck auf den Speicher aus und führt zu einem Absturz.

Selbst mit sparse.lil_matrix () gab es viele Nicht-Null-Elemente mit etwa 30.000 Zeilen Textdaten, die aufgrund des Speicherdrucks abstürzten.

Parametertraining

Teil der Handschuhformel: f (x_ij) ((theta_i ^ t e_j) + b_i + b_j --log (X_ij)) (Einzelheiten siehe Papier)

i und j entsprechen der Anzahl der Vokabularelemente, und das Gewicht und die Vorspannung der Parameter haben eine symmetrische Beziehung. Zusätzlich wird die Operation von theta_i und e_j durch das Punktprodukt (inneres Produkt) ausgeführt.

train_glove.py


import numpy as np
import h5py
from random import shuffle

#Parameterinitialisierung.
#Tun Sie dies nur beim ersten Mal und leiten Sie es dann weiter, während Sie im HDF5-Format speichern.
#W = (np.random.rand(vocab_size * 2) - 0.5) / float(vector_size + 1)
#biases = (np.random.rand(vocab_size * 2) - 0.5 / float(vector_size + 1)
#gradient_squared = np.ones((vocab_size * 2, vector_size), dtype=np.float64)
#gradient_squared_biases = np.ones(vocab_size * 2, dtype=np.float64)

with h5py.File('glove_weight_relay.h5', 'r') as f:
  W = f['glove']['weights'][...]
  biases = f['glove']['biases'][...]
  gradient_squared = f['glove']['gradient_squared'][...]
  gradient_squared_biases = f['glove']['gradient_squared_biases'][...]

#Die berechneten Informationen und Parameter der Matrix für das gleichzeitige Auftreten werden in einem Taple zusammengefasst.
data = [(W[i_target], 
         W[i_context + vocab_size],
         biases[i_target : i_target + 1],
         biases[i_context + vocab_size : i_context + vocab_size + 1],
         gradient_squared[i_target],
         gradient_squared[i_context + vocab_size],
         gradient_squared_biases[i_target : i_target + 1],
         gradient_squared_biases[i_context + vocab_size : i_context + vocab_size + 1],
         cooccurrence)
         for i_target, i_context, cooccurrence in cooccurrences]


iterations = 7
learning_rate = 0.05
x_max = 100
alpha = 0.75
for i in range(iterations):
  shuffle(data)
  
  for (v_target, v_context, b_target, b_context, gradsq_W_target, gradsq_W_context, gradsq_b_target, gradsq_b_context, cooccurrence) in data:
# f(X_ij)Teil von
    weight = (cooccurrence / x_max) ** alpha if cooccurrence < x_max else 1
#Gewicht inneres Produkt, Vorspannungsaddition, Subtraktion durch log
    cost_inner = (v_target.dot(v_context) + b_target[0] + b_context[0] - log(cooccurrence))

    cost = weight * (cost_innner ** 2)
    #Code zum Verständnis der Kosten pro Iteration
    #global_cost += 0.5 * cost_inner

#Berechnung der partiellen Differenzierung
    grad_target = weight * cost_inner * v_context
    grad_context = weight * cost_inner * v_target
    
    grad_bias_target = weight * cost_inner
    grad_bias_context = weight * cost_inner

#Gradientenaktualisierung = Lernprozess
    v_target -= (learning_rate * grad_target / np.sqrt(gradsq_W_target))
    v_context -= (learning_rate * grad_context / np.sqrt(gradsq_W_context))

    b_target -= (learning_rate * grad_bias_target / np.sqrt(gradsq_b_target))
    b_context -= (learning_rate * grad_bias_context / np.sqrt(gradsq_b_context))

#Berechnen Sie den Wert, der bei der nächsten Aktualisierung für die Division verwendet werden soll
    gradsq_W_target += np.square(grad_target)
    gradsq_W_context += np.square(grad_context)
    gradsq_b_target += grad_bias_target ** 2
    gradsq_b_context += grad_bias_context ** 2

#Parameter speichern
with h5py.File('glove_weight_relay.h5', 'a') as f:
  f['glove']['weights'][...] = W
  f['glove']['biases'][...] = biases
  f['glove']['gradient_squared'][...] = gradient_squared
  f['glove']['gradient_squared_biases'][...] = gradient_squared_biases 

Parameter wie Gewichte und Vorspannungen sind während des gesamten Trainings gleich, sodass die Initialisierung nur beim ersten Mal erfolgt. Danach werden Speichern und Lesen wiederholt.

Stellen Sie die Anzahl der Trainingswiederholungen auf 7 ein. Je öfter es wiederholt wurde, desto besser waren die Parameter, aber es wurde reduziert, da jedes Mal, wenn es lange dauert und es probeweise erstellt wird.

Bei der Gradientenaktualisierung wird die quadratische durch die Quadratwurzel geteilt, da in GloVe der Lernprozess "Adaptiver Gradientenabstieg" verwendet wird. Eine Methode ähnlich der stochastischen Gradientenabstiegsmethode.

Bestätigung der Trainingsergebnisse

test_similarity.py



#Summieren Sie die symmetrischen Parameter und berechnen Sie den Durchschnitt.
vocab_size = int(len(W) / 2)
for i, row in enumerate(W[:vocab_size]):
  merged = merge_fun(row, W[i + vocab_size])
  if normalize:
    merged /= np.linalg.norm(merged)
  W[i, :] = merged

merge_W = W[:vocab_size]


#Finden Sie die 15 besten Wörter mit ähnlichen Vektoren.
word = 'Wort'
n = 15
word_id = vocab[word][0]

dists = np.dot(merge_W, W[word_id])
top_ids = np.argsort(dists)[::-1][:n + 1]
similar = [id2word[id] for id in to_ids if id!= word_id][:n]

numpy.argsort () gibt nach dem Sortieren in aufsteigender Reihenfolge einen Index zurück. Informationen zur absteigenden Reihenfolge finden Sie im Index mit [:: -1].

スクリーンショット 2020-06-10 18.25.49.png 15 Wörter mit einem Vektor in der Nähe von "Wörtern". Eine Liste von Wörtern, von denen angenommen wird, dass sie in Bezug auf Semantik und syntaktische Theorie auf ähnliche Weise verwendet werden.

スクリーンショット 2020-06-10 18.33.16.png Als ich nach einem Wort mit einem ähnlichen Vektor wie "Amerika" suchte, wurde es mit dem Ländernamen zentriert aufgelistet.

Recommended Posts

GloVe: Prototyp der Worteinbettung durch Glovalvektoren zur Wortrepräsentation
Liste der gebrauchsfertigen Worteinbettungsvektoren
Vektordarstellung einfacher Wörter: word2vec, GloVe
Pandas des Anfängers, vom Anfänger, für den Anfänger [Python]