5-wierszowe generowanie tekstu w stylu GPT w Pythonie z TensorFlow/Keras

Transformatory, mimo że wydane w 2017 roku, zaczęły zyskiwać znaczącą popularność dopiero w ciągu ostatnich kilku lat. Wraz z rozprzestrzenianiem się technologii za pośrednictwem platform takich jak HuggingFace, NLP i Modele dużych języków (LLM) stały się bardziej dostępne niż kiedykolwiek.

Jednak – nawet przy całym szumie wokół nich i z wiele przewodników zorientowanych na teorię, nie ma wielu niestandardowych implementacji online, a zasoby nie są tak łatwo dostępne, jak w przypadku niektórych innych typów sieci, które istnieją od dłuższego czasu. Chociaż możesz uprościć swój cykl pracy, korzystając z gotowego Transformera od HuggingFace (temat innego przewodnika) – możesz przejść do czuć jak to działa, budując je samemu, zanim wyabstrahujesz je za pomocą biblioteki. Skupimy się tutaj na budowaniu, a nie na teorii i optymalizacji.

W tym przewodniku będziemy budować Autoregresywny model języka do wygeneruj tekst. Skoncentrujemy się na praktycznych i minimalistycznych/zwięzłych aspektach ładowania danych, dzielenia ich, wektoryzacji, budowania modelu, pisania niestandardowego wywołania zwrotnego i uczenia/wnioskowania. Każde z tych zadań można podzielić na bardziej szczegółowe przewodniki, więc zachowamy implementację jako ogólną, pozostawiając miejsce na dostosowanie i optymalizację w zależności od własnego zestawu danych.

Rodzaje LLM i GPT-Fyodor

Podczas gdy kategoryzacja może być znacznie bardziej skomplikowana – możesz szeroko kategoryzować modele językowe oparte na programie Transformer na trzy kategorie:

  • Modele oparte na koderze – ALBERT, BERT, DistilBERT, ROBERTA
  • Oparty na dekoderze – GPT, GPT-2, GPT-3, TransformerXL
  • Modele Seq2Seq – BART, mBART, T5

Oparty na koderze modele wykorzystują tylko koder Transformer w swojej architekturze (zazwyczaj stos) i doskonale nadają się do rozumienia zdań (klasyfikacja, rozpoznawanie nazwanych jednostek, odpowiadanie na pytania).

Oparty na dekoderze modele używają tylko dekodera Transformer w swojej architekturze (również zazwyczaj ułożonej w stos) i są świetne do przewidywania przyszłości, co czyni je odpowiednimi do generowania tekstu.

Sekwencja2sekw modele łączą zarówno kodery, jak i dekodery i świetnie sprawdzają się w generowaniu tekstu, podsumowaniu i co najważniejsze – tłumaczeniu.

Rodzina modeli GPT, która zyskała dużą popularność w ciągu ostatnich kilku lat, to modele transformatorowe oparte na dekoderach i świetnie radzą sobie z tworzeniem tekstu podobnego do człowieka, uczonego na dużych zbiorach danych i otrzymującego monit jako nowy początkowe ziarno dla pokolenia. Na przykład:

generate_text('the truth ultimately is')

Który pod maską wprowadza ten monit do modelu podobnego do GPT i wytwarza:

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

To tak naprawdę mały spoiler z końca poradnika! Kolejnym małym spoilerem jest architektura, która stworzyła ten tekst:

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 linii wystarczy, aby zbudować model transformatora tylko z dekoderem – symulując mały GPT. Ponieważ będziemy szkolić model na powieściach Fiodora Dostojewskiego (które można zastąpić czymkolwiek innym, od Wikipedii po komentarze Reddit) – wstępnie nazwiemy model GPT-Fiodor.

KerasNLP

Trik do 5-liniowego GPT-Fiodora leży w KerasNLP, który jest rozwijany przez oficjalny zespół Keras, jako horyzontalne rozszerzenie Keras, które w prawdziwym stylu Keras ma na celu udostępnienie silnego w branży NLP na wyciągnięcie ręki, z nowymi warstwami (enkodery, dekodery, osadzanie tokenów, osadzanie pozycji, metryki, tokenizatory itp.).

