Ein Memorandum über den Prozess der Erstellung von Word Embedding (Embedding Matrix) für die Verarbeitung natürlicher Sprache unter Verwendung des GloVe-Algorithmus.
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.
Datenvorverarbeitung
Ausbildung
GloVe Papers and Commentary Page (Stanford University)
Referenzierter GloVe Python-Code (GitHub)
Verwendeter Python-Code (GitHub)
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.
Verwenden Sie das morphologische Analysetool MeCab, um japanischen Text in Bedeutungseinheiten zu schreiben.
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.
Es war zu groß, um es zu handhaben, da es einfach ein Array von Vokabeln x Vokabeln (218.438 x 218.438) verwendet.
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.
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.
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.
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.
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.
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.
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].
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.
Als ich nach einem Wort mit einem ähnlichen Vektor wie "Amerika" suchte, wurde es mit dem Ländernamen zentriert aufgelistet.