Python, de la zero Lecția 58 / 60

Modele pre-antrenate + transfer learning + Hugging Face

Calea realista de la zero la un model deep learning functional in 2026: pleci de la unul pre-antrenat si il fine-tunezi pe datele tale.

În 2017, un proiect de deep learning însemna să descarci ImageNet, să închiriezi patru GPU-uri pentru o săptămână și să antrenezi un ResNet de la zero. În 2026 înseamnă să tastezi from_pretrained("...") și să termini în douăzeci de minute. Schimbarea nu e o mică optimizare. E forma întreagă a domeniului.

Motivul este transfer learning. Altcineva a ars deja un milion de ore de GPU învățând un model să înțeleagă imagini, sau engleză, sau cod. Iei modelul ăla, schimbi câteva straturi de lângă ieșire și îl fine-tunezi pe setul tău mic de date. Straturile inferioare ale rețelei, cele care au învățat să detecteze muchii sau token-uri de sintaxă, deja știu lucruri care se transferă gratis. Nu înveți modelul să vadă; îl înveți ce categorii te interesează pe tine.

Lecția 57 a parcurs antrenarea unei rețele mici de la cap la coadă. Lecția asta e versiunea care chiar se mapează pe ce faci la job zi de zi.

Intuiția: straturile învață la niveluri diferite de abstractizare

Straturile timpurii ale unei rețele convoluționale antrenate răspund la muchii și pete de culoare. Straturile din mijloc răspund la texturi și părți de obiecte. Straturile târzii răspund la concepte întregi: „arată ca un câine, arată ca o mașină”. Dacă vizualizezi filtrele stratului 1 al oricărui clasificator de imagini antrenat pe un dataset din lumea reală, arată în esență la fel: muchii orientate, gradiente de culoare. Trăsături vizuale generice.

Același lucru e valabil pentru modelele de limbaj. Straturile timpurii din transformer se ocupă de sintaxa locală, bigrame frecvente, morfologie. Straturile din mijloc urmăresc structura propoziției. Straturile de sus poartă semnal specific task-ului: pentru un model antrenat pe Wikipedia, asta înseamnă „e o entitate, ce fel de entitate, cum se raportează la alte entități”.

Transfer learning exploatează asimetria: straturile cu trăsături generice nu au nevoie de re-antrenare când schimbi task-ul, dar cele specifice task-ului au. Așa că păstrezi corpul modelului și înlocuiești capul.

În cod, șablonul e cam așa:

  1. Încarci un model pre-antrenat.
  2. Opțional, îngheți straturile inferioare (requires_grad = False).
  3. Înlocuiești clasificatorul final cu unul dimensionat pentru numărul tău de clase.
  4. Antrenezi cu o rată de învățare mică, ca să nu spulberi ce e deja acolo.

Hugging Face: hub-ul de modele

În 2026, când cineva spune „modelul” aproape întotdeauna înseamnă „un checkpoint pe Hugging Face”. Hub-ul găzduiește sute de mii de modele: orice variantă de BERT, orice variantă de Llama, orice fine-tune al lui Stable Diffusion, orice model audio și de viziune. Clientul Python e biblioteca transformers și companionii ei datasets, tokenizers, accelerate și peft.

Boilerplate-ul e identic între familiile de modele:

from transformers import AutoTokenizer, AutoModelForSequenceClassification

model_id = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForSequenceClassification.from_pretrained(
    model_id, num_labels=2,
)

Atât. Clasele Auto* se uită la configul checkpoint-ului și instanțiază clasa concretă potrivită. Poți schimba distilbert-base-uncased cu roberta-base sau bert-base-multilingual-cased și nu schimbi nicio linie de cod de antrenare. Ăsta e darul bibliotecii.

Un fine-tune real: DistilBERT pe un dataset de sentiment

Hai să fine-tunăm un clasificator de sentiment pe datasetul cu recenzii IMDB. Două clase, ~50.000 de recenzii. Dansul complet, de la cap la coadă.

from datasets import load_dataset
from transformers import (
    AutoTokenizer,
    AutoModelForSequenceClassification,
    TrainingArguments,
    Trainer,
    DataCollatorWithPadding,
)
import numpy as np
import evaluate

ds = load_dataset("imdb")
print(ds)
# DatasetDict({ train: 25000, test: 25000, unsupervised: 50000 })

