Δημιουργία κειμένου σε στυλ GPT 5 γραμμών σε Python με TensorFlow/Keras

Τα Transformers, παρόλο που κυκλοφόρησαν το 2017, έχουν αρχίσει να κερδίζουν σημαντική έλξη μόλις τα τελευταία δύο χρόνια. Με τη διάδοση της τεχνολογίας μέσω πλατφορμών όπως το HuggingFace, το NLP και Μεγάλα μοντέλα γλωσσών (LLM) έχουν γίνει πιο προσιτές από ποτέ.

Ωστόσο – ακόμα και με όλη τη διαφημιστική εκστρατεία γύρω τους και με πολοί οδηγοί προσανατολισμένοι στη θεωρία, δεν υπάρχουν πολλές προσαρμοσμένες εφαρμογές στο διαδίκτυο και οι πόροι δεν είναι τόσο άμεσα διαθέσιμοι όσο με ορισμένους άλλους τύπους δικτύων, που υπάρχουν εδώ και πολύ καιρό. Ενώ θα μπορούσατε να απλοποιήσετε τον κύκλο εργασίας σας χρησιμοποιώντας έναν προκατασκευασμένο Transformer από το HuggingFace (το θέμα ενός άλλου οδηγού) – μπορείτε να φτάσετε στο αισθάνομαι πώς λειτουργεί φτιάχνοντας ένα μόνοι σας, πριν το αφαιρέσετε μέσω μιας βιβλιοθήκης. Εδώ θα επικεντρωθούμε στη δημιουργία και όχι στη θεωρία και στη βελτιστοποίηση.

Σε αυτόν τον οδηγό, θα δημιουργήσουμε ένα Αυτοπαλινδρομικό μοντέλο γλώσσας προς την δημιουργία κειμένου. Θα επικεντρωθούμε στις πρακτικές και μινιμαλιστικές/συνοπτικές πτυχές της φόρτωσης δεδομένων, του διαχωρισμού τους, της διανυσματικής διαμόρφωσης, της κατασκευής ενός μοντέλου, της σύνταξης μιας προσαρμοσμένης επανάκλησης και της εκπαίδευσης/συμπερασμάτων. Κάθε μία από αυτές τις εργασίες μπορεί να χωριστεί σε πιο λεπτομερείς οδηγούς, επομένως θα διατηρήσουμε την υλοποίηση ως γενική, αφήνοντας χώρο για προσαρμογή και βελτιστοποίηση ανάλογα με το δικό σας σύνολο δεδομένων.

Τύποι LLM και GPT-Fyodor

Ενώ η κατηγοριοποίηση μπορεί να γίνει πολύ πιο περίπλοκη - μπορείτε γενικά κατηγοριοποιήστε τα μοντέλα γλώσσας που βασίζονται σε Transformer σε τρεις κατηγορίες:

  • Μοντέλα που βασίζονται σε κωδικοποιητή – ALBERT, BERT, DistilBERT, RoBERTa
  • Βασισμένο σε αποκωδικοποιητή – GPT, GPT-2, GPT-3, TransformerXL
  • Μοντέλα Seq2Seq – BART, mBART, T5

Βάσει κωδικοποιητή Τα μοντέλα χρησιμοποιούν μόνο έναν κωδικοποιητή Transformer στην αρχιτεκτονική τους (συνήθως, στοιβαγμένα) και είναι εξαιρετικά για την κατανόηση προτάσεων (ταξινόμηση, αναγνώριση ονομαστικών οντοτήτων, απάντηση ερωτήσεων).

Βασισμένο σε αποκωδικοποιητή Τα μοντέλα χρησιμοποιούν μόνο έναν αποκωδικοποιητή Transformer στην αρχιτεκτονική τους (επίσης τυπικά στοιβαγμένα) και είναι εξαιρετικά για μελλοντικές προβλέψεις, γεγονός που τα καθιστά κατάλληλα για δημιουργία κειμένου.

Seq2Seq Τα μοντέλα συνδυάζουν κωδικοποιητές και αποκωδικοποιητές και είναι εξαιρετικά στη δημιουργία κειμένου, τη σύνοψη και το πιο σημαντικό - τη μετάφραση.

