Word2Vec

Auf TensorFlow.org ansehen In Google Colab ausführen Quelle auf GitHub anzeigen Notizbuch herunterladen

Word2Vec ist kein singulärer Algorithmus, sondern eine Familie von Modellarchitekturen und Optimierungen, die verwendet werden können, um Worteinbettungen aus großen Datensätzen zu lernen. Mit Word2Vec erlernte Einbettungen haben sich bei einer Vielzahl von nachgelagerten Aufgaben der Verarbeitung natürlicher Sprache als erfolgreich erwiesen.

In diesen Papieren wurden zwei Methoden zum Erlernen von Wortrepräsentationen vorgeschlagen:

  • Kontinuierliche Bag-of-Words , die das mittlere Wort auf umgebenden Kontext Worten basiert prognostiziert. Der Kontext besteht aus wenigen Wörtern vor und nach dem aktuellen (mittleren) Wort. Diese Architektur wird als Bag-of-Words-Modell bezeichnet, da die Reihenfolge der Wörter im Kontext nicht wichtig ist.
  • Kontinuierliche Überspringen-Gramm - Modell , das vorherzusagen Worte innerhalb eines bestimmten Bereichs vor und nach dem aktuellen Wort im selben Satz. Ein funktionierendes Beispiel dafür ist unten angegeben.

In diesem Tutorial verwenden Sie den Skip-Gram-Ansatz. Zuerst erkunden Sie Skip-Grams und andere Konzepte mit einem einzigen Satz zur Veranschaulichung. Als Nächstes trainieren Sie Ihr eigenes Word2Vec-Modell an einem kleinen Datensatz. Dieses Tutorial enthält auch Code , um die ausgebildeten Einbettungen zu exportieren und visualisieren sie in dem TensorFlow Embedding - Projektor .

Skip-Gram und Negativ-Sampling

Während ein Wortbeutelmodell ein Wort aufgrund des benachbarten Kontexts vorhersagt, sagt ein Skip-Gramm-Modell den Kontext (oder die Nachbarn) eines Wortes anhand des Wortes selbst voraus. Das Modell wird auf Skip-Grams trainiert, bei denen es sich um n-Gramme handelt, die das Überspringen von Token ermöglichen (ein Beispiel finden Sie im Diagramm unten). Der Kontext eines Wortes kann durch einen Satz von skip-Grammpaare dargestellt werden (target_word, context_word) wo context_word erscheint in dem benachbarten Rahmen target_word .

Betrachten Sie den folgenden Satz mit 8 Wörtern.

Die breite Straße schimmerte in der heißen Sonne.

Die Kontextwörter für jedes der 8 Wörter dieses Satzes werden durch eine Fenstergröße definiert. Die Fenstergröße bestimmt die Spanne von Wörtern auf beiden Seiten eines target_word , die berücksichtigt werden können context word . Sehen Sie sich diese Tabelle mit Skip-Grams für Zielwörter an, die auf verschiedenen Fenstergrößen basieren.

word2vec_skipgrams

Das Trainingsziel des Skip-Gram-Modells besteht darin, die Wahrscheinlichkeit der Vorhersage von Kontextwörtern bei gegebenem Zielwort zu maximieren. Für eine Folge von Worten w 1, w 2, ... w T kann das Ziel als auch die durchschnittliche log - Wahrscheinlichkeit geschrieben werden

word2vec_skipgram_objective

wo c ist die Größe des Trainingskontext. Die grundlegende Skip-Gram-Formulierung definiert diese Wahrscheinlichkeit unter Verwendung der Softmax-Funktion.

word2vec_full_softmax

wobei v und v 'sind Ziel und Kontext Vektordarstellungen von Worten und W Vokabulargröße.

Die Berechnung der Nenner dieser Formulierung umfasst eine vollständige softmax über den gesamten Wortschatz Wörtern , die oft groß ist (10 5 -10 7) Bedingungen.

Der Lärm Kontras Estimation Verlustfunktion ist eine effiziente Annäherung für eine volle softmax. Mit einem Zielwort Einbettungen anstelle von Modellierung der Wortverteilung, NCE Verlust zu erfahren , kann vereinfacht negative Abtastung zu verwenden.

Die vereinfachte negativen Abtasten Ziel für ein Zielwort ist das Kontextwort aus num_ns negativen Proben von Rauschverteilung P gezogen zu unterscheiden n (w) von Worten. Eine effiziente Annäherung des vollen softmax über das Vokabular Genauer gesagt ist, für ein Skip-Gramm - Paar, um den Verlust für ein Zielwort als Klassifikationsproblem zwischen dem Kontextwort und num_ns negative Proben zu stellen.

Eine negative Probe wird als (target_word, context_word) Paar so definiert ist, dass die nicht in der context_word erscheint window_size Nachbarschaft der target_word. Für den Beispielsatz, das sind einige mögliche negative Proben (wenn window_size 2).

