[PYTHON] Utilisez ELMo et BERT pour déterminer la similitude des mots pour les mots polynomiaux

Aperçu

Nous vérifions en calculant la similitude des mots si ELMo, qui est un modèle de codeur qui calcule l'expression de distribution de mots (vecteur imbriqué) en tenant compte du contexte, peut distinguer les mots polynomiaux aux significations multiples. Les mots polynomiaux sont, par exemple:

La même vérification est effectuée sur BERT, qui est la norme de facto pour les modèles de traitement du langage naturel ces dernières années, et les résultats sont comparés.

(Ajouté le 08/04/2020) </ font> Nous avons également vérifié comment le résultat change en fonction de la couche à partir de laquelle le vecteur incorporé est extrait.

Modèle d'environnement / d'utilisation

Tous les calculs ont été effectués sur Google Colaboratory.

Pour ELMo et BERT, le modèle appris en anglais est utilisé tel quel sans réglage fin. ELMo utilise celui de Tensor Flow Hub et BERT utilise celui du Official Repository.

Problème de réglage

Les modèles tels que Word2vec et GloVe ont un vecteur intégré pour chaque mot. Du fait qu'il est obtenu, il n'est pas possible de distinguer à quoi sert le mot polynomial. Par contre, dans des modèles comme ELMo et BERT, des vecteurs embarqués qui diffèrent selon le contexte même pour le même mot. Par conséquent, on peut s'attendre à ce qu'il soit possible de distinguer les mots ambigus en fonction des significations utilisées.

Cette fois, nous utiliserons l'exemple de phrase suivant, en prenant «droit» qui signifie «droit», «correct» et «droit» comme exemple.

--Sens "droit" My right arm is broken. Cover your right eye. Please turn right at the next corner. I got into the right lane.

--Sens "correct" Your opinion is more or less right. I got the answer right. Please try to make things right again. It was quite right of you to refuse the offer.

--Sens "droit" I don't have a right to access that computer. Everyone has a right to enjoy his liberty. She has the right to criticize the government. Every person has a right to defend themselves.

Entrez ces exemples de phrases dans le modèle entraîné pour extraire le vecteur incorporé correspondant à la similitude «droite» et cosinus

cossim(\mathbf{u} ,\mathbf{v} ) = \frac{\mathbf{u} \cdot \mathbf{v}}{|\mathbf{u}| \, |\mathbf{v}|}

En calculant, nous découvrirons si «juste» avec le même sens a un degré élevé de similitude.

la mise en oeuvre

Importez les bibliothèques requises. La version TensorFlow utilise la série 1.x pour ELMo et BERT, mais depuis le 27 mars 2020, la valeur par défaut de Google Colaboratory est la série 2.x, donc la commande magique % tensorflow_version 1.x Le système 1.x est spécifié dans.

import json
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

%tensorflow_version 1.x
import tensorflow as tf
import tensorflow_hub as hub

Préparez le texte à utiliser pour la vérification. BERT a besoin de lire les données d'entrée du fichier, donc écrivez-les également dans le fichier texte.

right_texts = ["My right arm is broken",
               "Cover your right eye",
               "Please turn right at the next corner",
               "I got into the right lane",
               "Your opinion is more or less right",
               "I got the answer right",
               "Please try to make things right again",
               "It was quite right of you to refuse the offer",
               "I don't have a right to access that computer",
               "Everyone has a right to enjoy his liberty",
               "She has the right to criticize the government",
               "Every person has a right to defend themselves",]

with open('right_texts.txt', mode='w') as f:
    f.write('\n'.join(right_texts))

Préparez une fonction pour calculer la matrice de corrélation de la similitude cosinus.

def cos_sim(v1, v2):
  return np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))
  
def calc_sim_mat(arr):
  num = len(arr) # number of vectors contained in arr
  sim_mat = np.zeros((num, num))
  norm = np.apply_along_axis(lambda x: np.linalg.norm(x), 1, arr) # norm of each vector
  normed_arr = arr / np.reshape(norm, (-1,1))
  for i, vec in enumerate(normed_arr):
    sim = np.dot(normed_arr, np.reshape(vec, (-1,1)))
    sim = np.reshape(sim, -1) # flatten
    sim_mat[i] = sim
  return sim_mat

ELMo ELMo utilise le modèle entraîné (v3) de Tensor Flow Hub. Comment utiliser est écrit sur la page d'origine,

