Faza 05 · lecția 04

GloVe, FastText și reprezentări vectoriale de subcuvinte

Scopul lecției: Word2Vec antrena o reprezentare vectorială per cuvânt. GloVe factoriza matricea de coapariții. FastText reprezenta piesele. BPE a făcut legătura către Transformere.

Versiunea curentă AlexBred.com: primele 100 de lecții ale programului în limba română.

Curs
AI Engineering from Scratch
Fază
NLP — de la fundamente la subiecte avansate
Lectură
15 min.
Verificat
Cuprinsul lecției
  1. Problema
  2. Conceptul
  3. Construiți
  4. GloVe: factorizați matricea de coapariții
  5. FastText: reprezentări vectoriale conștiente de subcuvinte
  6. BPE: vocabular de subcuvinte învățat
  7. Folosiți
  8. Când alegeți fiecare opțiune
  9. Livrați
  10. Exerciții
  11. Termeni-cheie
  12. Lecturi suplimentare

Word2Vec antrena o reprezentare vectorială per cuvânt. GloVe factoriza matricea de coapariții. FastText reprezenta piesele. BPE a făcut legătura către Transformere.

Tip: Construire Limbaje: Python Cerințe prealabile: Faza 5 · 03 (Word2Vec de la zero) Timp: ~45 de minute

Problema

Word2Vec a lăsat două întrebări deschise.

Mai întâi, exista o direcție paralelă de cercetare care factoriza direct matricea de coapariții (LSA, HAL), în loc să efectueze actualizări skip-gram online. Era abordarea iterativă a Word2Vec fundamental mai bună sau diferența era un artefact al modului în care cele două metode tratau numărările? GloVe a răspuns: factorizarea matricei, cu o funcție de pierdere aleasă cu grijă, egalează sau depășește Word2Vec și costă mai puțin la antrenare.

În al doilea rând, niciuna dintre metode nu oferea o soluție pentru cuvintele nevăzute. Zoomer-approved, dogecoin, orice nume propriu inventat săptămâna trecută, fiecare formă flexionată a unei rădăcini rare. FastText a rezolvat aceasta prin reprezentări vectoriale ale n-gramelor de caractere: un cuvânt este suma părților sale, inclusiv a morfemelor, astfel încât chiar și cuvintele din afara vocabularului primesc un vector rezonabil.

În al treilea rând, odată cu Transformerele, întrebarea s-a schimbat din nou. Vocabularul de cuvinte întregi se plafonează la circa un milion de intrări; limbajul real este mai deschis decât atât. Codificarea de perechi de octeți (byte-pair encoding, BPE) și rudele sale au rezolvat problema învățând un vocabular de unități frecvente de subcuvinte care acoperă totul. Fiecare tokenizer modern pentru fiecare LLM modern este un tokenizer de subcuvinte.

Această lecție parcurge toate cele trei abordări, apoi explică ce să alegeți în fiecare situație.

Notă tehnică a traducerii: Comparațiile dintre GloVe și Word2Vec nu sunt universal valabile: rezultatul și costul depind de corpus, vocabular, obiectiv, implementare, hardware și protocolul de evaluare. Lucrarea GloVe raportează rezultate competitive în benchmarkurile sale, nu o superioritate garantată pentru orice problemă. Consultați lucrarea originală GloVe.

Conceptul

GloVe (vectori globali). Construiți matricea de coapariții cuvânt–cuvânt X, unde X[i][j] arată de câte ori cuvântul j apare în contextul cuvântului i. Antrenați vectori astfel încât v_i · v_j + b_i + b_j ≈ log(X[i][j]). Ponderați pierderea, astfel încât perechile frecvente să nu o domine. Gata.

FastText. Un cuvânt este suma n-gramelor sale de caractere plus cuvântul însuși. where devine <wh, whe, her, ere, re>, <where>. Vectorul cuvântului este suma vectorilor acestor componente. Antrenați ca Word2Vec. Avantaj: cuvintele nevăzute (whereupon) se compun din n-grame cunoscute.