KerasNLP nie jest modelowym zoo. Jest to część Keras (jako osobny pakiet), która obniża barierę wejścia dla rozwoju modelu NLP, tak jak obniża barierę wejścia dla ogólnego rozwoju głębokiego uczenia się z głównym pakietem.

Uwaga: W chwili pisania KerasNLP jest nadal produkowany i na wczesnych etapach. Subtelne różnice mogą pojawić się w przyszłych wersjach. Zapis wykorzystuje wersję 0.3.0.

Aby móc korzystać z KerasNLP, musisz zainstalować go przez pip:

$ pip install keras_nlp

I możesz zweryfikować wersję za pomocą:

keras_nlp.__version__

Implementacja modelu w stylu GPT z Keras

Zacznijmy od zaimportowania bibliotek, których będziemy używać – TensorFlow, Keras, KerasNLP i NumPy:

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

Ładowanie danych

Załadujmy kilka powieści Dostojewskiego – jedna byłaby zbyt krótka, aby pasował do modelu, bez sporego przesadnego dopasowania od wczesnych etapów. Będziemy z wdziękiem używać nieprzetworzonych plików tekstowych z Project Gutenberg, ze względu na prostotę pracy z takimi danymi:

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

Po prostu pobraliśmy wszystkie pliki, przejrzeliśmy je i połączyliśmy jeden na drugim. Obejmuje to pewną różnorodność używanego języka, przy jednoczesnym zachowaniu go wyraźnie Fiodor! Dla każdego pliku pominęliśmy pierwsze 10 tys. znaków, czyli mniej więcej średnią długość przedmowy i wstępu Gutenberga, więc w każdej iteracji pozostaje nam w dużej mierze nienaruszona treść książki. Rzućmy okiem na kilka losowych 500 znaków w texts ciąg teraz:


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'

Rozdzielmy ciąg na zdania przed wykonaniem jakiegokolwiek innego przetwarzania:

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

Mamy 69 tys. zdań. Po wymianie n znaki ze spacjami i policz słowa:

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

Uwaga: Generalnie będziesz chciał mieć co najmniej milion słów w zbiorze danych, a najlepiej znacznie więcej. Pracujemy z kilkoma megabajtami danych (~5 MB), podczas gdy modele językowe są częściej trenowane na dziesiątkach gigabajtów tekstu. To oczywiście sprawi, że bardzo łatwo będzie przesadzić wprowadzany tekst i będzie trudne do uogólnień (duże zmieszanie bez nadmiernego dopasowania lub niskie zmieszanie z dużą ilością przesunięć). Potraktuj wyniki z przymrużeniem oka.

Niemniej jednak podzielmy je na trening, test i uprawomocnienie ustawić. Najpierw usuńmy puste ciągi i przetasujmy zdania:


text_list = list(filter(None, text_list))

import random
random.shuffle(text_list)

Następnie dokonamy podziału 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):]

Jest to prosty, ale skuteczny sposób na przeprowadzenie podziału walidacja-test pociągu. Rzućmy okiem na 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', ...

Czas na standaryzację i wektoryzację!

Wektoryzacja tekstu

Sieci nie rozumieją słów – rozumieją liczby. Będziemy chcieli tokenizować słowa:

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

Ponadto, ponieważ zdania różnią się długością – dopełnienie jest zwykle dodawane po lewej lub prawej stronie, aby zapewnić ten sam kształt we wszystkich podawanych zdaniach. Załóżmy, że nasze najdłuższe zdanie ma długość 5 słów (tokenów). W takim przypadku zdanie Wall-E zostałoby uzupełnione dwoma zerami, więc zapewniamy ten sam kształt wejściowy:

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

Tradycyjnie robiono to za pomocą TensorFlow Tokenizer i Kerasa pad_sequences() metody – jednak dużo wygodniejsza warstwa, TextVectorization, może być użyty, który tokenizuje i dopełnia twoje dane wejściowe, pozwalając ci wydobyć słownictwo i jego rozmiar, bez znajomości słownictwa z góry!

Zapoznaj się z naszym praktycznym, praktycznym przewodnikiem dotyczącym nauki Git, zawierającym najlepsze praktyki, standardy przyjęte w branży i dołączoną ściągawkę. Zatrzymaj polecenia Google Git, a właściwie uczyć się to!