Essayez Word Embedding, ELMo en tenant compte du contexte en utilisant TensorFlow Hub

J'ai également évoqué. (En fait, j'ai décidé d'écrire cet article parce que j'ai lu l'article ci-dessus.)

Le module ELMo a un mode signature =" default " pour entrer des phrases séparées par des espaces et un mode signature =" tokens " pour entrer une liste de jetons divisés par mot, mais cette fois nous utiliserons ce dernier. Je vais. Par conséquent, nous avons préparé une fonction appelée «tokenizer» pour tokeniser et remplir des phrases.

elmo_url = "https://tfhub.dev/google/elmo/3"

def tokenizer(texts):
  PAD = ""
  tokens = [s.lower().split() for s in texts]
  lengths = [len(t) for t in tokens]
  max_len = max(lengths)
  tokens = [t + [PAD] * (max_len - len(t)) for t in tokens]
  return tokens, lengths

def elmo_embed(texts):
    tokens, lengths = tokenizer(texts)
    elmo = hub.Module(elmo_url, trainable=False)
    embeddings = elmo(
        inputs={
        "tokens": tokens,
        "sequence_len": lengths
        },
        signature="tokens",
        as_dict=True)

    with tf.Session() as sess:
        sess.run(tf.global_variables_initializer())
        sess.run(tf.tables_initializer())
        embeddings_dict = sess.run(embeddings)

    return tokens, embeddings_dict

Effectue le calcul et génère le vecteur intégré.

tokens, elmo_embeddings_dict = elmo_embed(right_texts)
print(elmo_embeddings_dict.keys())
# dict_keys(['lstm_outputs1', 'lstm_outputs2', 'word_emb', 'sequence_len', 'elmo', 'default'])

