[PYTHON] Verwenden Sie ELMo und BERT, um die Wortähnlichkeit für Polynomwörter zu bestimmen

Überblick

Wir überprüfen durch Berechnung der Ähnlichkeit von Wörtern, ob ELMo, ein Encoder-Modell, das den Wortverteilungsausdruck (eingebetteter Vektor) unter Berücksichtigung des Kontexts berechnet, Polynomwörter mit mehreren Bedeutungen unterscheiden kann. Die Polynomwörter sind zum Beispiel:

--Was meinst du **? ** (Was meinst du ** **?)

Die gleiche Überprüfung wird bei BERT durchgeführt, dem De-facto-Standard für Modelle zur Verarbeitung natürlicher Sprache in den letzten Jahren, und die Ergebnisse werden verglichen.

(Hinzugefügt am 08.04.2018) </ font> Wir haben außerdem überprüft, wie sich das Ergebnis in Abhängigkeit von der Ebene ändert, aus der der eingebettete Vektor extrahiert wird.

Umwelt- / Nutzungsmodell

Alle Berechnungen wurden in Google Colaboratory durchgeführt.

Sowohl für ELMo als auch für BERT wird das englischsprachige Modell verwendet, da es ohne Feinabstimmung ist. ELMo verwendet den von Tensor Flow Hub und BERT den von Official Repository.

Problemstellung

Modelle wie Word2vec und GloVe haben einen eingebetteten Vektor für jedes Wort. Da es erhalten wird, ist es nicht möglich zu unterscheiden, wofür das Polynomwort verwendet wird. In Modellen wie ELMo und BERT weist sogar dasselbe Wort je nach Kontext unterschiedliche eingebettete Vektoren auf. Daher ist zu erwarten, dass es möglich ist, die mehrdeutigen Wörter nach den verwendeten Bedeutungen zu unterscheiden.

Dieses Mal verwenden wir den folgenden Beispielsatz, wobei "richtig" als Beispiel "richtig", "richtig" und "richtig" bedeutet.

  • Bedeutet "richtig" My right arm is broken. Cover your right eye. Please turn right at the next corner. I got into the right lane.

  • Bedeutet "richtig" 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.

  • Bedeutet "richtig" 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.

Geben Sie diese Beispielsätze in das trainierte Modell ein, um den eingebetteten Vektor zu extrahieren, der der Ähnlichkeit von "rechts" und Kosinus entspricht

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

Durch die Berechnung werden wir herausfinden, ob "richtig" mit derselben Bedeutung einen hohen Grad an Ähnlichkeit aufweist.

Implementierung

Importieren Sie die erforderlichen Bibliotheken. Die TensorFlow-Version verwendet die 1.x-Serie sowohl für ELMo als auch für BERT. Seit dem 27. März 2020 ist die Standardeinstellung von Google Colaboratory die 2.x-Serie, daher der magische Befehl "% tensorflow_version 1.x" 1.x System ist angegeben in.

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

Bereiten Sie den Text vor, der zur Überprüfung verwendet werden soll. BERT muss die Eingabedaten aus der Datei lesen, also schreiben Sie sie auch in die Textdatei.

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))

Bereiten Sie eine Funktion vor, um die Korrelationsmatrix der Kosinusähnlichkeit zu berechnen.

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 verwendet das trainierte Modell (v3) von Tensor Flow Hub. Die Verwendung ist auf der Originalseite beschrieben.

Versuchen Sie Word Embedding, ELMo unter Berücksichtigung des Kontexts mit TensorFlow Hub

Ich habe auch darauf hingewiesen. (Eigentlich habe ich beschlossen, diesen Artikel zu schreiben, weil ich den obigen Artikel gelesen habe.)

Das ELMo-Modul hat einen Modus "Signatur =" Standard "" für die Eingabe von durch Leerzeichen getrennten Sätzen und einen Modus "Signatur =" Token "" für die Eingabe einer Liste von Token, die für jedes Wort unterteilt sind. Dieses Mal verwenden wir jedoch letzteres. Ich werde. Aus diesem Grund haben wir eine Funktion namens "Tokenizer" vorbereitet, um Sätze zu tokenisieren und aufzufüllen.

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

Führt die Berechnung durch und gibt den eingebetteten Vektor aus.

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'])

