Salta el contingut

Transfer Learning i l'ecosistema Hugging Face

Fins ara hem vist com usar models ja entrenats (Tasques NLP) i com funcionen per dins (Xarxes neuronals i Transformers). Aquesta pàgina respon a una pregunta molt pràctica: com reutilitzar el coneixement d'un model ja entrenat per resoldre una tasca pròpia amb molt poques dades i temps de càlcul, i quines eines de l'ecosistema Hugging Face i Gradio fan possible portar això a producció.

Què és el Transfer Learning

El Transfer Learning (aprenentatge per transferència) és la tècnica de reutilitzar el coneixement après per un model sobre una tasca (habitualment amb un corpus enorme i molt de còmput) per resoldre una tasca diferent però relacionada, amb molt menys dades i temps d'entrenament propis. En PLN, aquest coneixement sol venir en dues formes:

  • Embeddings preentrenats (Word2Vec, GloVe, fastText — vegeu Evolució del PLN): vectors de paraules ja apresos sobre milers de milions de paraules, que es reutilitzen com a punt de partida en lloc de començar amb vectors aleatoris.
  • Models de llenguatge preentrenats (BERT, GPT, i tots els models del Hugging Face Hub — vegeu Evolució del PLN): xarxes senceres ja entrenades sobre corpus massius, que s'adapten a la tasca pròpia.

Hi ha dues estratègies principals per aplicar-ho:

  1. Extracció de característiques (feature extraction): es "congelen" (trainable=False) les capes preentrenades i només s'entrenen les capes noves que s'hi afegeixen a sobre. És ràpid, necessita poques dades pròpies i és la tècnica que es fa servir a l'exemple pràctic següent.
  2. Ajustament fi (fine-tuning): es descongelen (totes o algunes de) les capes preentrenades i es continua l'entrenament sobre les dades pròpies amb una taxa d'aprenentatge molt baixa, perquè el model s'especialitzi sense "oblidar" el que ja sabia. És més costós computacionalment però sol donar millor rendiment quan es disposa de prou dades pròpies.
graph TB
    subgraph Model preentrenat
        A[Corpus massiu] --> B[Embeddings o xarxa preentrenada]
    end
    B -->|Congelat: feature extraction| C[Noves capes entrenables]
    B -->|Descongelat: fine-tuning| D[Mateixes capes, LR molt baix]
    C --> E[Tasca propia amb poques dades]
    D --> E

Per què no entrenar sempre des de zero?

Entrenar uns embeddings o un Transformer des de zero requereix corpus de milers de milions de paraules i dies o setmanes de càlcul en GPU. La majoria d'organitzacions no tenen ni aquestes dades ni aquest pressupost. El Transfer Learning permet obtenir un rendiment molt bo amb un dataset propi de només uns milers d'exemples i minuts d'entrenament en un portàtil normal.

Cas pràctic: embeddings GloVe congelats + xarxa recurrent entrenable

L'exemple següent aplica feature extraction a una tasca real: classificar el sentiment (positiu/negatiu) de notícies financeres. S'hi reutilitzen els embeddings GloVe (vegeu Evolució del PLN, entrenats per Stanford sobre corpus generals) com a capa congelada, i s'hi afegeix a sobre una xarxa LSTM bidireccional (vegeu Xarxes neuronals recurrents) entrenada des de zero només amb el dataset propi.

import numpy as np
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.layers import Embedding, Bidirectional, LSTM, Dense
from tensorflow.keras.models import Sequential

# 1. Tokenitzar les frases del dataset propi (p. ex. notícies financeres etiquetades)
MAX_PARAULES = 10_000
MAX_LONGITUD = 100

tokenizer = Tokenizer(num_words=MAX_PARAULES, oov_token="<OOV>")
tokenizer.fit_on_texts(frases_entrenament)

sequencies = tokenizer.texts_to_sequences(frases_entrenament)
X = pad_sequences(sequencies, maxlen=MAX_LONGITUD, padding="post")

# 2. Carregar els vectors GloVe preentrenats (p. ex. glove.6B.100d.txt)
DIMENSIO_EMBEDDING = 100
index_embeddings = {}

with open("glove.6B.100d.txt", encoding="utf-8") as f:
    for linia in f:
        valors = linia.split()
        paraula = valors[0]
        vector = np.asarray(valors[1:], dtype="float32")
        index_embeddings[paraula] = vector

