CANON_122023 CANON_122023 CANON_122023

ML v Pythone 13 – neurónová sieť na generovanie textu II

0

Zaujíma vás ako funguje ChatGPT a podobné AI chatboty? Najlepšie to pochopíte keď si takého chatbota naprogramujete sami.  V jednom z predchádzajúcich príkladov sme vytvorili neurónovú sieť, ktorá bola natrénovaná na texte knihy, pričom tréning spočíval v skúmaní slovosledu. Ukážeme iný postup kedy sa neurónová sieť počas trénovania nebude učiť postupnosť slov, ale postupnosť znakov. 

Jazykové modely generujú postupnosť znakov, prípadne slov v prirodzenom jazyku. Po natrénovaní na veľkom množstve textu vedia aké druhy slov za sebou nasledujú v príslušnom jazyku. Zadáme mu začiatok sekvencie a jazykový model sa snaží dokončiť text. Na pozadí jazykového modelu je neurónová sieť. Na trénovanie použijeme rovnaký text, konkrétne knihy Rivers of Babylon od Petra Pišťanka. Na rozdiel od predchádzajúceho príkladu nebudeme text členiť na slová, ale budeme ho analyzovať ako postupnosť znakov.

Motiváciou k tomuto príkladu je video Andrej Karpathy Let's build GPT: from scratch, in code, spelled out Po úspešnom vyskúšaní príkladu v článku odporúčam pozrieť si toto video a vyskúšať príklad v ňom uvedený. Andrej trénuje neurónovú sieť na Shakespearových textoch.

Najskôr ukážeme princíp trénovania. Z náhodného miesta v texte sa vyberie vzorka, čiže úsek textu o stanovenej dĺžke, text sa konvertuje na čísla, pretože neurónová sieť pracuje s číslami, a následne na tenzory. V našom ilustračnom príklade je ukázané spracovanie vzorky textu „Úlohy súvisiace so strojovým učením“ s dĺžkou 36 znakov.  Pred pomlčkou je vstup a za pomlčkou hrubým fontom výstup. Z každej takejto vzorky sa dá vyextrahovať 36 prípadov aké písmeno nasleduje po prvom znaku, po prvých dvoch znakoch, troch... a napokon aké písmeno nasleduje po 35 znakoch

Ú-l
Úl-o
Úlo-h
Úloh-y
Úlohy-„ „
Úlohy -s
Úlohy s-ú
Úlohy sú-v
úlohy súv-i
Úlohy súvi-s
Úlohy súvis-i
Úlohy súvisia-c
...
Úlohy súvisiace so strojovým uč-e
Úlohy súvisiace so strojovým uče-n
Úlohy súvisiace so strojovým učen-í
Úlohy súvisiace so strojovým učení-m

Z prvého riadku sa NS dozvie, že za „ú“ by mohlo nasledovať „l“, avšak z riadku „Úlohy sú-v“ sa dozvie, že za fragmentom textu „Úlohy sú“ bude pravdepodobne nasledovať „v“ a podobne. Takýchto vzoriek sa počas trénovania NS na veľkom objeme textu odoberú a spracujú stovky miliónov. Spracovanie žiadnej zo vzoriek nie je závislé od inej vzorky, takže paralelne je možné takýchto vzoriek spracovať neobmedzený počet, v praxi toľko vzoriek, koľko ich výpočtové zdroje pridelené na túto úlohu spracovať dokážu. A tu sa ukáže rozdiel medzi CPU, ktorý má typicky 16 - 32 jadier  a grafickou kartou, ktorá má takýchto jadier niekoľko tisíc. Napríklad  grafická karta NVIDIA RTX 4090 má 16 384 CUDA jadier.

Na úvod všetky importy potrebné pre príklad

# importy
import torch
import torch.nn as nn
from torch.nn import functional as F
import string
import time

Výhodnejšie je spúšťať výpočty na GPU

# Ak máte k dispozícii GPU s podporou NVIDIA Cuda výpočty budú rýchlejšie
device = 'cuda' if torch.cuda.is_available() else 'cpu'
device

Načítame text z lokálneho PC, alebo disku Google, pričom vynecháme znaky CR a LF indikujúce prechod na nový riadok

# ALTERNATÍVA načítanie knihy z lokálneho PC
kniha = open('./Rivers of Babylon.txt', 'r', encoding="utf8")
text = kniha.read().replace('\n', '')