(hot, shimmered)
(wide, hot)
(wide, sun)

Im nächsten Abschnitt generieren Sie Skip-Grams und negative Samples für einen einzelnen Satz. Später in diesem Tutorial lernen Sie auch Subsampling-Techniken kennen und trainieren ein Klassifizierungsmodell für positive und negative Trainingsbeispiele.

Aufstellen

import io
import re
import string
import tqdm

import numpy as np

import tensorflow as tf
from tensorflow.keras import layers
2021-08-06 01:26:32.691070: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcudart.so.11.0
# Load the TensorBoard notebook extension
%load_ext tensorboard
SEED = 42
AUTOTUNE = tf.data.AUTOTUNE

Vektorisieren Sie einen Beispielsatz

Betrachten Sie den folgenden Satz:
The wide road shimmered in the hot sun.

Den Satz tokenisieren:

sentence = "The wide road shimmered in the hot sun"
tokens = list(sentence.lower().split())
print(len(tokens))
8

Erstellen Sie ein Vokabular, um Zuordnungen von Token zu Integer-Indizes zu speichern.

vocab, index = {}, 1  # start indexing from 1
vocab['<pad>'] = 0  # add a padding token
for token in tokens:
  if token not in vocab:
    vocab[token] = index
    index += 1
vocab_size = len(vocab)
print(vocab)
{'<pad>': 0, 'the': 1, 'wide': 2, 'road': 3, 'shimmered': 4, 'in': 5, 'hot': 6, 'sun': 7}

Erstellen Sie ein inverses Vokabular, um Zuordnungen von Integer-Indizes zu Tokens zu speichern.

inverse_vocab = {index: token for token, index in vocab.items()}
print(inverse_vocab)
{0: '<pad>', 1: 'the', 2: 'wide', 3: 'road', 4: 'shimmered', 5: 'in', 6: 'hot', 7: 'sun'}

Vektorisieren Sie Ihren Satz.

example_sequence = [vocab[word] for word in tokens]
print(example_sequence)
[1, 2, 3, 4, 5, 1, 6, 7]

Erzeuge Skip-Grams aus einem Satz

Das tf.keras.preprocessing.sequence Modul bietet nützliche Funktionen , die Datenaufbereitung für Vereinfachen Word2Vec. Sie können die Verwendung tf.keras.preprocessing.sequence.skipgrams zu skip-Gramm - Paare aus dem erzeugen example_sequence mit einem gegebenen window_size von Tokens im Bereich [0, vocab_size) .

window_size = 2
positive_skip_grams, _ = tf.keras.preprocessing.sequence.skipgrams(
      example_sequence,
      vocabulary_size=vocab_size,
      window_size=window_size,
      negative_samples=0)
print(len(positive_skip_grams))
26

Schauen Sie sich einige positive Skip-Grams an.

for target, context in positive_skip_grams[:5]:
  print(f"({target}, {context}): ({inverse_vocab[target]}, {inverse_vocab[context]})")
(5, 1): (in, the)
(7, 6): (sun, hot)
(6, 5): (hot, in)
(4, 1): (shimmered, the)
(4, 5): (shimmered, in)

Negatives Sampling für ein Skip-Gramm

Die skipgrams Funktion werden alle positiven skip-Gramm - Paaren , die durch sich über einen bestimmten Fensterspanne gleiten. Um zusätzliche Skip-Gram-Paare zu erzeugen, die als negative Stichproben für das Training dienen würden, müssen Sie zufällige Wörter aus dem Vokabular auswählen. Verwenden Sie die tf.random.log_uniform_candidate_sampler Funktion Probe num_ns Anzahl negativer Proben für ein gegebenes Zielwort in einem Fenster. Sie können die Funktion für das Zielwort eines Skip-Grams aufrufen und das Kontextwort als wahre Klasse übergeben, um es von der Abtastung auszuschließen.

# Get target and context words for one positive skip-gram.
target_word, context_word = positive_skip_grams[0]

# Set the number of negative samples per positive context.
num_ns = 4