# 3. Construir la matriu d'embeddings només amb les paraules del nostre vocabulari
matriu_embeddings = np.zeros((MAX_PARAULES, DIMENSIO_EMBEDDING))
for paraula, index in tokenizer.word_index.items():
    if index >= MAX_PARAULES:
        continue
    vector = index_embeddings.get(paraula)
    if vector is not None:
        matriu_embeddings[index] = vector

# 4. Model: capa d'embeddings CONGELADA (transfer learning) + LSTM bidireccional entrenable
model = Sequential([
    Embedding(
        input_dim=MAX_PARAULES,
        output_dim=DIMENSIO_EMBEDDING,
        weights=[matriu_embeddings],
        trainable=False,           # <- aquí està el transfer learning: no reentrenem GloVe
        input_length=MAX_LONGITUD,
    ),
    Bidirectional(LSTM(64, return_sequences=False)),
    Dense(32, activation="relu"),
    Dense(1, activation="sigmoid"),  # sentiment binari: positiu/negatiu
])

model.compile(optimizer="adam", loss="binary_crossentropy", metrics=["accuracy"])
model.summary()

# 5. Entrenar només les capes noves (Bidirectional LSTM + Dense) durant unes poques èpoques
model.fit(X, y_entrenament, epochs=10, batch_size=32, validation_split=0.2)

Un cop entrenat, s'avalua amb classification_report i una matriu de confusió, exactament com qualsevol altre classificador. La conclusió pràctica és clara: el model aconsegueix una precisió notable amb un dataset propi relativament petit, perquè no ha calgut aprendre el significat de les paraules des de zero —ja el porta après de GloVe— sinó només com combinar-les per detectar sentiment financer.

Miniactivitat — AC5071/03/07 (CA3.7) — Transfer learning amb embeddings preentrenats