Comme expliqué sur la page TensorFlow Hub (https://tfhub.dev/google/elmo/3), la sortie du module ELMo est un dictionnaire contenant divers vecteurs intégrés. L'explication de chaque touche est la suivante.

  • word_emb: the character-based word representations with shape [batch_size, max_length, 512].
  • lstm_outputs1: the first LSTM hidden state with shape [batch_size, max_length, 1024].
  • lstm_outputs2: the second LSTM hidden state with shape [batch_size, max_length, 1024].
  • elmo: the weighted sum of the 3 layers, where the weights are trainable. This tensor has shape [batch_size, max_length, 1024]
  • default: a fixed mean-pooling of all contextualized word representations with shape [batch_size, 1024].

word_emb est la sortie de la couche incorporée qui ne prend pas en compte le contexte de la première couche. Seul ce vecteur a une dimension de 512, mais en le sommant avec d'autres vecteurs, il semble que deux vecteurs word_emb soient combinés pour former la dimension 1024. Comme indiqué dans l 'article original, la sortie ELMo est une somme linéaire des trois vecteurs incorporés «word_emb», «lstm_outputs1» et «lstm_outputs2» avec des coefficients d'apprentissage. Je l'ai pris et il est stocké dans ʻelmo. Comme nous ne formons pas de tâches en aval cette fois, nous avons spécifié trainable = Falselors de l'appel du module, mais TensorFlow Hub ne mentionne pas ce qui arrive au coefficient du vecteur ELMo dans ce cas. Quand j'ai examiné la valeur du vecteur obtenu par ce calcul, il semble que le coefficient soit simplement 1/3 chacun. De plus, la valeur du vecteur ELMo n'a pas changé même sitrainable = Trueétait spécifié, il semble donc que les valeurs initiales de tous les poids entraînables soient également 1/3. defaultest la moyenne des vecteurs ELMo de tous les mots de la phrase. Je pense que cela peut être interprété comme une expression distribuée de la phrase entière. sequence_len` n'est pas inclus dans l'explication ci-dessus, mais il s'agit d'une liste contenant le nombre de jetons (hors remplissage) de chaque phrase.

Selon l'article original, la sortie de la première couche de LSTM capture les informations syntaxiques et la seconde couche les informations sémantiques. Il semble y avoir une tendance, donc compte tenu du contenu de cette tâche de classification du sens des mots, j'utiliserai d'abord lstm_outputs2. La fonction suivante récupère uniquement le vecteur incorporé «droit».

def my_index(l, x, default=False):
  return l.index(x) if x in l else default

def find_position(tokens, word):
  pos = [my_index(t, word) for t in tokens]
  assert False not in pos
  return pos

def extract_elmo_vectors(embeddings_dict, tokens, word, layer):
  embeddings = embeddings_dict[layer]
  num_sentences = embeddings.shape[0]
  vec_dim = embeddings.shape[2]
  vectors = np.zeros((num_sentences, vec_dim))
  pos = find_position(tokens, word)
  for i in range(num_sentences):
    vectors[i] = embeddings[i][pos[i]][:]
  return vectors
elmo_vectors = extract_elmo_vectors(elmo_embeddings_dict, tokens, 'right', 'lstm_outputs2')
print(elmo_vectors.shape)
# (12, 1024)
elmo_sim_mat = calc_sim_mat(elmo_vectors)

Vous avez maintenant le vecteur intégré ʻelmo_vectors pour le "droit" dans chaque phrase et la matrice de corrélation de similarité ʻelmo_sim_mat. Faites de même avec BERT avant de regarder les résultats.

BERT BERT est un modèle avec l'intention de peaufiner et d'utiliser des tâches en aval par apprentissage supervisé, mais bert-as-service Il peut également être utilisé comme encodeur pour obtenir une représentation distribuée de phrases. Cette fois, nous utiliserons BERT pour calculer la représentation distribuée des mots.

Tout d'abord, clonez le dépôt officiel de BERT (https://github.com/google-research/bert).

!git clone https://github.com/google-research/bert.git

Le modèle utilisera BERT-Base, Uncased. Téléchargez et décompressez les paramètres entraînés.

!wget https://storage.googleapis.com/bert_models/2018_10_18/uncased_L-12_H-768_A-12.zip && \
unzip uncased_L-12_H-768_A-12.zip && \
rm uncased_L-12_H-768_A-12.zip

Le code pour extraire le vecteur intégré est fourni dans le référentiel officiel sous le nom ʻextract_features.py, donc exécutez-le simplement comme suit. Spécifiez le fichier d'entrée préparé par --input_file, et --output_filespécifie un fichier jsonl avec un nom arbitraire pour enregistrer la sortie. Les trois arguments suivants spécifient le modèle entraîné téléchargé ci-dessus.--layers` spécifie les couches de sortie à utiliser comme vecteur incorporé, et toutes les couches sont spécifiées pour une vérification ultérieure.

!python ./bert/extract_features.py \
  --input_file=right_texts.txt \
  --output_file=right_output.jsonl \
  --vocab_file=uncased_L-12_H-768_A-12/vocab.txt \
  --bert_config_file=uncased_L-12_H-768_A-12/bert_config.json \
  --init_checkpoint=uncased_L-12_H-768_A-12/bert_model.ckpt \
  --do_lower=True \
  --layers 0,1,2,3,4,5,6,7,8,9,10,11

Préparez une fonction pour extraire le vecteur incorporé correspondant au jeton de mot cible du fichier jsonl de sortie. J'ai fait référence à cette page.

def extract_bert_vectors(input_path, target_layer=-2, target_token): 
  with open(input_path, 'r') as f:
      output_jsons = f.readlines()
  
  vectors = []
  for output_json in output_jsons:
      output = json.loads(output_json)
      for feature in output['features']:
          if feature['token'] != target_token: continue
          for layer in feature['layers']:
              if layer['index'] != target_layer: continue
              vectors.append(layer['values'])
  return np.array(vectors)

Prenez le vecteur correspondant à «droite» et calculez la matrice de similarité. Le calque à partir duquel le vecteur est extrait est l'avant-dernier calque.

bert_vectors = extract_bert_vectors('./right_output.jsonl', target_layer=10, target_token='right')
print(bert_vectors.shape)
# (12, 768)
bert_sim_mat = calc_sim_mat(bert_vectors)

résultat

Maintenant, traçons le résultat du calcul. Définissez une fonction de traçage à l'aide de la carte thermique de Seaborn.

def show_sim_mat(sim_mat, texts, title=None, export_fig=False):
  sns.set(font_scale=1)
  g = sns.heatmap(
      sim_mat,
      vmin=0,
      vmax=1,
      cmap="YlOrRd")
  g.set_xticklabels(texts, rotation='vertical')
  g.set_yticklabels(texts, rotation=False)
  if title:
    plt.title(title, fontsize=24)
  if export_fig:
    plt.savefig(export_fig, bbox_inches='tight')
  plt.show()

Exécutez pour les résultats ELMo et BERT.

show_sim_mat(elmo_sim_mat, right_texts, title='ELMo')
show_sim_mat(bert_sim_mat, right_texts, title='BERT')

Le résultat est le suivant. La similitude du vecteur correspondant à «droite» est tracée, mais l'étiquette montre la phrase entière. J'ai arrangé quatre phrases qui utilisent «droit» dans le même sens, donc idéalement les quatre blocs diagonaux devraient être plus foncés et les autres blocs hors diagonale devraient être plus clairs. Mais qu'en est-il? elmo_sim_mat.png bert_sim_mat.png

Premièrement, dans les deux figures, nous pouvons voir que la similitude du dernier bloc de la signification de «droits» est nettement plus élevée. Tous ces «droits» sont utilisés comme un ensemble avec «avoir / a» et «à», et la structure de la phrase est similaire, ce n'est donc pas un résultat convaincant qu'il soit facile de la distinguer des autres significations. C'est ça? «Bien» et «correct» ne sont pas aussi clairs que «droits», mais il y a des endroits où les mêmes significations sont certainement plus similaires, comme dans les deux premières phrases.

Quand il s'agit de comparer ELMo et BERT, BERT semble être meilleur à l'œil. Cependant, il est important de regarder l'ordre de similitude plutôt que la valeur de similitude elle-même, car la similitude cosinus a tendance à avoir des niveaux de valeur globale différents selon le modèle. Par conséquent, nous allons introduire un indice quantitatif basé sur l'ordre de similitude pour comparer les deux modèles.

La fonction suivante définit le point de similitude. block_size est le nombre de phrases dans lesquelles les mots sont utilisés de manière interchangeable, qui est de 4 dans l'exemple actuel. Organisez chaque phrase dans l'ordre décroissant de similitude, et des points seront ajoutés si des phrases qui sont réellement utilisées avec la même signification sont incluses dans le rang block_size. Cependant, je vais exclure la 1ère place car je suis toujours moi-même. Dans ce cas, si les phrases qui utilisent «droit» dans le même sens sont inscrites de la 2e à la 4e place, le score sera attribué. Les points sont normalisés pour que le point le plus élevé soit 1. Les points de similarité pour chaque phrase sont stockés dans points_arr, où ʻav_point` est leur moyenne.

def eval_sim_points(sim_mat, block_size):
  num_data = len(sim_mat)
  points_list = []
  for i in range(num_data):
    block_id = int(i / block_size)
    points = np.array([1 if (block_id * block_size <= j and j < (block_id+1) * block_size) else 0 for j in range(num_data)])
    sorted_args = np.argsort(sim_mat[i])[::-1]
    sorted_points = points[sorted_args]
    point = np.mean(sorted_points[1:block_size])
    points_list.append(point)
  points_arr = np.array(points_list)
  av_point = np.mean(points_arr)
  return av_point, points_arr

Le résultat de l'exécution est le suivant.

# ELMo
elmo_point, elmo_points_arr = eval_sim_points(elmo_sim_mat, 4)
print(np.round(elmo_point, 2))
# 0.61
print(np.round(elmo_points_arr, 2))
# [0.33 0.33 0.   0.67 0.67 0.67 0.67 0.   1.   1.   1.   1.  ]
# BERT
bert_point, bert_points_arr = eval_sim_points(bert_sim_mat, 4)
print(np.round(bert_point, 2))
# 0.78
print(np.round(bert_points_arr, 2))
# [1.   1.   0.67 1.   0.67 0.33 0.33 0.33 1.   1.   1.   1.  ]

Le peu de données laisse des questions sur la fiabilité, mais la quantification a permis d'évaluer clairement les résultats. Le score moyen était ELMo: 0,61, BERT: 0,78, et le rang a été élevé à BERT. En regardant les points pour chaque phrase, vous pouvez voir que les quatre phrases qui signifient «droits» sont toutes des notes complètes pour les deux modèles, et que les quatre phrases qui signifient «juste» ont des scores élevés pour BERT. Les deux modèles ont du mal avec les quatre phrases qui signifient «correct», mais en moyenne, ELMo donne de meilleurs résultats.

Comparaison des couches pour extraire les vecteurs

Dans les résultats présentés jusqu'à présent, ELMo a utilisé le vecteur intégré extrait de la deuxième couche de LSTM, et BERT a utilisé le vecteur intégré extrait de la deuxième couche à partir de la fin. Enfin, voyons comment les points de similitude changent en fonction de la couche à partir de laquelle le vecteur est extrait.

ELMo Il existe quatre types de vecteurs de mots sortis d'ELMo: la couche intégrée indépendante du contexte word_emb, la première couche LSTM lstm_outputs1, la deuxième couche LSTM lstm_outputs2, et le vecteur ELMo moyen ʻelmode ces trois. Comme pourword_emb`, le vecteur de mot" right "est le même dans chaque phrase, il n'est donc pas possible de distinguer les mots polynomiaux. Vecteur dépendant du contexte restant

elmo_vectors_e = extract_elmo_vectors(elmo_embeddings_dict, tokens, 'right', 'elmo')
elmo_vectors_1 = extract_elmo_vectors(elmo_embeddings_dict, tokens, 'right', 'lstm_outputs1')
elmo_vectors_2 = extract_elmo_vectors(elmo_embeddings_dict, tokens, 'right', 'lstm_outputs2')

En revanche, les résultats du calcul de la moyenne des points de similitude sont les suivants.

couche Points de similarité
LSTM 1ère couche 0.67
LSTM 2ème couche 0.61
ELMo 0.64

Selon le document original, la deuxième couche a tendance à capturer des informations significatives, donc je m'attendais à ce que la deuxième couche soit plus précise, mais le résultat était que la première couche était plus précise. devenu. Je ne peux pas le dire avec certitude car c'est le résultat d'un petit ensemble de données avec seulement 12 phrases, mais est-il important de regarder la structure des phrases pour distinguer les synonymes? Le résultat du vecteur ELMo est pire que celui de la première couche de LSTM, ce qui est raisonnable étant donné que le vecteur ELMo a également un vecteur indépendant du contexte.

BERT BERT_base se compose de 12 couches de Transformer, comparons donc les 12 couches. Puisque le fichier de sortie right_output.jsonl stocke la sortie de toutes les couches, le vecteur peut être récupéré comme suit. De plus, calculez le vecteur moyen pour toutes les couches et le vecteur moyen pour les 6 dernières couches, respectivement.

bert_vectors_list = []
for i in range(12):
  bert_vectors_list.append(extract_bert_vectors('./right_output.jsonl', target_layer=i, target_token='right'))

# average of all the layers
bert_vector_av_all = np.mean(bert_vectors_list, axis=0)
# average of the last 6 layers
bert_vector_av_last6 = np.mean(bert_vectors_list[6:], axis=0)

Le résultat de la moyenne des points de similitude pour ces vecteurs est le suivant.

couche Points de similarité
1ère couche 0.58
2ème couche 0.67
3e couche 0.78
4ème couche 0.83
5ème couche 0.83
6ème couche 0.83
7ème couche 0.81
8ème couche 0.78
9ème couche 0.83
10ème couche 0.81
11ème couche 0.78
12ème couche 0.75
Moyenne de toutes les couches 0.81
Moyenne finale sur 6 couches 0.83

N'est-ce pas un résultat convaincant que la couche peu profonde proche de l'entrée et la couche finale (12ème couche), qui est fortement influencée par la tâche de pré-apprentissage, ont une faible précision? La précision la plus élevée est obtenue par le vecteur moyen de plusieurs couches près du centre et des 6 couches finales. Même en comparant les plus hautes précisions, BERT était une grande différence par rapport à ELMo.

en conclusion

Bien qu'ELMo et BERT donnent des expressions de distribution de mots qui tiennent compte du contexte, je n'ai jamais vu une telle expérience, donc je l'ai résumée dans un article. En ce qui concerne l'exemple essayé ici, le résultat est que tant ELMo que BERT peuvent distinguer les mots polynomiaux en saisissant dans une certaine mesure le contexte. En comparant ELMo et BERT, BERT était encore plus efficace.

Cette fois, j'ai traité des expressions distribuées par mot, mais compte tenu de l'application dans le monde réel, je pense que les expressions distribuées de phrases ont un plus large éventail d'applications, alors je voudrais ensuite expérimenter les expressions distribuées de phrases. ..

Recommended Posts

Utilisez ELMo et BERT pour déterminer la similitude des mots pour les mots polynomiaux
[Version japonaise] Jugement de la similitude des mots pour les mots polynomiaux utilisant ELMo et BERT
Détecter les anomalies dans les phrases en utilisant ELMo, BERT, USE
Comment installer et utiliser Tesseract-OCR
Comment utiliser .bash_profile et .bashrc
Comment installer et utiliser Graphviz
Ajouter des mots au dictionnaire utilisateur de MeCab sur Ubuntu pour une utilisation en Python