# Sub-eșantionăm ca exemplul să ruleze in minute, nu ore
ds["train"] = ds["train"].shuffle(seed=42).select(range(2000))
ds["test"]  = ds["test"].shuffle(seed=42).select(range(500))

model_id = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_id)

def tokenize(batch):
    return tokenizer(batch["text"], truncation=True, max_length=256)

ds_tok = ds.map(tokenize, batched=True)
collator = DataCollatorWithPadding(tokenizer=tokenizer)

model = AutoModelForSequenceClassification.from_pretrained(
    model_id, num_labels=2,
)

accuracy = evaluate.load("accuracy")

def metrics(eval_pred):
    logits, labels = eval_pred
    preds = np.argmax(logits, axis=1)
    return accuracy.compute(predictions=preds, references=labels)

args = TrainingArguments(
    output_dir="./distilbert-imdb",
    eval_strategy="epoch",
    save_strategy="epoch",
    learning_rate=2e-5,
    per_device_train_batch_size=16,
    per_device_eval_batch_size=32,
    num_train_epochs=2,
    weight_decay=0.01,
    logging_steps=50,
    report_to="none",
)

trainer = Trainer(
    model=model,
    args=args,
    train_dataset=ds_tok["train"],
    eval_dataset=ds_tok["test"],
    tokenizer=tokenizer,
    data_collator=collator,
    compute_metrics=metrics,
)

trainer.train()
trainer.evaluate()

Pe un GPU T4 gratuit din Colab, asta se antrenează în vreo patru minute. Vei obține acuratețe în zona 80 de sus din prima. Ca să vezi ce se întâmplă, parcurgi părțile:

  • load_dataset("imdb") streamează datasetul de pe Hub. Primul apel îl pune în cache local; apelurile următoare sunt instant.
  • tokenizer(...) transformă textul în input_ids și attention_mask. max_length=256 trunchiază recenziile lungi; poți merge la 512 pentru acuratețe mai bună la cost mai mare.
  • DataCollatorWithPadding adaugă padding fiecărui batch până la cea mai lungă secvență din acel batch, în loc de maximul global. Speedup ieftin.
  • AutoModelForSequenceClassification schimbă capul de masked-language-modeling al lui DistilBERT pre-antrenat cu un cap de clasificare cu 2 clase, inițializat aleator. Corpul e pre-antrenat; doar capul e proaspăt.
  • Trainer împachetează boilerplate-ul din lecția 57: optimizatorul, scheduler-ul de learning rate, gradient accumulation, mixed precision, checkpointing, logging. Configurezi butoanele prin TrainingArguments și nu mai scrii bucla de antrenare singur.

Două epoci la lr=2e-5 e rețeta standard pentru fine-tuning-ul unui model BERT de mărime base. Mai sus de atât tinde să strice ponderile pre-antrenate; mai jos sub-antrenezi capul.

Salvare și reîncărcare

trainer.save_model("./distilbert-imdb-final")
tokenizer.save_pretrained("./distilbert-imdb-final")

# Foloseste-l mai tarziu:
from transformers import pipeline
clf = pipeline("sentiment-analysis", model="./distilbert-imdb-final")
clf("This movie was beautifully shot but the script dragged.")
# [{'label': 'LABEL_1', 'score': 0.78}]

Îl poți și împinge direct pe Hub:

trainer.push_to_hub("your-username/distilbert-imdb")

Asta face checkpoint-ul partajabil. Șase luni mai târziu, oricine (inclusiv tu) poate reîncărca exact modelul în trei linii.

PEFT și LoRA: fine-tune ieftin

Fine-tuning-ul complet al unui model de 7 miliarde de parametri are nevoie de hardware serios: fiecare pondere primește un gradient și o stare de optimizator, triplând ușor memoria. Parameter-efficient fine-tuning (PEFT) ocolește problema: în loc să actualizezi toate ponderile, antrenezi adaptoare mici antrenabile și îngheți restul.

Tehnica dominantă este LoRA: Low-Rank Adaptation. Pentru fiecare matrice de ponderi pe care vrei să o adaptezi, adaugi o pereche de matrice de rang scăzut A și B astfel încât W' = W + B @ A, unde A și B sunt minuscule (rang 8 sau 16) comparate cu W. Antrenezi doar A și B. Ponderile originale rămân înghețate.