alebo

# ALTERNATÍVA načítanie knihy z Google drive
drive.mount('/content/drive')
kniha = open('/content/drive/My Drive/ML Neuronova siet/Rivers of Babylon.txt', 'r')
text = kniha.read().replace('\n', '')

Zistíme počet znakov textu a na ilustráciu vypíšeme krátku ukážku z úvodu knihy

print("Počet znakov textu: ", len(text))
text[:500]
 
---výpis---
Počet znakov textu:  551955
Ráno sa kotolník zobúdza s takou nenávisťou v duši, že mu ani jesť nechutí. Vylihuje na drevenej lavici, škrabe si svrbiacu kožu...

Nakoľko neurónovú sieť budeme trénovať na úrovni znakov, potrebujeme zistiť počet unikátnych znakov v texte. Necháme si ich vypísať

# všetky unikátne znaky v texte
znaky = sorted(list(set(text))) #kvôli prehľadnosti utriedené
print(''.join(znaky))
 
---výpis---
!&(),-./0124589:;?ABCDEFGHIJKLMNOPRSTUVWXYZabcdefghijklmnopqrstuvwxyz|ÁÇÔÚáäçéëíóôöúýČčĎď弾ŁňඩšŤťŽž├┬┼

Budeme pracovať len s čistým textom, takže odstránime všetky nealfanumerické znaky. Ich zoznam získame pomocou funkcie string.punctuation(), pričom do takto získaného reťazca nealfanumerických znakov doplníme ďalšie znaky ktoré vidíme že sa tam vyskytujú.

neabecedne_znaky = string.punctuation #zoznam nealfanumerických znakov
neabecedne_znaky += '├┬┼'   #ďalšie znaky ktoré vidím že sa mi tam vyskytujú
neabecedne_znaky
 
---výpis---
!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~├┬┼

Následne získame množinu všetkých znakov, ktoré sa vyskytujú v tomto texte. Budú to alfanumerické znaky a medzery

# vynecháme nealfanumerické znaky
for znak in neabecedne_znaky:
   text = text.replace(znak, "")
znaky = sorted(list(set(text)))
unikatnych_znakov = len(znaky)   #vocab_size
print(''.join(znaky))
print(unikatnych_znakov)
 
---výpis---
0124589ABCDEFGHIJKLMNOPRSTUVWXYZabcdefghijklmnopqrstuvwxyzÁÇÔÚáäçéëíóôöúýČčĎď弾ŁňඩšŤťŽž
91

Nasleduje takzvaná tokenizácia vstupného textu, čiže konverzia postupnosti znakov na postupnosť celých čísel podľa nejakého slovníka znakov, ktoré sa v texte vyskytujú. Pre jednoduchosť vytvoríme jazykový model na úrovni znakov, takže jednoducho pretransformujeme jednotlivé znaky na celé čísla. V predchádzajúcom príklade sme použili jazykový model na úrovni slov, kedy sme číslo priradili každému unikátnemu slovu. Bolo ich niekoľko tisíc. Využívajú sa aj jazykové modely na báze častí slov. Pri našom modeli na úrovni znakov ich máme 91.  Teraz znaky nahradíme číslami

# mapovanie znakov na indexy
znaky_na_ix = { ch:i for i,ch in enumerate(znaky) }
znaky_na_ix
 
---výpis skrátený ---
{' ': 0,
 '0': 1,
 '1': 2,
 '2': 3,
 '4': 4,
 '5': 5,
 '8': 6,
 '9': 7,
 'A': 8,
 'B': 9,
 'C': 10,
 'D': 11,
...
 'Ť': 87,
 'ť': 88,
 'Ž': 89,
 'ž': 90}

Mapovanie indexov späť na znaky

# indexy na znaky
ix_na_znaky = { i:ch for i,ch in enumerate(znaky) }
ix_na_znaky
 