Dostosujmy się i dopasujmy TextVectorization warstwa:

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

Połączenia custom_standardization() metoda może trwać znacznie dłużej. Po prostu zmniejszyliśmy wszystkie dane wejściowe i zastąpiliśmy je n w " ". W tym miejscu możesz naprawdę umieścić większość swojego wstępnego przetwarzania tekstu – i dostarczyć go do warstwy wektoryzacji poprzez opcjonalną standardize argument. Kiedyś adapt() warstwa z tekstem (tablica NumPy lub lista tekstów) – stamtąd można pobrać słownik, a także jego rozmiar:

vocab_size = len(vocab)
vocab_size 

Na koniec, aby odtokenizować słowa, stworzymy index_lookup słownik:

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

Odwzorowuje wszystkie tokeny ([1, 2, 3, 4, ...]) do słów w słowniku (['a', 'the', 'i', ...]). Przekazując klucz (indeks tokenów), możemy łatwo odzyskać słowo. Możesz teraz uruchomić vectorize_layer() na dowolnym wejściu i obserwuj zwektoryzowane zdania:

vectorize_layer(['hello world!'])

Co skutkuje w:

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

Witam ma indeks 1 podczas gdy świat ma indeks 7509! Reszta to wyściółka do maxlen obliczyliśmy.

Mamy środki na wektoryzację tekstu – teraz stwórzmy zbiory danych z text_train, text_test i text_valid, używając naszej warstwy wektoryzacji jako medium konwersji między słowami i wektorami, które można wprowadzić do GPT-Fyodor.

Tworzenie zbioru danych

Będziemy tworzyć tf.data.Dataset dla każdego z naszych zestawów, używając from_tensor_slices() oraz podanie listy, cóż, wycinków tensorowych (zdań):

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)

Po utworzeniu i przetasowaniu (znowu na wszelki wypadek) – możemy zastosować funkcję preprocessingu (wektoryzacji i dzielenia sekwencji):

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)

Połączenia preprocess_text() funkcja po prostu rozszerza o ostatni wymiar, wektoryzuje tekst za pomocą naszego vectorize_layer i tworzy dane wejściowe i docelowe, przesunięte o jeden token. Model użyje [0..n] wywnioskować n+1, dając podpowiedź dla każdego słowa, uwzględniając wszystkie słowa przed nim. Przyjrzyjmy się pojedynczemu wpisowi w dowolnym zbiorze danych:

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

Badając zwrócone dane wejściowe i docelowe, w partiach po 64 (o długości 30 każda), możemy wyraźnie zobaczyć, jak są one przesunięte o jeden:

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

Wreszcie – czas zbudować model!

Definicja modelu

Wykorzystamy tutaj warstwy KerasNLP. Po Input, zakodujemy dane wejściowe przez a TokenAndPositionEmbedding warstwa, przechodząca w naszym vocab_size, maxlen i embed_dim. To samo embed_dim że ta warstwa wyprowadza i wprowadza dane do TransformerDecoder będzie zachowane w dekoderze. W chwili pisania Dekoder automatycznie zachowuje wymiarowość wejściową i nie pozwala na rzutowanie jej na inny wynik, ale umożliwia zdefiniowanie ukrytych wymiarów poprzez intermediate_dim argumenty.

Pomnożymy wymiary osadzenia przez dwa dla reprezentacji ukrytej, ale możesz zachować to samo lub użyć liczby oddzielonej od wymiarów osadzenia:

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

Oprócz dekodera mamy Dense warstwa, aby wybrać następne słowo w sekwencji, z a softmax aktywacja (która daje rozkład prawdopodobieństwa dla każdego następnego tokena). Rzućmy okiem na podsumowanie modelu:

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 łączy wiele dekoderów — GPT-2 Small ma 12 dekoderów ustawionych w stosie (117 mln parametrów), podczas gdy GPT-2 Extra Large ma 48 dekoderów ustawionych w stos (1.5 mld parametrów). Nasz model z jednym dekoderem o skromnych parametrach 13M powinien działać wystarczająco dobrze w celach edukacyjnych. Dzięki LLM – skalowanie w górę okazało się wyjątkowo dobrą strategią, a Transformatory pozwalają na dobre skalowanie, dzięki czemu możliwe jest trenowanie bardzo dużych modeli.