BPE (byte-pair encoding). Porniți de la un vocabular de octeți individuali (sau caractere). Numărați fiecare pereche adiacentă din corpus. Uniți cea mai frecventă pereche într-un token nou. Repetați timp de k iterații. Rezultatul este un vocabular de k + 256 tokenuri, în care secvențele frecvente (ing, tion, the) sunt tokenuri unice, iar cuvintele rare se sparg în piese familiare. Fiecare propoziție se tokenizează într-un fel sau altul.

Ilustrație originală: comparație între GloVe, FastText și BPE

Ilustrație originală din assets/embeddings.svg al lecției-sursă: compară factorizarea GloVe, n-gramele FastText și vocabularul BPE învățat. SVG-ul este păstrat fără modificări; etichetele sale originale în limba engleză sunt explicate de textul românesc din lecție.

Notă tehnică a traducerii: Trebuie distins BPE la nivel de octet de exemplul de cod din lecție. BPE la nivel de octet pornește de la 256 de valori de octet și poate reprezenta orice șir ca octeți; de aici provin afirmațiile despre k + 256 și absența OOV la nivel de octet. learn_bpe de mai jos pornește însă de la caractere Python și markerul </w>, deci este un exemplu de BPE la nivel de caracter; nu are automat aceste proprietăți pentru orice intrare Unicode. Consultați rezumatul Hugging Face despre tokenizatoare.

Construiți

GloVe: factorizați matricea de coapariții

import numpy as np
from collections import Counter


def build_cooccurrence(docs, window=5):
    pair_counts = Counter()
    vocab = {}
    for doc in docs:
        for token in doc:
            if token not in vocab:
                vocab[token] = len(vocab)
    for doc in docs:
        indexed = [vocab[t] for t in doc]
        for i, center in enumerate(indexed):
            for j in range(max(0, i - window), min(len(indexed), i + window + 1)):
                if i != j:
                    distance = abs(i - j)
                    pair_counts[(center, indexed[j])] += 1.0 / distance
    return vocab, pair_counts


def glove_train(vocab, pair_counts, dim=16, epochs=100, lr=0.05, x_max=100, alpha=0.75, seed=0):
    n = len(vocab)
    rng = np.random.default_rng(seed)
    W = rng.normal(0, 0.1, size=(n, dim))
    W_tilde = rng.normal(0, 0.1, size=(n, dim))
    b = np.zeros(n)
    b_tilde = np.zeros(n)

    for epoch in range(epochs):
        for (i, j), x_ij in pair_counts.items():
            weight = (x_ij / x_max) ** alpha if x_ij < x_max else 1.0
            diff = W[i] @ W_tilde[j] + b[i] + b_tilde[j] - np.log(x_ij)
            coef = weight * diff

            grad_W_i = coef * W_tilde[j]
            grad_W_tilde_j = coef * W[i]
            W[i] -= lr * grad_W_i
            W_tilde[j] -= lr * grad_W_tilde_j
            b[i] -= lr * coef
            b_tilde[j] -= lr * coef

    return W + W_tilde

Două piese mobile merită numite. Funcția de ponderare f(x) = (x/x_max)^alpha micșorează ponderea perechilor foarte frecvente (precum (the, and)), astfel încât acestea să nu domine pierderea. Reprezentarea vectorială finală este suma tabelelor W (central) și W_tilde (context). Însumarea ambelor este un artificiu publicat care tinde să depășească folosirea unui singur tabel.

Notă tehnică a traducerii: Exemplul este un SGD didactic rar, nu o implementare GloVe de producție. build_cooccurrence acumulează numărări ponderate cu inversul distanței, iar optimizarea omite, între altele, amestecarea perechilor și AdaGrad din implementarea de referință. Factorul constant al gradientului poate fi absorbit în rata de învățare, dar nu comparați direct hiperparametrii sau rezultatele cu implementarea oficială fără protocol identic. Consultați lucrarea GloVe.

FastText: reprezentări vectoriale conștiente de subcuvinte