---výpis skrátený---
{0: ' ',
 1: '0',
 2: '1',
 3: '2',
 4: '4',

Funkcie (typu lambda) na zakódovanie textu na čísla a na konverziu čísel späť na znaky. Vytvoríme kód aj na spätné mapovanie, takže môžeme zadať zoznam čísel a dekódovať ho, aby sme dostali textový reťazec

#kodovanie
text_na_cisla = lambda t: [znaky_na_ix[c] for c in t]         
#dekodovanie
cisla_na_text = lambda c: ''.join([ix_na_znaky[i] for i in c])

Vysvetlenie fungovania týchto funkcií

# --- kód na vysvetlenie ---
print(text_na_cisla("ML v Pythone"))
 
---výpis---
[20, 19, 0, 54, 0, 23, 57, 52, 40, 47, 46, 37]
 
# --- kód na vysvetlenie ---
print(cisla_na_text([20, 19, 0, 54, 0, 23, 57, 52, 40, 47, 46, 37]))
---výpis---
ML v Pythone

Text pretransformovaný na čísla prekonvertujeme na tenzor knižnice torch.

# konverzia textu na tenzory
tenzory_textu = torch.tensor(text_na_cisla(text), dtype=torch.long)
print(tenzory_textu.shape)
tenzory_textu
 
---výpis---
torch.Size([529722])
tensor([24, 63, 46,  ..., 50, 75, 68])

Vstupné údaje, v našom prípade text v podobe tenzora rozdelíme na trénovaciu a validačnú množinu, pričom trénovacia množina bude obsahovať  90 % údajov. Zvyšných 10%  budú overovacie údaje.

# rozdelenie dát na trénovaciu a testovaciu množinu
# tu záleží na postupnosti dát (tenzorov zastupujúcich znaky)
# takže to musí byť rozdelené sekvenčne nie náhodne
n = int(0.9*len(tenzory_textu)) # 90%  textu od začiatku budú trénovacie dáta
train_data = tenzory_textu[:n]
test_data = tenzory_textu[n:]

Pre zaujímavosť zistíme veľkosť každej množiny

print("Trénovacia množina:",train_data.shape)
print("Testovacia množina:",test_data.shape)
 
---výpis---
Trénovacia množina: torch.Size([476749])
Testovacia množina: torch.Size([52973])

Ani v tomto prípade nezavedieme pri trénovaní neurónovej siete naraz celý text. To by bolo výpočtovo veľmi náročné. Preto text rozdelíme na malé bloky a tie budeme z textu vyberať náhodne. Inak povedané hodnotu indexu označujúceho začiatok každej vzorky vygeneruje generátor náhodných čísel. Nebudeme teda trénovať kontinuálne, ale po náhodne vybraných úsekoch.

Budeme pracovať s blokom o zadanej veľkosti, v našom prípade 16 znakov + jeden znak. V takomto bloku je 16 individuálnych prípadov predikcie nasledujúceho znaku na základe predchádzajúcich znakov. Znak +1 bude porovnávaný s výstupom (cieľom) predikcie čo nasleduje za posledným znakom bloku.  Model neurónovej siete nikdy nedostane naraz viac vstupných údajov ako je veľkosť bloku. Súčasne sa síce bude spracovávať viac dávok, avšak ich spracovanie bude úplne nezávislé. Nijako medzi sebou neinteragujú. Budeme teda brať vzorky z náhodných miest textu a spracovávať ich.

Na ilustráciu ukážeme spracovanie bloku prvých 16-tich znakov od začiatku textu

# --- kód na vysvetlenie ---
# pre lepšiu názornosť nie s tenzormi, ale s textom
blok = 16
tx1 = text[:blok]
tx2 = text[1:blok+1]
for t in range(blok):
    vstup = tx1[:t+1]
    výstup = tx2[t]
    print(f"pre {vstup} je výstup: {výstup}")
 
---výpis---
pre R je výstup: á
pre Rá je výstup: n
pre Rán je výstup: o
pre Ráno je výstup: 
pre Ráno  je výstup: s
pre Ráno s je výstup: a
pre Ráno sa je výstup: 
pre Ráno sa  je výstup: k
pre Ráno sa k je výstup: o
pre Ráno sa ko je výstup: t
pre Ráno sa kot je výstup: o
pre Ráno sa koto je výstup: l
pre Ráno sa kotol je výstup: n
pre Ráno sa kotoln je výstup: í
pre Ráno sa kotolní je výstup: k
pre Ráno sa kotolník je výstup: 

Neurónová sieť bude toto isté robiť s tenzormi

# --- kód na vysvetlenie ---
# s tenzorom číselných hodnôt
blok = 16
td1 = train_data[:blok]
td2 = train_data[1:blok+1]
for t in range(blok):
    vstup = td1[:t+1]
    výstup = td2[t]
    print(f"pre {vstup} je výstup: {výstup}")
 
---výpis---
re tensor([24]) je výstup: 63
pre tensor([24, 63]) je výstup: 46
pre tensor([24, 63, 46]) je výstup: 47
pre tensor([24, 63, 46, 47]) je výstup: 0
pre tensor([24, 63, 46, 47,  0]) je výstup: 51
pre tensor([24, 63, 46, 47,  0, 51]) je výstup: 33
...

Kód na načítanie dávky tréningových, alebo testovacích údajov

# načítanie dávky vstupných a výstupných údajov začiatok dávky ix náhodne
def nacitanie_davky(split):
    temp_data = train_data if split == 'train' else test_data
    ix = torch.randint(len(temp_data) - block_size, (batch_size,)) #náhodná poloha v texte
    x = torch.stack([temp_data[i:i+block_size] for i in ix])      #vstupy
    y = torch.stack([temp_data[i+1:i+block_size+1] for i in ix])  #výstupy
    x, y = x.to(device), y.to(device)
    return x, y

Pre názornosť jednorozmerné tenzory usporiadame do riadkov. Získame štruktúru 4 x 8 čiže 4 riadky s ôsmimi stĺpcami. Vstup X sú tenzory 4 x 8, pričom každý z nich je kúskom tréningovej sady.

Ciele sú v pridruženom poli Y.  Poskytnú správnu odpoveď pre každú pozíciu vo vnútri vstupov X Vstupy X a Y sú zavedené na spracovanie do NS aby vytvorili stratovú funkciu. V každom riadku je 8 prípadov, takže dávka so štruktúrou 4 x 8 obsahuje celkom 32 prípadov na trénovanie, ktoré sú úplne nezávislé.

torch.manual_seed(1337)  #inicializácia generátora náhodných čísel
batch_size = 4   # počet paralelne spracovávaných sekvencií
block_size = 8   # maximálna dĺžka kontextu pre predikciu
 
# --- kód na vysvetlenie ---
xb, yb = nacitanie_davky('train')
print('vstupy:')
print(xb.shape)
print(xb)
print('ciele:')
print(yb.shape)
print(yb)
 
---výpis---
vstupy:
torch.Size([4, 8])
tensor([[75, 53, 44, 41,  0,  8, 44, 37],
        [58, 33,  0, 23, 47, 54, 37, 36],
        [88,  0, 12, 50, 90, 41, 43, 53],
        [50, 41, 43, 57,  0, 28,  0, 53]], device='cuda:0')
ciele:
torch.Size([4, 8])
tensor([[53, 44, 41,  0,  8, 44, 37,  0],
        [33,  0, 23, 47, 54, 37, 36, 33],
        [ 0, 12, 50, 90, 41, 43, 53,  0],
        [41, 43, 57,  0, 28,  0, 53, 48]], device='cuda:0')

Ďalší kód uvedieme zatiaľ bez podrobného vysvetlenia, budeme ho vysvetľlovať v ďalších dieloch. Podrobne je všetko vysvetlil Andrej Karpathy vo videu Let's build GPT: from scratch, in code, spelled out

My to budeme v ďalších dieloch seriálu preberať viac „polopatisticky“. Kód odporúčam spúšťať po blokoch. Pytorch očakáva multidimenzionálny vstup, ktorý má dimenzie B (Batch) x T (Time) x C (Channel).

@torch.no_grad()
def estimate_loss():
    out = {}
    model.eval()
    for split in ['train', 'val']:
        losses = torch.zeros(eval_iters)
        for k in range(eval_iters):
            X, Y = nacitanie_davky(split)
            logits, loss = model(X, Y)
            losses[k] = loss.item()
        out[split] = losses.mean()
    model.train()
    return out
 
# one head of self-attention
class Head(nn.Module):
    def __init__(self, head_size):
        super().__init__()
        self.key = nn.Linear(n_embd, head_size, bias=False)
        self.query = nn.Linear(n_embd, head_size, bias=False)
        self.value = nn.Linear(n_embd, head_size, bias=False)
        self.register_buffer('tril', torch.tril(torch.ones(block_size, block_size)))
 
        self.dropout = nn.Dropout(dropout)
 
    def forward(self, x):
        # input of size (batch, time-step, channels)
        # output of size (batch, time-step, head size)
        B,T,C = x.shape
        k = self.key(x)   # (B,T,hs)
        q = self.query(x) # (B,T,hs)
        # compute attention scores ("affinities")
        wei = q @ k.transpose(-2,-1) * k.shape[-1]**-0.5 # (B, T, hs) @ (B, hs, T) -> (B, T, T)
        wei = wei.masked_fill(self.tril[:T, :T] == 0, float('-inf')) # (B, T, T)
        wei = F.softmax(wei, dim=-1) # (B, T, T)
        wei = self.dropout(wei)
        # perform the weighted aggregation of the values
        v = self.value(x) # (B,T,hs)
        out = wei @ v # (B, T, T) @ (B, T, hs) -> (B, T, hs)
        return out

Multihead self Attention umožňuje paralelné použitie viacerých „hláv“ a zreťazenie ich výsledkov. Namiesto jedného komunikačného kanála máme teraz štyri komunikačné kanály paralelne Takže, máme 4 komunikačné kanály a z každého komunikačného kanála máme 8-rozmerné vektory.

Každý neurón vie, aký obsah má, a vie, na akej pozícii sa nachádza. Na základe toho vytvorí dotaz napríklad: „ som samohláska. Som na ôsmej pozícii. Hľadám akékoľvek spoluhlásky na pozíciách 1 až 4“. A potom všetky uzly začnú vysielať kľúče a možno by to mohol byť jeden z kanálov, napríklad  „som spoluhláska a som na pozícii do 4. Takto sa dopyt a kľúč, môžu navzájom nájsť a vytvoriť vysokú afinitu. Komunikačný mechanizmus si môžeme predstaviť ako niekoľko uzlov v orientovanom grafe. Každý uzol má nejaký vektor informácií a dostane sa k agregácii informácií prostredníctvom váženého súčtu zo všetkých uzlov, ktoré naň poukazujú. Tokeny cez komunikačné kanály chcú nájsť spoluhlásky, samohlásky, ktoré chcú nájsť samohlásky len z určitých pozícií. Cez viacero nezávislých komunikačných kanálov model zhromažďuje množstvo rôznych typov údajov.

# multiple heads of self-attention in parallel
class MultiHeadAttention(nn.Module):
    def __init__(self, num_heads, head_size):
        super().__init__()
        self.heads = nn.ModuleList([Head(head_size) for _ in range(num_heads)])
        self.proj = nn.Linear(head_size * num_heads, n_embd)
        self.dropout = nn.Dropout(dropout)
 
    def forward(self, x):
        out = torch.cat([h(x) for h in self.heads], dim=-1)
        out = self.dropout(self.proj(out))
        return out

Lineárna dopredná vrstva

# a simple linear layer followed by a non-linearity
class FeedFoward(nn.Module):
    def __init__(self, n_embd):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(n_embd, 4 * n_embd),
            nn.ReLU(),
            nn.Linear(4 * n_embd, n_embd),
            nn.Dropout(dropout),
        )
 
    def forward(self, x):
        return self.net(x)
 
