Eine Frage haben? Verbinden Sie sich mit der Community im TensorFlow Forum Visit Forum

Neuronale maschinelle Übersetzung mit Aufmerksamkeit

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

Dieses Notebook trainiert ein Sequenz-zu-Sequenz-Modell (seq2seq) für die Übersetzung vom Spanischen ins Englische basierend auf effektiven Ansätzen für aufmerksamkeitsbasierte neuronale maschinelle Übersetzung . Dies ist ein fortgeschrittenes Beispiel, das einige Kenntnisse voraussetzt:

  • Sequenz-zu-Sequenz-Modelle
  • TensorFlow-Grundlagen unterhalb der Keras-Schicht:

Obwohl diese Architektur etwas veraltet ist, ist sie immer noch ein sehr nützliches Projekt, um ein tieferes Verständnis der Aufmerksamkeitsmechanismen zu erlangen (bevor wir zu Transformers übergehen ).

Nachdem Sie das Modell in diesem Notizbuch trainiert haben, können Sie einen spanischen Satz wie " todavia estan en casa? " eingeben und die englische Übersetzung zurückgeben: " Sind Sie noch zu Hause? "

Das resultierende Modell kann als tf.saved_model exportiert werden, sodass es in anderen TensorFlow-Umgebungen verwendet werden kann.

Die Übersetzungsqualität ist für ein Spielzeugbeispiel angemessen, aber der generierte Aufmerksamkeitsplot ist vielleicht interessanter. Dies zeigt, welche Teile des Eingabesatzes beim Übersetzen die Aufmerksamkeit des Modells haben:

spanisch-englische Aufmerksamkeitsplot

Einrichten

pip install tensorflow_text
import numpy as np

import typing
from typing import Any, Tuple

import tensorflow as tf
from tensorflow.keras.layers.experimental import preprocessing

import tensorflow_text as tf_text

import matplotlib.pyplot as plt
import matplotlib.ticker as ticker

In diesem Tutorial werden einige Ebenen von Grund auf neu erstellt. Verwenden Sie diese Variable, wenn Sie zwischen der benutzerdefinierten und der integrierten Implementierung wechseln möchten.

use_builtins = True

Dieses Tutorial verwendet viele Low-Level-APIs, bei denen es leicht ist, Formen falsch zu machen. Diese Klasse wird verwendet, um Formen während des gesamten Tutorials zu überprüfen.

Formprüfer

Die Daten

Wir verwenden einen Sprachdatensatz, der von http://www.manythings.org/anki/ bereitgestellt wird. Dieser Datensatz enthält Sprachübersetzungspaare im Format:

May I borrow this book? ¿Puedo tomar prestado este libro?

Sie haben eine Vielzahl von Sprachen zur Verfügung, aber wir verwenden den englisch-spanischen Datensatz.

Laden Sie den Datensatz herunter und bereiten Sie ihn vor

Der Einfachheit halber haben wir eine Kopie dieses Datasets in Google Cloud gehostet, Sie können jedoch auch Ihre eigene Kopie herunterladen. Nach dem Herunterladen des Datasets führen wir die folgenden Schritte aus, um die Daten vorzubereiten:

  1. Fügen Sie jedem Satz ein Start- und ein Ende- Token hinzu.
  2. Bereinigen Sie die Sätze, indem Sie Sonderzeichen entfernen.
  3. Erstellen Sie einen Wortindex und einen umgekehrten Wortindex (Wörterbücher, die von Wort → ID und ID → Wort zugeordnet werden).
  4. Füllen Sie jeden Satz auf eine maximale Länge auf.
# Download the file
import pathlib

path_to_zip = tf.keras.utils.get_file(
    'spa-eng.zip', origin='http://storage.googleapis.com/download.tensorflow.org/data/spa-eng.zip',
    extract=True)

path_to_file = pathlib.Path(path_to_zip).parent/'spa-eng/spa.txt'
Downloading data from http://storage.googleapis.com/download.tensorflow.org/data/spa-eng.zip
2646016/2638744 [==============================] - 0s 0us/step
def load_data(path):
  text = path.read_text(encoding='utf-8')

  lines = text.splitlines()
  pairs = [line.split('\t') for line in lines]

  inp = [inp for targ, inp in pairs]
  targ = [targ for targ, inp in pairs]

  return targ, inp
targ, inp = load_data(path_to_file)
print(inp[-1])
Si quieres sonar como un hablante nativo, debes estar dispuesto a practicar diciendo la misma frase una y otra vez de la misma manera en que un músico de banjo practica el mismo fraseo una y otra vez hasta que lo puedan tocar correctamente y en el tiempo esperado.
print(targ[-1])
If you want to sound like a native speaker, you must be willing to practice saying the same sentence over and over in the same way that banjo players practice the same phrase over and over until they can play it correctly and at the desired tempo.

Erstellen Sie einen tf.data-Datensatz

Aus diesen String-Arrays können Sie eintf.data.Dataset von Strings erstellen, das sie effizient mischt undtf.data.Dataset :

BUFFER_SIZE = len(inp)
BATCH_SIZE = 64

dataset = tf.data.Dataset.from_tensor_slices((inp, targ)).shuffle(BUFFER_SIZE)
dataset = dataset.batch(BATCH_SIZE)
for example_input_batch, example_target_batch in dataset.take(1):
  print(example_input_batch[:5])
  print()
  print(example_target_batch[:5])
  break
tf.Tensor(
[b'Detesto jugar a las cartas.'
 b'\xc3\x89l est\xc3\xa1 teniendo una aventura con su secretaria.'
 b'Tom le dijo a Mary lo que hab\xc3\xada visto en televisi\xc3\xb3n.'
 b'Pens\xc3\xa9 que Mar\xc3\xada a\xc3\xban me amaba.'
 b'El hu\xc3\xa9rfano se reuni\xc3\xb3 con sus dos hermanas cuando ten\xc3\xada cinco a\xc3\xb1os.'], shape=(5,), dtype=string)

tf.Tensor(
[b'I hate playing cards.' b'He is having an affair with his secretary.'
 b"Tom told Mary what he'd seen on TV." b'I thought Mary still loved me.'
 b'The orphan met up with his two sisters when he was five years old.'], shape=(5,), dtype=string)

Textvorverarbeitung

Eines der Ziele dieses Tutorials besteht darin, ein Modell zu erstellen, das als tf.saved_model exportiert werden tf.saved_model . Um dieses exportierte Modell nützlich zu machen, sollte es tf.string Eingaben verwenden und tf.string Ausgaben erneut tf.string : Die gesamte Textverarbeitung findet innerhalb des Modells statt.

Standardisierung

Das Modell befasst sich mit mehrsprachigem Text mit begrenztem Wortschatz. Daher ist es wichtig, den Eingabetext zu standardisieren.

Der erste Schritt ist die Unicode-Normalisierung, um Zeichen mit Akzent aufzuteilen und Kompatibilitätszeichen durch ihre ASCII-Äquivalente zu ersetzen.

Das Paket tensroflow_text enthält eine Unicode-Normalisierungsoperation:

example_text = tf.constant('¿Todavía está en casa?')

print(example_text.numpy())
print(tf_text.normalize_utf8(example_text, 'NFKD').numpy())
b'\xc2\xbfTodav\xc3\xada est\xc3\xa1 en casa?'
b'\xc2\xbfTodavi\xcc\x81a esta\xcc\x81 en casa?'

Die Unicode-Normalisierung ist der erste Schritt in der Textstandardisierungsfunktion:

def tf_lower_and_split_punct(text):
  # Split accecented characters.
  text = tf_text.normalize_utf8(text, 'NFKD')
  text = tf.strings.lower(text)
  # Keep space, a to z, and select punctuation.
  text = tf.strings.regex_replace(text, '[^ a-z.?!,¿]', '')
  # Add spaces around punctuation.
  text = tf.strings.regex_replace(text, '[.?!,¿]', r' \0 ')
  # Strip whitespace.
  text = tf.strings.strip(text)

  text = tf.strings.join(['[START]', text, '[END]'], separator=' ')
  return text
print(example_text.numpy().decode())
print(tf_lower_and_split_punct(example_text).numpy().decode())
¿Todavía está en casa?
[START] ¿ todavia esta en casa ? [END]

Textvektorisierung

Diese Standardisierungsfunktion wird in eine preprocessing.TextVectorization Schicht eingeschlossen, die die Vokabularextraktion und die Konvertierung von Eingabetext in preprocessing.TextVectorization übernimmt.

max_vocab_size = 5000

input_text_processor = preprocessing.TextVectorization(
    standardize=tf_lower_and_split_punct,
    max_tokens=max_vocab_size)

Die TextVectorization Schicht und viele andere experimental.preprocessing Schichten haben eine adapt Methode. Diese Methode liest eine Epoche der Trainingsdaten und funktioniert ähnlich wie Model.fix . Dieses adapt initialisiert die Schicht basierend auf den Daten. Hier bestimmt es den Wortschatz:

input_text_processor.adapt(inp)

# Here are the first 10 words from the vocabulary:
input_text_processor.get_vocabulary()[:10]
['', '[UNK]', '[START]', '[END]', '.', 'que', 'de', 'el', 'a', 'no']

Das ist die spanische TextVectorization Ebene, jetzt erstellen und .adapt() die englische:

output_text_processor = preprocessing.TextVectorization(
    standardize=tf_lower_and_split_punct,
    max_tokens=max_vocab_size)

output_text_processor.adapt(targ)
output_text_processor.get_vocabulary()[:10]
['', '[UNK]', '[START]', '[END]', '.', 'the', 'i', 'to', 'you', 'tom']

Jetzt können diese Layer einen Stapel von Zeichenfolgen in einen Stapel von Token-IDs umwandeln:

example_tokens = input_text_processor(example_input_batch)
example_tokens[:3, :10]
<tf.Tensor: shape=(3, 10), dtype=int64, numpy=
array([[   2, 2053,  319,    8,   34,  930,    4,    3,    0,    0],
       [   2,    7,   20, 2381,   23,    1,   27,   25, 3693,    4],
       [   2,   10,   28,   92,    8,   32,   22,    5,  103,  218]])>

Die Methode get_vocabulary kann verwendet werden, um Token-IDs wieder in Text umzuwandeln:

input_vocab = np.array(input_text_processor.get_vocabulary())
tokens = input_vocab[example_tokens[0].numpy()]
' '.join(tokens)
'[START] detesto jugar a las cartas . [END]            '

Die zurückgegebenen Token-IDs werden mit Nullen aufgefüllt. Daraus lässt sich ganz einfach eine Maske machen:

plt.subplot(1, 2, 1)
plt.pcolormesh(example_tokens)
plt.title('Token IDs')

plt.subplot(1, 2, 2)
plt.pcolormesh(example_tokens != 0)
plt.title('Mask')
Text(0.5, 1.0, 'Mask')

png

Das Encoder/Decoder-Modell

Das folgende Diagramm zeigt einen Überblick über das Modell. Bei jedem Zeitschritt wird die Ausgabe des Decodierers mit einer gewichteten Summe über die codierte Eingabe kombiniert, um das nächste Wort vorherzusagen. Das Diagramm und die Formeln stammen aus Luongs Papier .

Aufmerksamkeitsmechanismus

Bevor Sie darauf eingehen, definieren Sie einige Konstanten für das Modell:

embedding_dim = 256
units = 1024

Der Encoder

Beginnen Sie mit dem Bau des Encoders, dem blauen Teil des obigen Diagramms.

Der Encoder:

  1. Nimmt eine Liste von Token-IDs (von input_text_processor ).
  2. layers.Embedding für jedes Token nach einem Einbettungsvektor (Using layers.Embedding ).
  3. Verarbeitet die Einbettungen in eine neue Sequenz (Using layers.GRU ).
  4. Kehrt zurück:
    • Die verarbeitete Sequenz. Dies wird an den Aufmerksamkeitskopf weitergegeben.
    • Der innere Zustand. Dies wird verwendet, um den Decoder zu initialisieren
class Encoder(tf.keras.layers.Layer):
  def __init__(self, input_vocab_size, embedding_dim, enc_units):
    super(Encoder, self).__init__()
    self.enc_units = enc_units
    self.input_vocab_size = input_vocab_size

    # The embedding layer converts tokens to vectors
    self.embedding = tf.keras.layers.Embedding(self.input_vocab_size,
                                               embedding_dim)

    # The GRU RNN layer processes those vectors sequentially.
    self.gru = tf.keras.layers.GRU(self.enc_units,
                                   # Return the sequence and state
                                   return_sequences=True,
                                   return_state=True,
                                   recurrent_initializer='glorot_uniform')

  def call(self, tokens, state=None):
    shape_checker = ShapeChecker()
    shape_checker(tokens, ('batch', 's'))

    # 2. The embedding layer looks up the embedding for each token.
    vectors = self.embedding(tokens)
    shape_checker(vectors, ('batch', 's', 'embed_dim'))

    # 3. The GRU processes the embedding sequence.
    #    output shape: (batch, s, enc_units)
    #    state shape: (batch, enc_units)
    output, state = self.gru(vectors, initial_state=state)
    shape_checker(output, ('batch', 's', 'enc_units'))
    shape_checker(state, ('batch', 'enc_units'))

    # 4. Returns the new sequence and its state.
    return output, state

So passt es bisher zusammen:

# Convert the input text to tokens.
example_tokens = input_text_processor(example_input_batch)

# Encode the input sequence.
encoder = Encoder(input_text_processor.vocabulary_size(),
                  embedding_dim, units)
example_enc_output, example_enc_state = encoder(example_tokens)

print(f'Input batch, shape (batch): {example_input_batch.shape}')
print(f'Input batch tokens, shape (batch, s): {example_tokens.shape}')
print(f'Encoder output, shape (batch, s, units): {example_enc_output.shape}')
print(f'Encoder state, shape (batch, units): {example_enc_state.shape}')
Input batch, shape (batch): (64,)
Input batch tokens, shape (batch, s): (64, 20)
Encoder output, shape (batch, s, units): (64, 20, 1024)
Encoder state, shape (batch, units): (64, 1024)

Der Encoder gibt seinen internen Zustand zurück, so dass sein Zustand verwendet werden kann, um den Decoder zu initialisieren.

Es ist auch üblich, dass ein RNN seinen Status zurückgibt, damit es eine Sequenz über mehrere Aufrufe hinweg verarbeiten kann. Sie werden mehr davon sehen, wenn Sie den Decoder bauen.

Der Aufmerksamkeitskopf

Der Decoder verwendet Aufmerksamkeit, um sich selektiv auf Teile der Eingabesequenz zu konzentrieren. Die Aufmerksamkeit nimmt für jedes Beispiel eine Folge von Vektoren als Eingabe und gibt für jedes Beispiel einen "Aufmerksamkeits"-Vektor zurück. Dieser Aufmerksamkeits-Layer ähnelt einem layers.GlobalAveragePoling1D aber der Aufmerksamkeits-Layer führt einen gewichteten Durchschnitt durch.

Schauen wir uns an, wie das funktioniert:

Aufmerksamkeitsgleichung 1

Aufmerksamkeitsgleichung 2