context_class = tf.reshape(tf.constant(context_word, dtype="int64"), (1, 1))
negative_sampling_candidates, _, _ = tf.random.log_uniform_candidate_sampler(
    true_classes=context_class,  # class that should be sampled as 'positive'
    num_true=1,  # each positive skip-gram has 1 positive context class
    num_sampled=num_ns,  # number of negative context words to sample
    unique=True,  # all the negative samples should be unique
    range_max=vocab_size,  # pick index of the samples from [0, vocab_size]
    seed=SEED,  # seed for reproducibility
    name="negative_sampling"  # name of this operation
)
print(negative_sampling_candidates)
print([inverse_vocab[index.numpy()] for index in negative_sampling_candidates])
2021-08-06 01:26:34.075025: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcuda.so.1
2021-08-06 01:26:34.740839: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-06 01:26:34.741765: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1733] Found device 0 with properties: 
pciBusID: 0000:00:05.0 name: Tesla V100-SXM2-16GB computeCapability: 7.0
coreClock: 1.53GHz coreCount: 80 deviceMemorySize: 15.78GiB deviceMemoryBandwidth: 836.37GiB/s
2021-08-06 01:26:34.741796: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcudart.so.11.0
2021-08-06 01:26:34.745310: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcublas.so.11
2021-08-06 01:26:34.745392: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcublasLt.so.11
2021-08-06 01:26:34.746507: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcufft.so.10
2021-08-06 01:26:34.746928: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcurand.so.10
2021-08-06 01:26:34.748082: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcusolver.so.11
2021-08-06 01:26:34.748959: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcusparse.so.11
2021-08-06 01:26:34.749109: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcudnn.so.8
2021-08-06 01:26:34.749218: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-06 01:26:34.750171: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-06 01:26:34.751064: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1871] Adding visible gpu devices: 0
2021-08-06 01:26:34.752096: I tensorflow/core/platform/cpu_feature_guard.cc:142] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations:  AVX2 AVX512F FMA
To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.
2021-08-06 01:26:34.752660: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-06 01:26:34.753551: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1733] Found device 0 with properties: 
pciBusID: 0000:00:05.0 name: Tesla V100-SXM2-16GB computeCapability: 7.0
coreClock: 1.53GHz coreCount: 80 deviceMemorySize: 15.78GiB deviceMemoryBandwidth: 836.37GiB/s
2021-08-06 01:26:34.753623: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-06 01:26:34.754495: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-06 01:26:34.755355: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1871] Adding visible gpu devices: 0
2021-08-06 01:26:34.755401: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcudart.so.11.0
2021-08-06 01:26:35.374285: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1258] Device interconnect StreamExecutor with strength 1 edge matrix:
2021-08-06 01:26:35.374324: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1264]      0 
2021-08-06 01:26:35.374332: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1277] 0:   N 
2021-08-06 01:26:35.374528: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-06 01:26:35.375516: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-06 01:26:35.376484: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-06 01:26:35.377445: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1418] Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:0 with 14646 MB memory) -> physical GPU (device: 0, name: Tesla V100-SXM2-16GB, pci bus id: 0000:00:05.0, compute capability: 7.0)
tf.Tensor([2 1 4 3], shape=(4,), dtype=int64)
['wide', 'the', 'shimmered', 'road']

Konstruiere ein Trainingsbeispiel

Für eine gegebene positive (target_word, context_word) skip-Gramm, haben Sie jetzt auch haben num_ns negativ abgetastete Kontext Wörter , die in der Fenstergröße Nachbarschaft erscheinen nicht target_word . Batch die 1 positive context_word und num_ns negativen Kontext Worte in ein Tensor. Dies erzeugt einen Satz von positivem skip-Gramm (bezeichnet als 1 ) und negative Proben (bezeichnet als 0 ) für jedes Zielwort.

# Add a dimension so you can use concatenation (on the next step).
negative_sampling_candidates = tf.expand_dims(negative_sampling_candidates, 1)

# Concat positive context word with negative sampled words.
context = tf.concat([context_class, negative_sampling_candidates], 0)

# Label first context word as 1 (positive) followed by num_ns 0s (negative).
label = tf.constant([1] + [0]*num_ns, dtype="int64")

# Reshape target to shape (1,) and context and label to (num_ns+1,).
target = tf.squeeze(target_word)
context = tf.squeeze(context)
label = tf.squeeze(label)

Schauen Sie sich den Kontext und die entsprechenden Bezeichnungen für das Zielwort aus dem obigen Skip-Gram-Beispiel an.

print(f"target_index    : {target}")
print(f"target_word     : {inverse_vocab[target_word]}")
print(f"context_indices : {context}")
print(f"context_words   : {[inverse_vocab[c.numpy()] for c in context]}")
print(f"label           : {label}")
target_index    : 5
target_word     : in
context_indices : [1 2 1 4 3]
context_words   : ['the', 'wide', 'the', 'shimmered', 'road']
label           : [1 0 0 0 0]

Ein Tupel von (target, context, label) den (target, context, label) Tensoren bildet ein Trainingsbeispiel für Ihre skip-gramnegative Abtasten Word2Vec Modell zu trainieren. Man beachte , dass das Ziel der Form (1,) , während der Rahmen und Label ist von Form (1+num_ns,)

print("target  :", target)
print("context :", context)
print("label   :", label)
target  : tf.Tensor(5, shape=(), dtype=int32)
context : tf.Tensor([1 2 1 4 3], shape=(5,), dtype=int64)
label   : tf.Tensor([1 0 0 0 0], shape=(5,), dtype=int64)

Zusammenfassung

Dieses Bild fasst das Verfahren zum Generieren eines Trainingsbeispiels aus einem Satz zusammen.