Cifrele sunt dramatice. Fine-tuning-ul unui model de 7B cu LoRA poate antrena ~10 milioane de parametri în loc de 7 miliarde. Memoria scade de 5-10x. Calitatea rămâne competitivă.

Biblioteca PEFT de la Hugging Face transformă asta într-o schimbare de câteva linii:

from peft import LoraConfig, get_peft_model, TaskType

lora_cfg = LoraConfig(
    task_type=TaskType.SEQ_CLS,
    r=8,
    lora_alpha=16,
    lora_dropout=0.05,
    target_modules=["q_lin", "v_lin"],  # ce matrice se adapteaza
)

base = AutoModelForSequenceClassification.from_pretrained(model_id, num_labels=2)
model = get_peft_model(base, lora_cfg)
model.print_trainable_parameters()
# trainable params: 296,450 || all params: 67,251,202 || trainable%: 0.44

Bagi model-ul ăsta în același Trainer ca mai devreme. Faci fine-tune la < 0,5% din parametri și obții cea mai mare parte din acuratețe. Pentru LLM-uri ăsta e workflow-ul implicit acum: un GPU consumer de 24 GB poate face fine-tune unui model de 7B cu LoRA + cuantizare pe 4 biți, ceva care în urmă cu doi ani ar fi cerut un mic data center.

Viziune și multimodal: același șablon

Șablonul e identic pentru alte modalități, doar că folosești alte clase Auto*:

# Computer vision: fine-tune unui ViT pentru clasificare de imagini
from transformers import AutoImageProcessor, AutoModelForImageClassification
processor = AutoImageProcessor.from_pretrained("google/vit-base-patch16-224")
model = AutoModelForImageClassification.from_pretrained(
    "google/vit-base-patch16-224",
    num_labels=10, ignore_mismatched_sizes=True,
)

Pentru proiecte pur de viziune, biblioteca timm (acum parte din Hugging Face) e standardul de aur: orice backbone interesant de viziune, schimbabil în spatele unui singur API. Pentru task-uri multimodale (imagine + text), modele ca CLIP, SigLIP și familia LLaVA sunt toate la o distanță de from_pretrained.

Ar trebui să faci fine-tune deloc?

În 2026, întrebarea adesea nu e „fine-tune sau antrenez de la zero?”. E „fine-tune sau pur și simplu apelez un LLM cu un prompt bun?”. Asta e lecția următoare. Spoiler: pentru o grămadă de task-uri NLP, prompting-ul unui model găzduit te duce la 90% din drum în zero timp de antrenare, iar abia când prompting + retrieval clar nu sunt suficiente apelezi la fine-tuning.

Modelul mental:

  • Apelezi un LLM găzduit. Cel mai mic cost de experimentare, fără date de antrenare necesare, scump la scară.
  • Faci fine-tune unui model open-source cu LoRA. Cost mediu, efort mediu, deții ponderile, se potrivește strâns pe un task cunoscut.
  • Antrenezi de la zero. Rezervat pentru embedding-uri, serii temporale, sisteme de recomandare, modalități noi sau „avem un miliard de exemple și un buget de cercetare”.

Rândul din mijloc obișnuia să fie default-ul pentru orice proiect non-trivial. În 2026 e opțiunea de linia a doua, aleasă când prompting-ul clar nu e de ajuns.

Ce urmează

Acum ai parcurs stack-ul practic de deep learning din 2026: tensori și autograd (lecția 56), o buclă de antrenare scrisă de mână (lecția 57) și transfer learning cu Hugging Face (asta). Lecția următoare face zoom out și pune întrebarea mai mare: când e unealta potrivită un apel la un LLM, când e un model fine-tunat și când e un pipeline clasic de ML? E cea mai consecventă întrebare de design din domeniu chiar acum, iar costul de a o nimeri greșit e real.


Referințe: documentația Hugging Face transformers (https://huggingface.co/docs/transformers), documentația datasets (https://huggingface.co/docs/datasets), documentația PEFT (https://huggingface.co/docs/peft), Hu et al. „LoRA: Low-Rank Adaptation of Large Language Models” (2021), biblioteca timm (https://huggingface.co/docs/timm). Consultat 2026-05-01.

Caută