Η οικογένεια μοντέλων GPT, η οποία κέρδισε μεγάλη έλξη τα τελευταία δύο χρόνια, είναι μοντέλα μετασχηματιστών που βασίζονται σε αποκωδικοποιητές και είναι εξαιρετικά στην παραγωγή κειμένου που μοιάζει με άνθρωπο, εκπαιδεύεται σε μεγάλα σώματα δεδομένων και δίνεται μια προτροπή ως νέο αρχικός σπόρος για τη γενιά. Για παράδειγμα:

generate_text('the truth ultimately is')

Το οποίο κάτω από την κουκούλα τροφοδοτεί αυτήν την προτροπή σε ένα μοντέλο που μοιάζει με GPT και παράγει:

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

Αυτό είναι, στην πραγματικότητα, ένα μικρό spoiler από το τέλος του οδηγού! Ένα άλλο μικρό spoiler είναι η αρχιτεκτονική που παρήγαγε αυτό το κείμενο:

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 γραμμές είναι το μόνο που χρειάζονται για να δημιουργηθεί ένα μοντέλο μετασχηματιστή μόνο με αποκωδικοποιητή - προσομοίωση ενός μικρού GPT. Δεδομένου ότι θα εκπαιδεύσουμε το μοντέλο στα μυθιστορήματα του Φιοντόρ Ντοστογιέφσκι (τα οποία μπορείτε να αντικαταστήσετε με οτιδήποτε άλλο, από τη Wikipedia έως τα σχόλια στο Reddit) – θα ονομάσουμε δοκιμαστικά το μοντέλο GPT-Φιόντορ.

KerasNLP

Το κόλπο για ένα GPT-Fyodor 5 γραμμών βρίσκεται μέσα KerasNLP, το οποίο αναπτύχθηκε από την επίσημη ομάδα Keras, ως οριζόντια επέκταση στο Keras, το οποίο με τον αληθινό τρόπο Keras, στοχεύει να φέρει το NLP ισχυρής βιομηχανίας στα χέρια σας, με νέα επίπεδα (κωδικοποιητές, αποκωδικοποιητές, ενσωματώσεις διακριτικών, ενσωματώσεις θέσης, μετρήσεις, tokenizers, κ.λπ.).

Το KerasNLP δεν είναι πρότυπο ζωολογικό κήπο. Είναι ένα μέρος του Keras (ως ξεχωριστό πακέτο), που μειώνει το εμπόδιο εισόδου για την ανάπτυξη μοντέλων NLP, όπως μειώνει το εμπόδιο εισόδου για γενική ανάπτυξη βαθιάς μάθησης με το κύριο πακέτο.

Σημείωση: Μέχρι τη στιγμή της συγγραφής, το KerasNLP εξακολουθεί να παράγεται, και σε πρώιμα στάδια. Ενδέχεται να υπάρχουν λεπτές διαφορές σε μελλοντικές εκδόσεις. Η εγγραφή χρησιμοποιεί την έκδοση 0.3.0.

Για να μπορέσετε να χρησιμοποιήσετε το KerasNLP, θα πρέπει να το εγκαταστήσετε μέσω pip:

$ pip install keras_nlp

Και μπορείτε να επαληθεύσετε την έκδοση με:

keras_nlp.__version__

Υλοποίηση μοντέλου στυλ GPT με Keras

Ας ξεκινήσουμε εισάγοντας τις βιβλιοθήκες που θα χρησιμοποιήσουμε – TensorFlow, Keras, KerasNLP και NumPy:

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

Τα δεδομένα φορτώνονται

Ας φορτώσουμε μερικά από τα μυθιστορήματα του Ντοστογιέφσκι – ένα θα ήταν πολύ σύντομο για να χωρέσει ένα μοντέλο, χωρίς λίγη υπερβολική προσαρμογή από τα πρώτα στάδια και μετά. Θα χρησιμοποιήσουμε με χάρη τα αρχεία ακατέργαστου κειμένου από Project Gutenberg, λόγω της απλότητας της εργασίας με τέτοια δεδομένα:

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:]