word2vec_negative_sampling

Alle Schritte in einer Funktion zusammenfassen

Skip-gram-Probenahmetabelle

Ein großer Datensatz bedeutet einen größeren Wortschatz mit einer höheren Anzahl häufiger Wörter wie Stoppwörter. Trainingsbeispiele aus der Abtastung häufig vorkommende Wörter erhalten (wie the , is , on ) fügen Sie nicht viel nützliche Informationen für das Modell zu lernen. Mikolovet al. schlagen eine Unterabtastung häufig vorkommender Wörter als hilfreiches Verfahren vor, um die Einbettungsqualität zu verbessern.

Die tf.keras.preprocessing.sequence.skipgrams Funktion akzeptiert eine Abtasttabelle Argument zu codieren Wahrscheinlichkeiten jeder Token - Abtastung. Sie können die Verwendung tf.keras.preprocessing.sequence.make_sampling_table ein Wort Frequenz Rang basierend Wahrscheinlichkeitsstichprobentabelle zu erzeugen , und es passieren skipgrams Funktion. Werfen Sie einen Blick auf die Probenahme Wahrscheinlichkeiten für eine vocab_size von 10.

sampling_table = tf.keras.preprocessing.sequence.make_sampling_table(size=10)
print(sampling_table)
[0.00315225 0.00315225 0.00547597 0.00741556 0.00912817 0.01068435
 0.01212381 0.01347162 0.01474487 0.0159558 ]

sampling_table[i] bezeichnet die Wahrscheinlichkeit, das in einem Datensatz i-ten häufigste Wort abgetastet wird . Die Funktion nimmt eine Zipfs Verteilung der Wortfrequenzen für die Probenahme.

Trainingsdaten generieren

Kompilieren Sie alle oben beschriebenen Schritte in eine Funktion, die für eine Liste von vektorisierten Sätzen aufgerufen werden kann, die aus einem beliebigen Textdatensatz stammen. Beachten Sie, dass die Sampling-Tabelle vor dem Sampling von Skip-Gram-Wortpaaren erstellt wird. Sie werden diese Funktion in den späteren Abschnitten verwenden.

# Generates skip-gram pairs with negative sampling for a list of sequences
# (int-encoded sentences) based on window size, number of negative samples
# and vocabulary size.
def generate_training_data(sequences, window_size, num_ns, vocab_size, seed):
  # Elements of each training example are appended to these lists.
  targets, contexts, labels = [], [], []

  # Build the sampling table for vocab_size tokens.
  sampling_table = tf.keras.preprocessing.sequence.make_sampling_table(vocab_size)

  # Iterate over all sequences (sentences) in dataset.
  for sequence in tqdm.tqdm(sequences):

    # Generate positive skip-gram pairs for a sequence (sentence).
    positive_skip_grams, _ = tf.keras.preprocessing.sequence.skipgrams(
          sequence,
          vocabulary_size=vocab_size,
          sampling_table=sampling_table,
          window_size=window_size,
          negative_samples=0)

    # Iterate over each positive skip-gram pair to produce training examples
    # with positive context word and negative samples.
    for target_word, context_word in positive_skip_grams:
      context_class = tf.expand_dims(
          tf.constant([context_word], dtype="int64"), 1)
      negative_sampling_candidates, _, _ = tf.random.log_uniform_candidate_sampler(
          true_classes=context_class,
          num_true=1,
          num_sampled=num_ns,
          unique=True,
          range_max=vocab_size,
          seed=SEED,
          name="negative_sampling")

      # Build context and label vectors (for one target word)
      negative_sampling_candidates = tf.expand_dims(
          negative_sampling_candidates, 1)

      context = tf.concat([context_class, negative_sampling_candidates], 0)
      label = tf.constant([1] + [0]*num_ns, dtype="int64")

      # Append each element from the training example to global lists.
      targets.append(target_word)
      contexts.append(context)
      labels.append(label)

  return targets, contexts, labels

Trainingsdaten für Word2Vec vorbereiten

Wenn Sie wissen, wie Sie mit einem Satz für ein auf Skip-Gram-Negativ-Sampling basierendes Word2Vec-Modell arbeiten, können Sie damit fortfahren, Trainingsbeispiele aus einer größeren Liste von Sätzen zu generieren!

Textkorpus herunterladen

Für dieses Tutorial verwenden Sie eine Textdatei mit Shakespeares Schriften. Ändern Sie die folgende Zeile, um diesen Code mit Ihren eigenen Daten auszuführen.

path_to_file = tf.keras.utils.get_file('shakespeare.txt', 'https://storage.googleapis.com/download.tensorflow.org/data/shakespeare.txt')
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/shakespeare.txt
1122304/1115394 [==============================] - 0s 0us/step

Lesen Sie den Text aus der Datei und sehen Sie sich die ersten Zeilen an.