Wie auf der TensorFlow Hub-Seite (https://tfhub.dev/google/elmo/3) erläutert, ist die Ausgabe des ELMo-Moduls ein Wörterbuch, das verschiedene eingebettete Vektoren enthält. Die Erklärung für jeden Schlüssel lautet wie folgt.

  • 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 ist die Ausgabe der eingebetteten Ebene, die den Kontext der ersten Ebene nicht berücksichtigt. Nur dieser Vektor hat eine Dimension von 512, aber wenn er mit anderen Vektoren summiert wird, scheinen zwei word_emb-Vektoren kombiniert zu werden, um die Dimension 1024 zu bilden. Wie im Originalartikel angegeben, ist die ELMo-Ausgabe eine lineare Summe der drei eingebetteten Vektoren "word_emb", "lstm_outputs1" und "lstm_outputs2" mit trainierbaren Koeffizienten. Ich habe es genommen und es ist in elmo gespeichert. Da wir diesmal keine Downstream-Aufgaben trainieren, haben wir beim Aufrufen des Moduls "trainable = False" angegeben, aber TensorFlow Hub erwähnt nicht, was in diesem Fall mit dem Koeffizienten des ELMo-Vektors passiert. Wenn ich den Wert des durch diese Berechnung erhaltenen Vektors untersuchte, scheint der Koeffizient einfach jeweils 1/3 zu betragen. Da sich der Wert des ELMo-Vektors auch dann nicht geändert hat, wenn "trainable = True" angegeben wurde, scheinen die Anfangswerte aller trainierbaren Gewichte ebenfalls 1/3 zu sein. default ist der Durchschnitt der ELMo-Vektoren aller Wörter im Satz. Ich denke, es kann als verteilter Ausdruck des gesamten Satzes interpretiert werden. sequence_len ist in der obigen Erklärung nicht enthalten, aber es ist eine Liste, die die Anzahl der Token (ohne Auffüllen) jedes Satzes enthält.

Gemäß dem Originalpapier erfasst die Ausgabe der ersten Schicht von LSTM syntaktische Informationen und die zweite Schicht semantische Informationen. Es scheint eine Tendenz zu geben, daher werde ich angesichts des Inhalts dieser Aufgabe, die Bedeutung von Wörtern zu klassifizieren, zuerst lstm_outputs2 verwenden. Die folgende Funktion ruft nur den "richtigen" eingebetteten Vektor ab.

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)

Sie haben jetzt den eingebetteten Vektor "elmo_vectors" für das "Recht" jedes Satzes und die Ähnlichkeitskorrelationsmatrix "elmo_sim_mat". Machen Sie dasselbe mit BERT, bevor Sie sich die Ergebnisse ansehen.

BERT BERT ist ein Modell mit der Absicht, nachgelagerte Aufgaben durch überwachtes Lernen zu optimieren und zu nutzen, aber bert-as-service Es kann auch als Codierer verwendet werden, um eine verteilte Darstellung von Sätzen zu erhalten. Dieses Mal werden wir BERT verwenden, um die verteilte Darstellung von Wörtern zu berechnen.

Klonen Sie zunächst das offizielle Repository von BERT (https://github.com/google-research/bert).

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

Das Modell verwendet "BERT-Base, Uncased". Laden Sie die trainierten Parameter herunter und entpacken Sie sie.

!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

Der Code zum Extrahieren des eingebetteten Vektors enthält "extract_features.py" im offiziellen Repository. Führen Sie ihn daher wie folgt aus. Geben Sie die von --input_file vorbereitete Eingabedatei an, und --output_file gibt eine jsonl-Datei mit einem beliebigen Namen zum Speichern der Ausgabe an. Die nächsten drei Argumente geben das oben heruntergeladene trainierte Modell an. --layers gibt die Ausgabeebenen an, die als eingebetteter Vektor verwendet werden sollen, und alle Ebenen werden zur späteren Überprüfung angegeben.

!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

Bereiten Sie eine Funktion vor, um den eingebetteten Vektor, der dem Zielwort-Token entspricht, aus der Ausgabe-JSONL-Datei zu extrahieren. Ich habe auf diese [Seite] verwiesen (https://dev.classmethod.jp/articles/bert-text-embedding/).

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)

Nehmen Sie den Vektor entsprechend "rechts" und berechnen Sie die Ähnlichkeitsmatrix. Die Schicht, aus der der Vektor extrahiert wird, ist die vorletzte Schicht.

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)

Ergebnis