Reprodueix l'exemple anterior (o un d'anàleg) amb un dataset propi de la teva elecció (ressenyes de productes, tuits, titulars de premsa, etc.) i vectors GloVe o fastText. Compara els resultats (accuracy, temps d'entrenament) entrenant el mateix model amb: (a) la capa d'embeddings congelada (trainable=False) i (b) la mateixa capa descongelada (trainable=True). Explica en 5 línies quina opció ha donat millor resultat i per què creus que ha estat així.

L'ecosistema Hugging Face: Hub, comptes i gestió de models

El Hugging Face Hub (huggingface.co) és el repositori central de l'ecosistema: allotja més de mig milió de models preentrenats, milers de datasets i les Spaces (vegeu més avall), tot organitzat com a repositoris Git.

Configuració inicial:

# Crear un entorn virtual dedicat (recomanat abans d'instal·lar cap llibreria de l'ecosistema)
python -m venv venv-hf
source venv-hf/bin/activate  # Windows: venv-hf\Scripts\activate

pip install transformers datasets gradio huggingface_hub

# Iniciar sessió amb el compte de Hugging Face (cal un token generat a huggingface.co/settings/tokens)
huggingface-cli login

La primera vegada que s'executa un pipeline(...), el model es descarrega i es guarda en una cache local (per defecte a ~/.cache/huggingface/hub); les execucions posteriors el reutilitzen sense tornar-lo a descarregar. En entorns amb molts models o poc espai de disc, convé revisar aquesta cache periòdicament amb huggingface-cli scan-cache i esborrar el que ja no es faci servir amb huggingface-cli delete-cache.

Pujar un model propi al Hub: qualsevol repositori de model del Hub és, per dins, un repositori Git (amb Git LFS per als pesos binaris). Es pot pujar un model entrenat propi (per exemple, un classificador d'imatges de gossos i gats basat en resnet ajustat) seguint aquests passos:

# 1. Crear el repositori del model al Hub (des de la web o amb la CLI)
huggingface-cli repo create classificador-gossos-gats

# 2. Clonar-lo per SSH (cal tenir una clau SSH configurada al compte de Hugging Face)
git clone git@hf.co:usuari/classificador-gossos-gats
cd classificador-gossos-gats

# 3. Copiar-hi els fitxers del model (pesos, config.json, tokenizer si escau)
cp -r ../model-entrenat/* .

git lfs track "*.bin" "*.safetensors"
git add .
git commit -m "Primera versio del classificador de gossos i gats"
git push

Un cop pujat, qualsevol persona (o tu mateix, des d'un altre entorn) pot fer-lo servir directament amb un pipeline, exactament igual que amb els models oficials:

from transformers import pipeline

classificador = pipeline("image-classification", model="usuari/classificador-gossos-gats")
resultat = classificador("foto_mascota.jpg")

for r in resultat:
    print(f"{r['label']}: {r['score']:.2%}")

Hugging Face Datasets

La llibreria datasets permet carregar, inspeccionar i transformar conjunts de dades del Hub (o propis) amb una API senzilla, sense haver de gestionar manualment fitxers CSV o Parquet:

from datasets import load_dataset
import pandas as pd

# Carregar un dataset públic del Hub (p. ex. el clàssic dataset del Titanic)
dataset = load_dataset("poumiquel/titanic")

print(dataset)               # Mostra els splits disponibles (train, test...)
print(dataset["train"][0])   # Primera fila del split d'entrenament

# Convertir-lo a pandas per explorar-lo amb les eines habituals
df = dataset["train"].to_pandas()
df.info()
df.describe()

També es pot crear i publicar un dataset propi al Hub (per exemple, el corpus de frases en català anotades a la miniactivitat AC5071/03/02) amb Dataset.push_to_hub("usuari/nom-del-dataset"), quedant disponible per a qualsevol projecte futur amb el mateix load_dataset.

Gradio: interfícies ràpides per a demos de PLN

Gradio és una llibreria Python que genera una interfície web interactiva per a qualsevol funció Python (típicament, un model de ML) amb molt poc codi. És l'eina estàndard per publicar demos ràpides al Hub com a Hugging Face Spaces.

Interfície simple (un xatbot bàsic):

import gradio as gr
from transformers import pipeline

generador = pipeline("text-generation", model="gpt2")

def respon(missatge, historial):
    resposta = generador(missatge, max_new_tokens=60, num_return_sequences=1)
    return resposta[0]["generated_text"]

demo = gr.ChatInterface(fn=respon, title="Xatbot de demostracio")
demo.launch()

Interfície amb Blocks (traductor bidireccional amb selector de direcció): quan una demo necessita més d'un control (per exemple, triar la direcció de la traducció), gr.Blocks permet compondre lliurement diversos components:

import gradio as gr
from transformers import pipeline

# Un model Helsinki-NLP per cada direcció de traducció
traductors = {
    "Català → Anglès": pipeline("translation", model="Helsinki-NLP/opus-mt-ca-en"),
    "Anglès → Català": pipeline("translation", model="Helsinki-NLP/opus-mt-en-ca"),
}

def tradueix(text, direccio):
    if not text.strip():
        return ""
    return traductors[direccio](text)[0]["translation_text"]

with gr.Blocks() as demo:
    gr.Markdown("## Traductor bidireccional Catala-Angles")
    direccio = gr.Radio(list(traductors.keys()), value="Català → Anglès", label="Direccio")
    entrada = gr.Textbox(label="Text original", lines=4)
    boto = gr.Button("Tradueix")
    sortida = gr.Textbox(label="Traduccio", lines=4)

    boto.click(fn=tradueix, inputs=[entrada, direccio], outputs=sortida)

demo.launch()

Autenticació bàsica: per no deixar una demo completament oberta (per exemple, mentre es prova abans de publicar-la), launch() accepta credencials senzilles d'usuari/contrasenya:

demo.launch(auth=("iabd", "iabd"))  # només accessible amb aquest usuari i contrasenya

L'autenticació bàsica de Gradio no substitueix la seguretat en producció

Aquest mecanisme és útil per a demos internes o de classe, però les credencials viatgen en text pla si la connexió no és HTTPS i no ofereix gestió d'usuaris real. Per a una Space pública amb dades sensibles cal una solució d'autenticació més robusta (OAuth, com el que fa servir aquest mateix repositori — vegeu l'arrel del monorepo).

Notebooks pràctics a Moodle

Els notebooks amb els exercicis complets de Hugging Face (traducció, Stable Diffusion, pujada de model propi, Datasets del Titanic), Gradio (xatbot, traductor amb Blocks, autenticació) i el Transfer Learning amb GloVe estan disponibles a l'aula Moodle del mòdul.