Απλώς κατεβάσαμε όλα τα αρχεία, τα περάσαμε και τα συνδέσαμε το ένα πάνω στο άλλο. Αυτό περιλαμβάνει κάποια ποικιλομορφία στη γλώσσα που χρησιμοποιείται, ενώ εξακολουθεί να διατηρείται ευδιάκριτα Fyodor! Για κάθε αρχείο, έχουμε παραλείψει τους πρώτους 10 χαρακτήρες, που είναι περίπου το μέσο μήκος του προλόγου και της εισαγωγής του Gutenberg, οπότε μας μένει ένα σε μεγάλο βαθμό άθικτο σώμα του βιβλίου για κάθε επανάληψη. Ας ρίξουμε μια ματιά σε μερικούς τυχαίους 500 χαρακτήρες στο texts string τώρα:


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'

Ας διαχωρίσουμε τη συμβολοσειρά σε προτάσεις πριν κάνουμε οποιαδήποτε άλλη επεξεργασία:

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

Έχουμε 69 χιλιάδες προτάσεις. Όταν αντικαθιστάτε το n χαρακτήρες με κενά και μετρήστε τις λέξεις:

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

Σημείωση: Γενικά θα θέλετε να έχετε τουλάχιστον ένα εκατομμύριο λέξεις σε ένα σύνολο δεδομένων, και ιδανικά, πολύ περισσότερα από αυτό. Εργαζόμαστε με λίγα megabyte δεδομένων (~5MB), ενώ τα μοντέλα γλώσσας εκπαιδεύονται πιο συχνά σε δεκάδες gigabyte κειμένου. Αυτό, φυσικά, θα καταστήσει πολύ εύκολη την υπερπροσαρμογή της εισαγωγής κειμένου και δύσκολη τη γενίκευση (υψηλή αμηχανία χωρίς υπερβολική προσαρμογή ή χαμηλή αμηχανία με πολλή υπερπροσαρμογή). Πάρτε τα αποτελέσματα με λίγο αλάτι.

Ωστόσο, ας τα χωρίσουμε σε ένα εκπαίδευση, δοκιμή και επικύρωση σειρά. Αρχικά, ας αφαιρέσουμε τις κενές συμβολοσειρές και ας ανακατέψουμε τις προτάσεις:


text_list = list(filter(None, text_list))

import random
random.shuffle(text_list)

Στη συνέχεια, θα κάνουμε ένα διαχωρισμό 70/15/15:

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

Αυτός είναι ένας απλός, αλλά αποτελεσματικός τρόπος για να πραγματοποιήσετε μια διαίρεση τρένου-δοκιμή-επικύρωσης. Ας ρίξουμε μια ματιά 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', ...

Ώρα για τυποποίηση και διανυσματοποίηση!

Διανυσματοποίηση κειμένου

Τα δίκτυα δεν καταλαβαίνουν λέξεις – καταλαβαίνουν αριθμούς. Θα θέλαμε να συνδυάσουμε τις λέξεις:

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

Επίσης, δεδομένου ότι οι προτάσεις διαφέρουν ως προς το μήκος – η γέμιση προστίθεται συνήθως προς τα αριστερά ή προς τα δεξιά για να διασφαλιστεί το ίδιο σχήμα στις προτάσεις που τροφοδοτούνται. Ας υποθέσουμε ότι η μεγαλύτερη φράση μας είναι 5 λέξεων (κουπόνια). Σε αυτήν την περίπτωση, η πρόταση Wall-E θα συμπληρώνεται με δύο μηδενικά, ώστε να διασφαλίζουμε το ίδιο σχήμα εισαγωγής:

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

Παραδοσιακά, αυτό γινόταν χρησιμοποιώντας ένα TensorFlow Tokenizer και ο Κέρας pad_sequences() μέθοδοι – ωστόσο, ένα πολύ πιο εύχρηστο στρώμα, TextVectorization, μπορεί να χρησιμοποιηθεί, το οποίο συμβολίζει και συμπληρώνει τα στοιχεία σας, επιτρέποντάς σας να εξαγάγετε το λεξιλόγιο και το μέγεθός του, χωρίς να γνωρίζετε τη λεξιλόγια εκ των προτέρων!