GPT-3 ma "skromny" Parametry 175B. Zespół Google Brain przeszkolił model parametryczny 1.6T, aby przeprowadzić badania rzadkości, jednocześnie utrzymując obliczenia na tym samym poziomie, co znacznie mniejsze modele.

Właściwie jeśli zwiększymy liczbę dekoderów z 1 do 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)

Nasz licznik parametrów zostałby zwiększony o 400k:

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

Większość parametrów w naszej sieci pochodzi z TokenAndPositionEmbedding i Dense warstwy!

Wypróbuj różne głębokości dekodera – od 1 do wszystkich możliwych do obsługi urządzenia i zanotuj wyniki. W każdym razie – jesteśmy już prawie gotowi do trenowania modelki! Utwórzmy niestandardowe wywołanie zwrotne, które wygeneruje próbkę tekstu w każdej epoce, abyśmy mogli zobaczyć, jak model uczy się tworzyć zdania poprzez szkolenie.

Niestandardowe wywołanie zwrotne

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

Trenowanie modelu

Wreszcie czas na trening! Wrzućmy do naszego train_dataset i validation_dataset z wywołaniami zwrotnymi:

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

Próbnik wybrał niefortunne zdanie, które zaczyna się od cytatu końcowego i cytatu początkowego, ale nadal daje ciekawe wyniki podczas treningu:

# 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

Zaczyna się od:

„Czym nie byłaś, byłam taka sama”…

Co tak naprawdę nie ma większego sensu. Pod koniec dziesięciu krótkich epok produkuje coś w stylu:

„Co masz na myśli, że jest to najzwyklejszy mężczyzna oczywiście”…

O ile drugie zdanie wciąż nie ma zbyt wiele sensu – jest o wiele bardziej sensowne niż pierwsze. Dłuższe szkolenie na większej ilości danych (z bardziej skomplikowanymi etapami przetwarzania wstępnego) przyniosłoby lepsze wyniki. Przeszkoliliśmy go tylko w 10 epokach z wysokimi przerwami, aby walczyć z małym rozmiarem zestawu danych. Jeśli pozostawiono by go do trenowania znacznie dłużej, wytworzyłby tekst bardzo podobny do Fiodora, ponieważ zapamiętywałby jego duże fragmenty.

Uwaga: Ponieważ dane wyjściowe są dość szczegółowe, możesz poprawić verbose argument podczas dopasowywania modelu, aby zmniejszyć ilość tekstu na ekranie.

Wnioskowanie o modelu

Aby przeprowadzić wnioskowanie, będziemy chcieli zreplikować interfejs TextSampler – metoda, która akceptuje ziarno i response_length (max_tokens). Użyjemy tych samych metod, co w samplerze:

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

Teraz możesz uruchomić metodę na nowych próbkach:

generate_text('the truth ultimately is')


generate_text('the truth ultimately is')

Poprawiasz wyniki?

Jak więc możesz poprawić wyniki? Jest kilka całkiem praktycznych rzeczy, które możesz zrobić:

  • Czyszczenie danych (dokładniej wyczyść dane wejściowe, po prostu przycięliśmy przybliżoną liczbę od początku i usunęliśmy znaki nowej linii)
  • Uzyskaj więcej danych (pracowaliśmy tylko z kilkoma megabajtami danych tekstowych)
  • Skaluj model wraz z danymi (układanie dekoderów nie jest trudne!)

Wnioski

Chociaż potok przetwarzania wstępnego jest minimalistyczny i można go ulepszyć – potok opisany w tym przewodniku stworzył przyzwoity model w stylu GPT, z zaledwie 5 wierszami kodu wymaganymi do zbudowania niestandardowego transformatora tylko z dekoderem, przy użyciu Keras!

Transformatory są popularne i szeroko stosowane do ogólnego modelowania sekwencji (i wiele rzeczy można wyrazić jako sekwencje). Do tej pory główną barierą wejścia była uciążliwa implementacja, ale dzięki KerasNLP – praktycy głębokiego uczenia się mogą wykorzystać implementacje do szybkiego i łatwego budowania modeli.

Znak czasu:

Więcej z Nadużycie stosu