with open(path_to_file) as f: 
  lines = f.read().splitlines()
for line in lines[:20]:
  print(line)
First Citizen:
Before we proceed any further, hear me speak.

All:
Speak, speak.

First Citizen:
You are all resolved rather to die than to famish?

All:
Resolved. resolved.

First Citizen:
First, you know Caius Marcius is chief enemy to the people.

All:
We know't, we know't.

First Citizen:
Let us kill him, and we'll have corn at our own price.

Verwenden der nicht leeren Zeilen einen konstruieren tf.data.TextLineDataset Objekt für die nächsten Schritte.

text_ds = tf.data.TextLineDataset(path_to_file).filter(lambda x: tf.cast(tf.strings.length(x), bool))

Sätze aus dem Korpus vektorisieren

Sie können die Verwendung TextVectorization Schicht Sätze aus dem Korpus vektorisieren. Erfahren Sie mehr über diese Ebene in diesem mit Text Classification Tutorial. Beachten Sie aus den ersten paar Sätzen oben, dass der Text in einem Fall sein muss und Satzzeichen entfernt werden müssen. Um dies zu tun, definiert eine custom_standardization function , die in der TextVectorization Schicht verwendet werden kann.

# Now, create a custom standardization function to lowercase the text and
# remove punctuation.
def custom_standardization(input_data):
  lowercase = tf.strings.lower(input_data)
  return tf.strings.regex_replace(lowercase,
                                  '[%s]' % re.escape(string.punctuation), '')


# Define the vocabulary size and number of words in a sequence.
vocab_size = 4096
sequence_length = 10

# Use the text vectorization layer to normalize, split, and map strings to
# integers. Set output_sequence_length length to pad all samples to same length.
vectorize_layer = layers.experimental.preprocessing.TextVectorization(
    standardize=custom_standardization,
    max_tokens=vocab_size,
    output_mode='int',
    output_sequence_length=sequence_length)

Rufen Sie adapt auf den Text - Datensatz Vokabular zu erstellen.

vectorize_layer.adapt(text_ds.batch(1024))
2021-08-06 01:26:36.280475: I tensorflow/compiler/mlir/mlir_graph_optimization_pass.cc:176] None of the MLIR Optimization Passes are enabled (registered 2)
2021-08-06 01:26:36.280911: I tensorflow/core/platform/profile_utils/cpu_utils.cc:114] CPU Frequency: 2000185000 Hz

Sobald der Zustand der Schicht , die den Textkorpus darzustellen angepasst wurde, kann der Wortschatz mit zugegriffen werden get_vocabulary() . Diese Funktion gibt eine Liste aller Vokabular-Tokens sortiert (absteigend) nach ihrer Häufigkeit zurück.

# Save the created vocabulary for reference.
inverse_vocab = vectorize_layer.get_vocabulary()
print(inverse_vocab[:20])
['', '[UNK]', 'the', 'and', 'to', 'i', 'of', 'you', 'my', 'a', 'that', 'in', 'is', 'not', 'for', 'with', 'me', 'it', 'be', 'your']

Die vectorize_layer kann nun verwendet werden , um Vektoren in dem für jedes Element zu erzeugen text_ds .

# Vectorize the data in text_ds.
text_vector_ds = text_ds.batch(1024).prefetch(AUTOTUNE).map(vectorize_layer).unbatch()

Erhalten Sie Sequenzen aus dem Datensatz

Sie haben nun eine tf.data.Dataset von Integer - codierten Sätze. Um den Datensatz für das Training eines Word2Vec-Modells vorzubereiten, reduzieren Sie den Datensatz in eine Liste von Satzvektorsequenzen. Dieser Schritt ist erforderlich, da Sie jeden Satz im Dataset durchlaufen würden, um positive und negative Beispiele zu erzeugen.

sequences = list(text_vector_ds.as_numpy_iterator())
print(len(sequences))
32777

Werfen Sie einen Blick auf einige Beispiele von sequences .

for seq in sequences[:5]:
  print(f"{seq} => {[inverse_vocab[i] for i in seq]}")
[ 89 270   0   0   0   0   0   0   0   0] => ['first', 'citizen', '', '', '', '', '', '', '', '']
[138  36 982 144 673 125  16 106   0   0] => ['before', 'we', 'proceed', 'any', 'further', 'hear', 'me', 'speak', '', '']
[34  0  0  0  0  0  0  0  0  0] => ['all', '', '', '', '', '', '', '', '', '']
[106 106   0   0   0   0   0   0   0   0] => ['speak', 'speak', '', '', '', '', '', '', '', '']
[ 89 270   0   0   0   0   0   0   0   0] => ['first', 'citizen', '', '', '', '', '', '', '', '']

Trainingsbeispiele aus Sequenzen generieren

