Un mémorandum du processus de création de Word Embedding (Embedding Matrix) pour le traitement du langage naturel à l'aide de l'algorithme GloVe.
Créé parce que Word Embedding est nécessaire pour créer un chatbot génératif. L'apprentissage par transfert, qui utilise des modules formés, est généralement considéré comme pratique. Cette fois, je me suis fait la mienne pour approfondir ma compréhension de la «matrice embarquée» qui permet «l'analogique» en vectorisant les relations entre les mots dans un espace linéaire.
Prétraitement des données
Entraînement
GloVe Papers and Commentary Page (Université de Stanford)
Code GloVe Python référencé (GitHub)
Utilisez le vidage de données Wikipedia comme données de texte japonais. La taille totale est d'environ 13 Go. Dans l'état actuel des choses, des balises XML, etc. sont attachées, utilisez donc "WikiExtractor" pour extraire la partie que vous souhaitez utiliser pour l'entraînement.
Utilisez l'outil d'analyse morphologique MeCab pour écrire du texte japonais en unités de sens.
Tout d'abord, visant la création d'un essai, j'ai essayé de n'utiliser que 100 Mo déposés par Wiki Extractor.
--Nombre de lignes: 254103 lignes --Nombre de vocabulaire: 218,438 mots
Chaque ligne contient une série de phrases. Lorsque j'ai dépassé 100 Mo de données prétraitées, la limite de RAM a été atteinte et la session s'est plantée.
Avec environ 12 Go de RAM disponible dans Colab, la forte augmentation de la pression de la mémoire était due à l'utilisation de matrices contenant des informations de fréquence de cooccurrence mot à mot. Les trois endroits suivants sont principalement utilisés.
Il était trop volumineux à gérer car il utilise simplement un tableau de vocabulaire x vocabulaire (218 438 x 218 438).
La formation a été menée en subdivisant le texte, qui compte environ 250 000 lignes. Après chaque apprentissage d'un ensemble de données, seuls les paramètres ont été enregistrés et la matrice pour laquelle la fréquence de cooccurrence a été calculée a été réinitialisée (initialisée) sans être héritée. Cela vous permet d'entraîner vos paramètres dans l'ensemble de données tout en les conservant dans la mémoire disponible.
L'avantage de GloVe est qu'il utilise des informations statistiques telles que la fréquence de cooccurrence entre les mots pour l'apprentissage, il est donc présumé que la précision de la tâche d'analogie de mot est réduite car elle n'est pas utilisée au maximum. À.
Les données d'entraînement pour chaque session sont divisées, mais les paramètres et le dictionnaire doivent être partagés dans l'ensemble de données afin qu'ils soient générés en premier.
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 () est une classe de type de données de conteneur qui ajoute des éléments tout en les comptant. Cela crée un dictionnaire qui contient des informations sur le nombre de fois qu'un mot apparaît.
À partir de là, créez un dictionnaire (jeton) qui attribue des nombres aux mots et un dictionnaire qui remplace Clé et Valeur.
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))
#Environ 11000 lignes
Le nombre de lignes de texte utilisé pour la formation est d'environ 250 000, et il est divisé en 25 de manière facile à comprendre afin qu'il puisse être traité sans planter.
Au moment de l'apprentissage pour chaque ensemble de données, on pense que les informations de fréquence de cooccurrence près de la rupture seront inférieures à ce qu'elles devraient être, de sorte que le début de l'index est défini sur 1000 lignes à partir de la rupture pour créer des données cohérentes près de la rupture.
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]
#La valeur de retour est functools.Décorez avec des wraps et retournez sous forme de liste
Pour économiser de la mémoire, créez une cooccurrences avec sparse.lil_matrix de scipy, qui a un mécanisme pour ne contenir que les valeurs des éléments non nuls. Lorsque j'essaye de créer une matrice avec Numpy, cela met juste une pression sur la mémoire et provoque un crash.
En outre, même avec sparse.lil_matrix (), il y avait de nombreux éléments non nuls autour de 30 000 lignes de données texte, et il s'est écrasé en raison de la pression de la mémoire.
Partie de la formule de Glove: f (x_ij) ((theta_i ^ t e_j) + b_i + b_j --log (X_ij)) (Voir le papier pour plus de détails)
i et j correspondent au nombre d'éléments de vocabulaire, et le poids et le biais des paramètres ont une relation symétrique. De plus, l'opération de theta_i et e_j est effectuée par le produit scalaire (produit interne).
train_glove.py
import numpy as np
import h5py
from random import shuffle
#Initialisation des paramètres.
#Ne le faites que la première fois, puis relancez tout en enregistrant au format HDF5.
#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'][...]
#Les informations et paramètres calculés de la matrice de cooccurrence sont résumés dans un taple.
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)Partie de
weight = (cooccurrence / x_max) ** alpha if cooccurrence < x_max else 1
#Poids produit interne, addition de biais, soustraction par log
cost_inner = (v_target.dot(v_context) + b_target[0] + b_context[0] - log(cooccurrence))
cost = weight * (cost_innner ** 2)
#Code utilisé pour comprendre le coût par itération
#global_cost += 0.5 * cost_inner
#Calcul de la différenciation partielle
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
#Mise à jour du dégradé = processus d'apprentissage
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))
#Calculez la valeur à utiliser pour la division lors de la prochaine mise à jour
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
#Enregistrer les paramètres
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
Les paramètres tels que les poids et les biais sont les mêmes tout au long de la formation, l'initialisation n'est donc effectuée que la première fois. Après cela, enregistrer et lire sont répétés.
Réglez le nombre de répétitions d'entraînement sur 7. Plus vous répétez, meilleurs sont les paramètres, mais je l'ai réduit car cela prend beaucoup de temps à faire à chaque fois et il est créé à titre d'essai.
Dans la mise à jour du gradient, le carré est divisé par la racine carrée en raison du processus d'apprentissage "Descente de gradient adaptative" utilisé dans GloVe. Une méthode similaire à la méthode de descente de gradient stochastique.
test_similarity.py
#Additionnez les paramètres symétriques et calculez la moyenne.
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]
#Trouvez les 15 premiers mots avec des vecteurs similaires.
word = 'mot'
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 () retourne un index après le tri par ordre croissant. Pour l'ordre décroissant, reportez-vous à l'index avec [:: -1].
15 mots avec un vecteur proche de "mots". Une liste de mots qui sont censés être utilisés de manière similaire en termes de sémantique et de théorie syntaxique.
Lorsque j'ai recherché un mot avec un vecteur similaire à "Amérique", il était principalement répertorié par nom de pays.