def char_ngrams(word, n_min=3, n_max=6):
    wrapped = f"<{word}>"
    grams = {wrapped}
    for n in range(n_min, n_max + 1):
        for i in range(len(wrapped) - n + 1):
            grams.add(wrapped[i:i + n])
    return grams
>>> char_ngrams("where")
{'<where>', '<wh', 'whe', 'her', 'ere', 're>', '<whe', 'wher', 'here', 'ere>', '<wher', 'where', 'here>'}

Fiecare cuvânt este reprezentat prin setul său de n-grame (de obicei 3–6 caractere). Reprezentarea vectorială a cuvântului este suma reprezentărilor vectoriale ale n-gramelor sale. Pentru antrenarea skip-gram, introduceți aceasta în locul în care Word2Vec folosea un singur vector.

def fasttext_vector(word, ngram_table):
    grams = char_ngrams(word)
    vecs = [ngram_table[g] for g in grams if g in ngram_table]
    if not vecs:
        return None
    return np.sum(vecs, axis=0)

Pentru un cuvânt nevăzut, obțineți totuși un vector atâta timp cât unele n-grame sunt cunoscute. whereupon are în comun <wh, her, ere și <where cu where, astfel încât cele două ajung aproape unul de celălalt.

Notă tehnică a traducerii: FastText-ul original folosește n-grame de caractere cu marcaje de frontieră și le mapează într-un spațiu hash; codul de mai sus păstrează explicit șiruri Python și nu reproduce hashingul, toate detaliile Unicode sau obiectivul complet FastText. OOV poate primi un vector numai dacă n-gramele sale au reprezentări disponibile. Consultați lucrarea FastText și documentația FastText.

BPE: vocabular de subcuvinte învățat

def learn_bpe(corpus, k_merges):
    vocab = Counter()
    for word, freq in corpus.items():
        tokens = tuple(word) + ("</w>",)
        vocab[tokens] = freq

    merges = []
    for _ in range(k_merges):
        pair_freq = Counter()
        for tokens, freq in vocab.items():
            for a, b in zip(tokens, tokens[1:]):
                pair_freq[(a, b)] += freq
        if not pair_freq:
            break
        best = pair_freq.most_common(1)[0][0]
        merges.append(best)

        new_vocab = Counter()
        for tokens, freq in vocab.items():
            new_tokens = []
            i = 0
            while i < len(tokens):
                if i + 1 < len(tokens) and (tokens[i], tokens[i + 1]) == best:
                    new_tokens.append(tokens[i] + tokens[i + 1])
                    i += 2
                else:
                    new_tokens.append(tokens[i])
                    i += 1
            new_vocab[tuple(new_tokens)] = freq
        vocab = new_vocab
    return merges


def apply_bpe(word, merges):
    tokens = list(word) + ["</w>"]
    for a, b in merges:
        new_tokens = []
        i = 0
        while i < len(tokens):
            if i + 1 < len(tokens) and tokens[i] == a and tokens[i + 1] == b:
                new_tokens.append(a + b)
                i += 2
            else:
                new_tokens.append(tokens[i])
                i += 1
        tokens = new_tokens
    return tokens
>>> corpus = Counter({"low": 5, "lower": 2, "newest": 6, "widest": 3})
>>> merges = learn_bpe(corpus, k_merges=10)
>>> apply_bpe("lowest", merges)
['low', 'est</w>']

Prima iterație unește perechea adiacentă cea mai frecventă. După suficiente iterații, subșirurile frecvente (low, est, tion) devin tokenuri unice, iar cuvintele rare se sparg curat.

Notă tehnică a traducerii: Rezultatul acestui exemplu depinde de corpus, ordine și de rezolvarea frecvențelor egale; nu este un format de tokenizer interschimbabil cu GPT-2, BERT sau T5. Implementările reale stochează și aplică reguli, vocabular, normalizare și tokenuri speciale exacte. Păstrați întregul artefact al tokenizerului împreună cu modelul.

Tokenizer-ele reale GPT / BERT / T5 învață 30k–100k de îmbinări. Rezultatul: orice text se tokenizează într-o secvență de lungime mărginită de ID-uri cunoscute, fără OOV vreodată.