sequences ist jetzt eine Liste von int codiert Sätze. Rufen Sie einfach die generate_training_data() Funktion früher definierten Trainingsbeispiele für das Word2Vec Modell zu erzeugen. Um es noch einmal zusammenzufassen, die Funktion iteriert über jedes Wort aus jeder Sequenz, um positive und negative Kontextwörter zu sammeln. Die Länge des Ziels, der Kontexte und der Labels sollten gleich sein und die Gesamtzahl der Trainingsbeispiele darstellen.

targets, contexts, labels = generate_training_data(
    sequences=sequences,
    window_size=2,
    num_ns=4,
    vocab_size=vocab_size,
    seed=SEED)

targets = np.array(targets)
contexts = np.array(contexts)[:,:,0]
labels = np.array(labels)

print('\n')
print(f"targets.shape: {targets.shape}")
print(f"contexts.shape: {contexts.shape}")
print(f"labels.shape: {labels.shape}")
100%|██████████| 32777/32777 [00:29<00:00, 1119.65it/s]
targets.shape: (65401,)
contexts.shape: (65401, 5)
labels.shape: (65401, 5)

Konfigurieren Sie das Dataset für die Leistung

Um eine effiziente Dosierung für die potenziell große Anzahl von Trainingsbeispielen auszuführen, verwenden Sie die tf.data.Dataset API. Nach diesem Schritt würden Sie eine haben tf.data.Dataset Aufgabe (target_word, context_word), (label) Elemente Ihre Word2Vec Modell zu trainieren!

BATCH_SIZE = 1024
BUFFER_SIZE = 10000
dataset = tf.data.Dataset.from_tensor_slices(((targets, contexts), labels))
dataset = dataset.shuffle(BUFFER_SIZE).batch(BATCH_SIZE, drop_remainder=True)
print(dataset)
<BatchDataset shapes: (((1024,), (1024, 5)), (1024, 5)), types: ((tf.int64, tf.int64), tf.int64)>

In cache() und prefetch() Leistung zu verbessern.

dataset = dataset.cache().prefetch(buffer_size=AUTOTUNE)
print(dataset)
<PrefetchDataset shapes: (((1024,), (1024, 5)), (1024, 5)), types: ((tf.int64, tf.int64), tf.int64)>

Modell und Training

Das Word2Vec-Modell kann als Klassifikator implementiert werden, um zwischen echten Kontextwörtern von Skip-Grams und falschen Kontextwörtern zu unterscheiden, die durch negative Abtastung erhalten wurden. Sie können ein Punktprodukt zwischen den Einbettungen von Ziel- und Kontextwörtern durchführen, um Vorhersagen für Labels zu erhalten und den Verlust gegenüber echten Labels im Dataset zu berechnen.

Untergeordnetes Word2Vec-Modell

Verwenden Sie die Keras Subclassing API Ihr Word2Vec Modell mit den folgenden Schichten zu definieren:

  • target_embedding : A tf.keras.layers.Embedding Schicht , die die Einbettung eines Wortes nachschlägt , wenn es als Zielwort erscheint. Die Anzahl der Parameter in dieser Schicht ist (vocab_size * embedding_dim) .
  • context_embedding : Eine weitere tf.keras.layers.Embedding Schicht , die die Einbettung eines Wortes sieht, wenn es als ein Kontextwort erscheint. Die Anzahl der Parameter in dieser Schicht sind die gleichen wie die in target_embedding , dh (vocab_size * embedding_dim) .
  • dots : A tf.keras.layers.Dot Schicht, die das Skalarprodukt des Ziel und Kontext Einbettungen aus einem Trainingspaar berechnet.
  • flatten : Eine tf.keras.layers.Flatten Schicht , die Ergebnisse zu glätten dots Schicht in Logits.

Mit dem Modell unterklassiert, können Sie die definieren , call() Funktion , die akzeptiert (target, context) Paare die dann in ihre entsprechenden Einbettungsschicht weitergegeben werden. Umformen die context_embedding eines Skalarprodukts auszuführen mit target_embedding und Rückkehr der abgeflachte Ergebnis.

class Word2Vec(tf.keras.Model):
  def __init__(self, vocab_size, embedding_dim):
    super(Word2Vec, self).__init__()
    self.target_embedding = layers.Embedding(vocab_size,
                                      embedding_dim,
                                      input_length=1,
                                      name="w2v_embedding")
    self.context_embedding = layers.Embedding(vocab_size,
                                       embedding_dim,
                                       input_length=num_ns+1)

  def call(self, pair):
    target, context = pair
    # target: (batch, dummy?)  # The dummy axis doesn't exist in TF2.7+
    # context: (batch, context)
    if len(target.shape) == 2:
      target = tf.squeeze(target, axis=1)
    # target: (batch,)
    word_emb = self.target_embedding(target)
    # word_emb: (batch, embed)
    context_emb = self.context_embedding(context)
    # context_emb: (batch, context, embed)
    dots = tf.einsum('be,bce->bc', word_emb, context_emb)
    # dots: (batch, context)
    return dots