# Transformer block: communication followed by computation
class Block(nn.Module):
    def __init__(self, n_embd, n_head):
        # n_embd: embedding dimension, n_head: the number of heads we'd like
        super().__init__()
        head_size = n_embd // n_head
        self.sa = MultiHeadAttention(n_head, head_size)
        self.ffwd = FeedFoward(n_embd)
        self.ln1 = nn.LayerNorm(n_embd)
        self.ln2 = nn.LayerNorm(n_embd)
 
    def forward(self, x):
        x = x + self.sa(self.ln1(x))
        x = x + self.ffwd(self.ln2(x))
        return x

Vytvoríme model neurónovej siete, ktorá je vhodná pre jazykové modelovanie. Bude to trieda odvodená od NN.Module knižnice Pytorch. Modelu odovzdávame vstupy a ciele. V konštruktore vytvárame tabuľku na vkladanie tokenov. Na meranie kvality predpovedí využijeme negatívnu logaritmickú stratu pravdepodobnosti, ktorá je tiež implementovaná v Pytorch pod názvom Cross Entropy, čiže krížová entropia. Aplikujeme ju na predpovede a ciele, čiže vyhodnocujeme kvalitu predpovedí vzhľadom na ciele.  

# ===  Model neurónovej siete ====
class GPTLanguageModel(nn.Module):
 
    def __init__(self):
        super().__init__()
        # each token directly reads off the logits for the next token from a lookup table
        self.token_embedding_table = nn.Embedding(unikatnych_znakov, n_embd)
        self.position_embedding_table = nn.Embedding(block_size, n_embd)
        self.blocks = nn.Sequential(*[Block(n_embd, n_head=n_head) for _ in range(n_layer)])
        self.ln_f = nn.LayerNorm(n_embd) # final layer norm
        self.lm_head = nn.Linear(n_embd, unikatnych_znakov)
 
        # better init, not covered in the original GPT video, but important, will cover in followup video
        self.apply(self._init_weights)
 
    def _init_weights(self, module):
        if isinstance(module, nn.Linear):
            torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
            if module.bias is not None:
                torch.nn.init.zeros_(module.bias)
        elif isinstance(module, nn.Embedding):
            torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
 
    def forward(self, idx, targets=None):
        B, T = idx.shape
 
        # idx and targets are both (B,T) tensor of integers
        tok_emb = self.token_embedding_table(idx) # (B,T,C)
        pos_emb = self.position_embedding_table(torch.arange(T, device=device)) # (T,C)
        x = tok_emb + pos_emb # (B,T,C)
        x = self.blocks(x) # (B,T,C)
        x = self.ln_f(x) # (B,T,C)
        logits = self.lm_head(x) # (B,T,vocab_size)
 
        if targets is None:
            loss = None
        else:
            B, T, C = logits.shape
            logits = logits.view(B*T, C)
            targets = targets.view(B*T)
            loss = F.cross_entropy(logits, targets)
 
        return logits, loss
 
    def generate(self, idx, max_new_tokens):
        # idx is (B, T) array of indices in the current context
        for _ in range(max_new_tokens):
            # crop idx to the last block_size tokens
            idx_cond = idx[:, -block_size:]
            # get the predictions
            logits, loss = self(idx_cond)
            # focus only on the last time step
            logits = logits[:, -1, :] # becomes (B, C)
            # apply softmax to get probabilities
            probs = F.softmax(logits, dim=-1) # (B, C)
            # sample from the distribution
            idx_next = torch.multinomial(probs, num_samples=1) # (B, 1)
            # append sampled index to the running sequence
            idx = torch.cat((idx, idx_next), dim=1) # (B, T+1)
        return idx