Wo:

  • $s$ ist der Encoder-Index.
  • $t$ ist der Decoderindex.
  • $\alpha_{ts}$ sind die Aufmerksamkeitsgewichte.
  • $h_s$ ist die Reihenfolge der Geberausgaben, die beachtet werden (die Aufmerksamkeit "Schlüssel" und "Wert" in der Terminologie des Transformators).
  • $h_t$ ist der Decoderzustand, der die Sequenz beachtet (die Aufmerksamkeits-"Abfrage" in der Terminologie des Transformators).
  • $c_t$ ist der resultierende Kontextvektor.
  • $a_t$ ist die endgültige Ausgabe, die "Kontext" und "Abfrage" kombiniert.

Die Gleichungen:

  1. Berechnet die Aufmerksamkeitsgewichte $\alpha_{ts}$ als Softmax über die Ausgabesequenz des Encoders.
  2. Berechnet den Kontextvektor als gewichtete Summe der Encoder-Ausgaben.

Zuletzt ist die $score$-Funktion. Seine Aufgabe besteht darin, für jedes Schlüssel-Abfrage-Paar einen skalaren Logit-Score zu berechnen. Es gibt zwei gängige Ansätze:

Aufmerksamkeitsgleichung 4

Dieses Tutorial verwendet Bahdanaus additive Aufmerksamkeit . TensorFlow enthält Implementierungen von beiden als layers.Attention und layers.AdditiveAttention . Die folgende Klasse behandelt die Gewichtungsmatrizen in einem Paar von Schichten. layers.Dense Schichten und ruft die eingebaute Implementierung auf.

class BahdanauAttention(tf.keras.layers.Layer):
  def __init__(self, units):
    super().__init__()
    # For Eqn. (4), the  Bahdanau attention
    self.W1 = tf.keras.layers.Dense(units, use_bias=False)
    self.W2 = tf.keras.layers.Dense(units, use_bias=False)

    self.attention = tf.keras.layers.AdditiveAttention()

  def call(self, query, value, mask):
    shape_checker = ShapeChecker()
    shape_checker(query, ('batch', 't', 'query_units'))
    shape_checker(value, ('batch', 's', 'value_units'))
    shape_checker(mask, ('batch', 's'))

    # From Eqn. (4), `W1@ht`.
    w1_query = self.W1(query)
    shape_checker(w1_query, ('batch', 't', 'attn_units'))

    # From Eqn. (4), `W2@hs`.
    w2_key = self.W2(value)
    shape_checker(w2_key, ('batch', 's', 'attn_units'))

    query_mask = tf.ones(tf.shape(query)[:-1], dtype=bool)
    value_mask = mask

    context_vector, attention_weights = self.attention(
        inputs = [w1_query, value, w2_key],
        mask=[query_mask, value_mask],
        return_attention_scores = True,
    )
    shape_checker(context_vector, ('batch', 't', 'value_units'))
    shape_checker(attention_weights, ('batch', 't', 's'))

    return context_vector, attention_weights

Testen Sie die Aufmerksamkeitsschicht

Erstellen Sie eine BahdanauAttention Ebene:

attention_layer = BahdanauAttention(units)

Diese Ebene benötigt 3 Eingaben:

  • Die query : Diese wird später vom Decoder generiert.
  • Der value : Dies ist die Ausgabe des Encoders.
  • Die mask : Um das Auffüllen auszuschließen, example_tokens != 0
(example_tokens != 0).shape
TensorShape([64, 20])

Mit der vektorisierten Implementierung der Aufmerksamkeitsschicht können Sie einen Stapel von Sequenzen von Abfragevektoren und einen Stapel von Sequenzen von Wertvektoren übergeben. Das Ergebnis ist:

  1. Ein Stapel von Folgen von Ergebnisvektoren bestimmt die Größe der Abfragen.
  2. Eine Batch-Aufmerksamkeitszuordnung mit size (query_length, value_length) .
# Later, the decoder will generate this attention query
example_attention_query = tf.random.normal(shape=[len(example_tokens), 2, 10])

# Attend to the encoded tokens

context_vector, attention_weights = attention_layer(
    query=example_attention_query,
    value=example_enc_output,
    mask=(example_tokens != 0))

print(f'Attention result shape: (batch_size, query_seq_length, units):           {context_vector.shape}')
print(f'Attention weights shape: (batch_size, query_seq_length, value_seq_length): {attention_weights.shape}')
Attention result shape: (batch_size, query_seq_length, units):           (64, 2, 1024)
Attention weights shape: (batch_size, query_seq_length, value_seq_length): (64, 2, 20)

Die Aufmerksamkeitsgewichte sollten für jede Sequenz 1.0 .

Hier sind die Aufmerksamkeitsgewichte über die Sequenzen bei t=0 :

plt.subplot(1, 2, 1)
plt.pcolormesh(attention_weights[:, 0, :])
plt.title('Attention weights')

plt.subplot(1, 2, 2)
plt.pcolormesh(example_tokens != 0)
plt.title('Mask')
Text(0.5, 1.0, 'Mask')

png

Wegen der kleinen zufälligen Initialisierung liegen die Aufmerksamkeitsgewichte alle nahe bei 1/(sequence_length) . Wenn Sie die Gewichtungen für eine einzelne Sequenz vergrößern, können Sie sehen, dass es einige kleine Variationen gibt, die das Modell lernen kann, zu erweitern und zu nutzen.

attention_weights.shape
TensorShape([64, 2, 20])
attention_slice = attention_weights[0, 0].numpy()
attention_slice = attention_slice[attention_slice != 0]
plt.suptitle('Attention weights for one sequence')

plt.figure(figsize=(12, 6))
a1 = plt.subplot(1, 2, 1)
plt.bar(range(len(attention_slice)), attention_slice)
# freeze the xlim
plt.xlim(plt.xlim())
plt.xlabel('Attention weights')

a2 = plt.subplot(1, 2, 2)
plt.bar(range(len(attention_slice)), attention_slice)
plt.xlabel('Attention weights, zoomed')

# zoom in
top = max(a1.get_ylim())
zoom = 0.85*top
a2.set_ylim([0.90*top, top])
a1.plot(a1.get_xlim(), [zoom, zoom], color='k')
[<matplotlib.lines.Line2D at 0x7f7f3976aed0>]
<Figure size 432x288 with 0 Axes>

png

Der Decoder

Die Aufgabe des Decoders besteht darin, Vorhersagen für das nächste Ausgabe-Token zu generieren.

  1. Der Decoder erhält die komplette Encoder-Ausgabe.
  2. Es verwendet ein RNN, um zu verfolgen, was es bisher generiert hat.
  3. Es verwendet seine RNN-Ausgabe als Abfrage zur Aufmerksamkeit über die Ausgabe des Encoders und erzeugt den Kontextvektor.
  4. Es kombiniert die RNN-Ausgabe und den Kontextvektor unter Verwendung von Gleichung 3 (unten), um den "Aufmerksamkeitsvektor" zu erzeugen.
  5. Es generiert Logit-Vorhersagen für das nächste Token basierend auf dem "Aufmerksamkeitsvektor".

Aufmerksamkeitsgleichung 3

Hier ist die Decoder Klasse und ihr Initialisierer. Der Initialisierer erstellt alle erforderlichen Ebenen.

class Decoder(tf.keras.layers.Layer):
  def __init__(self, output_vocab_size, embedding_dim, dec_units):
    super(Decoder, self).__init__()
    self.dec_units = dec_units
    self.output_vocab_size = output_vocab_size
    self.embedding_dim = embedding_dim

    # For Step 1. The embedding layer convets token IDs to vectors
    self.embedding = tf.keras.layers.Embedding(self.output_vocab_size,
                                               embedding_dim)

    # For Step 2. The RNN keeps track of what's been generated so far.
    self.gru = tf.keras.layers.GRU(self.dec_units,
                                   return_sequences=True,
                                   return_state=True,
                                   recurrent_initializer='glorot_uniform')

    # For step 3. The RNN output will be the query for the attention layer.
    self.attention = BahdanauAttention(self.dec_units)

    # For step 4. Eqn. (3): converting `ct` to `at`
    self.Wc = tf.keras.layers.Dense(dec_units, activation=tf.math.tanh,
                                    use_bias=False)

    # For step 5. This fully connected layer produces the logits for each
    # output token.
    self.fc = tf.keras.layers.Dense(self.output_vocab_size)

