5-zeilige Textgenerierung im GPT-Stil in Python mit TensorFlow/Keras

Transformers, obwohl sie 2017 veröffentlicht wurden, haben erst in den letzten Jahren an Bedeutung gewonnen. Mit der Verbreitung der Technologie durch Plattformen wie HuggingFace, NLP und Große Sprachmodelle (LLMs) sind zugänglicher denn je geworden.

Und doch – trotz des ganzen Rummels um sie herum und mit viele theorieorientierten Leitfäden, es gibt nicht viele benutzerdefinierte Implementierungen online, und die Ressourcen sind nicht so leicht verfügbar wie bei einigen anderen Netzwerktypen, die es schon länger gibt. Während Sie Ihren Arbeitszyklus vereinfachen könnten, indem Sie einen vorgefertigten Transformer von HuggingFace (das Thema eines anderen Handbuchs) verwenden, können Sie dazu gelangen fühlen wie es funktioniert, indem Sie selbst eines bauen, bevor Sie es durch eine Bibliothek abstrahieren. Wir werden uns hier eher auf das Bauen als auf Theorie und Optimierung konzentrieren.

In diesem Handbuch erstellen wir eine Autoregressives Sprachmodell zu Text generieren. Wir konzentrieren uns auf die praktischen und minimalistischen/prägnanten Aspekte des Ladens von Daten, Teilen, Vektorisieren, Erstellen eines Modells, Schreiben eines benutzerdefinierten Rückrufs und Training/Inferenz. Jede dieser Aufgaben kann in detailliertere Leitfäden ausgegliedert werden, sodass wir die Implementierung als generische beibehalten und Raum für Anpassungen und Optimierungen je nach Ihrem eigenen Datensatz lassen.

Arten von LLMs und GPT-Fyodor

Während die Kategorisierung viel komplizierter werden kann – Sie können allgemein Kategorisieren Sie Transformer-basierte Sprachmodelle in drei Kategorien:

  • Encoder-basierte Modelle – ALBERT, BERT, DistilBERT, RoBERTa
  • Decoder-basiert – GPT, GPT-2, GPT-3, TransformerXL
  • Seq2Seq-Modelle – BART, mBART, T5

Encoder-basiert Modelle verwenden nur einen Transformer-Encoder in ihrer Architektur (normalerweise gestapelt) und eignen sich hervorragend zum Verstehen von Sätzen (Klassifizierung, Erkennung benannter Entitäten, Beantwortung von Fragen).

Decoder-basiert Modelle verwenden nur einen Transformer-Decoder in ihrer Architektur (ebenfalls typischerweise gestapelt) und eignen sich hervorragend für zukünftige Vorhersagen, was sie für die Textgenerierung geeignet macht.

Seq2Seq Modelle kombinieren sowohl Encoder als auch Decoder und eignen sich hervorragend zur Textgenerierung, Zusammenfassung und vor allem – Übersetzung.

Die GPT-Modellfamilie, die in den letzten Jahren viel Anklang gefunden hat, sind Decoder-basierte Transformatormodelle und eignen sich hervorragend zum Erstellen von menschenähnlichem Text, der auf großen Datenkorpora trainiert und als neue Eingabeaufforderung erhalten wird Startsaat für die Generation. Zum Beispiel:

generate_text('the truth ultimately is')

Was unter der Haube diese Eingabeaufforderung in ein GPT-ähnliches Modell einspeist und Folgendes erzeugt:

'the truth ultimately is really a joy in history, this state of life through which is almost invisible, superfluous  teleological...'

Dies ist in der Tat ein kleiner Spoiler vom Ende des Leitfadens! Ein weiterer kleiner Spoiler ist die Architektur, die diesen Text produziert hat:

inputs = layers.Input(shape=(maxlen,))
embedding_layer = keras_nlp.layers.TokenAndPositionEmbedding(vocab_size, maxlen, embed_dim)(inputs)
transformer_block = keras_nlp.layers.TransformerDecoder(embed_dim, num_heads)(embedding_layer)
outputs = layers.Dense(vocab_size, activation='softmax')(transformer_block)
    
model = keras.Model(inputs=inputs, outputs=outputs)

