[PYTHON] GloVe: Prototype d'incorporation de mots par des vecteurs Gloval pour la représentation de mots

introduction

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.

Objectif

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.

environnement

Prétraitement des données

Entraînement

Informations de référence

GloVe Papers and Commentary Page (Université de Stanford)

Code GloVe Python référencé (GitHub)

Code Python utilisé (GitHub)

Données de texte japonais et prétraitement

Données textuelles japonaises

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.

Division japonaise

Utilisez l'outil d'analyse morphologique MeCab pour écrire du texte japonais en unités de sens.

Les défis rencontrés

Énorme quantité de données et limite de RAM

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.

  1. Lors de la création d'une matrice
  2. Lors du calcul de la fréquence de cooccurrence
  3. Pendant la formation des paramètres

Il était trop volumineux à gérer car il utilise simplement un tableau de vocabulaire x vocabulaire (218 438 x 218 438).

La méthode prise à la place

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.

problème

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. À.

code

Créer un dictionnaire à partir de l'ensemble des données textuelles

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.

Division des données d'entraînement

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.

Génération de la matrice de cooccurrence et calcul de la fréquence de cooccurrence

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.

Formation aux paramètres

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.

Confirmation des résultats de la formation

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].

スクリーンショット 2020-06-10 18.25.49.png 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.

スクリーンショット 2020-06-10 18.33.16.png Lorsque j'ai recherché un mot avec un vecteur similaire à "Amérique", il était principalement répertorié par nom de pays.

Recommended Posts

GloVe: Prototype d'incorporation de mots par des vecteurs Gloval pour la représentation de mots
Liste des vecteurs d'incorporation de mots prêts à l'emploi
Représentation vectorielle de mots simples: word2vec, GloVe
Pandas du débutant, par le débutant, pour le débutant [Python]