BERT vysvětlil: Architektura, příprava a jemné ladění
Rok 2018 znamenal zlom v historii zpracování přirozeného jazyka. Se zveřejněním BERT (Obousměrné reprezentace kodéru od Transformers), má tým Google AI představila model, který redefinoval současný stav techniky na 11 benchmarkech NLP současně. Pro Poprvé mohl být jeden předem vyškolený model přizpůsoben velmi odlišným úkolům (klasifikace, zodpovězení otázek, NER) dosahující lepších výsledků než všechny specializované systémy precedenty.
Ale co dělá BERT tak revolučním? Odpověď spočívá ve třech zásadních inovacích: a hluboká obousměrnost, The rozsáhlé předškolní přípravy a jednoduchost dolaďování pro následné úkoly. V tomto článku budeme analyzovat každý aspekt architektury BERT, od mechanismů vnitřní pozornosti až po implementaci cvičit s HuggingFace a procházet variacemi, které jsou z něj odvozeny.
Toto je druhý článek ze série Moderní NLP: od BERT po LLM. Pokud jste ještě nečetli první článek o základech (tokenizace, vkládání a kanály NLP), Radím vám, abyste to udělali předtím, než budete pokračovat: mnoho zde uvedených konceptů je vytvořeno na těch základech.
Co se naučíte
- proč BERT představoval revoluci v NLP a omezení předchozích modelů
- Architektura pouze kodéru Transformer, která je základem BERT
- Jak funguje vícehlavá sebepozornost a matematické vzorce
- Vstupní reprezentace: vložení tokenu, segmentu a pozice
- Dvě předtréninkové strategie: Masked Language Model (MLM) a Next Sentence Prediction (NSP)
- Tokenizace WordPiece a speciální tokeny [CLS], [SEP], [MASK]
- Jak provést jemné doladění klasifikace, NER, zodpovězení otázek
- Kompletní praktická implementace s HuggingFace Transformers
- Varianty BERT: RoBERTa, ALBERT, DistilBERT, DeBERTa, ELECTRA
- BERT pro italský jazyk: dostupné modely a srovnání
- Limity BERT a jak je pozdější modely překonaly
Přehled série
| # | Položka | Soustředit |
|---|---|---|
| 1 | Základy NLP | Tokenizace, vkládání, potrubí |
| 2 | Jste zde – BERT a Transformer | Pozornost Architektura, Pre-školení |
| 3 | Analýza sentimentu | Klasifikace textu pomocí BERT |
| 4 | Rozpoznávání pojmenované entity | Extrakce entit z textu |
| 5 | Transformátory HuggingFace | Předtrénovaná knihovna a modely |
| 6 | Jemné ladění modelů | Přizpůsobte BERT vaší doméně |
| 7 | NLP pro italštinu | Šablony a prostředky pro italský jazyk |
| 8 | Od BERT po LLM | GPT, LLaMA a generování textu |
1. Proč BERT způsobil revoluci v NLP
Abychom pochopili dopad BERT, musíme se vrátit do prostředí NLP před rokem 2018 a pochopit jaké zásadní problémy předchozí modely nedokázaly vyřešit.
1.1 Omezení aplikací Word2Vec a GloVe
Jak jsme viděli v prvním článku série, Word2Old (2013) e Rukavice (2014) byly důležitým zlomem: poprvé slov byly reprezentovány jako husté vektory v souvislém prostoru, kde sémantické vztahy jako "král - muž + žena = královna" přirozeně vyplynulo z geometrie prostoru.
Tyto modely však trpí zásadním omezením: vyrábějí statické reprezentace. Každé slovo má jedinečný vektor, bez ohledu na kontext, ve kterém se objeví. Podívejme se na slovo „lavička“:
Problém statických zobrazení
- „Vložil jsem peníze lavice“ (bankovní instituce)
- "The lavice byl ve škole příliš mladý" (mobil)
- "A lavice mlha zahalila údolí“ (masa mlhy)
- "The lavice ryb bylo obrovské“ (skupina ryb)
S Word2Vec má slovo "banka" a unikátní vektor, což je fuzzy průměr všech těchto významů. Model nemá žádný způsob, jak rozlišit kontext.
1.2 ELMo: První krok ke kontextu
V roce 2018, před BERT, ELMo (vložení z jazykových modelů) od AllenAI se již pokusil vyřešit problém kontextů. ELMo použil síť biLSTM (obousměrná dlouhodobá krátkodobá paměť) předem vyškolený na úkol jazykového modelování pro vytváření kontextových vložení.
Problém ELMo byl dvojí: zaprvé byl obousměrný povrchní, v tom smyslu, že dva LSTM (jeden vpřed, jeden vzad) byly zřetězeny, ale ne interagovaly během zpracování. Za druhé, LSTM trpí a informace úzké místo: dlouhodobé závislosti se postupně "zapomínají" jak se sekvence prodlužuje.
1.3 Bod obratu: Hluboká obousměrnost
BERT oba problémy řeší díky mechanismu sebepozornost z Transformeru. Každý žeton v sekvenci může „sledovat“ všechny ostatní žetony současně ti nalevo i napravo. Tato obousměrnost není zřetězení dvou samostatných modelů (jako v ELMo), ale hluboká interakce, která se děje v každé jednotlivé vrstvě architektury.
Srovnání: Přístupy k obousměrnosti
| Model | Typ | Kontext | Omezení |
|---|---|---|---|
| Word2Vec/GloVe | Statický | Bez kontextu | Jeden vektor na slovo |
| GPT-1 | Jednosměrné (vlevo -> vpravo) | Pouze předchozí kontext | Nevidí budoucnost |
| Helma | Povrchní obousměrný | Propojený kontext | Žádná interakce mezi směry |
| BERT | Hluboké obousměrné | Úplný kontext v každé vrstvě | Pouze kodér (žádné generování) |
Tato hluboká obousměrnost je důvodem, proč BERT dokázal zavést nové záznamy na 11 benchmarků NLP v době jeho zveřejnění, včetně GLUE, SQuAD 1.1, SQuAD 2.0 a MultiNLI.
2. Architektura BERT: Transformátor kodéru
BERT je model pouze kodér, to znamená, že používá pouze část kodéru původní architektury Transformer popsané v článku „Attention Is All You Need“ (2017). Podívejme se na jednotlivé komponenty podrobně.
2.1 Přehled architektury
Architekturu BERT lze vizualizovat jako sérii bloků Transformer naskládané vertikálně. Každý blok obsahuje vícehlavou samopozornou vrstvu následuje dopředná síť s normalizační vrstvou a zbytkovými spojeními.
Input: [CLS] Il gatto si siede sul tappeto [SEP]
| | | | | | |
+-----+-------+-----+----+-----+----+-------+-----+
| Token Embeddings |
| + Segment Embeddings |
| + Position Embeddings |
+-----+-------+-----+----+-----+----+-------+-----+
| | | | | | |
+---------------------------------------------------------+
| Transformer Encoder Block 1 |
| +---------------------------------------------------+ |
| | Multi-Head Self-Attention | |
| | Q = XWq K = XWk V = XWv | |
| | Attention(Q,K,V) = softmax(QK^T/sqrt(dk))V | |
| +---------------------------------------------------+ |
| | Add & Layer Norm | |
| +---------------------------------------------------+ |
| | Feed-Forward Network | |
| | FFN(x) = max(0, xW1 + b1)W2 + b2 | |
| +---------------------------------------------------+ |
| | Add & Layer Norm | |
+---------------------------------------------------------+
| | | | | | |
... ... ... ... ... ... ...
| | | | | | |
+---------------------------------------------------------+
| Transformer Encoder Block L (12 o 24) |
+---------------------------------------------------------+
| | | | | | |
[CLS]out T1 T2 T3 T4 T5 T6 [SEP]out
|
Pooling --> Classificazione / Output per task
2.2 BERT-základ vs BERT-velký
Původní článek navrhuje dvě konfigurace:
Konfigurace BERT
| Parametr | BERT-základní | BERT-velký |
|---|---|---|
| Vrstvy transformátoru (L) | 12 | 24 |
| Skrytá velikost (H) | 768 | 1024 |
| Pozornost hlavy (A) | 12 | 16 |
| Celkové parametry | 110 mil | 340 mil |
| Ztlumit. pro Head | 768/12 = 64 | 1024/16 = 64 |
| Feed-Forward Dim. | 3072 (4 x 768) | 4096 (4 x 1024) |
| Max Seq. Délka | 512 | 512 |
| Velikost slovní zásoby | 30,522 | 30,522 |
2.3 Vstupní reprezentace: Tři vložení
Jedna z inovací BERT je ta vaše reprezentace vstupu, skládá se ze součtu tří různých vložení:
Input: [CLS] Il gatto dorme [SEP] Il cane corre [SEP]
| | | | | | | | |
Token Emb: E[CLS] E_il E_gatto E_dorme E[SEP] E_il E_cane E_corre E[SEP]
+ + + + + + + + +
Segment Emb: EA EA EA EA EA EB EB EB EB
+ + + + + + + + +
Position Emb: E0 E1 E2 E3 E4 E5 E6 E7 E8
= = = = = = = = =
Input finale: I0 I1 I2 I3 I4 I5 I6 I7 I8
Vložení tokenů: Každý token v sekvenci je mapován na hustý vektor velikosti H (768 pro BERT-bázi) prostřednictvím vkládací matice naučené během školení. Slovníček WordPiece obsahuje 30 522 tokenů.
Vložení segmentů: BERT může jako vstup přijmout jednu nebo dvě samostatné věty token [SEP]. Vložení segmentů označuje, ke které větě každý token patří: všechny tokeny první věty přijmout vložení E_A, ty druhé E_B. To je zásadní pro úlohy, jako je odvození přirozeného jazyka, kde musí BERT uvažovat o vztahu dvou vět.
Vložení pozic: Na rozdíl od původního Transformeru, který používá funkce sinusový, BERT používá vložení polohy Naučil jsem se. Každá pozice (0 až 511) má vektor naučený během tréninku, což znamená, že to BERT zvládne sekvence až do maximálního počtu 512 tokenů.
from transformers import BertTokenizer, BertModel
import torch
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertModel.from_pretrained('bert-base-uncased')
# Tokenizzare un input con due frasi
text_a = "Il gatto dorme"
text_b = "Il cane corre"
encoded = tokenizer(text_a, text_b, return_tensors='pt')
print("Token IDs:", encoded['input_ids'])
print("Token Type IDs (Segments):", encoded['token_type_ids'])
print("Attention Mask:", encoded['attention_mask'])
# Decodifica per vedere i token
tokens = tokenizer.convert_ids_to_tokens(encoded['input_ids'][0])
print("Tokens:", tokens)
# Output: ['[CLS]', 'il', 'gatto', 'dor', '##me', '[SEP]', 'il', 'cane', 'cor', '##re', '[SEP]']
# Accesso agli embedding layers
token_embeddings = model.embeddings.word_embeddings
position_embeddings = model.embeddings.position_embeddings
segment_embeddings = model.embeddings.token_type_embeddings
print(f"Token embedding matrix: {token_embeddings.weight.shape}")
# Output: Token embedding matrix: torch.Size([30522, 768])
print(f"Position embedding matrix: {position_embeddings.weight.shape}")
# Output: Position embedding matrix: torch.Size([512, 768])
print(f"Segment embedding matrix: {segment_embeddings.weight.shape}")
# Output: Segment embedding matrix: torch.Size([2, 768])
2.4 Vícehlavá sebepozornost
Srdce BERT (a každého Transformeru) a mechanismus sebepozornost. Samostatná pozornost intuitivně umožňuje každému tokenu „sledovat“ všechny ostatní tokeny v pořadí a rozhodněte, jak je každý z nich relevantní pro vaši reprezentaci.
Scaled Dot-Product Attention
Pro každý token pozornost počítá tři vektory pomocí naučených lineárních projekcí:
- Dotazy (Q): "Co hledám?"
- Klíč (K): "Co nabízím?"
- Hodnota (V): "Jaké informace mám přinést?"
Vzorec pozornosti je:
Pozor (Q, K, V) = softmax\left(\frac{QK^T}{\sqrt{d_k}}\right)VKde d_k a velikost klíčů (64 pro BERT-base). Faktor \frac{1}{\sqrt{d_k}} slouží ke stabilizaci přechody: bez tohoto měřítka vytváří bodový součin velmi velké hodnoty pro vysoká rozměrnost a softmax se stává "nasyceným" a vytváří téměř jednoteplé rozvody.
Vícehlavá pozornost
Místo výpočtu jediné funkce pozornosti používá BERT vícehlavý pozornost: Výpočet je replikován h krát (12 pro BERT-base, 16 pro BERT-large), každý s různými projekčními maticemi. To modelu umožňuje zachytit různé typy vztahů současně: hlava by se mohla specializovat v syntaktických vztazích, jiný v koreferencích, jiný v sémantických závislostech.
MultiHead(Q, K, V) = Concat(head_1, ..., head_h)W^O head_i = Pozor (QW_i^Q, KW_i^K, VW_i^V)Kde W_i^Q \in \mathbb{R}^{d_{model} \times d_k}, W_i^K \in \mathbb{R}^{d_{model} \times d_k}, W_i^V \in \mathbb{R}^{d_{model} \times d_v} e W^O \in \mathbb{R}^{hd_v \times d_{model}}.
import torch
import torch.nn.functional as F
import math
def scaled_dot_product_attention(Q, K, V, mask=None):
"""Calcolo dell'attention scalata."""
d_k = Q.size(-1)
# Calcolo dei punteggi di attention
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(d_k)
# Applicazione della maschera (opzionale)
if mask is not None:
scores = scores.masked_fill(mask == 0, float('-inf'))
# Softmax per ottenere i pesi
attention_weights = F.softmax(scores, dim=-1)
# Output pesato
output = torch.matmul(attention_weights, V)
return output, attention_weights
class MultiHeadAttention(torch.nn.Module):
"""Multi-Head Attention come in BERT."""
def __init__(self, d_model=768, num_heads=12):
super().__init__()
self.d_model = d_model
self.num_heads = num_heads
self.d_k = d_model // num_heads # 64 per BERT-base
self.W_q = torch.nn.Linear(d_model, d_model)
self.W_k = torch.nn.Linear(d_model, d_model)
self.W_v = torch.nn.Linear(d_model, d_model)
self.W_o = torch.nn.Linear(d_model, d_model)
def forward(self, x, mask=None):
batch_size, seq_len, _ = x.size()
# Proiezioni lineari
Q = self.W_q(x) # (batch, seq_len, d_model)
K = self.W_k(x)
V = self.W_v(x)
# Reshape per multi-head: (batch, heads, seq_len, d_k)
Q = Q.view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2)
K = K.view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2)
V = V.view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2)
# Attention per ogni head
attn_output, attn_weights = scaled_dot_product_attention(Q, K, V, mask)
# Concatenazione delle head
attn_output = attn_output.transpose(1, 2).contiguous()
attn_output = attn_output.view(batch_size, seq_len, self.d_model)
# Proiezione finale
output = self.W_o(attn_output)
return output, attn_weights
# Test
mha = MultiHeadAttention(d_model=768, num_heads=12)
x = torch.randn(1, 10, 768) # batch=1, seq_len=10, dim=768
output, weights = mha(x)
print(f"Output shape: {output.shape}") # torch.Size([1, 10, 768])
print(f"Weights shape: {weights.shape}") # torch.Size([1, 12, 10, 10])
2.5 Dopředná síť
Po každé vrstvě pozornosti BERT aplikuje poziční dopřednou síť, tzn stejná síť je aplikována nezávisle na každé pozici sekvence:
FFN(x) = \text{GELU}(xW_1 + b_1)W_2 + b_2Střední velikost je obvykle 4krát skrytý rozměr (3072 pro BERT-základ, 4096 pro BERT-velký). BERT používá aktivační funkci GELU (Gaussian Error Linear Unit) namísto tradiční ReLU, která poskytuje plynulejší přechod a lepší výkon v praxi.
2.6 Normalizace vrstev a zbytková připojení
Každá podvrstva (pozornost a dopředná vazba) v BERT je obalena a zbytkové spojení následuje normalizace vrstvy:
\text{output} = \text{LayerNorm}(x + \text{Podvrstva}(x))Zbytková spojení umožňují gradientům proudit přímo sítí během zpětného šíření, což zabraňuje problému mizejícího gradientu ve velmi sítích hluboký. Normalizace vrstvy stabilizuje trénink normalizací aktivací podél dimenze prvku.
import torch
import torch.nn as nn
class TransformerEncoderBlock(nn.Module):
"""Un singolo blocco encoder come in BERT."""
def __init__(self, d_model=768, num_heads=12, d_ff=3072, dropout=0.1):
super().__init__()
# Multi-Head Attention
self.attention = nn.MultiheadAttention(
embed_dim=d_model,
num_heads=num_heads,
dropout=dropout,
batch_first=True
)
# Feed-Forward Network
self.ffn = nn.Sequential(
nn.Linear(d_model, d_ff),
nn.GELU(),
nn.Dropout(dropout),
nn.Linear(d_ff, d_model),
nn.Dropout(dropout)
)
# Layer Normalization
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.dropout = nn.Dropout(dropout)
def forward(self, x, mask=None):
# Sub-layer 1: Multi-Head Attention + Residual + LayerNorm
attn_output, _ = self.attention(x, x, x, key_padding_mask=mask)
x = self.norm1(x + self.dropout(attn_output))
# Sub-layer 2: FFN + Residual + LayerNorm
ffn_output = self.ffn(x)
x = self.norm2(x + ffn_output)
return x
class BERTEncoder(nn.Module):
"""Stack di encoder blocks come in BERT-base."""
def __init__(self, num_layers=12, d_model=768, num_heads=12, d_ff=3072):
super().__init__()
self.layers = nn.ModuleList([
TransformerEncoderBlock(d_model, num_heads, d_ff)
for _ in range(num_layers)
])
def forward(self, x, mask=None):
for layer in self.layers:
x = layer(x, mask)
return x
# Test: simula BERT-base
encoder = BERTEncoder(num_layers=12, d_model=768, num_heads=12, d_ff=3072)
x = torch.randn(2, 128, 768) # batch=2, seq_len=128
output = encoder(x)
print(f"Output: {output.shape}") # torch.Size([2, 128, 768])
# Conta parametri
total_params = sum(p.numel() for p in encoder.parameters())
print(f"Parametri encoder: {total_params:,}")
# ~85M (solo encoder, senza embeddings)
3. Předškolní příprava: Jak se BERT učí jazyk
BERT je předem trénován na používání velkého množství neoznačeného textu dva doplňkové cíle školení: Maskovaný jazykový model (MLM) e il Predikce další věty (NSP). Základní myšlenkou je, že model naučíte se bohaté reprezentace jazyka z těchto úkolů, na které budete sami dohlížet, a to tyto znalosti lze poté přenést na konkrétní úkoly prostřednictvím jemného ladění.
Předtréninkové údaje
- BooksCorpus: ~800 milionů slov (11 000 nepublikovaných knih)
- Anglická Wikipedie: ~2 500 milionů slov (pouze text, žádné tabulky/seznamy)
- Celkový: ~3,3 miliardy slov
- Výcvik: 4 TPU pody (64 TPU čipů), 4 dny pro BERT-base
3.1 Maskovaný jazykový model (MLM)
Maskovaný jazykový model je hlavní inovací BERT. Problém s jazykem tradiční modelování (předvídání dalšího slova) a to je ve své podstatě jednosměrný: Model se může dívat pouze doleva. K výrobě obousměrný model, BERT zavádí elegantní trik: maskování porce žetony náhodného vstupu a požádejte model, aby je předpověděl.
Strategie 80/10/10
Pro každou vstupní sekvenci je 15 % pro predikci je vybráno tokenů. Z těchto vybraných tokenů:
- 80 % je nahrazen speciálním žetonem [MASK]
- 10 % je nahrazen náhodným tokenem ze slovníku
- 10 % je ponechána beze změny
Tato strategie řeší drobný problém: během jemného ladění selže token [MASK] se nikdy neobjeví ve vstupu. Pokud by model viděl [MASK] pouze během předtréninku, byl by tam a nesoulad mezi předtréninkem a doladěním. Nahrazení 10 % žetony náhodný a ponechání 10 % beze změny, model se naučí vytvářet dobré reprezentace pro každý žetony, nejen pro ty převlečené.
import random
import torch
from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
def apply_mlm_masking(tokens, tokenizer, mask_prob=0.15):
"""Applica la strategia di masking 80/10/10 di BERT."""
masked_tokens = list(tokens)
labels = [-100] * len(tokens) # -100 = ignora nella loss
for i in range(len(tokens)):
# Non mascherare token speciali
if tokens[i] in [tokenizer.cls_token_id, tokenizer.sep_token_id,
tokenizer.pad_token_id]:
continue
if random.random() < mask_prob:
labels[i] = tokens[i] # Salva il token originale come label
rand = random.random()
if rand < 0.8:
# 80%: sostituisci con [MASK]
masked_tokens[i] = tokenizer.mask_token_id
elif rand < 0.9:
# 10%: sostituisci con token casuale
masked_tokens[i] = random.randint(0, tokenizer.vocab_size - 1)
# else: 10%: lascia invariato
return masked_tokens, labels
# Esempio
text = "Il gatto si siede sul tappeto rosso"
encoded = tokenizer.encode(text)
print("Originale:", tokenizer.decode(encoded))
masked, labels = apply_mlm_masking(encoded, tokenizer)
print("Mascherato:", tokenizer.decode(masked))
print("Labels (posizioni da predire):",
[(i, tokenizer.decode([labels[i]])) for i in range(len(labels)) if labels[i] != -100])
3.2 Predikce další věty (NSP)
Druhým předtréninkovým cílem je Předpověď další věty. Pro každého dvojice vět (A, B) na vstupu, model musí předpovědět, zda B je věta, která následuje vlastně A v původním textu (Je Další) nebo jde-li o náhodnou frázi (NeDalší). Soubor dat je konstruován tak, že 50 % párů je pozitivních a 50 % negativních.
Input = [CLS] Il gatto dorme [SEP] E' stanco dopo aver giocato [SEP]
Label = IsNext (frase B segue A nel testo originale)
Input = [CLS] Il gatto dorme [SEP] Roma e' la capitale d'Italia [SEP]
Label = NotNext (frase B e' casuale, non correlata ad A)
Cíl NSP pomáhá BERT zachytit vztahy mezi větami, užitečné pro úkoly jako např Odpovídání na otázky a vyvozování přirozeného jazyka. Nicméně následné studie (zejména RoBERTa) ukázaly, že NSP nemusí být nutné a může dokonce poškození výkonu, jak uvidíme v části o variantách.
3.3 Kombinovaná ztráta
Celková ztráta BERT během předtréninku je součtem těchto dvou ztrát:
\mathcal{L}_{total} = \mathcal{L}_{MLM} + \mathcal{L}_{NSP}Kde \mathcal{L}_{MLM} a ztráta křížové entropie na tokenech maskované a \mathcal{L}_{NSP} a ztrátu křížové entropie binární na klasifikaci IsNext/NotNext.
4. Tokenizace WordPiece
BERT používá tokenizaci WordPiece, tokenizační algoritmus podslovo, které vyvažuje efektivitu slovní zásoby a jazykové pokrytí. WordPiece a stát původně vyvinut pro systém strojového překladu Google.
4.1 Jak WordPiece funguje
Myšlenka WordPiece je jednoduchá, ale účinná: vychází ze slovní zásoby jednotlivců znaky a iterativně slučujte páry tokenů, které maximalizují věrohodnosti tréninkového korpusu. Proces pokračuje, dokud slovní zásoba nedohoní cílovou velikost (30 522 pro BERT).
Výsledkem je slovní zásoba, která obsahuje:
- Celá běžná slova (např. "the", "of", "and")
- Běžné předpony a kořeny (např. "un", "re", "pre")
- Přípony a koncovky označené
##(např. "##ing", "##tion", "##ed") - Jednotlivé znaky pro zpracování jakéhokoli neznámého slova
4.2 Speciální tokeny
Kromě tokenů slovní zásoby WordPiece používá BERT několik speciálních tokenů:
Speciální tokeny BERT
| Tokeny | ID | Rozsah |
|---|---|---|
| [PODLOŽKA] | 0 | Padding pro standardizaci délky sekvencí v dávce |
| [UNK] | 100 | Neznámý token (není ve slovníku) |
| [CLS] | 101 | Začátek sekvence, její výsledná reprezentace se používá pro klasifikaci |
| [SEP] | 102 | Oddělovač mezi dvěma vstupními větami |
| [MASKA] | 103 | Maska pro maskovaný jazykový model během předtréninku |
from transformers import BertTokenizer
# Carica il tokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
# Tokenizzazione di parole comuni e rare
examples = [
"The cat sat on the mat",
"Electroencephalography is fascinating",
"L'intelligenza artificiale rivoluzionera il mondo",
"Tokenizzazione subword con WordPiece"
]
for text in examples:
tokens = tokenizer.tokenize(text)
ids = tokenizer.encode(text)
print(f"Testo: {text}")
print(f" Tokens: {tokens}")
print(f" IDs: {ids}")
print(f" Num tokens: {len(tokens)}")
print()
# Output per "Electroencephalography is fascinating":
# Tokens: ['electro', '##ence', '##pha', '##log', '##raphy', 'is', 'fascinating']
# La parola lunga viene spezzata in subword, ma "is" e "fascinating" restano intere
# Decodifica
encoded = tokenizer("Hello world!", return_tensors="pt")
decoded = tokenizer.decode(encoded['input_ids'][0])
print(f"Encoded -> Decoded: {decoded}")
# Output: [CLS] hello world ! [SEP]
# Vocabolario
print(f"Dimensione vocabolario: {tokenizer.vocab_size}")
# Output: 30522
5. Jemné doladění BERT pro následné úlohy
Paradigma zavedené BERT je „předtrénovat, pak dolaďovat“. Předtréninková fáze vytváří model s hlubokým porozuměním jazyku. Fáze jemného ladění přizpůsobte tento model konkrétní úloze přidáním výstupní vrstvy a školením celý model (nebo jeho část) na označených datech úlohy.
Krása jemného doladění je v tom, že vyžaduje málo označených údajů e málo tréninkového času ve srovnání s trénováním modelu od nuly. Obvykle postačují 2-4 epochy s nízkou rychlostí učení (2e-5 až 5e-5).
5.1 Klasifikace textu
Pro klasifikaci textu (analýza sentimentu, klasifikace témat, detekce spamu), používá se reprezentace tokenů [CLS] jako vstup do klasifikátoru lineární:
Input: [CLS] Questo film e' fantastico [SEP]
|
BERT: 12 layers di Transformer Encoder
|
[CLS] output (768-dim) --> Linear(768, num_classes) --> Softmax --> Predizione
|
"Positivo" (p=0.95)
from transformers import BertForSequenceClassification, BertTokenizer
from transformers import Trainer, TrainingArguments
from datasets import load_dataset
import torch
# 1. Carica modello pre-addestrato con classification head
model = BertForSequenceClassification.from_pretrained(
'bert-base-uncased',
num_labels=2 # positivo/negativo
)
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
# 2. Prepara il dataset (esempio: IMDB reviews)
dataset = load_dataset('imdb')
def tokenize_function(examples):
return tokenizer(
examples['text'],
padding='max_length',
truncation=True,
max_length=256
)
tokenized_datasets = dataset.map(tokenize_function, batched=True)
# 3. Configura il training
training_args = TrainingArguments(
output_dir='./results',
num_train_epochs=3,
per_device_train_batch_size=16,
per_device_eval_batch_size=64,
warmup_steps=500,
weight_decay=0.01,
learning_rate=2e-5,
logging_dir='./logs',
evaluation_strategy='epoch',
save_strategy='epoch',
load_best_model_at_end=True,
)
# 4. Crea il Trainer e avvia il fine-tuning
trainer = Trainer(
model=model,
args=training_args,
train_dataset=tokenized_datasets['train'],
eval_dataset=tokenized_datasets['test'],
)
trainer.train()
# 5. Valutazione
results = trainer.evaluate()
print(f"Accuracy: {results['eval_loss']:.4f}")
5.2 Rozpoznání pojmenované entity (NER)
Pro NER se místo pouhého použití tokenu [CLS] použije výstup z každý token zařadit každou do kategorie entity (osoba, místo, organizace atd.):
Input: [CLS] Mario Rossi vive a Roma [SEP]
| | | | | | |
BERT: 12 layers di Transformer Encoder
| | | | | | |
Output: O B-PER I-PER O O B-LOC O
Ogni token --> Linear(768, num_entity_types) --> Softmax --> Tipo entità
from transformers import BertForTokenClassification, BertTokenizer
from transformers import pipeline
# Usa un modello già fine-tuned per NER
ner_pipeline = pipeline(
"ner",
model="dbmdz/bert-large-cased-finetuned-conll03-english",
aggregation_strategy="simple"
)
text = "Mario Rossi works at Google in Mountain View, California"
entities = ner_pipeline(text)
for entity in entities:
print(f" {entity['word']}: {entity['entity_group']} "
f"(score: {entity['score']:.3f})")
# Output:
# Mario Rossi: PER (score: 0.998)
# Google: ORG (score: 0.997)
# Mountain View: LOC (score: 0.995)
# California: LOC (score: 0.999)
5.3 Odpovědi na otázky
Pro extrakční odpovědi na otázky dostává BERT jako vstup otázku a kontext odděleno [SEP]. Model musí předpovídat pozice start e konec odpovědi v kontextu:
Input: [CLS] Quando e' nato Einstein? [SEP] Albert Einstein e' nato il 14 marzo 1879 [SEP]
| | | | | | | | | | | | | |
BERT: 12 layers di Transformer Encoder
| | | | | | | | | | | | | |
Start: 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.02 0.8 0.02 0.01 0.01 0.01
End: 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.85 0.01
Risposta estratta: "14 marzo 1879"
from transformers import pipeline
# Pipeline di Question Answering
qa_pipeline = pipeline(
"question-answering",
model="deepset/bert-base-cased-squad2"
)
context = """
BERT (Bidirectional Encoder Representations from Transformers)
e' un modello di linguaggio sviluppato da Google AI nel 2018.
E' stato addestrato su Wikipedia e BookCorpus, per un totale
di circa 3.3 miliardi di parole. BERT-base ha 110 milioni di
parametri e 12 layer di Transformer encoder.
"""
questions = [
"Chi ha sviluppato BERT?",
"Quanti parametri ha BERT-base?",
"Su quali dati e' stato addestrato BERT?",
"In che anno e' stato pubblicato BERT?"
]
for question in questions:
result = qa_pipeline(question=question, context=context)
print(f"D: {question}")
print(f"R: {result['answer']} (score: {result['score']:.3f})")
print()
5.4 Klasifikace dvojice vět
U úloh, jako je odvození přirozeného jazyka (NLI) nebo detekce parafrází, BERT přijímá dvě věty a musí klasifikovat jejich vztah (návaznost, rozpor, neutrální):
from transformers import pipeline
# Natural Language Inference
nli_pipeline = pipeline(
"text-classification",
model="cross-encoder/nli-deberta-v3-small"
)
pairs = [
("Il gatto dorme sul divano", "Un animale sta riposando"),
("Il gatto dorme sul divano", "Il cane corre nel parco"),
("Tutti gli studenti hanno superato l'esame", "Nessuno studente ha fallito"),
]
for premise, hypothesis in pairs:
result = nli_pipeline(f"{premise} [SEP] {hypothesis}")
print(f"Premessa: {premise}")
print(f"Ipotesi: {hypothesis}")
print(f"Risultato: {result[0]['label']} ({result[0]['score']:.3f})")
print()
6. Extrahujte vložení z BERT
Kromě jemného ladění pro konkrétní úkoly je BERT mimořádně užitečný jako extraktor funkcí. Reprezentace vytvořené vnitřními vrstvami zachycují jazykové informace na různých úrovních abstrakce.
from transformers import BertTokenizer, BertModel
import torch
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertModel.from_pretrained('bert-base-uncased', output_hidden_states=True)
model.eval()
text = "Il Natural Language Processing e' affascinante"
inputs = tokenizer(text, return_tensors='pt')
with torch.no_grad():
outputs = model(**inputs)
# outputs contiene:
# - last_hidden_state: output dell'ultimo layer (batch, seq_len, 768)
# - pooler_output: [CLS] passato attraverso un linear + tanh (batch, 768)
# - hidden_states: tuple di 13 tensori (embedding + 12 layers)
last_hidden = outputs.last_hidden_state
pooler = outputs.pooler_output
all_layers = outputs.hidden_states
print(f"Last hidden state: {last_hidden.shape}")
# torch.Size([1, N, 768])
print(f"Pooler output: {pooler.shape}")
# torch.Size([1, 768])
print(f"Numero di layer: {len(all_layers)}")
# 13 (embedding layer + 12 transformer layers)
# Strategie per ottenere sentence embeddings:
# 1. Usa il [CLS] token dell'ultimo layer
cls_embedding = last_hidden[:, 0, :]
print(f"[CLS] embedding: {cls_embedding.shape}")
# 2. Mean pooling sull'ultimo layer (spesso migliore)
attention_mask = inputs['attention_mask'].unsqueeze(-1)
mean_embedding = (last_hidden * attention_mask).sum(1) / attention_mask.sum(1)
print(f"Mean pooling: {mean_embedding.shape}")
# 3. Concatena gli ultimi 4 layer (cattura più informazione)
last_4_layers = torch.cat(
[all_layers[i] for i in [-1, -2, -3, -4]],
dim=-1
)
print(f"Last 4 layers concatenated: {last_4_layers.shape}")
# torch.Size([1, N, 3072])
Jakou vrstvu použít?
Výzkum ukázal, že různé vrstvy zachycují různé typy informací:
- Vrstvy 1-4 (basy): Základní morfologické a syntaktické informace
- Vrstvy 5-8 (střední): Komplexní syntaktické informace, závislosti
- Vrstvy 9–12 (vysoké): Sémantické informace na vysoké úrovni
- Poslední vrstva: Nejlepší pro sémantické úlohy (NLI, podobnost)
- Střední vrstvy: Nejlepší pro syntaktické úlohy (označování POS, analýza)
7. Varianty BERT: Evoluce rodiny
Od vydání BERT navrhlo mnoho výzkumných skupin jeho varianty zlepšit několik aspektů. Zde je přehled těch nejdůležitějších.
Srovnávací tabulka variant BERT
| Model | Rok | Vedoucí inovace | Parametry |
|---|---|---|---|
| BERT-základní | 2018 | MLM + NSP, hluboké obousměrné | 110 mil |
| Roberta | 2019 | Žádné NSP, dynamické maskování, více dat, delší školení | 125 mil |
| ALBERT | 2019 | Sdílení parametrů, faktorizované vkládání | 12M-235M |
| DistilBERT | 2019 | Destilace znalostí, o 60 % rychlejší | 66 mil |
| ELECTRA | 2020 | Nahrazená detekce tokenů (efektivnější než MLM) | 110 mil |
| DeBERTa | 2020 | Oddělená pozornost (samostatný obsah + pozice) | 134M-390M |
| XLNet | 2019 | Permutační jazykové modelování | 340 mil |
| SpanBERT | 2019 | Maskování souvislých polí + SBO | 110 mil |
| ERNIE | 2019 | Maskování entit, rozšířené o znalosti | 110 mil |
7.1 RoBERTa: Optimalizace školení BERT
RoBERTa (důkladně optimalizovaný přístup BERT), kterou vám přináší Facebook AI (nyní Meta), dokázal, že BERT byl málo trénovaný. Změny hlavní jsou:
- Odstranění NSP: Cíl NSP nepomáhá a může dokonce snížit výkon
- Dynamické maskování: Místo maskování pouze jednou během předběžného zpracování, maskovací vzory jsou generovány dynamicky v každé epoše
- Více dat: 160 GB textu (vs. 16 GB BERT), včetně CC-News, OpenWebText, Stories
- Delší trénink: 500K krok (vs. 100K BERT), větší velikost dávky
- Delší sekvence: Stále 512 tokenů (BERT začínal na 128)
7.2 ALBERT: Inteligentní komprese
ALBERT (A Lite BERT) společnosti Google řeší problém růstu parametry pomocí dvou elegantních technik:
- Faktorizovaná parametrizace vkládání: Samostatná velikost slovní zásoby ze skryté dimenze. Místo matice V x H použijte V x E a E x H (s E mnohem menším než H)
- Sdílení parametrů mezi vrstvami: Všechny vrstvy Transformeru sdílejí stejné parametry. To drasticky snižuje počet parametrů bez ztráty hodně kvality
ALBERT-xxlarge dosahuje vyššího výkonu než BERT-large s parametry pouze 235M (oproti 340 mil.), i když doba inference se nezkracuje, protože počet vrstev zůstává beze změny.
7.3 DistilBERT: Destilace znalostí
DistilBERT by Hugging Face používá znalostní destilace vytvořit menší a rychlejší model. Trénuje se „studentský“ model (6 vrstev). napodobit výstupy "učitelského" modelu (BERT-base, 12 vrstev).
- o 40 % menší: 66M parametrů vs 110M
- o 60% rychlejší: 6 vrstev vs 12
- 97 % výkonu: Zachovává téměř veškerou kvalitu BERT-base
7.4 ELECTRA: Zcela odlišný přístup
ELECTRA nahrazuje Masked Language Model inspirovaným přístupem na GANs (Generative Adversarial Networks). Místo maskování tokenů a jejich předpovídání, ELECTRA:
- Malý generátor (malý BERT) produkuje věrohodné náhradní žetony
- Diskriminátor (hlavní model) musí identifikovat který žeton byly nahrazeny (nahrazena detekce tokenu)
Klíčová výhoda: diskriminátor se učí každý žetony sekvence, nejen maskovaných 15% jako v BERT. Díky tomu je ELECTRA mnohem více efektivní z hlediska dat a výpočetní techniky.
7.5 DeBERTa: Oddělená pozornost
DeBERTa (dekódováním vylepšený BERT s rozpojenou pozorností) společností Microsoft přináší dvě novinky:
- Rozptýlená pozornost: Odděluje informace o obsahu a poloze do dvou odlišných vektorů, což umožňuje modelu uvažovat o „co“ a „kde“ bez ohledu na to
- Vylepšený dekodér masky: Přidá informace o absolutní poloze v dekodéru pro zlepšení předpovědí MLM
DeBERTa v3 je v současnosti jedním z nejvýkonnějších modelů kodéru v mnoha benchmarcích, často předčí mnohem větší modely.
8. BERT pro italský jazyk
Původní BERT byl vyškolen na anglický text. Pro italštinu máme několik možností, každý s výhodami a nevýhodami.
Modely BERT pro italštinu
| Model | Základní | Údaje o školení | Slovník |
|---|---|---|---|
| mBERT | BERT-základní | Wikipedie ve 104 jazycích | 110 tisíc vícejazyčných tokenů |
| dbmdz/bert-base-italian-xxl-cased | BERT-základní | OPUS, Wikipedia IT (~13 GB) | 30 000 italských žetonů |
| Alberto | BERT-základní | Tweet v italštině | 30 000 italských žetonů |
| UmBERTo | Roberta | Italský korpus OSCAR | 32 000 tokenů SentencePiece |
| XLM-RoBERTa | Roberta | CC-100 ve 100 jazycích | 250 tisíc vícejazyčných tokenů |
from transformers import pipeline, AutoTokenizer, AutoModelForMaskedLM
# 1. BERT Italiano (dbmdz)
fill_mask_it = pipeline(
"fill-mask",
model="dbmdz/bert-base-italian-xxl-cased"
)
results = fill_mask_it("Roma e' la [MASK] d'Italia.")
for r in results[:3]:
print(f" {r['token_str']}: {r['score']:.4f}")
# Output: capitale: 0.8234, citta: 0.0521, ...
# 2. UmBERTo (basato su RoBERTa)
fill_mask_umberto = pipeline(
"fill-mask",
model="Musixmatch/umberto-commoncrawl-cased-v1"
)
results = fill_mask_umberto("L'intelligenza artificiale <mask> il futuro.")
for r in results[:3]:
print(f" {r['token_str']}: {r['score']:.4f}")
# 3. Multilingual BERT
fill_mask_mbert = pipeline(
"fill-mask",
model="bert-base-multilingual-cased"
)
results = fill_mask_mbert("Roma e' la [MASK] d'Italia.")
for r in results[:3]:
print(f" {r['token_str']}: {r['score']:.4f}")
# mBERT funziona, ma il modello italiano dedicato e' molto più' preciso
Jaký model byste si měli vybrat pro italštinu?
- Obecné úkoly (NER, klasifikace, QA):
dbmdz/bert-base-italian-xxl-casednebo UmBERTo - Analýza sociálních médií: AlBERTo (školení na tweetu)
- Vícejazyčné úkoly: XLM-RoBERTa (skvělé pro cross-lingual zero-shot)
- Maximální kvalita: Doladění XLM-RoBERTa-large na italských datech
9. Limity BERT
Navzdory svému revolučnímu dopadu má BERT několik strukturálních omezení ovlivnit jeho použitelnost:
9.1 Limit 512 tokenů
BERT dokáže zpracovat maximální sekvence 512 žetonů. Pro dlouhé dokumenty (články, právní smlouvy, vědecké práce), to je přísný limit. Strategie zmírnění zahrnují zkrácení, to posuvné okno e la hierarchické sdružování, ale žádný není optimální.
9.2 Kvadratická složitost pozornosti
Vlastní pozornost má výpočetní složitost O(n^2) respekt na délku sekvence n. To znamená dvojnásobnou délku vstupu čtyřnásobně zdvojnásobí výpočetní náklady a spotřebu paměti. Modelky jako Longformer, BigBird e Linformer navrhují mechanismy řídké pozornosti, jak tento problém obejít.
9.3 Pouze kodér: Žádná generativní schopnost
BERT je model pouze kodér. Může vytvářet bohaté reprezentace textu, ale nemůže generovat nový text. Pro generaci potřebujete dekodér (jako v GPT) nebo architektura kodér-dekodér (jako v T5 nebo BART).
9.4 Nesoulad před tréninkem/jemným doladěním
Token [MASK] se objeví během předběžného tréninku, ale nikdy během jemného ladění nebo vyvozování. Přestože strategie 80/10/10 problém částečně zmírňuje, tento nesoulad přetrvává teoretickou slabinou. ELECTRA zcela řeší problém tím, že nikdy nepoužije [MASK].
9.5 Nezávislost maskovaných tokenů
Když BERT maskuje více tokenů, předpovídá je podle toho nezávislý. Ne modeluje závislosti mezi maskovanými tokeny. Pokud je například fráze „Nová [MASK] [MASK]“ a odpověď je „New York City“, BERT předpovídá „York“ a „City“ nezávisle, bez ovlivnit predikci jednoho na druhého. XLNet řeší tento problém pomocí permutační jazykové modelování.
Souhrn omezení a řešení
| Omezit | Dopad | Řešení |
|---|---|---|
| Maximálně 512 tokenů | Žádné dlouhé dokumenty | Longformer, BigBird, posuvné okno |
| Pozor O(n^2) | Vysoké náklady na výpočetní techniku | Linformer, Performer, Flash Attention |
| Pouze kodéry | Žádné generování textu | T5, BART, GPT |
| [MASK] nesoulad | Nesoulad vlaku/odvození | ELECTRA, XLNet |
| Nezávislé předpovědi | Žádné závislosti mezi maskovanými tokeny | XLNet, autoregresivní modely |
10. Od BERT k moderním modelům
BERT zahájil éru „předtrénuj a pak dolaď“ v NLP a ovlivnil hluboce celou následující architekturu. Podívejme se, jak se krajina vyvíjela.
10.1 Modelový rodokmen
Transformer (2017)
|
+------ Encoder-Only ------+------ Decoder-Only ------+--- Encoder-Decoder ---+
| | | |
BERT (2018) GPT-1 (2018) T5 (2019) BART (2019)
| | |
+-- RoBERTa (2019) GPT-2 (2019) mT5 (2020)
+-- ALBERT (2019) |
+-- DistilBERT (2019) GPT-3 (2020)
+-- XLNet (2019) |
+-- ELECTRA (2020) GPT-4 (2023)
+-- DeBERTa (2020) |
+-- DeBERTa v3 (2021) LLaMA (2023)
|
LLaMA 2 (2023)
|
Mistral (2023)
|
LLaMA 3 (2024)
10.2 Klíčová lekce BERT
Nejdůležitějším dědictvím BERT není konkrétní architektura, ale a paradigma: Trénujte model na velkém množství neoznačeného textu pomocí samokontrolovaných cílů a poté jej přizpůsobit konkrétním úkolům s malým množstvím dat označené. Toto paradigma je základem všech moderních modelů, od GPT po LLaMA.
10.3 Kde je BERT stále relevantní
Navzdory vzniku mnohem větších generativních modelů, BERT a jeho potomci Pouze kodér zůstává nejlepší volbou pro mnoho scénářů:
- Klasifikace textu: Sentiment, téma, detekce spamu
- Rozpoznávání pojmenované entity: Extrakce strukturovaných entit
- Sémantické vyhledávání: Hledání na základě významu (s převodníky vět)
- Vyhledávání informací: Pořadí relevantních dokumentů
- Generování vkládání: Husté zobrazení textu
Modelů kodérů jako BERT je více účinný, více rychle e levnější ve srovnání s velkými generativními modely. Na mnoho úkolů porozumění jazyku, vyladěný DeBERTa-v3 překonává modely se 100krát vyššími výkony parametry.
11. Závěry a kdy použít BERT
V tomto článku jsme podrobně analyzovali BERT: z architektury pouze s kodérem Transformátoru, k vícehlavému mechanismu sebepozornosti, k předtréninkovým strategiím (MLM a NSP), až po praktické implementace s HuggingFace a jeho variantami jsou odvozené.
Rozhodovací rámec: Kdy použít BERT
| Scénář | Doporučený model | Motivace |
|---|---|---|
| Klasifikace textu (produkce) | DeBERTa-v3 nebo RoBERTa | Maximální přesnost, rozumné náklady |
| NER (italsky) | dbmdz BERT IT nebo UmBERTo | Rodná italská slovní zásoba |
| Omezené zdroje / okraj | DistilBERT nebo TinyBERT | Rychlý, kompaktní, 97% kvalita |
| Efektivita tréninku | ELECTRA | Učte se ze všech tokenů, nejen 15 % |
| Generování textu | GPT / T5 / LLaMA | BERT nemůže generovat |
| Dlouhé dokumenty | Longformer / BigBird | BERT omezen na 512 tokenů |
| Vícejazyčný / zero-shot | XLM-RoBERTa | Vynikající mezijazyčný přenos |
BERT zůstává základem moderního NLP. Ačkoli Velké jazykové modely Generativní modely dominují titulkům, modely kodérů jako BERT jsou páteří nesčetných aplikací ve výrobě: vyhledávače, systémy doporučení, spamové filtry, právní asistenti, analýza sentimentu a mnoho dalšího.
V dalším článku série uvedeme tyto znalosti do praxe vytvořením a systém Analýza sentimentu kompletní pro italský text, od příprava datové sady pro nasazení modelu.
Zdroje, kde se dozvíte více
- Původní článek: "BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding" (Devlin et al., 2018)
- Referát RoBERTa: „Robustně optimalizovaný přístup k předtréninkové přípravě BERT“ (Liu et al., 2019)
- ELECTRA Paper: „ELECTRA: Pre-training Text Encoders as Discriminators spíše než generátory“ (Clark et al., 2020)
- Papír DeBERTa: "DeBERTa: Dekódováním vylepšený BERT s rozvolněnou pozorností" (He et al., 2020)
- Dokumentace HuggingFace BERT: huggingface.co/docs/transformers/model_doc/bert
- Ilustrovaný BERT: jalammar.github.io/illustrated-bert/