Notă tehnică a traducerii: Dimensiunile vocabularului, algoritmii și garanțiile OOV diferă între modele. GPT-2 folosește BPE la nivel de octet, BERT WordPiece, iar T5 SentencePiece; nu toate tokenizatoarele moderne sunt variante BPE în sens strict. „Fără OOV” trebuie precizat după nivelul de reprezentare și după politica pentru caractere neacoperite. Consultați rezumatul Hugging Face despre tokenizatoare.

Folosiți

În practică, rareori antrenați vreuna dintre acestea de la zero. Încărcați checkpointuri preantrenate.

import fasttext.util
fasttext.util.download_model("en", if_exists="ignore")
ft = fasttext.load_model("cc.en.300.bin")
print(ft.get_word_vector("whereupon").shape)
print(ft.get_word_vector("zoomerapproved").shape)

Pentru tokenizarea de subcuvinte în epoca Transformerelor:

from transformers import AutoTokenizer

tok = AutoTokenizer.from_pretrained("gpt2")
print(tok.tokenize("unbelievably tokenized"))
['un', 'bel', 'iev', 'ably', 'Ġtoken', 'ized']

Prefixul Ġ marchează limitele de cuvinte (o convenție GPT-2). Fiecare tokenizer modern este o variantă BPE, WordPiece (BERT) sau SentencePiece (T5, LLaMA).

Notă tehnică a traducerii: Descărcarea FastText, numele fișierului, modelul și licența trebuie verificate pentru versiunea folosită; pregătiți artefactele înaintea unei rulări offline sau de producție. Pentru AutoTokenizer, rezultatului îi pot influența revizia modelului, versiunea bibliotecii și fișierele tokenizerului. Încărcați tokenizerul livrat cu checkpointul ales, fixați revizia și testați ID-urile, nu doar șirurile afișate. Consultați documentația FastText și API-ul Hugging Face pentru tokenizatoare.

Când alegeți fiecare opțiune

Situație Alegeți
Reprezentări vectoriale de cuvinte preantrenate, de uz general, fără nevoie de toleranță OOV GloVe 300d
Reprezentări vectoriale de cuvinte preantrenate, de uz general, care trebuie să gestioneze greșeli de scriere / neologisme / limbi bogate morfologic FastText
Orice intră într-un Transformer (antrenare sau inferență) Tokenizerul cu care a fost livrat modelul. Nu îl înlocuiți niciodată.
Antrenați propriul model de limbaj de la zero Antrenați mai întâi un tokenizer BPE sau SentencePiece pe corpusul propriu
Clasificare de text în producție cu un model liniar Tot TF-IDF. Lecția 02.

Notă tehnică a traducerii: Tabelul oferă euristici, nu selecții garantate. Un tokenizer nu se schimbă pentru un checkpoint fără reantrenare sau evaluare riguroasă, deoarece ID-urile și embeddingurile nu se mai aliniază; totuși, un nou model poate fi antrenat sau ajustat cu alt tokenizer. Alegerea dintre TF-IDF, reprezentări statice și Transformere depinde de date, latență, memorie, limbă, metrică și validare.

Livrați

Salvați ca outputs/skill-embeddings-picker.md:

---
name: tokenizer-picker
description: Alege o abordare de tokenizare pentru un nou model de limbaj sau flux de procesare a textului.
version: 1.0.0
phase: 5
lesson: 04
tags: [nlp, tokenization, embeddings]
---

Pentru o sarcină și o descriere a setului de date, produceți:

1. Strategia de tokenizare (la nivel de cuvânt, BPE, WordPiece, SentencePiece, la nivel de octet). Un motiv într-o propoziție.
2. Dimensiunea-țintă a vocabularului (de exemplu, 32k pentru un LM numai în engleză, 64k–100k pentru unul multilingv).
3. Apelul de bibliotecă cu comanda exactă de antrenare. Numiți biblioteca. Indicați argumentele.
4. Un risc pentru reproductibilitate. Nepotrivirea tokenizer–model este cel mai frecvent bug silențios din producție; precizați ce pereche trebuie folosită împreună.