Die call für diese Schicht nimmt mehrere Tensoren an und gibt sie zurück. Organisieren Sie diese in einfachen Containerklassen:

class DecoderInput(typing.NamedTuple):
  new_tokens: Any
  enc_output: Any
  mask: Any

class DecoderOutput(typing.NamedTuple):
  logits: Any
  attention_weights: Any

Hier ist die Implementierung der call Methode:

def call(self,
         inputs: DecoderInput,
         state=None) -> Tuple[DecoderOutput, tf.Tensor]:
  shape_checker = ShapeChecker()
  shape_checker(inputs.new_tokens, ('batch', 't'))
  shape_checker(inputs.enc_output, ('batch', 's', 'enc_units'))
  shape_checker(inputs.mask, ('batch', 's'))

  if state is not None:
    shape_checker(state, ('batch', 'dec_units'))

  # Step 1. Lookup the embeddings
  vectors = self.embedding(inputs.new_tokens)
  shape_checker(vectors, ('batch', 't', 'embedding_dim'))

  # Step 2. Process one step with the RNN
  rnn_output, state = self.gru(vectors, initial_state=state)

  shape_checker(rnn_output, ('batch', 't', 'dec_units'))
  shape_checker(state, ('batch', 'dec_units'))

  # Step 3. Use the RNN output as the query for the attention over the
  # encoder output.
  context_vector, attention_weights = self.attention(
      query=rnn_output, value=inputs.enc_output, mask=inputs.mask)
  shape_checker(context_vector, ('batch', 't', 'dec_units'))
  shape_checker(attention_weights, ('batch', 't', 's'))

  # Step 4. Eqn. (3): Join the context_vector and rnn_output
  #     [ct; ht] shape: (batch t, value_units + query_units)
  context_and_rnn_output = tf.concat([context_vector, rnn_output], axis=-1)

  # Step 4. Eqn. (3): `at = tanh(Wc@[ct; ht])`
  attention_vector = self.Wc(context_and_rnn_output)
  shape_checker(attention_vector, ('batch', 't', 'dec_units'))

  # Step 5. Generate logit predictions:
  logits = self.fc(attention_vector)
  shape_checker(logits, ('batch', 't', 'output_vocab_size'))

  return DecoderOutput(logits, attention_weights), state
Decoder.call = call

Der Codierer verarbeitet seine vollständige Eingabesequenz mit einem einzigen Aufruf seines RNN. Diese Implementierung des Decoders kann dies auch für ein effizientes Training tun. In diesem Tutorial wird der Decoder jedoch aus mehreren Gründen in einer Schleife ausgeführt:

  • Flexibilität: Durch das Schreiben der Schleife haben Sie die direkte Kontrolle über den Trainingsablauf.
  • Klarheit: Es ist möglich, Maskierungstricks tfa.seq2seq APIs von layers.RNN oder tfa.seq2seq zu verwenden, um dies alles in einen einzigen Aufruf zu packen. Aber es kann klarer sein, es als Schleife auszuschreiben.

Versuchen Sie nun, diesen Decoder zu verwenden.

decoder = Decoder(output_text_processor.vocabulary_size(),
                  embedding_dim, units)

Der Decoder benötigt 4 Eingänge.

  • new_tokens - Das zuletzt generierte Token. Initialisieren Sie den Decoder mit dem Token "[START]" .
  • enc_outputenc_output vom Encoder generiert.
  • mask - Ein boolescher Tensor, der angibt, wo tokens != 0
  • state - Der vorherige state des Decoders (der interne Zustand des RNN des Decoders). Übergeben Sie None , um es auf Null zu initialisieren. Das Originalpapier initialisiert es vom endgültigen RNN-Zustand des Encoders.
# Convert the target sequence, and collect the "[START]" tokens
example_output_tokens = output_text_processor(example_target_batch)

start_index = output_text_processor._index_lookup_layer('[START]').numpy()
first_token = tf.constant([[start_index]] * example_output_tokens.shape[0])
# Run the decoder
dec_result, dec_state = decoder(
    inputs = DecoderInput(new_tokens=first_token,
                          enc_output=example_enc_output,
                          mask=(example_tokens != 0)),
    state = example_enc_state
)

print(f'logits shape: (batch_size, t, output_vocab_size) {dec_result.logits.shape}')
print(f'state shape: (batch_size, dec_units) {dec_state.shape}')
logits shape: (batch_size, t, output_vocab_size) (64, 1, 5000)
state shape: (batch_size, dec_units) (64, 1024)

Beispiel eines Tokens gemäß den Logits:

sampled_token = tf.random.categorical(dec_result.logits[:, 0, :], num_samples=1)

Decodieren Sie das Token als erstes Wort der Ausgabe:

vocab = np.array(output_text_processor.get_vocabulary())
first_word = vocab[sampled_token.numpy()]
first_word[:5]
array([['sounds'],
       ['does'],
       ['valid'],
       ['international'],
       ['loudly']], dtype='<U16')

Verwenden Sie nun den Decoder, um einen zweiten Satz von Logits zu generieren.

  • enc_output die gleiche enc_output und mask , diese haben sich nicht geändert.
  • new_tokens das abgetastete Token als new_tokens .
  • decoder_state den decoder_state den der Decoder das letzte Mal zurückgegeben hat, damit das RNN mit einer Erinnerung an die Stelle decoder_state der es beim letzten Mal decoder_state .
dec_result, dec_state = decoder(
    DecoderInput(sampled_token,
                 example_enc_output,
                 mask=(example_tokens != 0)),
    state=dec_state)
sampled_token = tf.random.categorical(dec_result.logits[:, 0, :], num_samples=1)
first_word = vocab[sampled_token.numpy()]
first_word[:5]
array([['fact'],
       ['worthless'],
       ['communicate'],
       ['darker'],
       ['plates']], dtype='<U16')

Ausbildung

Nachdem Sie nun über alle Modellkomponenten verfügen, können Sie mit dem Trainieren des Modells beginnen. Du brauchst:

  • Eine Verlustfunktion und ein Optimierer zum Durchführen der Optimierung.
  • Eine Trainingsschrittfunktion, die definiert, wie das Modell für jeden Eingabe-/Zielstapel zu aktualisieren ist.
  • Eine Trainingsschleife, um das Training voranzutreiben und Kontrollpunkte zu speichern.

Definieren Sie die Verlustfunktion

class MaskedLoss(tf.keras.losses.Loss):
  def __init__(self):
    self.name = 'masked_loss'
    self.loss = tf.keras.losses.SparseCategoricalCrossentropy(
        from_logits=True, reduction='none')

  def __call__(self, y_true, y_pred):
    shape_checker = ShapeChecker()
    shape_checker(y_true, ('batch', 't'))
    shape_checker(y_pred, ('batch', 't', 'logits'))

    # Calculate the loss for each item in the batch.
    loss = self.loss(y_true, y_pred)
    shape_checker(loss, ('batch', 't'))

    # Mask off the losses on padding.
    mask = tf.cast(y_true != 0, tf.float32)
    shape_checker(mask, ('batch', 't'))
    loss *= mask

    # Return the total.
    return tf.reduce_sum(loss)

Implementieren Sie den Trainingsschritt