Zeichnen wir nun das Berechnungsergebnis. Definieren Sie eine Funktion zum Zeichnen mithilfe der Heatmap von 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()

Für ELMo- und BERT-Ergebnisse ausführen.

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

Das Ergebnis ist wie folgt. Die Ähnlichkeit des Vektors, der "rechts" entspricht, ist aufgetragen, aber die Beschriftung zeigt den gesamten Satz. Ich habe vier Sätze angeordnet, die "richtig" im gleichen Sinne verwenden. Idealerweise sollten die vier diagonalen Blöcke dunkler und die anderen nicht diagonalen Blöcke heller sein. Aber wie wäre es damit? elmo_sim_mat.png bert_sim_mat.png

Erstens können wir in beiden Figuren sehen, dass die Ähnlichkeit des letzten Blocks der Bedeutung von "Rechten" deutlich höher ist. Alle diese "Rechte" werden als Menge mit "haben / hat" und "bis" verwendet, und die Struktur des Satzes ist ähnlich, so dass es kein überzeugendes Ergebnis ist, dass es leicht ist, von anderen Bedeutungen zu unterscheiden. Ist es? "Richtig" und "richtig" sind nicht so klar wie "Rechte", aber es gibt einige Stellen, an denen die gleichen Bedeutungen sicherlich ähnlicher sind als in den ersten beiden Sätzen.

Wenn es darum geht, ELMo und BERT zu vergleichen, scheint BERT für das Auge besser zu sein. Es ist jedoch wichtig, die Reihenfolge der Ähnlichkeit und nicht den Ähnlichkeitswert selbst zu betrachten, da die Kosinusähnlichkeit in den Modellen tendenziell unterschiedliche Niveaus des Gesamtwerts aufweist. Daher werden wir einen quantitativen Index basierend auf der Reihenfolge der Ähnlichkeit einführen, um beide Modelle zu vergleichen.

Die folgende Funktion definiert den Ähnlichkeitspunkt. block_size ist die Anzahl der Sätze, in denen Wörter austauschbar verwendet werden, im aktuellen Beispiel 4. Ordnen Sie jeden Satz in absteigender Reihenfolge der Ähnlichkeit an, und Punkte werden hinzugefügt, wenn Sätze, die tatsächlich mit derselben Bedeutung verwendet werden, im Rang "block_size" enthalten sind. Ich werde jedoch den 1. Platz ausschließen, weil ich immer ich selbst bin. In diesem Fall wird die Punktzahl vergeben, wenn die Sätze, die "richtig" in derselben Bedeutung verwenden, vom 2. bis zum 4. Platz eingegeben werden. Die Punkte werden so normalisiert, dass der höchste Punkt 1 ist. Ähnlichkeitspunkte für jeden Satz werden in points_arr gespeichert, wobei av_point ihr Durchschnitt ist.

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

Das Ergebnis der Ausführung ist wie folgt.

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

Die geringe Datenmenge lässt Fragen zur Zuverlässigkeit offen, aber die Quantifizierung hat es ermöglicht, die Ergebnisse klar auszuwerten. Die durchschnittliche Punktzahl betrug ELMo: 0,61, BERT: 0,78, und der Rang wurde auf BERT angehoben. Wenn Sie sich die Punkte für jeden Satz ansehen, können Sie sehen, dass die vier Sätze, die "Rechte" bedeuten, für beide Modelle alle volle Punktzahl sind und dass die vier Sätze, die "richtig" bedeuten, hohe Punktzahlen für BERT haben. Beide Modelle haben Probleme mit den vier Sätzen, die "richtig" bedeuten, aber im Durchschnitt liefert ELMo bessere Ergebnisse.

Vergleich von Schichten zum Extrahieren von Vektoren

In den bisher gezeigten Ergebnissen verwendete ELMo den eingebetteten Vektor, der aus der zweiten Schicht von LSTM extrahiert wurde, und BERT verwendete den eingebetteten Vektor, der aus der zweiten Schicht vom Ende extrahiert wurde. Lassen Sie uns abschließend sehen, wie sich die Ähnlichkeitspunkte in Abhängigkeit von der Ebene ändern, aus der der Vektor extrahiert wird.