Na aktualizáciu parametrov použijeme optimalizátor AdamW, ktorý na tento typ úloh funguje veľmi dobre a typické nastavenie pre rýchlosť učenia je 0,0001. Veľkosť dávky je 64 a veľkosť bloku 256 znakov. Inak povedané 257 znaku je k dispozícii 256 znakov kontextu. Rozmer vloženia je 384 a je tam 6 hláv, takže 384 delí. 6 znamená, že každá hlava je štandardne 64 rozmerná.  Ak nemáte GPU, nebudete to môcť v rozumnom čase spustiť na CPU. Aby to na CPU zbehlo v rozumnom čase budete musieť znížiť počet vrstiev a rozmer vloženia atď.

# globálne parametre
batch_size = 64         # počet paralelne spracovávaných sekvencií
block_size = 256        # maximálna dĺžka kontextu pre predikciu (slovo ako blok znakov)
max_iters = 5000        # počet iterácií
eval_interval = 500
learning_rate = 3e-4
 
eval_iters = 200
n_embd = 384
n_head = 6
n_layer = 6
dropout = 0.2
 
model = GPTLanguageModel()
m = model.to(device)
# priebežný výpis trénovania
print(sum(p.numel() for p in m.parameters())/1e6, 'M parameters')
 
# PyTorch optimizer
optimizer = torch.optim.AdamW(model.parameters(), lr=learning_rate)
 