Beginnen Sie mit einer Modellklasse, der Trainingsprozess wird als train_step Methode auf diesem Modell implementiert. Weitere Informationen finden Sie unter Anpassen der Passform .

Hier ist die Methode train_step ein Wrapper um die Implementierung von _train_step , die später _train_step wird. Dieser Wrapper enthält einen Schalter zum Ein- und Ausschalten der tf.function Kompilierung, um das Debuggen zu vereinfachen.

class TrainTranslator(tf.keras.Model):
  def __init__(self, embedding_dim, units,
               input_text_processor,
               output_text_processor, 
               use_tf_function=True):
    super().__init__()
    # Build the encoder and decoder
    encoder = Encoder(input_text_processor.vocabulary_size(),
                      embedding_dim, units)
    decoder = Decoder(output_text_processor.vocabulary_size(),
                      embedding_dim, units)

    self.encoder = encoder
    self.decoder = decoder
    self.input_text_processor = input_text_processor
    self.output_text_processor = output_text_processor
    self.use_tf_function = use_tf_function
    self.shape_checker = ShapeChecker()

  def train_step(self, inputs):
    self.shape_checker = ShapeChecker()
    if self.use_tf_function:
      return self._tf_train_step(inputs)
    else:
      return self._train_step(inputs)

Insgesamt Model.train_step die Implementierung für die Methode Model.train_step wie folgt aus:

  1. Empfangen Sie einen Stapel von input_text, target_text aus demtf.data.Dataset .
  2. Konvertieren Sie diese Rohtexteingaben in Token-Einbettungen und -Masken.
  3. Führen Sie den Geber an dem input_tokens den bekommen encoder_output und encoder_state .
  4. Initialisieren Sie den Decoderstatus und den Verlust.
  5. Schleife über die target_tokens :
    1. Führen Sie den Decoder Schritt für Schritt aus.
    2. Berechnen Sie den Verlust für jeden Schritt.
    3. Akkumulieren Sie den durchschnittlichen Verlust.
  6. Berechnen Sie den Gradienten des Verlusts und verwenden Sie den Optimierer, um Aktualisierungen auf die trainable_variables des Modells anzuwenden.

Die _preprocess hinzugefügte Methode _preprocess implementiert die Schritte 1 und 2:

def _preprocess(self, input_text, target_text):
  self.shape_checker(input_text, ('batch',))
  self.shape_checker(target_text, ('batch',))

  # Convert the text to token IDs
  input_tokens = self.input_text_processor(input_text)
  target_tokens = self.output_text_processor(target_text)
  self.shape_checker(input_tokens, ('batch', 's'))
  self.shape_checker(target_tokens, ('batch', 't'))

  # Convert IDs to masks.
  input_mask = input_tokens != 0
  self.shape_checker(input_mask, ('batch', 's'))

  target_mask = target_tokens != 0
  self.shape_checker(target_mask, ('batch', 't'))

  return input_tokens, input_mask, target_tokens, target_mask
TrainTranslator._preprocess = _preprocess

Die _train_step hinzugefügte Methode _train_step verarbeitet die verbleibenden Schritte, außer dass der Decoder tatsächlich ausgeführt wird:

def _train_step(self, inputs):
  input_text, target_text = inputs  

  (input_tokens, input_mask,
   target_tokens, target_mask) = self._preprocess(input_text, target_text)

  max_target_length = tf.shape(target_tokens)[1]

  with tf.GradientTape() as tape:
    # Encode the input
    enc_output, enc_state = self.encoder(input_tokens)
    self.shape_checker(enc_output, ('batch', 's', 'enc_units'))
    self.shape_checker(enc_state, ('batch', 'enc_units'))

    # Initialize the decoder's state to the encoder's final state.
    # This only works if the encoder and decoder have the same number of
    # units.
    dec_state = enc_state
    loss = tf.constant(0.0)

    for t in tf.range(max_target_length-1):
      # Pass in two tokens from the target sequence:
      # 1. The current input to the decoder.
      # 2. The target the target for the decoder's next prediction.
      new_tokens = target_tokens[:, t:t+2]
      step_loss, dec_state = self._loop_step(new_tokens, input_mask,
                                             enc_output, dec_state)
      loss = loss + step_loss

    # Average the loss over all non padding tokens.
    average_loss = loss / tf.reduce_sum(tf.cast(target_mask, tf.float32))

  # Apply an optimization step
  variables = self.trainable_variables 
  gradients = tape.gradient(average_loss, variables)
  self.optimizer.apply_gradients(zip(gradients, variables))

  # Return a dict mapping metric names to current value
  return {'batch_loss': average_loss}
TrainTranslator._train_step = _train_step

Die _loop_step hinzugefügte _loop_step Methode führt den Decoder aus und berechnet den inkrementellen Verlust und den neuen Decoder-Zustand ( dec_state ).

def _loop_step(self, new_tokens, input_mask, enc_output, dec_state):
  input_token, target_token = new_tokens[:, 0:1], new_tokens[:, 1:2]

  # Run the decoder one step.
  decoder_input = DecoderInput(new_tokens=input_token,
                               enc_output=enc_output,
                               mask=input_mask)

  dec_result, dec_state = self.decoder(decoder_input, state=dec_state)
  self.shape_checker(dec_result.logits, ('batch', 't1', 'logits'))
  self.shape_checker(dec_result.attention_weights, ('batch', 't1', 's'))
  self.shape_checker(dec_state, ('batch', 'dec_units'))

  # `self.loss` returns the total for non-padded tokens
  y = target_token
  y_pred = dec_result.logits
  step_loss = self.loss(y, y_pred)

  return step_loss, dec_state
TrainTranslator._loop_step = _loop_step

Teste den Trainingsschritt

Erstellen Sie einen TrainTranslator und konfigurieren Sie ihn für das Training mit der Model.compile Methode:

translator = TrainTranslator(
    embedding_dim, units,
    input_text_processor=input_text_processor,
    output_text_processor=output_text_processor,
    use_tf_function=False)

# Configure the loss and optimizer
translator.compile(
    optimizer=tf.optimizers.Adam(),
    loss=MaskedLoss(),
)

Testen Sie den train_step . Für ein Textmodell wie dieses sollte der Verlust in der Nähe von:

np.log(output_text_processor.vocabulary_size())
8.517193191416238
%%time
for n in range(10):
  print(translator.train_step([example_input_batch, example_target_batch]))
print()
{'batch_loss': <tf.Tensor: shape=(), dtype=float32, numpy=7.637956>}
{'batch_loss': <tf.Tensor: shape=(), dtype=float32, numpy=7.6078267>}
{'batch_loss': <tf.Tensor: shape=(), dtype=float32, numpy=7.5514717>}
{'batch_loss': <tf.Tensor: shape=(), dtype=float32, numpy=7.388698>}
{'batch_loss': <tf.Tensor: shape=(), dtype=float32, numpy=6.7904134>}
{'batch_loss': <tf.Tensor: shape=(), dtype=float32, numpy=5.087918>}
{'batch_loss': <tf.Tensor: shape=(), dtype=float32, numpy=4.8723435>}
{'batch_loss': <tf.Tensor: shape=(), dtype=float32, numpy=4.475624>}
{'batch_loss': <tf.Tensor: shape=(), dtype=float32, numpy=4.2487626>}
{'batch_loss': <tf.Tensor: shape=(), dtype=float32, numpy=4.150057>}

CPU times: user 5.24 s, sys: 187 ms, total: 5.43 s
Wall time: 4.94 s

Ohne eine tf.function es zwar einfacher zu debuggen, bringt aber eine Leistungssteigerung. Jetzt, da die _train_step Methode funktioniert, versuchen Sie es mit der tf.function -wrapped _tf_train_step , um die Leistung während des Trainings zu maximieren:

@tf.function(input_signature=[[tf.TensorSpec(dtype=tf.string, shape=[None]),
                               tf.TensorSpec(dtype=tf.string, shape=[None])]])
def _tf_train_step(self, inputs):
  return self._train_step(inputs)
TrainTranslator._tf_train_step = _tf_train_step
translator.use_tf_function = True

Der erste Aufruf wird langsam sein, da er die Funktion verfolgt.

translator.train_step([example_input_batch, example_target_batch])
{'batch_loss': <tf.Tensor: shape=(), dtype=float32, numpy=4.1792917>}

Aber danach ist es normalerweise 2-3x schneller als die Eager- train_step Methode:

%%time
for n in range(10):
  print(translator.train_step([example_input_batch, example_target_batch]))
print()
{'batch_loss': <tf.Tensor: shape=(), dtype=float32, numpy=4.1361>}
{'batch_loss': <tf.Tensor: shape=(), dtype=float32, numpy=4.0575933>}
{'batch_loss': <tf.Tensor: shape=(), dtype=float32, numpy=3.9899843>}
{'batch_loss': <tf.Tensor: shape=(), dtype=float32, numpy=3.8948655>}
{'batch_loss': <tf.Tensor: shape=(), dtype=float32, numpy=3.7954547>}
{'batch_loss': <tf.Tensor: shape=(), dtype=float32, numpy=3.7969203>}
{'batch_loss': <tf.Tensor: shape=(), dtype=float32, numpy=3.7787275>}
{'batch_loss': <tf.Tensor: shape=(), dtype=float32, numpy=3.7324097>}
{'batch_loss': <tf.Tensor: shape=(), dtype=float32, numpy=3.72061>}
{'batch_loss': <tf.Tensor: shape=(), dtype=float32, numpy=3.6613982>}

CPU times: user 5.06 s, sys: 1.16 s, total: 6.22 s
Wall time: 1.86 s

Ein guter Test eines neuen Modells besteht darin, zu sehen, dass es einen einzelnen Batch von Eingaben überfüllen kann. Probieren Sie es aus, der Verlust sollte schnell gegen Null gehen:

losses = []
for n in range(100):
  print('.', end='')
  logs = translator.train_step([example_input_batch, example_target_batch])
  losses.append(logs['batch_loss'].numpy())

print()
plt.plot(losses)
....................................................................................................
[<matplotlib.lines.Line2D at 0x7f7f3879d310>]

png

Nachdem Sie nun sicher sind, dass der Trainingsschritt funktioniert, erstellen Sie eine neue Kopie des Modells, um von Grund auf zu trainieren:

train_translator = TrainTranslator(
    embedding_dim, units,
    input_text_processor=input_text_processor,
    output_text_processor=output_text_processor)

# Configure the loss and optimizer
train_translator.compile(
    optimizer=tf.optimizers.Adam(),
    loss=MaskedLoss(),
)

Trainiere das Modell

Es ist zwar nichts falsch daran, eine eigene benutzerdefinierte Trainingsschleife zu schreiben, aber die Implementierung der Model.train_step Methode, wie im vorherigen Abschnitt, ermöglicht es Ihnen, Model.fit und den ganzen Boiler-Plate-Code nicht neu zu schreiben.

Dieses Tutorial trainiert nur für ein paar Epochen, also verwenden Sie einen callbacks.Callback , um den Verlauf der Batch-Verluste zum Plotten zu sammeln:

class BatchLogs(tf.keras.callbacks.Callback):
  def __init__(self, key):
    self.key = key
    self.logs = []

  def on_train_batch_end(self, n, logs):
    self.logs.append(logs[self.key])

batch_loss = BatchLogs('batch_loss')
train_translator.fit(dataset, epochs=3,
                     callbacks=[batch_loss])
Epoch 1/3
1859/1859 [==============================] - 354s 188ms/step - batch_loss: 2.0805
Epoch 2/3
1859/1859 [==============================] - 348s 187ms/step - batch_loss: 1.0402
Epoch 3/3
1859/1859 [==============================] - 347s 186ms/step - batch_loss: 0.8109
<tensorflow.python.keras.callbacks.History at 0x7f7f3809cfd0>
plt.plot(batch_loss.logs)
plt.ylim([0, 3])
plt.xlabel('Batch #')
plt.ylabel('CE/token')
Text(0, 0.5, 'CE/token')

png

Die sichtbaren Sprünge im Plot liegen an den Epochengrenzen.

Übersetzen

Nachdem das Modell trainiert wurde, implementieren Sie eine Funktion zum Ausführen des text => text .

Dazu muss das Modell die vom output_text_processor bereitgestellte Zuordnung von text => token IDs output_text_processor . Es muss auch die IDs für spezielle Token kennen. Dies ist alles im Konstruktor der neuen Klasse implementiert. Die Implementierung der eigentlichen translate-Methode folgt.

Insgesamt ist dies der Trainingsschleife ähnlich, außer dass die Eingabe in den Decoder bei jedem Zeitschritt ein Abtastwert aus der letzten Vorhersage des Decoders ist.

class Translator(tf.Module):
  def __init__(self,
               encoder, decoder, 
               input_text_processor,
               output_text_processor):
    self.encoder = encoder
    self.decoder = decoder
    self.input_text_processor = input_text_processor
    self.output_text_processor = output_text_processor

    self.output_token_string_from_index = (
        tf.keras.layers.experimental.preprocessing.StringLookup(
            vocabulary=output_text_processor.get_vocabulary(),
            invert=True))

    # The output should never generate padding, unknown, or start.
    index_from_string = tf.keras.layers.experimental.preprocessing.StringLookup(
        vocabulary=output_text_processor.get_vocabulary())
    token_mask_ids = index_from_string(['',
                                        '[UNK]',
                                        '[START]']).numpy()

    token_mask = np.zeros([index_from_string.vocabulary_size()], dtype=np.bool)
    token_mask[np.array(token_mask_ids)] = True
    self.token_mask = token_mask

    self.start_token = index_from_string('[START]')
    self.end_token = index_from_string('[END]')
translator = Translator(
    encoder=train_translator.encoder,
    decoder=train_translator.decoder,
    input_text_processor=input_text_processor,
    output_text_processor=output_text_processor,
)

Token-IDs in Text umwandeln

Die erste zu implementierende Methode ist tokens_to_text , die von Token-IDs in lesbaren Text konvertiert.

def tokens_to_text(self, result_tokens):
  shape_checker = ShapeChecker()
  shape_checker(result_tokens, ('batch', 't'))
  result_text_tokens = self.output_token_string_from_index(result_tokens)
  shape_checker(result_text_tokens, ('batch', 't'))

  result_text = tf.strings.reduce_join(result_text_tokens,
                                       axis=1, separator=' ')
  shape_checker(result_text, ('batch'))

  result_text = tf.strings.strip(result_text)
  shape_checker(result_text, ('batch',))
  return result_text
Translator.tokens_to_text = tokens_to_text

Geben Sie einige zufällige Token-IDs ein und sehen Sie, was es generiert:

example_output_tokens = tf.random.uniform(
    shape=[5, 2], minval=0, dtype=tf.int64,
    maxval=output_text_processor.vocabulary_size())
translator.tokens_to_text(example_output_tokens).numpy()
array([b'adult store', b'phoned doorknob', b'vegetable coast',
       b'observe explanation', b'blanket avoid'], dtype=object)

Beispiel aus den Vorhersagen des Decoders

Diese Funktion nimmt die Logit-Ausgänge des Decoders und tastet Token-IDs aus dieser Verteilung ab:

def sample(self, logits, temperature):
  shape_checker = ShapeChecker()
  # 't' is usually 1 here.
  shape_checker(logits, ('batch', 't', 'vocab'))
  shape_checker(self.token_mask, ('vocab',))

  token_mask = self.token_mask[tf.newaxis, tf.newaxis, :]
  shape_checker(token_mask, ('batch', 't', 'vocab'), broadcast=True)

  # Set the logits for all masked tokens to -inf, so they are never chosen.
  logits = tf.where(self.token_mask, -np.inf, logits)

  if temperature == 0.0:
    new_tokens = tf.argmax(logits, axis=-1)
  else: 
    logits = tf.squeeze(logits, axis=1)
    new_tokens = tf.random.categorical(logits/temperature,
                                        num_samples=1)

  shape_checker(new_tokens, ('batch', 't'))

  return new_tokens
Translator.sample = sample

Testen Sie diese Funktion mit einigen zufälligen Eingaben:

example_logits = tf.random.normal([5, 1, output_text_processor.vocabulary_size()])
example_output_tokens = translator.sample(example_logits, temperature=1.0)
example_output_tokens
<tf.Tensor: shape=(5, 1), dtype=int64, numpy=
array([[1607],
       [2497],
       [3862],
       [4653],
       [1229]])>

Implementieren Sie die Übersetzungsschleife

Hier ist eine vollständige Implementierung der Text-zu-Text-Übersetzungsschleife.

Diese Implementierung sammelt die Ergebnisse in Python-Listen, bevor sie mit tf.concat zu Tensoren verbunden werden.

Diese Implementierung entrollt den Graphen statisch auf max_length Iterationen. Dies ist in Ordnung mit der eifrigen Ausführung in Python.

def translate_unrolled(self,
                       input_text, *,
                       max_length=50,
                       return_attention=True,
                       temperature=1.0):
  batch_size = tf.shape(input_text)[0]
  input_tokens = self.input_text_processor(input_text)
  enc_output, enc_state = self.encoder(input_tokens)

  dec_state = enc_state
  new_tokens = tf.fill([batch_size, 1], self.start_token)

  result_tokens = []
  attention = []
  done = tf.zeros([batch_size, 1], dtype=tf.bool)

  for _ in range(max_length):
    dec_input = DecoderInput(new_tokens=new_tokens,
                             enc_output=enc_output,
                             mask=(input_tokens!=0))

    dec_result, dec_state = self.decoder(dec_input, state=dec_state)

    attention.append(dec_result.attention_weights)

    new_tokens = self.sample(dec_result.logits, temperature)

    # If a sequence produces an `end_token`, set it `done`
    done = done | (new_tokens == self.end_token)
    # Once a sequence is done it only produces 0-padding.
    new_tokens = tf.where(done, tf.constant(0, dtype=tf.int64), new_tokens)

    # Collect the generated tokens
    result_tokens.append(new_tokens)

    if tf.executing_eagerly() and tf.reduce_all(done):
      break

  # Convert the list of generates token ids to a list of strings.
  result_tokens = tf.concat(result_tokens, axis=-1)
  result_text = self.tokens_to_text(result_tokens)

  if return_attention:
    attention_stack = tf.concat(attention, axis=1)
    return {'text': result_text, 'attention': attention_stack}
  else:
    return {'text': result_text}
Translator.translate = translate_unrolled

Führen Sie es auf einer einfachen Eingabe aus:

%%time
input_text = tf.constant([
    'hace mucho frio aqui.', # "It's really cold here."
    'Esta es mi vida.', # "This is my life.""
])

result = translator.translate(
    input_text = input_text)

print(result['text'][0].numpy().decode())
print(result['text'][1].numpy().decode())
print()
its been a cold ink here .
this is my life .

CPU times: user 165 ms, sys: 0 ns, total: 165 ms
Wall time: 159 ms

Wenn Sie dieses Modell exportieren möchten, müssen Sie diese Methode in eine tf.function . Diese grundlegende Implementierung weist einige Probleme auf, wenn Sie dies versuchen:

  1. Die resultierenden Diagramme sind sehr groß und benötigen einige Sekunden zum Erstellen, Speichern oder Laden.
  2. Sie können eine statisch entrollte Schleife nicht max_length , daher werden immer max_length Iterationen ausgeführt, selbst wenn alle Ausgaben abgeschlossen sind. Aber selbst dann ist es geringfügig schneller als die eifrige Ausführung.
f = tf.function(input_signature=[tf.TensorSpec(dtype=tf.string, shape=[None])])
def tf_translate(self, input_text):
  return self.translate(input_text)

Translator.tf_translate = tf_translate

Führen Sie die tf.function einmal aus, um sie zu kompilieren:

%%time
result = translator.tf_translate(
    input_text = input_text)
CPU times: user 151 ms, sys: 0 ns, total: 151 ms
Wall time: 146 ms
%%time
result = translator.tf_translate(
    input_text = input_text)

print(result['text'][0].numpy().decode())
print(result['text'][1].numpy().decode())
print()
its been very cold here .
this is my life .

CPU times: user 139 ms, sys: 0 ns, total: 139 ms
Wall time: 133 ms

[Optional] Verwenden Sie eine symbolische Schleife

Translator.translate = translate_symbolic

Die anfängliche Implementierung verwendete Python-Listen, um die Ausgaben zu sammeln. Dies verwendet tf.range als Schleifeniterator, sodass tf.autograph die Schleife konvertieren kann. Die größte Änderung in dieser Implementierung ist die Verwendung von tf.TensorArray anstelle von Python- list , um Tensoren zu akkumulieren. tf.TensorArray wird benötigt, um im tf.TensorArray eine variable Anzahl von Tensoren zu sammeln.

Bei eifriger Ausführung ist diese Implementierung auf Augenhöhe mit dem Original:

%%time
result = translator.translate(
    input_text = input_text)

print(result['text'][0].numpy().decode())
print(result['text'][1].numpy().decode())
print()
its a long cold here .
this is my life .

CPU times: user 154 ms, sys: 0 ns, total: 154 ms
Wall time: 148 ms

Aber wenn Sie es in eine tf.function Sie zwei Unterschiede bemerken.

@tf.function(input_signature=[tf.TensorSpec(dtype=tf.string, shape=[None])])
def tf_translate(self, input_text):
  return self.translate(input_text)

Translator.tf_translate = tf_translate

Erstens: Die Diagrammerstellung ist viel schneller (~10x), da keine max_iterations Kopien des Modells erstellt werden.

%%time
result = translator.tf_translate(
    input_text = input_text)
CPU times: user 1.32 s, sys: 0 ns, total: 1.32 s
Wall time: 1.29 s

Zweitens: Die kompilierte Funktion ist bei kleinen Eingaben (in diesem Beispiel 5x) viel schneller, da sie aus der Schleife ausbrechen kann.

%%time
result = translator.tf_translate(
    input_text = input_text)

print(result['text'][0].numpy().decode())
print(result['text'][1].numpy().decode())
print()
it is really cold here .
this is my life .

CPU times: user 50.7 ms, sys: 0 ns, total: 50.7 ms
Wall time: 18.7 ms

Visualisiere den Prozess

Die von der translate Methode zurückgegebenen Aufmerksamkeitsgewichtungen zeigen, wo das Modell "ausgesehen" hat, als es jedes Ausgabetoken generierte.

Die Summe der Aufmerksamkeit über die Eingabe sollte also alle Einsen zurückgeben:

a = result['attention'][0]

print(np.sum(a, axis=-1))
[1.         1.         0.9999999  0.99999994 1.         1.

 1.        ]

Hier ist die Aufmerksamkeitsverteilung für den ersten Ausgabeschritt des ersten Beispiels. Beachten Sie, dass die Aufmerksamkeit jetzt viel fokussierter ist als beim ungeschulten Modell:

_ = plt.bar(range(len(a[0, :])), a[0, :])

png

Da es eine grobe Ausrichtung zwischen den Eingabe- und Ausgabewörtern gibt, erwarten Sie, dass sich die Aufmerksamkeit in der Nähe der Diagonale konzentriert:

plt.imshow(np.array(a), vmin=0.0)
<matplotlib.image.AxesImage at 0x7f7f38f20bd0>

png

Hier ist ein Code, um ein besseres Aufmerksamkeitsdiagramm zu erstellen:

Beschriftete Aufmerksamkeitsplots

i=0
plot_attention(result['attention'][i], input_text[i], result['text'][i])
/home/kbuilder/.local/lib/python3.7/site-packages/ipykernel_launcher.py:14: UserWarning: FixedFormatter should only be used together with FixedLocator
  
/home/kbuilder/.local/lib/python3.7/site-packages/ipykernel_launcher.py:15: UserWarning: FixedFormatter should only be used together with FixedLocator
  from ipykernel import kernelapp as app

png

Übersetze noch ein paar Sätze und zeichne sie:

%%time
three_input_text = tf.constant([
    # This is my life.
    'Esta es mi vida.',
    # Are they still home?
    '¿Todavía están en casa?',
    # Try to find out.'
    'Tratar de descubrir.',
])

result = translator.tf_translate(three_input_text)

for tr in result['text']:
  print(tr.numpy().decode())

print()
this is my life .
are you still at home ?
try it to discover .

CPU times: user 54.4 ms, sys: 52.1 ms, total: 107 ms
Wall time: 22.8 ms
result['text']
<tf.Tensor: shape=(3,), dtype=string, numpy=
array([b'this is my life .', b'are you still at home ?',
       b'try it to discover .'], dtype=object)>
i = 0
plot_attention(result['attention'][i], three_input_text[i], result['text'][i])
/home/kbuilder/.local/lib/python3.7/site-packages/ipykernel_launcher.py:14: UserWarning: FixedFormatter should only be used together with FixedLocator
  
/home/kbuilder/.local/lib/python3.7/site-packages/ipykernel_launcher.py:15: UserWarning: FixedFormatter should only be used together with FixedLocator
  from ipykernel import kernelapp as app

png

i = 1
plot_attention(result['attention'][i], three_input_text[i], result['text'][i])
/home/kbuilder/.local/lib/python3.7/site-packages/ipykernel_launcher.py:14: UserWarning: FixedFormatter should only be used together with FixedLocator
  
/home/kbuilder/.local/lib/python3.7/site-packages/ipykernel_launcher.py:15: UserWarning: FixedFormatter should only be used together with FixedLocator
  from ipykernel import kernelapp as app

png

i = 2
plot_attention(result['attention'][i], three_input_text[i], result['text'][i])
/home/kbuilder/.local/lib/python3.7/site-packages/ipykernel_launcher.py:14: UserWarning: FixedFormatter should only be used together with FixedLocator
  
/home/kbuilder/.local/lib/python3.7/site-packages/ipykernel_launcher.py:15: UserWarning: FixedFormatter should only be used together with FixedLocator
  from ipykernel import kernelapp as app

png

Die kurzen Sätze funktionieren oft gut, aber wenn die Eingabe zu lang ist, verliert das Modell buchstäblich den Fokus und liefert keine vernünftigen Vorhersagen. Dafür gibt es zwei Hauptgründe:

  1. Das Modell wurde trainiert, indem der Lehrer bei jedem Schritt das richtige Token fütterte, unabhängig von den Vorhersagen des Modells. Das Modell könnte robuster gemacht werden, wenn es manchmal mit eigenen Vorhersagen gefüttert würde.
  2. Das Modell hat nur über den RNN-Zustand Zugriff auf seine vorherige Ausgabe. Wenn der RNN-Zustand beschädigt wird, kann das Modell nicht wiederhergestellt werden. Transformatoren lösen dies, indem sie die Eigenaufmerksamkeit im Encoder und Decoder verwenden.
long_input_text = tf.constant([inp[-1]])

import textwrap
print('Expected output:\n', '\n'.join(textwrap.wrap(targ[-1])))
Expected output:
 If you want to sound like a native speaker, you must be willing to
practice saying the same sentence over and over in the same way that
banjo players practice the same phrase over and over until they can
play it correctly and at the desired tempo.
result = translator.tf_translate(long_input_text)

i = 0
plot_attention(result['attention'][i], long_input_text[i], result['text'][i])
_ = plt.suptitle('This never works')
/home/kbuilder/.local/lib/python3.7/site-packages/ipykernel_launcher.py:14: UserWarning: FixedFormatter should only be used together with FixedLocator
  
/home/kbuilder/.local/lib/python3.7/site-packages/ipykernel_launcher.py:15: UserWarning: FixedFormatter should only be used together with FixedLocator
  from ipykernel import kernelapp as app

png

Export

Sobald Sie ein Modell haben, mit dem Sie zufrieden sind, möchten Sie es vielleicht als tf.saved_model exportieren, um es außerhalb dieses Python-Programms zu verwenden, das es erstellt hat.

Da das Modell eine Unterklasse von tf.Module (durch keras.Model ) ist und alle Funktionen für den Export in einer tf.function kompiliert sind, tf.function das Modell sauber mit tf.saved_model.save :

saved_model.save die Funktion nun verfolgt wurde, kann sie mit saved_model.save exportiert werden:

tf.saved_model.save(translator, 'translator',
                    signatures={'serving_default': translator.tf_translate})
WARNING:absl:Found untraced functions such as encoder_2_layer_call_and_return_conditional_losses, encoder_2_layer_call_fn, decoder_2_layer_call_and_return_conditional_losses, decoder_2_layer_call_fn, embedding_4_layer_call_and_return_conditional_losses while saving (showing 5 of 60). These functions will not be directly callable after loading.
WARNING:tensorflow:FOR KERAS USERS: The object that you are saving contains one or more Keras models or layers. If you are loading the SavedModel with `tf.keras.models.load_model`, continue reading (otherwise, you may ignore the following instructions). Please change your code to save with `tf.keras.models.save_model` or `model.save`, and confirm that the file "keras.metadata" exists in the export directory. In the future, Keras will only load the SavedModels that have this file. In other words, `tf.saved_model.save` will no longer write SavedModels that can be recovered as Keras models (this will apply in TF 2.5).

FOR DEVS: If you are overwriting _tracking_metadata in your class, this property has been used to save metadata in the SavedModel. The metadta field will be deprecated soon, so please move the metadata to a different file.
WARNING:tensorflow:FOR KERAS USERS: The object that you are saving contains one or more Keras models or layers. If you are loading the SavedModel with `tf.keras.models.load_model`, continue reading (otherwise, you may ignore the following instructions). Please change your code to save with `tf.keras.models.save_model` or `model.save`, and confirm that the file "keras.metadata" exists in the export directory. In the future, Keras will only load the SavedModels that have this file. In other words, `tf.saved_model.save` will no longer write SavedModels that can be recovered as Keras models (this will apply in TF 2.5).

FOR DEVS: If you are overwriting _tracking_metadata in your class, this property has been used to save metadata in the SavedModel. The metadta field will be deprecated soon, so please move the metadata to a different file.
INFO:tensorflow:Assets written to: translator/assets
INFO:tensorflow:Assets written to: translator/assets
reloaded = tf.saved_model.load('translator')
result = reloaded.tf_translate(three_input_text)
%%time
result = reloaded.tf_translate(three_input_text)

for tr in result['text']:
  print(tr.numpy().decode())

print()
this is my life .
are you still at home ?
try it under discover .

CPU times: user 39 ms, sys: 7.02 ms, total: 46 ms
Wall time: 19.5 ms

Nächste Schritte