Refuzați să recomandați antrenarea unui tokenizer personalizat când utilizatorul face fine-tuning pe un LLM preantrenat. Refuzați să recomandați tokenizarea la nivel de cuvânt pentru orice model destinat inferenței în producție. Semnalați corpusurile non-engleze / cu mai multe sisteme de scriere ca necesitând SentencePiece cu rezervă de siguranță la nivel de octet.

Notă tehnică a traducerii: Numerele 32k și 64k–100k sunt puncte de plecare, nu ținte universale, iar „cel mai frecvent” nu este un clasament demonstrat pentru orice producție. Fine-tuning-ul necesită în mod normal tokenizerul checkpointului, însă unele fluxuri adaugă tokenuri sau reantrenează modelul cu evaluare corespunzătoare. SentencePiece nu activează automat rezerva la nivel de octet în orice configurație; verificați opțiunea byte_fallback și acoperirea Unicode ale tokenizerului concret.

Exerciții

  1. Ușor. Rulați char_ngrams("playing") și char_ngrams("played"). Calculați suprapunerea Jaccard a celor două seturi de n-grame. Ar trebui să vedeți multe piese comune (pla, lay, play), motiv pentru care FastText transferă bine între variante morfologice.
  2. Mediu. Extindeți learn_bpe pentru a urmări creșterea vocabularului. Reprezentați tokenurile per caracter din corpus ca funcție de numărul de îmbinări. Ar trebui să vedeți la început o comprimare rapidă, care se apropie asimptotic de circa 2–3 caractere per token.
  3. Dificil. Antrenați un BPE cu 1k de îmbinări pe operele complete ale lui Shakespeare. Comparați tokenizarea cuvintelor frecvente cu a numelor proprii rare. Măsurați numărul mediu de tokenuri per cuvânt înainte și după. Scrieți ce v-a surprins.

Notă tehnică a traducerii: Suprapunerea n-gramelor și raportul caractere/token depind de limbă, corpus, preprocesare, algoritm, vocabular și metrică; nu există un prag universal de 2–3. Evaluați atât comprimarea, cât și efectele asupra lungimii secvenței, memoriei, ratei OOV și calității sarcinii.

Termeni-cheie

Termen Ce spun oamenii Ce înseamnă de fapt
Matrice de coapariții Tabel de frecvențe cuvânt–cuvânt X[i][j] = de câte ori cuvântul j apare într-o fereastră în jurul cuvântului i.
Subcuvânt Bucată dintr-un cuvânt Un n-gram de caractere (FastText) sau un token învățat (BPE/WordPiece/SentencePiece).
BPE Codificare de perechi de octeți Îmbinarea iterativă a celor mai frecvente perechi adiacente până când vocabularul atinge dimensiunea-țintă.
OOV În afara vocabularului Cuvânt pe care modelul nu l-a văzut niciodată. Word2Vec/GloVe eșuează. FastText și BPE îl gestionează.
BPE la nivel de octet BPE pe octeți bruti Schema GPT-2. Vocabularul pornește de la 256 de octeți, astfel încât nimic nu este vreodată OOV.

Notă tehnică a traducerii: OOV este o proprietate a reprezentării și a preprocesării, nu o etichetă absolută pentru o bibliotecă. Word2Vec sau GloVe pot folosi un token necunoscut, dar nu compun implicit un vector specific unui cuvânt nou; FastText compune doar din n-grame disponibile. În BPE la nivel de octet, orice text poate fi codificat ca octeți, dar pot rămâne politici pentru tokenuri speciale și normalizare.

Lecturi suplimentare

Sursă: Originalul în limba engleză

Navigare: ← Lecția 05.03 — Reprezentări vectoriale ale cuvintelor — Word2Vec de la zero · Faza 5 — NLP: de la fundamente la subiecte avansate · Lecția 05.05 — Analiza sentimentului → · Catalog complet