---výpis---
10.808923 M parameters

Kód na spustenie trénovania, na konci vypíše dobu trvania

#trénovanie pre daný počet iterácií
 
start_time = time.time()
for iter in range(max_iters):
 
    # výpis aktuálnych hodnôt každých 500 iterácií
    if iter % eval_interval == 0 or iter == max_iters - 1:
        losses = estimate_loss()      
        print(f"step {iter}: train loss {losses['train']:.4f}, val loss {losses['val']:.4f}")
 
    # načítasnie dávky údajov z háhodného miesta textu
    xb, yb = nacitanie_davky('train')
 
    # evaluate the loss
    logits, loss = model(xb, yb)
    optimizer.zero_grad(set_to_none=True)
    loss.backward()
    optimizer.step()
print("Trénovanie neurónovej siete trvalo {} minút".format(round((start_time - time.time())/60),2))
 
---výpis---
step 0: train loss 4.6792, val loss 4.6759
step 500: train loss 2.0566, val loss 2.0886
step 1000: train loss 1.5786, val loss 1.7503
step 1500: train loss 1.3470, val loss 1.6788
step 2000: train loss 1.1496, val loss 1.6840
step 2500: train loss 0.9729, val loss 1.7388
step 3000: train loss 0.7987, val loss 1.8176
step 3500: train loss 0.6398, val loss 1.9411
step 4000: train loss 0.5021, val loss 2.0530
step 4500: train loss 0.3823, val loss 2.1744
step 4999: train loss 0.2993, val loss 2.3039
Trénovanie neurónovej siete trvalo -18 minút