Ρίξτε μια ματιά στον πρακτικό μας οδηγό για την εκμάθηση του Git, με βέλτιστες πρακτικές, πρότυπα αποδεκτά από τον κλάδο και συμπεριλαμβανόμενο φύλλο εξαπάτησης. Σταματήστε τις εντολές του Git στο Google και πραγματικά μαθαίνουν το!

Ας προσαρμοστούμε και ας χωρέσουμε α TextVectorization στρώμα:

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

Η custom_standardization() η μέθοδος μπορεί να είναι πολύ μεγαλύτερη από αυτή. Απλώς μειώσαμε όλα τα στοιχεία εισόδου και αντικαταστήσαμε n με " ". Εδώ μπορείτε πραγματικά να βάλετε το μεγαλύτερο μέρος της προεπεξεργασίας σας για κείμενο – και να το παρέχετε στο επίπεδο διανυσματοποίησης μέσω του προαιρετικού standardize διαφωνία. Μόλις εσύ adapt() το επίπεδο στο κείμενο (πίνακας NumPy ή λίστα κειμένων) – μπορείτε να λάβετε το λεξιλόγιο, καθώς και το μέγεθός του από εκεί:

vocab_size = len(vocab)
vocab_size 

Τέλος, για να αποχαρακτηρίσουμε τις λέξεις, θα δημιουργήσουμε ένα index_lookup λεξικό:

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

Αντιστοιχίζει όλα τα διακριτικά ([1, 2, 3, 4, ...]) σε λέξεις στο λεξιλόγιο (['a', 'the', 'i', ...]). Περνώντας ένα κλειδί (token index), μπορούμε εύκολα να πάρουμε τη λέξη πίσω. Τώρα μπορείτε να εκτελέσετε το vectorize_layer() σε οποιαδήποτε είσοδο και παρατηρήστε τις διανυσματικές προτάσεις:

vectorize_layer(['hello world!'])

Το οποίο έχει ως αποτέλεσμα:

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

Hello έχει το ευρετήριο του 1 ενώ ο κόσμος έχει τον δείκτη του 7509! Το υπόλοιπο είναι η γέμιση στο maxlen έχουμε υπολογίσει.

Έχουμε τα μέσα για να διανυσματοποιήσουμε το κείμενο – τώρα, ας δημιουργήσουμε σύνολα δεδομένων από text_train, text_test και text_valid, χρησιμοποιώντας το επίπεδο διανυσματοποίησης ως μέσο μετατροπής μεταξύ λέξεων και διανυσμάτων που μπορούν να τροφοδοτηθούν στο GPT-Fyodor.

Δημιουργία συνόλου δεδομένων

Θα δημιουργήσουμε ένα tf.data.Dataset για κάθε σετ μας, χρησιμοποιώντας from_tensor_slices() και παρέχοντας μια λίστα με, λοιπόν, φέτες τανυστήρα (προτάσεις):

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)

Μόλις δημιουργηθεί και ανακατευτεί (και πάλι, για καλό μέτρο) – μπορούμε να εφαρμόσουμε μια συνάρτηση προεπεξεργασίας (διανύσμα και διαχωρισμός ακολουθίας):

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)

Η preprocess_text() η συνάρτηση απλώς επεκτείνεται κατά την τελευταία διάσταση, διανυσματοποιεί το κείμενο χρησιμοποιώντας το δικό μας vectorize_layer και δημιουργεί τις εισόδους και τους στόχους, που αντισταθμίζονται από ένα μόνο διακριτικό. Το μοντέλο θα χρησιμοποιήσει [0..n] να συμπεράνω n+1, δίνοντας μια πρόβλεψη για κάθε λέξη, λαμβάνοντας υπόψη όλες τις λέξεις πριν από αυτήν. Ας ρίξουμε μια ματιά σε μια μεμονωμένη καταχώρηση σε οποιοδήποτε από τα σύνολα δεδομένων:

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

Διερευνώντας τις εισροές και τους στόχους που επιστράφηκαν, σε παρτίδες των 64 (με μήκος 30 η καθεμία), μπορούμε να δούμε ξεκάθαρα πώς αντισταθμίζονται κατά ένα:

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

Επιτέλους – ήρθε η ώρα να φτιάξετε το μοντέλο!

Ορισμός μοντέλου