Verlustfunktion definieren und Modell kompilieren

Der Einfachheit halber können Sie verwenden tf.keras.losses.CategoricalCrossEntropy als Alternative zu der negativen Stichprobenverlust. Wenn Sie Ihre eigene benutzerdefinierte Verlustfunktion schreiben möchten, können Sie dies auch wie folgt tun:

def custom_loss(x_logit, y_true):
      return tf.nn.sigmoid_cross_entropy_with_logits(logits=x_logit, labels=y_true)

Es ist Zeit, Ihr Modell zu bauen! Instanziieren Sie Ihre Word2Vec-Klasse mit einer Einbettungsdimension von 128 (Sie können mit verschiedenen Werten experimentieren). Kompilieren Sie das Modell mit dem tf.keras.optimizers.Adam Optimierer.

embedding_dim = 128
word2vec = Word2Vec(vocab_size, embedding_dim)
word2vec.compile(optimizer='adam',
                 loss=tf.keras.losses.CategoricalCrossentropy(from_logits=True),
                 metrics=['accuracy'])

Definieren Sie auch einen Callback, um Trainingsstatistiken für Tensorboard zu protokollieren.

tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir="logs")
2021-08-06 01:27:14.223981: I tensorflow/core/profiler/lib/profiler_session.cc:126] Profiler session initializing.
2021-08-06 01:27:14.224026: I tensorflow/core/profiler/lib/profiler_session.cc:141] Profiler session started.
2021-08-06 01:27:14.224091: I tensorflow/core/profiler/internal/gpu/cupti_tracer.cc:1611] Profiler found 1 GPUs
2021-08-06 01:27:14.272709: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcupti.so.11.2
2021-08-06 01:27:14.483511: I tensorflow/core/profiler/lib/profiler_session.cc:159] Profiler session tear down.
2021-08-06 01:27:14.486344: I tensorflow/core/profiler/internal/gpu/cupti_tracer.cc:1743] CUPTI activity buffer flushed

Trainieren Sie das Modell mit dataset - dataset oben für eine bestimmte Anzahl von Epochen vorbereitet.