5 Zeilen reichen aus, um ein reines Decoder-Transformatormodell zu bauen – das ein kleines GPT simuliert. Da wir das Modell anhand der Romane von Fjodor Dostojewski trainieren werden (die Sie durch alles andere ersetzen können, von Wikipedia bis zu Reddit-Kommentaren) – nennen wir das Modell vorläufig GPT-Fjodor.

KerasNLP

Der Trick zu einem 5-zeiligen GPT-Fjodor liegt darin KerasNLP, das vom offiziellen Keras-Team entwickelt wurde, als horizontale Erweiterung von Keras, das in echter Keras-Manier darauf abzielt, branchenführendes NLP mit neuen Ebenen (Encoder, Decoder, Token-Einbettungen, Positionseinbettungen, Metriken, Tokenizer usw.).

KerasNLP ist kein Musterzoo. Es ist ein Teil von Keras (als separates Paket), das die Eintrittsbarriere für die NLP-Modellentwicklung senkt, genauso wie es die Eintrittsbarriere für die allgemeine Deep-Learning-Entwicklung mit dem Hauptpaket senkt.

Hinweis: Zum jetzigen Zeitpunkt wird KerasNLP noch produziert und befindet sich in einem frühen Stadium. In zukünftigen Versionen können geringfügige Unterschiede vorhanden sein. Die Beschreibung verwendet die Version 0.3.0.

Um KerasNLP verwenden zu können, müssen Sie es über installieren pip:

$ pip install keras_nlp

Und Sie können die Version überprüfen mit:

keras_nlp.__version__

Implementieren eines GPT-Modells mit Keras

Beginnen wir mit dem Importieren der Bibliotheken, die wir verwenden werden – TensorFlow, Keras, KerasNLP und NumPy:

import tensorflow as tf
from tensorflow import keras
import keras_nlp
import numpy as np

Daten werden geladen

Laden wir ein paar von Dostojewskis Romanen ein – einer wäre viel zu kurz, als dass ein Modell passen würde, ohne ein bisschen Overfitting von Anfang an. Wir werden die Rohtextdateien von elegant verwenden Project Gutenberg, aufgrund der Einfachheit der Arbeit mit solchen Daten:

crime_and_punishment_url = 'https://www.gutenberg.org/files/2554/2554-0.txt'
brothers_of_karamazov_url = 'https://www.gutenberg.org/files/28054/28054-0.txt'
the_idiot_url = 'https://www.gutenberg.org/files/2638/2638-0.txt'
the_possessed_url = 'https://www.gutenberg.org/files/8117/8117-0.txt'

paths = [crime_and_punishment_url, brothers_of_karamazov_url, the_idiot_url, the_possessed_url]
names = ['Crime and Punishment', 'Brothers of Karamazov', 'The Idiot', 'The Possessed']
texts = ''
for index, path in enumerate(paths):
    filepath = keras.utils.get_file(f'{names[index]}.txt', origin=path)
    text = ''
    with open(filepath, encoding='utf-8') as f:
        text = f.read()
        
        
        
        texts += text[10000:]

Wir haben einfach alle Dateien heruntergeladen, sie durchgesehen und übereinander verkettet. Dies schließt eine gewisse Vielfalt in der verwendeten Sprache ein, während sie dennoch deutlich Fjodor bleibt! Für jede Datei haben wir die ersten 10 Zeichen übersprungen, was ungefähr der durchschnittlichen Länge des Vorworts und der Gutenberg-Einführung entspricht, sodass uns für jede Iteration ein weitgehend intakter Hauptteil des Buchs bleibt. Werfen wir einen Blick auf einige zufällige 500 Zeichen in der texts Zeichenfolge jetzt:


texts[25000:25500]
'nd that was whynI addressed you at once. For in unfolding to you the story of my life, Indo not wish to make myself a laughing-stock before these idle listeners,nwho indeed know all about it already, but I am looking for a mannof feeling and education. Know then that my wife was educated in anhigh-class school for the daughters of noblemen, and on leaving shendanced the shawl dance before the governor and other personages fornwhich she was presented with a gold medal and a certificate of merit.n'

Trennen wir die Zeichenfolge in Sätze, bevor wir eine andere Verarbeitung vornehmen:

text_list = texts.split('.')
len(text_list) 

Wir haben 69 Sätze. Wenn Sie die ersetzen n Zeichen mit Leerzeichen und zählen Sie die Wörter:

len(texts.replace('n', ' ').split(' ')) 

Hinweis: Im Allgemeinen möchten Sie mindestens eine Million Wörter in einem Datensatz haben, und idealerweise viel, viel mehr als das. Wir arbeiten mit einigen Megabyte an Daten (~5 MB), während Sprachmodelle häufiger mit Dutzenden von Gigabyte an Text trainiert werden. Dadurch wird es natürlich sehr einfach, die Texteingabe zu überpassen, und schwer zu verallgemeinern (hohe Perplexität ohne Overfitting oder geringe Perplexität mit viel Overfitting). Nehmen Sie die Ergebnisse mit einem Körnchen Salz.

Lassen Sie uns diese dennoch in a aufteilen TAUCHERAUSBILDUNG, Test und Bestätigung einstellen. Lassen Sie uns zuerst die leeren Zeichenfolgen entfernen und die Sätze mischen:


text_list = list(filter(None, text_list))

import random
random.shuffle(text_list)

Dann machen wir eine 70/15/15-Aufteilung:

length = len(text_list)
text_train = text_list[:int(0.7*length)]
text_test = text_list[int(0.7*length):int(0.85*length)]
text_valid = text_list[int(0.85*length):]

Dies ist eine einfache, aber effektive Methode, um eine Trainings-Test-Validierungsaufteilung durchzuführen. Werfen wir einen Blick auf text_train:

[' It was a dull morning, but the snow had ceased',
 'nn"Pierre, you who know so much of what goes on here, can you really havenknown nothing of this business and have heard nothing about it?"nn"What? What a set! So it's not enough to be a child in your old age,nyou must be a spiteful child too! Varvara Petrovna, did you hear what hensaid?"nnThere was a general outcry; but then suddenly an incident took placenwhich no one could have anticipated', ...

Zeit für Standardisierung und Vektorisierung!

Textvektorisierung

Netzwerke verstehen keine Worte – sie verstehen Zahlen. Wir möchten die Wörter tokenisieren:

...
sequence = ['I', 'am', 'Wall-E']
sequence = tokenize(sequence)
print(sequence) # [4, 26, 472]
...

Da sich Sätze in der Länge unterscheiden, wird normalerweise links oder rechts eine Auffüllung hinzugefügt, um sicherzustellen, dass die eingegebenen Sätze die gleiche Form haben. Angenommen, unser längster Satz ist 5 Wörter (Token) lang. In diesem Fall würde der Wall-E-Satz mit zwei Nullen aufgefüllt, sodass wir dieselbe Eingabeform sicherstellen:

sequence = pad_sequence(sequence)
print(sequence) # [4, 26, 472, 0, 0]

Traditionell wurde dies mit einem TensorFlow durchgeführt Tokenizer und Keras' pad_sequences() Methoden – jedoch eine viel handlichere Schicht, TextVectorization, verwendet werden, die tokenisiert und füllt Ihre Eingabe auf, sodass Sie das Vokabular und seine Größe extrahieren können, ohne das Vokabular im Voraus zu kennen!

Sehen Sie sich unseren praxisnahen, praktischen Leitfaden zum Erlernen von Git an, mit Best Practices, branchenweit akzeptierten Standards und einem mitgelieferten Spickzettel. Hören Sie auf, Git-Befehle zu googeln und tatsächlich in Verbindung, um es!

Lassen Sie uns anpassen und anpassen a TextVectorization Schicht:

from tensorflow.keras.layers import TextVectorization

def custom_standardization(input_string):
    sentence = tf.strings.lower(input_string)
    sentence = tf.strings.regex_replace(sentence, "n", " ")
    return sentence

maxlen = 50



vectorize_layer = TextVectorization(
    standardize = custom_standardization,
    output_mode="int",
    output_sequence_length=maxlen + 1,
)

vectorize_layer.adapt(text_list)
vocab = vectorize_layer.get_vocabulary()

Das custom_standardization() Methode kann viel länger dauern. Wir haben einfach alle Eingaben kleingeschrieben und ersetzt n mit " ". Hier können Sie wirklich den größten Teil Ihrer Vorverarbeitung für Text einsetzen – und ihn über die Option an die Vektorisierungsebene liefern standardize Streit. Wenn du adapt() die Ebene zum Text (NumPy-Array oder Liste von Texten) – von dort können Sie das Vokabular sowie seine Größe erhalten:

vocab_size = len(vocab)
vocab_size 

Um Wörter zu de-tokenisieren, erstellen wir schließlich eine index_lookup Wörterbuch:

index_lookup = dict(zip(range(len(vocab)), vocab))    
index_lookup[5] 

Es bildet alle Token ab ([1, 2, 3, 4, ...]) zu Wörtern im Vokabular (['a', 'the', 'i', ...]). Indem wir einen Schlüssel (Token-Index) übergeben, können wir das Wort leicht zurückbekommen. Sie können jetzt die ausführen vectorize_layer() bei jeder Eingabe und beachten Sie die vektorisierten Sätze:

vectorize_layer(['hello world!'])

Was in ... endet:

<tf.Tensor: shape=(1, 51), dtype=int64, numpy=
array([[   1, 7509,    0,    0,    0,    0,    0,    0,    0,    0,    0,
           0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
           0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
           0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
           0,    0,    0,    0,    0,    0,    0]], dtype=int64)>

Hallo hat den Index von 1 während Welt den Index von hat 7509! Der Rest ist die Polsterung zum maxlen wir haben gerechnet.

Wir haben die Mittel, um Text zu vektorisieren – jetzt erstellen wir daraus Datensätze text_train, text_test und text_valid, wobei unsere Vektorisierungsebene als Konvertierungsmedium zwischen Wörtern und Vektoren verwendet wird, die in GPT-Fjodor eingespeist werden können.

Datensatzerstellung

Wir erstellen eine tf.data.Dataset für jedes unserer Sets, mit from_tensor_slices() und Bereitstellung einer Liste von Tensor-Slices (Sätzen):

batch_size = 64

train_dataset = tf.data.Dataset.from_tensor_slices(text_train)
train_dataset = train_dataset.shuffle(buffer_size=256)
train_dataset = train_dataset.batch(batch_size)

test_dataset = tf.data.Dataset.from_tensor_slices(text_test)
test_dataset = test_dataset.shuffle(buffer_size=256)
test_dataset = test_dataset.batch(batch_size)

valid_dataset = tf.data.Dataset.from_tensor_slices(text_valid)
valid_dataset = valid_dataset.shuffle(buffer_size=256)
valid_dataset = valid_dataset.batch(batch_size)

Einmal erstellt und gemischt (wieder für ein gutes Maß) – wir können eine Vorverarbeitungsfunktion (Vektorisierung und Sequenzaufteilung) anwenden:

def preprocess_text(text):
    text = tf.expand_dims(text, -1)
    tokenized_sentences = vectorize_layer(text)
    x = tokenized_sentences[:, :-1]
    y = tokenized_sentences[:, 1:]
    return x, y


train_dataset = train_dataset.map(preprocess_text)
train_dataset = train_dataset.prefetch(tf.data.AUTOTUNE)

test_dataset = test_dataset.map(preprocess_text)
test_dataset = test_dataset.prefetch(tf.data.AUTOTUNE)

valid_dataset = valid_dataset.map(preprocess_text)
valid_dataset = valid_dataset.prefetch(tf.data.AUTOTUNE)

Das preprocess_text() Funktion erweitert einfach um die letzte Dimension, vektorisiert den Text mit unserer vectorize_layer und erstellt die Eingaben und Ziele, versetzt durch ein einzelnes Token. Das Modell wird verwendet [0..n] folgern n+1, was eine Vorhersage für jedes Wort ergibt, wobei alle Wörter davor berücksichtigt werden. Schauen wir uns einen einzelnen Eintrag in einem der Datensätze an:

for entry in train_dataset.take(1):
    print(entry)

Wenn wir die zurückgegebenen Eingaben und Ziele in Stapeln von 64 (mit einer Länge von jeweils 30) untersuchen, können wir deutlich sehen, wie sie um eins versetzt sind:

(<tf.Tensor: shape=(64, 50), dtype=int64, numpy=
array([[17018,   851,     2, ...,     0,     0,     0],
       [  330,    74,     4, ...,     0,     0,     0],
       [   68,   752, 30273, ...,     0,     0,     0],
       ...,
       [    7,    73,  2004, ...,     0,     0,     0],
       [   44,    42,    67, ...,     0,     0,     0],
       [  195,   252,   102, ...,     0,     0,     0]], dtype=int64)>, <tf.Tensor: shape=(64, 50), dtype=int64, numpy=
array([[  851,     2,  8289, ...,     0,     0,     0],
       [   74,     4,    34, ...,     0,     0,     0],
       [  752, 30273,  7514, ...,     0,     0,     0],
       ...,
       [   73,  2004,    31, ...,     0,     0,     0],
       [   42,    67,    76, ...,     0,     0,     0],
       [  252,   102,  8596, ...,     0,     0,     0]], dtype=int64)>)

Endlich – es ist Zeit, das Modell zu bauen!

Modelldefinition

Wir verwenden hier KerasNLP-Schichten. Nach einem Input, codieren wir die Eingabe durch a TokenAndPositionEmbedding Schicht, vorbei an unserem vocab_size, maxlen und embed_dim. Das Gleiche embed_dim dass diese Schicht Ausgaben und Eingaben in die TransformerDecoder wird sein im Decoder gespeichert. Zum jetzigen Zeitpunkt behält der Decoder automatisch die Eingabedimensionalität bei und erlaubt Ihnen nicht, sie in eine andere Ausgabe zu projizieren, aber Sie können die latenten Dimensionen durch definieren intermediate_dim Argument.

Wir multiplizieren die Einbettungsdimensionen für die latente Darstellung mit zwei, aber Sie können sie gleich lassen oder eine von den Einbettungsdimensionen getrennte Zahl verwenden:

embed_dim = 128
num_heads = 4

def create_model():
    inputs = keras.layers.Input(shape=(maxlen,), dtype=tf.int32)
    embedding_layer = keras_nlp.layers.TokenAndPositionEmbedding(vocab_size, maxlen, embed_dim)(inputs)
    decoder = keras_nlp.layers.TransformerDecoder(intermediate_dim=embed_dim, 
                                                            num_heads=num_heads, 
                                                            dropout=0.5)(embedding_layer)
    
    outputs = keras.layers.Dense(vocab_size, activation='softmax')(decoder)
    
    model = keras.Model(inputs=inputs, outputs=outputs)
    
    model.compile(
        optimizer="adam", 
        loss='sparse_categorical_crossentropy',
        metrics=[keras_nlp.metrics.Perplexity(), 'accuracy']
    )
    return model

model = create_model()
model.summary()

Auf dem Decoder haben wir einen Dense Schicht, um das nächste Wort in der Folge auszuwählen, mit a softmax Aktivierung (die die Wahrscheinlichkeitsverteilung für jeden nächsten Token erzeugt). Werfen wir einen Blick auf die Zusammenfassung des Modells:

Model: "model_5"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 input_6 (InputLayer)        [(None, 30)]              0         
                                                                 
 token_and_position_embeddin  (None, 30, 128)          6365824   
 g_5 (TokenAndPositionEmbedd                                     
 ing)                                                            
                                                                 
 transformer_decoder_5 (Tran  (None, 30, 128)          132480    
 sformerDecoder)                                                 
                                                                 
 dense_5 (Dense)             (None, 30, 49703)         6411687   
                                                                 
=================================================================
Total params: 13,234,315
Trainable params: 13,234,315
Non-trainable params: 0
_________________________________________________________________

GPT-2 stapelt viele Decoder – GPT-2 Small hat 12 gestapelte Decoder (117 M Parameter), während GPT-2 Extra Large 48 gestapelte Decoder (1.5 B Parameter) hat. Unser Single-Decoder-Modell mit bescheidenen 13 Millionen Parametern sollte für Bildungszwecke gut genug funktionieren. Bei LLMs hat sich das Hochskalieren als eine äußerst gute Strategie erwiesen, und Transformatoren ermöglichen eine gute Skalierung, wodurch es möglich ist, extrem große Modelle zu trainieren.

GPT-3 hat eine „dürftig“ 175B-Parameter. Das Team von Google Brain trainierte ein 1.6-T-Parametermodell, um Sparsity-Forschung durchzuführen, während die Berechnung auf dem gleichen Niveau wie bei viel kleineren Modellen gehalten wird.

In der Tat, wenn wir die Anzahl der Decoder von 1 auf 3 erhöhen:

def create_model():
    inputs = keras.layers.Input(shape=(maxlen,), dtype=tf.int32)
    x = keras_nlp.layers.TokenAndPositionEmbedding(vocab_size, maxlen, embed_dim)(inputs)
    for i in range(4):
        x = keras_nlp.layers.TransformerDecoder(intermediate_dim=embed_dim*2, num_heads=num_heads,                                                             dropout=0.5)(x)
    do = keras.layers.Dropout(0.4)(x)
    outputs = keras.layers.Dense(vocab_size, activation='softmax')(do)
    
    model = keras.Model(inputs=inputs, outputs=outputs)

Unsere Parameteranzahl würde um 400 erhöht:

Total params: 13,631,755
Trainable params: 13,631,755
Non-trainable params: 0

Die meisten Parameter in unserem Netzwerk stammen aus der TokenAndPositionEmbedding und Dense Schichten!

Probieren Sie verschiedene Tiefen des Decoders aus – von 1 bis zu allen, die Ihre Maschine verarbeiten kann, und notieren Sie sich die Ergebnisse. Auf jeden Fall – wir sind fast bereit, das Modell zu trainieren! Lassen Sie uns einen benutzerdefinierten Rückruf erstellen, der ein Textbeispiel für jede Epoche erzeugt, damit wir sehen können, wie das Modell durch Training lernt, Sätze zu bilden.

Benutzerdefinierter Rückruf

class TextSampler(keras.callbacks.Callback):
    def __init__(self, start_prompt, max_tokens):
        self.start_prompt = start_prompt
        self.max_tokens = max_tokens
        
    
    
    def sample_token(self, logits):
        logits, indices = tf.math.top_k(logits, k=5, sorted=True)
        indices = np.asarray(indices).astype("int32")
        preds = keras.activations.softmax(tf.expand_dims(logits, 0))[0]
        preds = np.asarray(preds).astype("float32")
        return np.random.choice(indices, p=preds)

    def on_epoch_end(self, epoch, logs=None):
        decoded_sample = self.start_prompt
        
        for i in range(self.max_tokens-1):
            tokenized_prompt = vectorize_layer([decoded_sample])[:, :-1]
            predictions = self.model.predict([tokenized_prompt], verbose=0)
            
            
            
            
            sample_index = len(decoded_sample.strip().split())-1
            
            sampled_token = self.sample_token(predictions[0][sample_index])
            sampled_token = index_lookup[sampled_token]
            decoded_sample += " " + sampled_token
            
        print(f"nSample text:n{decoded_sample}...n")


random_sentence = ' '.join(random.choice(text_valid).replace('n', ' ').split(' ')[:4])
sampler = TextSampler(random_sentence, 30)
reducelr = keras.callbacks.ReduceLROnPlateau(patience=10, monitor='val_loss')

Das Modell trainieren

Endlich Zeit zum Trainieren! Lassen Sie uns in unsere schmeißen train_dataset und validation_dataset mit den Rückrufen an Ort und Stelle:

model = create_model()
history = model.fit(train_dataset, 
                    validation_data=valid_dataset,
                    epochs=10, 
                    callbacks=[sampler, reducelr])

Der Sampler hat einen unglücklichen Satz gewählt, der mit dem End- und Startzitat beginnt, aber dennoch interessante Ergebnisse beim Training liefert:

# Epoch training
Epoch 1/10
658/658 [==============================] - ETA: 0s - loss: 2.7480 - perplexity: 15.6119 - accuracy: 0.6711
# on_epoch_end() sample generation
Sample text:
”  “What do you had not been i had been the same man was not be the same eyes to been a whole man and he did a whole man to the own...
# Validation
658/658 [==============================] - 158s 236ms/step - loss: 2.7480 - perplexity: 15.6119 - accuracy: 0.6711 - val_loss: 2.2130 - val_perplexity: 9.1434 - val_accuracy: 0.6864 - lr: 0.0010
...
Sample text:
”  “What do you know it is it all this very much as i should not have a great impression  in the room to be  able of it in my heart...

658/658 [==============================] - 149s 227ms/step - loss: 1.7753 - perplexity: 5.9019 - accuracy: 0.7183 - val_loss: 2.0039 - val_perplexity: 7.4178 - val_accuracy: 0.7057 - lr: 0.0010

Es beginnt mit:

„Was warst du nicht, ich war derselbe“…

Was nicht wirklich viel Sinn macht. Am Ende der zehn kurzen Epochen produziert es etwas in der Art von:

„Was meinst du damit, das ist natürlich der gewöhnlichste Mann eines Mannes“…

Auch wenn der zweite Satz noch nicht allzu viel Sinn macht – er ist viel sinnvoller als der erste. Ein längeres Training mit mehr Daten (mit komplizierteren Vorverarbeitungsschritten) würde zu besseren Ergebnissen führen. Wir haben es nur auf 10 Epochen mit hohem Ausfall trainiert, um die kleine Datensatzgröße zu bekämpfen. Wenn es viel länger trainiert würde, würde es sehr Fjodor-ähnlichen Text produzieren, weil es große Teile davon auswendig gelernt hätte.

Hinweis: Da die Ausgabe ziemlich ausführlich ist, können Sie sie optimieren verbose Argument beim Anpassen des Modells, um die Textmenge auf dem Bildschirm zu reduzieren.

Modellschlussfolgerung

Um eine Inferenz durchzuführen, möchten wir die Schnittstelle von replizieren TextSampler – eine Methode, die einen Seed akzeptiert und a response_length (max_tokens). Wir verwenden die gleichen Methoden wie im Sampler:

def sample_token(logits):
        logits, indices = tf.math.top_k(logits, k=5, sorted=True)
        indices = np.asarray(indices).astype("int32")
        preds = keras.activations.softmax(tf.expand_dims(logits, 0))[0]
        preds = np.asarray(preds).astype("float32")
        return np.random.choice(indices, p=preds)

def generate_text(prompt, response_length=20):
    decoded_sample = prompt
    for i in range(response_length-1):
        tokenized_prompt = vectorize_layer([decoded_sample])[:, :-1]
        predictions = model.predict([tokenized_prompt], verbose=0)
        sample_index = len(decoded_sample.strip().split())-1

        sampled_token = sample_token(predictions[0][sample_index])
        sampled_token = index_lookup[sampled_token]
        decoded_sample += " " + sampled_token
    return decoded_sample

Jetzt können Sie die Methode mit neuen Proben ausführen:

generate_text('the truth ultimately is')


generate_text('the truth ultimately is')

Ergebnisse verbessern?

Wie können Sie also die Ergebnisse verbessern? Es gibt einige ziemlich umsetzbare Dinge, die Sie tun könnten:

  • Datenbereinigung (bereinigen Sie die Eingabedaten sorgfältiger, wir haben nur eine ungefähre Zahl von Anfang an gekürzt und Zeilenumbruchzeichen entfernt)
  • Holen Sie sich mehr Daten (wir haben nur mit ein paar Megabyte Textdaten gearbeitet)
  • Skalieren Sie das Modell neben den Daten (das Stapeln von Decodern ist nicht schwer!)

Zusammenfassung

Während die Vorverarbeitungspipeline minimalistisch ist und verbessert werden kann, hat die in diesem Handbuch beschriebene Pipeline ein anständiges Modell im GPT-Stil erzeugt, mit nur 5 Codezeilen, die erforderlich sind, um einen benutzerdefinierten Nur-Decoder-Transformator mit Keras zu erstellen!

Transformer sind beliebt und weit verbreitet für die generische Sequenzmodellierung (und viele Dinge können als Sequenzen ausgedrückt werden). Bisher war die Haupteintrittsbarriere eine umständliche Implementierung, aber mit KerasNLP können Deep-Learning-Praktiker die Implementierungen nutzen, um Modelle schnell und einfach zu erstellen.

Zeitstempel:

Mehr von Stapelmissbrauch