ELMo Es gibt vier Arten von Wortvektoren, die von ELMo ausgegeben werden: die kontextunabhängige eingebettete Schicht "word_emb", die LSTM-1. Schicht "lstm_outputs1", die LSTM 2. Schicht "lstm_outputs2" und die durchschnittliche ELMo-Vektor "elmo" dieser drei. Was "word_emb" betrifft, so ist der Wortvektor von "rechts" in jedem Satz der gleiche, so dass es nicht möglich ist, Polynomwörter zu unterscheiden. Verbleibender kontextabhängiger Vektor

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')

Andererseits sind die Ergebnisse der Berechnung des Durchschnitts der Ähnlichkeitspunkte wie folgt.

Schicht Ähnlichkeitspunkte
LSTM 1. Schicht 0.67
LSTM 2. Schicht 0.61
ELMo 0.64

Laut dem Originalpapier erfasst die zweite Schicht tendenziell aussagekräftige Informationen, daher habe ich erwartet, dass die zweite Schicht genauer ist, aber das Ergebnis war, dass die erste Schicht genauer ist. wurde. Es ist das Ergebnis eines kleinen Datensatzes mit nur 12 Sätzen, daher kann ich nicht sicher sagen, aber ist es wichtig, die Struktur der Sätze zu betrachten, um Synonyme zu unterscheiden? Das Ergebnis des ELMo-Vektors ist schlechter als das der ersten Schicht von LSTM, was angesichts der Tatsache, dass der ELMo-Vektor auch einen kontextunabhängigen Vektor aufweist, vernünftig ist.

BERT BERT_base besteht aus 12 Schichten Transformer. Vergleichen wir also alle 12 Schichten. Da die Ausgabedatei "right_output.jsonl" die Ausgabe aller Ebenen speichert, kann der Vektor wie folgt abgerufen werden. Berechnen Sie außerdem den Durchschnittsvektor für alle Ebenen und den Durchschnittsvektor für die letzten 6 Ebenen.

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)

Das Ergebnis der Mittelung der Ähnlichkeitspunkte für diese Vektoren ist wie folgt.

Schicht Ähnlichkeitspunkte
1. Schicht 0.58
2. Schicht 0.67
3. Schicht 0.78
4. Schicht 0.83
5. Schicht 0.83
6. Schicht 0.83
7. Schicht 0.81
8. Schicht 0.78
9. Schicht 0.83
10. Schicht 0.81
11. Schicht 0.78
12. Schicht 0.75
Alle Schichten durchschnittlich 0.81
Endgültiger 6-Schicht-Durchschnitt 0.83

Ist es nicht ein überzeugendes Ergebnis, dass die flache Schicht in der Nähe des Eingangs und die letzte Schicht (12. Schicht), die stark von der Vorlernaufgabe beeinflusst wird, eine geringe Genauigkeit aufweisen? Die höchste Genauigkeit wird durch den durchschnittlichen Vektor mehrerer Schichten nahe der Mitte und der letzten 6 Schichten erreicht. Selbst beim Vergleich der höchsten Präzisionen war BERT ein großer Unterschied zu ELMo.

abschließend

Obwohl ELMo und BERT Wortverteilungsausdrücke geben sollen, die den Kontext berücksichtigen, habe ich ein solches Experiment noch nie gesehen, also habe ich es in einem Artikel zusammengefasst. In Bezug auf das hier versuchte Beispiel ist das Ergebnis, dass sowohl ELMo als auch BERT Polynomwörter unterscheiden können, indem sie den Kontext bis zu einem gewissen Grad erfassen. Im Vergleich von ELMo und BERT war BERT noch effizienter.

Dieses Mal habe ich mich mit wortverteilten Ausdrücken befasst, aber angesichts der Anwendung in der realen Welt denke ich, dass die verteilten Ausdrücke von Sätzen ein breiteres Anwendungsspektrum haben. Als nächstes möchte ich mit verteilten Ausdrücken von Sätzen experimentieren. ..

Recommended Posts

Verwenden Sie ELMo und BERT, um die Wortähnlichkeit für Polynomwörter zu bestimmen
[Japanische Version] Beurteilung der Wortähnlichkeit für Polynomwörter mit ELMo und BERT
Erkennen Sie Anomalien in Sätzen mit ELMo, BERT, USE
So installieren und verwenden Sie Tesseract-OCR
Verwendung von .bash_profile und .bashrc
So installieren und verwenden Sie Graphviz
Fügen Sie dem Benutzerwörterbuch von MeCab unter Ubuntu Wörter zur Verwendung in Python hinzu