word2vec.fit(dataset, epochs=20, callbacks=[tensorboard_callback])
Epoch 1/20
2021-08-06 01:27:15.135011: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcublas.so.11
1/63 [..............................] - ETA: 1:04 - loss: 1.6093 - accuracy: 0.1914
2021-08-06 01:27:15.575680: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcublasLt.so.11
2021-08-06 01:27:15.596099: I tensorflow/core/profiler/lib/profiler_session.cc:126] Profiler session initializing.
2021-08-06 01:27:15.596148: I tensorflow/core/profiler/lib/profiler_session.cc:141] Profiler session started.
18/63 [=======>......................] - ETA: 1s - loss: 1.6091 - accuracy: 0.2089
2021-08-06 01:27:15.855510: I tensorflow/core/profiler/lib/profiler_session.cc:66] Profiler session collecting data.
2021-08-06 01:27:15.858154: I tensorflow/core/profiler/internal/gpu/cupti_tracer.cc:1743] CUPTI activity buffer flushed
2021-08-06 01:27:15.892426: I tensorflow/core/profiler/internal/gpu/cupti_collector.cc:673]  GpuTracer has collected 49 callback api events and 47 activity events. 
2021-08-06 01:27:15.895770: I tensorflow/core/profiler/lib/profiler_session.cc:159] Profiler session tear down.
2021-08-06 01:27:15.910927: I tensorflow/core/profiler/rpc/client/save_profile.cc:137] Creating directory: logs/train/plugins/profile/2021_08_06_01_27_15
2021-08-06 01:27:15.920946: I tensorflow/core/profiler/rpc/client/save_profile.cc:143] Dumped gzipped tool data for trace.json.gz to logs/train/plugins/profile/2021_08_06_01_27_15/kokoro-gcp-ubuntu-prod-365443786.trace.json.gz
2021-08-06 01:27:15.930146: I tensorflow/core/profiler/rpc/client/save_profile.cc:137] Creating directory: logs/train/plugins/profile/2021_08_06_01_27_15
2021-08-06 01:27:15.930835: I tensorflow/core/profiler/rpc/client/save_profile.cc:143] Dumped gzipped tool data for memory_profile.json.gz to logs/train/plugins/profile/2021_08_06_01_27_15/kokoro-gcp-ubuntu-prod-365443786.memory_profile.json.gz
2021-08-06 01:27:15.931239: I tensorflow/core/profiler/rpc/client/capture_profile.cc:251] Creating directory: logs/train/plugins/profile/2021_08_06_01_27_15Dumped tool data for xplane.pb to logs/train/plugins/profile/2021_08_06_01_27_15/kokoro-gcp-ubuntu-prod-365443786.xplane.pb
Dumped tool data for overview_page.pb to logs/train/plugins/profile/2021_08_06_01_27_15/kokoro-gcp-ubuntu-prod-365443786.overview_page.pb
Dumped tool data for input_pipeline.pb to logs/train/plugins/profile/2021_08_06_01_27_15/kokoro-gcp-ubuntu-prod-365443786.input_pipeline.pb
Dumped tool data for tensorflow_stats.pb to logs/train/plugins/profile/2021_08_06_01_27_15/kokoro-gcp-ubuntu-prod-365443786.tensorflow_stats.pb
Dumped tool data for kernel_stats.pb to logs/train/plugins/profile/2021_08_06_01_27_15/kokoro-gcp-ubuntu-prod-365443786.kernel_stats.pb
63/63 [==============================] - 2s 12ms/step - loss: 1.6082 - accuracy: 0.2318
Epoch 2/20
63/63 [==============================] - 0s 6ms/step - loss: 1.5887 - accuracy: 0.5565
Epoch 3/20
63/63 [==============================] - 0s 6ms/step - loss: 1.5412 - accuracy: 0.6051
Epoch 4/20
63/63 [==============================] - 0s 6ms/step - loss: 1.4597 - accuracy: 0.5785
Epoch 5/20
63/63 [==============================] - 0s 6ms/step - loss: 1.3627 - accuracy: 0.5845
Epoch 6/20
63/63 [==============================] - 0s 6ms/step - loss: 1.2659 - accuracy: 0.6119
Epoch 7/20
63/63 [==============================] - 0s 6ms/step - loss: 1.1751 - accuracy: 0.6462
Epoch 8/20
63/63 [==============================] - 0s 6ms/step - loss: 1.0908 - accuracy: 0.6804
Epoch 9/20
63/63 [==============================] - 0s 6ms/step - loss: 1.0126 - accuracy: 0.7119
Epoch 10/20
63/63 [==============================] - 0s 7ms/step - loss: 0.9400 - accuracy: 0.7401
Epoch 11/20
63/63 [==============================] - 0s 6ms/step - loss: 0.8728 - accuracy: 0.7648
Epoch 12/20
63/63 [==============================] - 0s 6ms/step - loss: 0.8108 - accuracy: 0.7865
Epoch 13/20
63/63 [==============================] - 0s 6ms/step - loss: 0.7538 - accuracy: 0.8052
Epoch 14/20
63/63 [==============================] - 0s 6ms/step - loss: 0.7015 - accuracy: 0.8216
Epoch 15/20
63/63 [==============================] - 0s 6ms/step - loss: 0.6537 - accuracy: 0.8368
Epoch 16/20
63/63 [==============================] - 0s 6ms/step - loss: 0.6100 - accuracy: 0.8496
Epoch 17/20
63/63 [==============================] - 0s 6ms/step - loss: 0.5703 - accuracy: 0.8616
Epoch 18/20
63/63 [==============================] - 0s 6ms/step - loss: 0.5341 - accuracy: 0.8727
Epoch 19/20
63/63 [==============================] - 0s 6ms/step - loss: 0.5012 - accuracy: 0.8821
Epoch 20/20
63/63 [==============================] - 0s 6ms/step - loss: 0.4712 - accuracy: 0.8916
<tensorflow.python.keras.callbacks.History at 0x7f33bc6fd5d0>

Tensorboard zeigt jetzt die Genauigkeit und den Verlust des Word2Vec-Modells an.

%tensorboard --logdir logs

Einbetten von Lookup und Analyse

Erhalten , um die Gewichte aus dem Modell unter Verwendung get_layer() und get_weights() . Die get_vocabulary() Funktion liefert das Vokabular eine Metadatendatei mit einem Token pro Zeile zu bauen.

weights = word2vec.get_layer('w2v_embedding').get_weights()[0]
vocab = vectorize_layer.get_vocabulary()

Erstellen und speichern Sie die Vektor- und Metadatendatei.

out_v = io.open('vectors.tsv', 'w', encoding='utf-8')
out_m = io.open('metadata.tsv', 'w', encoding='utf-8')

for index, word in enumerate(vocab):
  if index == 0:
    continue  # skip 0, it's padding.
  vec = weights[index]
  out_v.write('\t'.join([str(x) for x in vec]) + "\n")
  out_m.write(word + "\n")
out_v.close()
out_m.close()

Laden Sie die vectors.tsv und metadata.tsv die erhaltenen Einbettungen in der Analyse Embedding - Projektor .

try:
  from google.colab import files
  files.download('vectors.tsv')
  files.download('metadata.tsv')
except Exception:
  pass

Nächste Schritte

Dieses Tutorial hat Ihnen gezeigt, wie Sie ein Skip-Gram-Word2Vec-Modell mit negativem Sampling von Grund auf neu implementieren und die erhaltenen Worteinbettungen visualisieren.