Θα χρησιμοποιήσουμε τα επίπεδα KerasNLP εδώ. Μετά από ένα Input, θα κωδικοποιήσουμε την είσοδο μέσω α TokenAndPositionEmbedding στρώμα, περνώντας μέσα μας vocab_size, maxlen και embed_dim. Το ίδιο embed_dim ότι αυτό το στρώμα εξάγει και εισάγει στο TransformerDecoder θα είναι που διατηρείται στον αποκωδικοποιητή. Κατά τη σύνταξη, ο αποκωδικοποιητής διατηρεί αυτόματα τη διάσταση εισόδου και δεν σας επιτρέπει να την προβάλλετε σε διαφορετική έξοδο, αλλά σας επιτρέπει να ορίσετε τις λανθάνουσες διαστάσεις μέσω του intermediate_dim διαφωνία.

Θα πολλαπλασιάσουμε τις διαστάσεις της ενσωμάτωσης επί δύο για την λανθάνουσα αναπαράσταση, αλλά μπορείτε να τη διατηρήσετε ίδια ή να χρησιμοποιήσετε έναν αριθμό που αποσπάται από τα ρολά ενσωμάτωσης:

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

Πάνω από τον αποκωδικοποιητή, έχουμε ένα Dense επίπεδο για να επιλέξετε την επόμενη λέξη στην ακολουθία, με α softmax ενεργοποίηση (η οποία παράγει την κατανομή πιθανοτήτων για κάθε επόμενο διακριτικό). Ας ρίξουμε μια ματιά στην περίληψη του μοντέλου:

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 στοιβάζει πολλούς αποκωδικοποιητές – Το GPT-2 Small έχει 12 στοιβαγμένους αποκωδικοποιητές (117M params), ενώ το GPT-2 Extra Large έχει 48 στοιβαγμένους αποκωδικοποιητές (1.5B params). Το μοντέλο μας με έναν αποκωδικοποιητή με ταπεινές παραμέτρους 13M θα πρέπει να λειτουργεί αρκετά καλά για εκπαιδευτικούς σκοπούς. Με τα LLM – η κλιμάκωση έχει αποδειχθεί εξαιρετικά καλή στρατηγική και τα Transformers επιτρέπουν την καλή κλιμάκωση, καθιστώντας εφικτή την εκπαίδευση εξαιρετικά μεγάλων μοντέλων.

Το GPT-3 έχει α "γλίσχρος" 175Β παράμετροι. Η ομάδα του Google Brain εκπαίδευσε ένα μοντέλο παραμέτρων 1.6Τ για να εκτελεί έρευνα σπανιότητας, διατηρώντας παράλληλα τους υπολογισμούς στο ίδιο επίπεδο με πολύ μικρότερα μοντέλα.

Στην πραγματικότητα, αν αυξήσουμε τον αριθμό των αποκωδικοποιητών από 1 σε 3:

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)

Ο αριθμός παραμέτρων μας θα αυξηθεί κατά 400 χιλιάδες:

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

Οι περισσότερες από τις παραμέτρους στο δίκτυό μας προέρχονται από το TokenAndPositionEmbedding και Dense στρώσεις!

Δοκιμάστε διαφορετικά βάθη του αποκωδικοποιητή – από 1 έως ό,τι μπορεί να χειριστεί το μηχάνημά σας και σημειώστε τα αποτελέσματα. Σε κάθε περίπτωση – είμαστε σχεδόν έτοιμοι να εκπαιδεύσουμε το μοντέλο! Ας δημιουργήσουμε μια προσαρμοσμένη επανάκληση που θα παράγει ένα δείγμα κειμένου σε κάθε εποχή, ώστε να μπορούμε να δούμε πώς το μοντέλο μαθαίνει να σχηματίζει προτάσεις μέσω της εκπαίδευσης.

Προσαρμοσμένη επανάκληση

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

Εκπαίδευση του Μοντέλου

Επιτέλους, ώρα για προπόνηση! Ας τσιμπήσουμε στα δικά μας train_dataset και validation_dataset με τις επανακλήσεις στη θέση τους:

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