Trénovali sme na výkonnej grafickej karte NVIDIA RTX A5000

Ak v texte, ktorý sme použili na trénovanie neurónovej siete máme 91 možných znakov takže môžeme približne vypočítať chybu podľa vzorca ln (1/91), kde ln je prirodzený logaritmus. V našom prípade -4,51. A očakávame chybu približne 4,51, ale dostávame napríklad 4,9 znamená to, že počiatočné predpovede sú príliš rozptýlené. Takže zatiaľ odhadujeme nepresne a vygenerovaný text budú tvoriť  náhodne usporiadané znaky, respektíve zhluky znakov, nakoľko v množine znakov je aj medzera. Ak hodnota chyby klesá je to znak, že optimalizácia prebieha. Ak sa dostaneme na hodnotu 2,5 výsledok stále nebude dávať zmysel, ale bude to dramatické zlepšenie v porovnaní s netrénovaným modelom.

A napokon kód na aktivovanie predpovedí, čiže generovanie textu

# generovanie textu
context = torch.zeros((1, 1), dtype=torch.long, device=device)
print(cisla_na_text(m.generate(context, max_new_tokens=500)[0].tolist()))
 
---výpis---
na Silviintou dúfať Už sa cíti až hnus k trýždňou ako by ste trazz mastnav kych nič nepríde Novanie zdvihnutou hotelovým kotlom Rozhodol sa znášali kriku Za tmichu s nami Volá sa dlho kvetmi smelo vložili a tri Znežnosti hostia do trieľa Vriedkej la žiste Silvia sa usmieva Onlec by predtým tvoji pozrieť na vidie v aute Potom čase obchodec držia v dlanom svojich Je skladnutý teminforké preletívne zredvede vysetlí ventil a oni majú ho tedaté ko šéfkuchár Po šťastnej strane nosiliace za sebou oženo

Po každom spustení bude vygenerovaný iný text

Rekapitulácia doterajších dielov

ML v Pythone 12 – vytvorenie a natrénovanie neurónovej siete ktorá rozozná či je huba jedlá

ML v Pythone 12 – neurónová sieť predpovedá výskyt cukrovky u indiánskeho kmeňa

ML v Pythone 11 – vytvorenie a učenie neurónovej siete na generovanie poviedok

ML v Pythone 10 – trénovanie neurónovej siete, príprava

ML v Pythone 9 – trénovanie neurónu

ML v Pythone 8 – príklad rozpoznávanie obrazu

ML v Pythone 7 – Využitie grafickej karty NVIDIA na výpočtovo náročné úlohy

ML v Pythone 6 – animované grafy a ich export ako video

ML v Pythone 5 – vizualizácia údajov pomocou grafov

Strojové učenie v Pythone 4 – práca s údajmi

ML v Pythone 3 – export a import údajov vo formáte CSV a Excel

Strojové učenie v Pythone 2 – knižnica Pandas na prácu s údajmi

Strojové učenie v Pythone 1 – prostredie Google Colab

 

Zobrazit Galériu

Luboslav Lacko

Všetky autorove články
Python ChatGPT neuronova siet strojove ucenie machine learning

Pridať komentár

Mohlo by vás zaujímať

Mohlo by vás zaujímať