Ο δειγματολήπτης επέλεξε μια ατυχή πρόταση που ξεκινά με το απόσπασμα τέλους και το απόσπασμα έναρξης, αλλά εξακολουθεί να παράγει ενδιαφέροντα αποτελέσματα κατά την προπόνηση:

# 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

Ξεκινά με:

«Τι δεν ήσουν, ήμουν το ίδιο»…

Κάτι που δεν έχει και πολύ νόημα. Μέχρι το τέλος των δέκα σύντομων εποχών, παράγει κάτι σύμφωνα με τις γραμμές:

«Τι εννοείς ότι είναι ο πιο συνηθισμένος άντρας φυσικά»…

Ενώ η δεύτερη πρόταση εξακολουθεί να μην έχει πολύ νόημα – είναι πολύ πιο λογικό από την πρώτη. Η μεγαλύτερη εκπαίδευση σε περισσότερα δεδομένα (με πιο περίπλοκα βήματα προεπεξεργασίας) θα απέφερε καλύτερα αποτελέσματα. Το έχουμε εκπαιδεύσει μόνο σε 10 εποχές με μεγάλη εγκατάλειψη για την καταπολέμηση του μικρού μεγέθους δεδομένων. Αν αφεθεί να προπονείται για πολύ περισσότερο, θα δημιουργούσε κείμενο που μοιάζει πολύ με τον Φιόντορ, γιατί θα είχε απομνημονεύσει μεγάλα κομμάτια του.

Σημείωση: Δεδομένου ότι η έξοδος είναι αρκετά περιεκτική, μπορείτε να τροποποιήσετε το verbose όρισμα κατά την προσαρμογή του μοντέλου για μείωση του όγκου του κειμένου στην οθόνη.

Συμπέρασμα μοντέλου

Για να εκτελέσουμε συμπέρασμα, θα θέλουμε να αναπαράγουμε τη διεπαφή του TextSampler – μέθοδος που δέχεται σπόρο και α response_length (max_tokens). Θα χρησιμοποιήσουμε τις ίδιες μεθόδους όπως στο δειγματολήπτη:

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

Τώρα, μπορείτε να εκτελέσετε τη μέθοδο σε νέα δείγματα:

generate_text('the truth ultimately is')


generate_text('the truth ultimately is')

Βελτίωση των αποτελεσμάτων;

Λοιπόν, πώς μπορείτε να βελτιώσετε τα αποτελέσματα; Υπάρχουν μερικά αρκετά δραστικά πράγματα που μπορείτε να κάνετε:

  • Καθαρισμός δεδομένων (καθαρίστε τα δεδομένα εισόδου πιο σχολαστικά, απλώς περικόψαμε έναν κατά προσέγγιση αριθμό από την αρχή και αφαιρέσαμε χαρακτήρες νέας γραμμής)
  • Λάβετε περισσότερα δεδομένα (δουλέψαμε μόνο με λίγα megabyte δεδομένων κειμένου)
  • Κλιμακώστε το μοντέλο παράλληλα με τα δεδομένα (η στοίβαξη των αποκωδικοποιητών δεν είναι δύσκολη!)

Συμπέρασμα

Ενώ ο αγωγός προεπεξεργασίας είναι μινιμαλιστικός και μπορεί να βελτιωθεί – ο αγωγός που περιγράφεται σε αυτόν τον οδηγό παρήγαγε ένα αξιοπρεπές μοντέλο τύπου GPT, με μόλις 5 γραμμές κώδικα που απαιτούνται για την κατασκευή ενός προσαρμοσμένου μετασχηματιστή μόνο για αποκωδικοποιητή, χρησιμοποιώντας Keras!

Οι μετασχηματιστές είναι δημοφιλείς και ευρέως εφαρμόσιμοι για τη γενική μοντελοποίηση ακολουθιών (και πολλά πράγματα μπορούν να εκφραστούν ως ακολουθίες). Μέχρι στιγμής, το κύριο εμπόδιο για την είσοδο ήταν μια δυσκίνητη υλοποίηση, αλλά με το KerasNLP – οι επαγγελματίες βαθιάς μάθησης μπορούν να αξιοποιήσουν τις υλοποιήσεις για να δημιουργήσουν μοντέλα γρήγορα και εύκολα.

Σφραγίδα ώρας:

Περισσότερα από Stackabuse