Objetivo

Dominar o ecossistema Hugging Face para carregar, usar e adaptar modelos pré-treinados de estado da arte. Aprender a usar pipelines, tokenizers, AutoModel e o Hub para resolver tarefas reais de NLP sem treinar do zero.


1. O Ecossistema Hugging Face

Hugging Face é a plataforma que democratizou o acesso a LLMs. Com poucas linhas de código você acessa modelos que levaram meses e milhões de dólares para treinar.

Componentes principais:
  transformers  → modelos e tokenizers pré-treinados
  datasets      → datasets prontos para uso
  accelerate    → treinamento distribuído
  peft          → LoRA e outros métodos eficientes
  tokenizers    → tokenização rápida em Rust
  hub           → repositório de modelos (huggingface.co/models)

2. Instalação e Configuração

# Verificar instalação
# pip install transformers datasets accelerate
# pip install sentencepiece   # para alguns tokenizers

import torch
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings("ignore")

torch.manual_seed(42)
np.random.seed(42)
sns.set_theme(style="whitegrid")

device = torch.device("cuda" if torch.cuda.is_available()
                       else "cpu")
print(f"Dispositivo: {device}")

# Verificar se transformers está disponível
try:
    import transformers
    print(f"Transformers versão: {transformers.__version__}")
    HF_DISPONIVEL = True
except ImportError:
    print("transformers não instalado.")
    print("Execute: pip install transformers")
    HF_DISPONIVEL = False

3. Pipeline API — A Forma Mais Simples

A Pipeline API encapsula tudo em uma única chamada: tokenização, modelo, pós-processamento.

if HF_DISPONIVEL:
    from transformers import pipeline

    print("=" * 55)
    print("PIPELINE API — Uso mais simples possível")
    print("=" * 55)

    # ── Análise de Sentimentos ────────────────────────────
    print("\n1. Análise de Sentimentos:")
    print("   (carregando distilbert-base-uncased-finetuned-sst-2...)")

    classificador = pipeline(
        "sentiment-analysis",
        model="distilbert-base-uncased-finetuned-sst-2-english",
        device=0 if torch.cuda.is_available() else -1
    )

    textos_sentimento = [
        "I love this product, it's amazing!",
        "This is terrible, worst purchase ever.",
        "It's okay, nothing special.",
        "Absolutely fantastic experience!",
    ]

    resultados_sent = classificador(textos_sentimento)
    print(f"\n   {'Texto':<45} {'Label':<12} {'Score'}")
    print(f"   {'-'*75}")
    for texto, res in zip(textos_sentimento, resultados_sent):
        print(f"   {texto[:45]:<45} "
              f"{res['label']:<12} "
              f"{res['score']:.4f}")

    # ── Geração de Texto ──────────────────────────────────
    print("\n2. Geração de Texto com GPT-2:")
    print("   (carregando gpt2...)")

    gerador = pipeline(
        "text-generation",
        model="gpt2",
        device=0 if torch.cuda.is_available() else -1
    )

    prompts = [
        "The future of artificial intelligence",
        "In machine learning, the most important",
    ]

    for prompt in prompts:
        resultado = gerador(
            prompt,
            max_new_tokens=30,
            num_return_sequences=1,
            temperature=0.8,
            do_sample=True,
            pad_token_id=50256
        )
        texto_gerado = resultado[0]["generated_text"]
        print(f"\n   Prompt: '{prompt}'")
        print(f"   Gerado: '{texto_gerado}'")

    # ── NER (Named Entity Recognition) ───────────────────
    print("\n3. NER — Reconhecimento de Entidades:")
    print("   (carregando dbmdz/bert-large-cased-finetuned-conll03...)")

    ner = pipeline(
        "ner",
        model="dbmdz/bert-large-cased-finetuned-conll03-english",
        aggregation_strategy="simple",
        device=0 if torch.cuda.is_available() else -1
    )

    texto_ner = ("Apple was founded by Steve Jobs "
                  "in Cupertino, California in 1976.")
    entidades = ner(texto_ner)
    print(f"\n   Texto: '{texto_ner}'")
    print(f"\n   Entidades encontradas:")
    for ent in entidades:
        print(f"     '{ent['word']}' → "
              f"{ent['entity_group']} "
              f"(conf: {ent['score']:.3f})")

    # ── Question Answering ────────────────────────────────
    print("\n4. Question Answering:")
    print("   (carregando deepset/roberta-base-squad2...)")

    qa = pipeline(
        "question-answering",
        model="deepset/roberta-base-squad2",
        device=0 if torch.cuda.is_available() else -1
    )

    contexto = ("PyTorch is an open source machine learning "
                 "framework developed by Meta AI. It provides "
                 "automatic differentiation and GPU acceleration. "
                 "PyTorch was first released in 2016.")

    perguntas = [
        "Who developed PyTorch?",
        "When was PyTorch released?",
        "What does PyTorch provide?",
    ]

    print(f"\n   Contexto: '{contexto[:60]}...'")
    for pergunta in perguntas:
        resposta = qa(question=pergunta, context=contexto)
        print(f"\n   Q: {pergunta}")
        print(f"   A: {resposta['answer']} "
              f"(conf: {resposta['score']:.3f})")

    # ── Sumarização ───────────────────────────────────────
    print("\n5. Sumarização:")
    print("   (carregando facebook/bart-large-cnn...)")

    sumarizador = pipeline(
        "summarization",
        model="facebook/bart-large-cnn",
        device=0 if torch.cuda.is_available() else -1
    )

    texto_longo = """
    Machine learning is a subset of artificial intelligence that
    enables systems to learn from data and improve from experience
    without being explicitly programmed. It focuses on developing
    computer programs that can access data and use it to learn for
    themselves. The process begins with observations or data, such
    as examples, direct experience, or instruction, so that computers
    can learn to make better decisions in the future. The primary
    aim is to allow computers to learn automatically without human
    intervention and adjust actions accordingly.
    """

    resumo = sumarizador(
        texto_longo,
        max_length=60,
        min_length=20,
        do_sample=False
    )
    print(f"\n   Original (truncado): '{texto_longo[:80].strip()}...'")
    print(f"   Resumo: '{resumo[0]['summary_text']}'")

else:
    print("\nSimulando saídas do pipeline (sem transformers):")
    print("""
  Sentiment:  "I love this!" → POSITIVE (0.9998)
  Generation: "The future of AI..." → "...will transform every industry"
  NER:        "Apple" → ORG, "Steve Jobs" → PER, "Cupertino" → LOC
  QA:         "Who developed PyTorch?" → "Meta AI"
  Summary:    "Machine learning is..." → "ML enables systems to learn"
    """)

4. Tokenizers em Detalhe

if HF_DISPONIVEL:
    from transformers import AutoTokenizer

    print("=" * 55)
    print("TOKENIZERS — Entendendo a Tokenização")
    print("=" * 55)

    tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")

    # Propriedades do tokenizer
    print(f"\nTokenizer BERT:")
    print(f"  Tamanho do vocabulário: {tokenizer.vocab_size:,}")
    print(f"  Tokens especiais: {tokenizer.all_special_tokens}")
    print(f"  [CLS] id: {tokenizer.cls_token_id}")
    print(f"  [SEP] id: {tokenizer.sep_token_id}")
    print(f"  [MASK] id: {tokenizer.mask_token_id}")
    print(f"  [PAD] id: {tokenizer.pad_token_id}")

    # Tokenizar texto simples
    texto = "The quick brown fox jumps over the lazy dog."
    tokens = tokenizer.tokenize(texto)
    ids    = tokenizer.encode(texto)
    print(f"\nTexto:  '{texto}'")
    print(f"Tokens: {tokens}")
    print(f"IDs:    {ids}")

    # Decodificar de volta
    texto_decodificado = tokenizer.decode(ids,
                                            skip_special_tokens=True)
    print(f"Decodificado: '{texto_decodificado}'")

    # Tokenização em batch com padding
    textos = [
        "Hello world",
        "This is a much longer sentence with more tokens",
        "Short",
    ]

    encoded = tokenizer(
        textos,
        padding=True,         # pad para mesmo comprimento
        truncation=True,      # truncar se muito longo
        max_length=20,
        return_tensors="pt"   # retornar tensors PyTorch
    )

    print(f"\nBatch tokenizado:")
    print(f"  input_ids shape:      {encoded['input_ids'].shape}")
    print(f"  attention_mask shape: {encoded['attention_mask'].shape}")
    print(f"\n  input_ids:")
    for i, (texto, ids) in enumerate(zip(textos, encoded['input_ids'])):
        print(f"    '{texto[:25]}': {ids.tolist()}")
    print(f"\n  attention_mask (1=real, 0=padding):")
    for i, (texto, mask) in enumerate(zip(textos, encoded['attention_mask'])):
        print(f"    '{texto[:25]}': {mask.tolist()}")

    # Tokenizar par de sentenças (BERT)
    sentenca_a = "What is the capital of France?"
    sentenca_b = "Paris is the capital of France."

    encoded_par = tokenizer(
        sentenca_a, sentenca_b,
        return_token_type_ids=True,
        return_tensors="pt"
    )

    tokens_par = tokenizer.convert_ids_to_tokens(
        encoded_par['input_ids'][0]
    )
    tipos     = encoded_par['token_type_ids'][0].tolist()

    print(f"\nPar de sentenças (BERT NSP):")
    print(f"  Token types (0=A, 1=B):")
    for tok, tipo in zip(tokens_par, tipos):
        print(f"    {tok:<20} tipo={tipo}")

    # Tokenizadores de subpalavras
    print(f"\nTokenização de subpalavras:")
    palavras_especiais = [
        "tokenization", "unbelievable",
        "artificialintelligence", "supercalifragilistic"
    ]
    for palavra in palavras_especiais:
        tokens_p = tokenizer.tokenize(palavra)
        print(f"  '{palavra}' → {tokens_p}")

else:
    print("\nSimulando tokenização:")
    print("""
  Vocabulário BERT: 30.522 tokens
  'tokenization' → ['token', '##iza', '##tion']
  'unbelievable' → ['un', '##bel', '##iev', '##able']
    """)

5. AutoModel — Carregando Modelos para Diferentes Tarefas

if HF_DISPONIVEL:
    from transformers import (
        AutoTokenizer,
        AutoModel,
        AutoModelForSequenceClassification,
        AutoModelForTokenClassification,
        AutoModelForQuestionAnswering,
        AutoModelForCausalLM,
        AutoModelForSeq2SeqLM,
    )

    print("=" * 55)
    print("AutoModel — Diferentes Cabeças para Cada Tarefa")
    print("=" * 55)
    print("""
  AutoModel                           → embeddings brutos
  AutoModelForSequenceClassification  → classificação de texto
  AutoModelForTokenClassification     → NER, POS tagging
  AutoModelForQuestionAnswering       → QA extrativa
  AutoModelForCausalLM                → geração autoregressiva
  AutoModelForSeq2SeqLM               → tradução, sumarização
    """)

    # Carregar BERT para extração de embeddings
    print("Carregando BERT para extração de embeddings...")
    tokenizer_bert = AutoTokenizer.from_pretrained(
        "bert-base-uncased"
    )
    modelo_bert = AutoModel.from_pretrained(
        "bert-base-uncased"
    ).to(device)
    modelo_bert.eval()

    print(f"  Parâmetros: "
          f"{sum(p.numel() for p in modelo_bert.parameters()):,}")

    # Extrair embeddings
    frases = [
        "The bank is on the river bank.",   # banco ambíguo
        "I deposited money at the bank.",
        "The cat sat on the mat.",
        "The dog sat on the mat.",
    ]

    embeddings = []
    with torch.no_grad():
        for frase in frases:
            inputs  = tokenizer_bert(
                frase, return_tensors="pt",
                truncation=True, max_length=64
            ).to(device)
            outputs = modelo_bert(**inputs)

            # CLS embedding: representação da frase inteira
            cls_emb = outputs.last_hidden_state[:, 0, :].cpu()
            embeddings.append(cls_emb.squeeze().numpy())

    embeddings = np.array(embeddings)

    # Calcular similaridades
    from sklearn.metrics.pairwise import cosine_similarity
    sim_matrix = cosine_similarity(embeddings)

    fig, axes = plt.subplots(1, 2, figsize=(14, 5))

    # Heatmap de similaridades
    labels_frases = [f[:35] + "..." for f in frases]
    sns.heatmap(
        sim_matrix,
        xticklabels=labels_frases,
        yticklabels=labels_frases,
        annot=True, fmt=".3f",
        cmap="Blues", vmin=0.7, vmax=1.0,
        ax=axes[0]
    )
    axes[0].set_title("Similaridade Cosseno entre Frases\n(BERT embeddings)")
    axes[0].tick_params(axis="x", rotation=45)

    # PCA dos embeddings
    from sklearn.decomposition import PCA
    pca    = PCA(n_components=2)
    coords = pca.fit_transform(embeddings)
    cores_emb = ["steelblue", "steelblue",
                  "coral", "coral"]

    for i, (coord, frase, cor) in enumerate(
        zip(coords, frases, cores_emb)
    ):
        axes[1].scatter(coord[0], coord[1], c=cor,
                         s=150, zorder=3)
        axes[1].annotate(
            f"F{i+1}", coord,
            xytext=(5, 5), textcoords="offset points",
            fontsize=10
        )
    axes[1].set_title("PCA dos Embeddings BERT\n"
                       "Azul=banco, Coral=gato/cachorro")
    axes[1].set_xlabel("PC1")
    axes[1].set_ylabel("PC2")

    plt.tight_layout()
    plt.show()

    print("\nObservação semântica:")
    print(f"  sim('banco do rio', 'banco financeiro') = "
          f"{sim_matrix[0,1]:.4f}")
    print(f"  sim('gato no tapete', 'cachorro no tapete') = "
          f"{sim_matrix[2,3]:.4f}")
    print(f"  → Mesmo 'banco' em contextos diferentes tem "
          f"embeddings diferentes!")

else:
    print("\nSimulando extração de embeddings BERT:")
    print("""
  BERT: 110M parâmetros
  Embedding shape: (1, 768)  [CLS token]
  sim('banco rio', 'banco financeiro') ≈ 0.821
  sim('gato tapete', 'cachorro tapete') ≈ 0.934
    """)

6. Fine-tuning com Hugging Face Trainer

if HF_DISPONIVEL:
    from transformers import (
        AutoTokenizer,
        AutoModelForSequenceClassification,
        TrainingArguments,
        Trainer,
        DataCollatorWithPadding,
    )
    from datasets import Dataset as HFDataset
    import evaluate

    print("=" * 55)
    print("FINE-TUNING COM HUGGING FACE TRAINER")
    print("=" * 55)

    # Criar dataset sintético de classificação
    np.random.seed(42)
    n_exemplos = 1000

    palavras_pos = ["great", "excellent", "amazing",
                     "wonderful", "fantastic", "love",
                     "perfect", "best", "awesome"]
    palavras_neg = ["terrible", "awful", "horrible",
                     "worst", "hate", "disgusting",
                     "bad", "poor", "useless"]
    palavras_neu = ["product", "received", "ordered",
                     "used", "bought", "tried", "the",
                     "is", "was", "and", "very", "quite"]

    textos_ft, labels_ft = [], []
    for _ in range(n_exemplos):
        label = np.random.randint(0, 2)
        if label == 1:
            n_p, n_n = np.random.randint(2, 5), np.random.randint(0, 2)
        else:
            n_p, n_n = np.random.randint(0, 2), np.random.randint(2, 5)
        n_neu = np.random.randint(4, 10)

        palavras = (
            list(np.random.choice(palavras_pos, n_p)) +
            list(np.random.choice(palavras_neg, n_n)) +
            list(np.random.choice(palavras_neu, n_neu))
        )
        np.random.shuffle(palavras)
        textos_ft.append(" ".join(palavras))
        labels_ft.append(label)

    # Converter para Hugging Face Dataset
    dados_dict = {"text": textos_ft, "label": labels_ft}
    dataset_hf = HFDataset.from_dict(dados_dict)

    # Dividir
    split = dataset_hf.train_test_split(
        test_size=0.2, seed=42
    )
    ds_treino_hf = split["train"]
    ds_teste_hf  = split["test"]

    print(f"\nDataset:")
    print(f"  Treino: {len(ds_treino_hf)} exemplos")
    print(f"  Teste:  {len(ds_teste_hf)} exemplos")

    # Tokenizer e tokenização
    tokenizer_ft = AutoTokenizer.from_pretrained(
        "distilbert-base-uncased"
    )

    def tokenizar_funcao(exemplos):
        return tokenizer_ft(
            exemplos["text"],
            truncation=True,
            max_length=64,
            padding=False   # DataCollator faz padding dinâmico
        )

    ds_treino_tokenizado = ds_treino_hf.map(
        tokenizar_funcao, batched=True
    )
    ds_teste_tokenizado  = ds_teste_hf.map(
        tokenizar_funcao, batched=True
    )

    # Carregar modelo para classificação
    print("\nCarregando DistilBERT para classificação...")
    modelo_ft = AutoModelForSequenceClassification.from_pretrained(
        "distilbert-base-uncased",
        num_labels=2,
        id2label={0: "NEGATIVE", 1: "POSITIVE"},
        label2id={"NEGATIVE": 0, "POSITIVE": 1}
    ).to(device)

    # Data collator
    data_collator = DataCollatorWithPadding(
        tokenizer=tokenizer_ft
    )

    # Configurar treinamento
    training_args = TrainingArguments(
        output_dir="./resultados_ft",
        num_train_epochs=3,
        per_device_train_batch_size=32,
        per_device_eval_batch_size=64,
        warmup_steps=100,
        weight_decay=0.01,
        logging_dir="./logs",
        logging_steps=50,
        evaluation_strategy="epoch",
        save_strategy="epoch",
        load_best_model_at_end=True,
        metric_for_best_model="accuracy",
        report_to="none",   # não usar wandb/tensorboard
    )

    # Função de métricas
    metric_acc = evaluate.load("accuracy")
    metric_f1  = evaluate.load("f1")

    def calcular_metricas(eval_pred):
        logits, labels = eval_pred
        preds = logits.argmax(axis=-1)
        acc  = metric_acc.compute(
            predictions=preds, references=labels
        )
        f1   = metric_f1.compute(
            predictions=preds, references=labels,
            average="weighted"
        )
        return {"accuracy": acc["accuracy"],
                "f1": f1["f1"]}

    # Treinar com Trainer
    trainer = Trainer(
        model=modelo_ft,
        args=training_args,
        train_dataset=ds_treino_tokenizado,
        eval_dataset=ds_teste_tokenizado,
        tokenizer=tokenizer_ft,
        data_collator=data_collator,
        compute_metrics=calcular_metricas,
    )

    print("\nIniciando fine-tuning com Hugging Face Trainer...")
    trainer.train()

    # Avaliar
    resultados_eval = trainer.evaluate()
    print(f"\nResultados no teste:")
    print(f"  Acurácia: {resultados_eval['eval_accuracy']:.4f}")
    print(f"  F1:       {resultados_eval['eval_f1']:.4f}")
    print(f"  Loss:     {resultados_eval['eval_loss']:.4f}")

    # Salvar modelo
    trainer.save_model("./modelo_sentimento_ft")
    tokenizer_ft.save_pretrained("./modelo_sentimento_ft")
    print("\nModelo salvo em './modelo_sentimento_ft'")

else:
    print("\nSimulando fine-tuning com Trainer:")
    print("""
  Dataset: 800 treino, 200 teste
  Épocas: 3
  Resultado esperado:
    Acurácia: ~0.94
    F1: ~0.94
    Loss: ~0.18
    """)

7. Sentence Transformers — Embeddings de Alta Qualidade

if HF_DISPONIVEL:
    from transformers import AutoTokenizer, AutoModel

    print("=" * 55)
    print("SENTENCE TRANSFORMERS — Embeddings de Sentenças")
    print("=" * 55)

    def mean_pooling(output, attention_mask):
        """
        Mean pooling sobre os tokens com máscara de atenção.
        Mais eficaz que apenas usar o [CLS] token.
        """
        token_embs = output.last_hidden_state
        mask_exp   = attention_mask.unsqueeze(-1).expand(
            token_embs.size()
        ).float()
        soma       = (token_embs * mask_exp).sum(1)
        n_tokens   = mask_exp.sum(1).clamp(min=1e-9)
        return soma / n_tokens

    def obter_embeddings(textos: list,
                          modelo, tokenizer,
                          device: torch.device,
                          batch_size: int = 16) -> np.ndarray:
        """Obtém embeddings para lista de textos."""
        todos_embs = []

        for i in range(0, len(textos), batch_size):
            batch_textos = textos[i:i+batch_size]
            encoded      = tokenizer(
                batch_textos,
                padding=True, truncation=True,
                max_length=128,
                return_tensors="pt"
            ).to(device)

            with torch.no_grad():
                output = modelo(**encoded)

            embs = mean_pooling(
                output, encoded["attention_mask"]
            )

            # Normalizar L2
            embs = torch.nn.functional.normalize(embs, p=2,
                                                    dim=1)
            todos_embs.append(embs.cpu().numpy())

        return np.vstack(todos_embs)

    # Carregar modelo de sentence embeddings
    print("\nCarregando modelo de sentence embeddings...")
    tokenizer_se = AutoTokenizer.from_pretrained(
        "sentence-transformers/paraphrase-MiniLM-L6-v2"
    )
    modelo_se = AutoModel.from_pretrained(
        "sentence-transformers/paraphrase-MiniLM-L6-v2"
    ).to(device)
    modelo_se.eval()

    # Exemplos de use cases
    print("\n1. Busca Semântica (Semantic Search):")

    corpus_busca = [
        "How to cook pasta?",
        "Best restaurants in New York",
        "Python programming tutorial",
        "Machine learning algorithms explained",
        "How to prepare spaghetti bolognese",
        "Introduction to deep learning",
        "Top 10 NYC dining spots",
        "Neural network architecture",
    ]

    queries = [
        "cooking pasta recipe",
        "deep learning tutorial",
        "restaurants to eat in Manhattan",
    ]

    embs_corpus = obter_embeddings(
        corpus_busca, modelo_se, tokenizer_se, device
    )
    embs_query  = obter_embeddings(
        queries, modelo_se, tokenizer_se, device
    )

    sim_busca = cosine_similarity(embs_query, embs_corpus)

    for query, sims in zip(queries, sim_busca):
        top_k_idx = sims.argsort()[::-1][:3]
        print(f"\n  Query: '{query}'")
        print(f"  Top 3 resultados:")
        for idx in top_k_idx:
            print(f"    [{sims[idx]:.3f}] '{corpus_busca[idx]}'")

    print("\n2. Detecção de Paráfrase:")
    pares_parafrase = [
        ("The weather is nice today.",
         "It's a beautiful day outside."),
        ("I love machine learning.",
         "I hate cooking food."),
        ("Python is a programming language.",
         "Python is used for coding."),
    ]

    for t1, t2 in pares_parafrase:
        e1 = obter_embeddings([t1], modelo_se,
                                tokenizer_se, device)
        e2 = obter_embeddings([t2], modelo_se,
                                tokenizer_se, device)
        sim = cosine_similarity(e1, e2)[0][0]
        eh_parafrase = "✓ Paráfrase" if sim > 0.75 else "✗ Diferente"
        print(f"\n  T1: '{t1[:40]}'")
        print(f"  T2: '{t2[:40]}'")
        print(f"  Sim: {sim:.4f} → {eh_parafrase}")

else:
    print("\nSimulando Sentence Transformers:")
    print("""
  Busca Semântica:
    Query: 'cooking pasta recipe'
    Top: 'How to cook pasta?' (0.89), 'How to prepare spaghetti' (0.84)

  Paráfrase:
    'The weather is nice' / 'It's a beautiful day' → sim=0.84 ✓
    'I love ML' / 'I hate cooking' → sim=0.12 ✗
    """)

8. Geração Avançada com GPT-2

if HF_DISPONIVEL:
    from transformers import (
        GPT2LMHeadModel,
        GPT2Tokenizer,
        set_seed,
    )

    print("=" * 55)
    print("GERAÇÃO AVANÇADA COM GPT-2")
    print("=" * 55)

    print("\nCarregando GPT-2...")
    tokenizer_gpt2 = GPT2Tokenizer.from_pretrained("gpt2")
    modelo_gpt2    = GPT2LMHeadModel.from_pretrained(
        "gpt2"
    ).to(device)
    modelo_gpt2.eval()

    # Adicionar pad token
    tokenizer_gpt2.pad_token = tokenizer_gpt2.eos_token

    n_params_gpt2 = sum(
        p.numel() for p in modelo_gpt2.parameters()
    )
    print(f"GPT-2 small: {n_params_gpt2/1e6:.0f}M parâmetros")

    prompt = "Artificial intelligence will"

    # Diferentes estratégias de decodificação
    print(f"\nPrompt: '{prompt}'")
    print("\nEstratégias de decodificação:")
    print("-" * 55)

    input_ids = tokenizer_gpt2.encode(
        prompt, return_tensors="pt"
    ).to(device)

    estrategias = {
        "Greedy (determinístico)": dict(
            max_new_tokens=30,
            do_sample=False
        ),
        "Beam Search (k=5)": dict(
            max_new_tokens=30,
            num_beams=5,
            no_repeat_ngram_size=2,
            do_sample=False
        ),
        "Sampling (temp=0.7)": dict(
            max_new_tokens=30,
            do_sample=True,
            temperature=0.7,
            top_k=50
        ),
        "Top-p / Nucleus (p=0.9)": dict(
            max_new_tokens=30,
            do_sample=True,
            top_p=0.9,
            top_k=0
        ),
        "Top-k + Top-p (k=50, p=0.9)": dict(
            max_new_tokens=30,
            do_sample=True,
            top_k=50,
            top_p=0.9,
            temperature=0.8
        ),
    }

    set_seed(42)
    for nome, params in estrategias.items():
        output = modelo_gpt2.generate(
            input_ids,
            pad_token_id=tokenizer_gpt2.eos_token_id,
            **params
        )
        texto = tokenizer_gpt2.decode(
            output[0], skip_special_tokens=True
        )
        # Mostrar apenas a parte gerada
        gerado = texto[len(prompt):]
        print(f"\n  {nome}:")
        print(f"    {prompt}[{gerado.strip()}]")

    # Gerar múltiplas sequências
    print("\nGeração múltipla (5 sequências diferentes):")
    outputs = modelo_gpt2.generate(
        input_ids,
        max_new_tokens=20,
        do_sample=True,
        temperature=1.0,
        top_k=50,
        num_return_sequences=5,
        pad_token_id=tokenizer_gpt2.eos_token_id
    )
    for i, out in enumerate(outputs):
        texto = tokenizer_gpt2.decode(out, skip_special_tokens=True)
        gerado = texto[len(prompt):]
        print(f"  {i+1}: {prompt}[{gerado.strip()[:50]}]")

else:
    print("\nSimulando GPT-2:")
    print("""
  Prompt: 'Artificial intelligence will'

  Greedy:      '...change the way we work and live forever'
  Beam Search: '...transform every aspect of human society'
  Sampling:    '...reshape industries and create new opportunities'
  Top-p:       '...become increasingly important in our lives'
    """)

9. Hub do Hugging Face

print("\n" + "=" * 55)
print("HUB DO HUGGING FACE — Navegando Modelos")
print("=" * 55)
print("""
O Hub (huggingface.co/models) tem +500.000 modelos.

Como encontrar o modelo certo:

  Por tarefa:
    text-classification    → BERT, RoBERTa, DistilBERT
    text-generation        → GPT-2, GPT-J, LLaMA, Mistral
    translation            → Helsinki-NLP/opus-mt-*
    summarization          → facebook/bart-large-cnn
    question-answering     → deepset/roberta-base-squad2
    sentence-similarity    → sentence-transformers/*
    zero-shot-classification → facebook/bart-large-mnli
    fill-mask              → bert-base-uncased
    token-classification   → dslim/bert-base-NER

  Por idioma:
    Inglês:    bert-base-uncased, roberta-base
    Português: neuralmind/bert-base-portuguese-cased
               unicamp-dl/ptt5-base-portuguese-vocab
    Multilingual: bert-base-multilingual-cased (104 idiomas)
                  xlm-roberta-base

  Por tamanho (parâmetros):
    Tiny:    distilbert (~66M) — rápido, menos preciso
    Base:    bert-base (~110M) — equilíbrio
    Large:   bert-large (~340M) — mais preciso, mais lento
    XL/XXL:  GPT-2-XL (1.5B), GPT-J (6B), LLaMA (7B-70B)

  Por eficiência:
    distilbert-base → 40% menor que BERT, 97% performance
    albert-base-v2  → parâmetros compartilhados, muito eficiente
    mobile-bert     → otimizado para mobile/edge

Modelos em português notáveis:
  neuralmind/bert-base-portuguese-cased
  → BERT treinado em Wikipedia PT + BrWaC
  → Bom para: NER, classificação, QA

  unicamp-dl/ptt5-base-portuguese-vocab
  → T5 adaptado para PT-BR
  → Bom para: geração, sumarização, tradução

  pierreguillou/bert-base-cased-squad-v1.1-portuguese
  → Fine-tuned em QA em PT
""")

if HF_DISPONIVEL:
    # Zero-shot classification (sem fine-tuning para novas classes!)
    from transformers import pipeline as hf_pipeline

    print("Zero-Shot Classification:")
    print("  (classifica em categorias nunca vistas no treino)")

    zero_shot = hf_pipeline(
        "zero-shot-classification",
        model="facebook/bart-large-mnli",
        device=0 if torch.cuda.is_available() else -1
    )

    textos_zs = [
        "The stock market crashed today.",
        "Scientists discover new treatment for cancer.",
        "The football team won the championship.",
    ]
    categorias = ["finance", "health", "sports", "technology"]

    for texto in textos_zs:
        resultado = zero_shot(texto, categorias)
        top_label = resultado["labels"][0]
        top_score = resultado["scores"][0]
        print(f"\n  Texto: '{texto[:45]}...'")
        print(f"  Classe: {top_label} (conf: {top_score:.3f})")

else:
    print("""
  Zero-Shot (sem fine-tuning):
    'Stock market crashed' → finance (0.93)
    'New cancer treatment' → health (0.91)
    'Football championship' → sports (0.97)
    """)

10. Exemplo Completo: Sistema de NLP em Produção

print("\n" + "=" * 60)
print("SISTEMA NLP COMPLETO EM PRODUÇÃO")
print("=" * 60)

if HF_DISPONIVEL:
    from transformers import pipeline as hf_pipeline

    class SistemaNLPProducao:
        """
        Sistema NLP completo usando múltiplos modelos HF.
        Simula um sistema de análise de feedback de clientes.
        """

        def __init__(self):
            print("Inicializando sistema NLP...")
            self.sentiment   = hf_pipeline(
                "sentiment-analysis",
                model="distilbert-base-uncased-finetuned-sst-2-english",
                device=0 if torch.cuda.is_available() else -1
            )
            self.ner         = hf_pipeline(
                "ner",
                model="dbmdz/bert-large-cased-finetuned-conll03-english",
                aggregation_strategy="simple",
                device=0 if torch.cuda.is_available() else -1
            )
            self.zero_shot   = hf_pipeline(
                "zero-shot-classification",
                model="facebook/bart-large-mnli",
                device=0 if torch.cuda.is_available() else -1
            )
            print("Sistema pronto!")

        def analisar_feedback(self, feedback: str) -> dict:
            """Análise completa de um feedback de cliente."""
            # 1. Sentimento
            sent = self.sentiment(feedback)[0]

            # 2. Entidades
            entidades = self.ner(feedback)

            # 3. Categorização
            categorias = ["product quality", "customer service",
                           "shipping", "pricing", "user experience"]
            cat_result  = self.zero_shot(feedback, categorias)

            return {
                "sentimento":  sent["label"],
                "confianca_sent": round(sent["score"], 3),
                "entidades":   [(e["word"], e["entity_group"])
                                 for e in entidades],
                "categoria":   cat_result["labels"][0],
                "conf_cat":    round(cat_result["scores"][0], 3),
            }

        def processar_lote(self, feedbacks: list) -> list:
            """Processa múltiplos feedbacks."""
            return [self.analisar_feedback(f) for f in feedbacks]

    # Demonstrar o sistema
    sistema = SistemaNLPProducao()

    feedbacks_demo = [
        "The product from Apple broke after 2 days. Terrible quality!",
        "Amazing customer service from John! Very helpful and fast shipping.",
        "Price is too high for what you get. Not worth it.",
    ]

    print("\nAnálise de Feedbacks:")
    print("=" * 60)

    for feedback in feedbacks_demo:
        resultado = sistema.analisar_feedback(feedback)
        print(f"\n📝 Feedback: '{feedback[:55]}...'")
        print(f"   Sentimento: {resultado['sentimento']} "
              f"({resultado['confianca_sent']:.3f})")
        print(f"   Categoria:  {resultado['categoria']} "
              f"({resultado['conf_cat']:.3f})")
        if resultado['entidades']:
            print(f"   Entidades:  {resultado['entidades']}")

else:
    print("""
Sistema NLP simulado:

  Feedback: 'Product from Apple broke...'
  → Sentimento: NEGATIVE (0.998)
  → Categoria:  product quality (0.892)
  → Entidades:  [('Apple', 'ORG')]

  Feedback: 'Amazing customer service from John!'
  → Sentimento: POSITIVE (0.997)
  → Categoria:  customer service (0.934)
  → Entidades:  [('John', 'PER')]

  Feedback: 'Price is too high...'
  → Sentimento: NEGATIVE (0.976)
  → Categoria:  pricing (0.871)
  → Entidades:  []
    """)

# Resumo das melhores práticas
print("\n" + "=" * 60)
print("BOAS PRÁTICAS COM HUGGING FACE")
print("=" * 60)
print("""
1. ESCOLHA DO MODELO:
   Comece com o menor modelo que atenda seus requisitos
   DistilBERT > BERT quando performance ≈ mas latência importa

2. TOKENIZAÇÃO:
   Sempre use o tokenizer do modelo (nunca tokenize manualmente)
   Use truncation=True e max_length para evitar erros de memória
   Use padding dinâmico com DataCollatorWithPadding

3. FINE-TUNING:
   Use LoRA/PEFT para modelos grandes
   Warm-up nas primeiras 6-10% das steps
   Learning rate baixo: 2e-5 a 5e-5 para classificação

4. INFERÊNCIA:
   model.eval() + torch.no_grad() sempre
   Batch processing para throughput
   .half() ou quantização para reduzir memória

5. PRODUÇÃO:
   Salvar model + tokenizer juntos
   Usar AutoModel para compatibilidade futura
   Versionar com model card no Hub

6. CACHE:
   Modelos são cacheados em ~/.cache/huggingface/
   Use TRANSFORMERS_CACHE para mudar o diretório
   Em produção, carregue o modelo uma vez e reutilize
""")

Resumo da Aula

  • O ecossistema Hugging Face democratiza o acesso a LLMs com APIs simples e padronizadas
  • A Pipeline API encapsula tokenização + modelo + pós-processamento em uma única chamada
  • AutoTokenizer e AutoModel carregam automaticamente o tokenizer e modelo certos para cada checkpoint
  • Tokenizers BPE dividem palavras em sub-tokens — nunca tokenize manualmente, sempre use o tokenizer do modelo
  • AutoModelForSequenceClassification, ForTokenClassification, ForCausalLM etc. adicionam a cabeça certa para cada tarefa
  • O Trainer do HF encapsula o loop de treinamento com métricas, checkpointing e early stopping
  • Mean pooling sobre todos os tokens geralmente produz embeddings de sentença melhores que apenas o [CLS]
  • Estratégias de decodificação: greedy (determinístico), beam search (qualidade), sampling (diversidade), top-k/top-p (equilíbrio)
  • Zero-shot classification classifica em categorias novas sem fine-tuning usando MNLI
  • Em produção: salvar modelo + tokenizer juntos, usar batch inference e quantização para eficiência

Exercícios

  1. Explique a diferença entre pipeline("sentiment-analysis") e usar AutoModelForSequenceClassification diretamente. Em qual situação você usaria cada abordagem?

    ✓ Resposta:

    pipeline("sentiment-analysis") é uma abstração de alto nível que encapsula tokenização, inferência e pós-processamento em uma única chamada. É ideal para prototipagem rápida, uso simples e casos onde você não precisa de controle fino sobre o processo. Retorna resultados formatados diretamente.

    AutoModelForSequenceClassification usado diretamente dá controle total sobre cada etapa: tokenização personalizada, acesso aos logits brutos antes do softmax, controle sobre batch size, integração em loops de treino customizados, e acesso às representações internas. É necessário quando você precisa de fine-tuning, quando quer integrar o modelo em uma pipeline mais complexa, quando precisa de performance máxima otimizando a tokenização, ou quando precisa de recursos não disponíveis no pipeline (como acessar embeddings de camadas intermediárias).

    Use pipeline para demonstrações, prototipagem e casos de uso simples. Use AutoModel direto para produção, fine-tuning e sistemas complexos.

  2. Por que você deve sempre usar o tokenizer fornecido pelo modelo e nunca criar o seu próprio? O que aconteceria se você usasse o tokenizer do BERT para um modelo GPT-2?

    ✓ Resposta:

    Cada modelo usa um tokenizer específico que define o vocabulário, o esquema de tokenização (WordPiece para BERT, BPE para GPT) e os tokens especiais. Os pesos do modelo foram treinados com exatamente esses tokens — o embedding de índice 1024 representa uma sub-palavra específica, e o modelo aprendeu representações baseadas nessa correspondência.

    Se você usasse o tokenizer do BERT para um modelo GPT-2: o vocabulário é completamente diferente (30.522 tokens no BERT vs 50.257 no GPT-2). A mesma palavra seria tokenizada em sub-tokens diferentes com IDs diferentes. O modelo GPT-2 receberia IDs que correspondem a sub-palavras completamente diferentes das que ele aprendeu a processar. O resultado seria lixo semântico — como dar a um culinário instruções em japonês quando ele só lê português.

  3. Explique as quatro estratégias de decodificação: greedy, beam search, top-k sampling e top-p (nucleus) sampling. Quais são os trade-offs de cada uma em termos de qualidade e diversidade?

    ✓ Resposta:

    Greedy decoding: sempre escolhe o token com maior probabilidade. Determinístico, rápido, sem parâmetros. Desvantagem: frequentemente cai em loops e textos repetitivos porque escolhas localmente ótimas não são globalmente ótimas.

    Beam search: mantém k hipóteses em paralelo ("beams"), escolhendo a sequência com maior probabilidade acumulada. Produz textos mais coerentes e gramaticalmente corretos. Desvantagem: textos previsíveis e pouco criativos, computacionalmente mais caro (k vezes mais).

    Top-k sampling: sorteia o próximo token apenas entre os k mais prováveis. Mais diverso que greedy, evita tokens muito improváveis. Desvantagem: k fixo não é ideal — quando a distribuição é concentrada, k=50 ainda inclui tokens ruins; quando é flat, k=50 exclui tokens válidos.

    Top-p (nucleus) sampling: inclui apenas tokens que cobrem uma massa de probabilidade cumulativa p. Adapta-se dinamicamente à incerteza do modelo. Com p=0.9, o número de tokens considerados varia: 2 tokens quando o modelo está confiante, 200 quando incerto. É o mais sofisticado e geralmente o melhor trade-off entre qualidade e diversidade.

  4. O que é zero-shot classification e como o modelo BART-MNLI consegue classificar textos em categorias que nunca viu durante o treinamento? Qual é a limitação desta abordagem?

    ✓ Resposta:

    BART-MNLI foi treinado no dataset MNLI (Multi-Genre Natural Language Inference) para reconhecer relações entre pares de texto: entailment (hipótese é implicada pela premissa), contradiction (hipótese contradiz premissa) e neutral.

    Para classificação zero-shot, reusa este conhecimento: dado um texto e uma categoria, formula a questão como NLI: "Este texto trata de {categoria}?" O score de entailment indica quão bem o texto se encaixa na categoria. Quando você passa categorias novas, o modelo usa seu conhecimento geral de linguagem para inferir se o texto implica cada categoria.

    Limitação principal: o MNLI foi treinado em inglês — funciona mal para outros idiomas. O desempenho depende de quão bem a categoria pode ser descrita em uma hipótese de NLI — categorias abstratas como "humor" ou "ironia" são difíceis. Para categorias muito específicas do domínio (terminologia médica, legal, técnica), o modelo pode não ter visto exemplos suficientes e terá performance inferior ao fine-tuning. É significativamente mais lento que um modelo fine-tuned equivalente.

  5. Compare mean pooling com a representação [CLS] para obter embeddings de sentenças. Por que mean pooling geralmente funciona melhor para modelos BERT padrão (não fine-tuned para sentence similarity)?

    ✓ Resposta:

    O [CLS] token foi treinado para capturar a representação global da sequência, mas apenas para a tarefa específica de NSP (Next Sentence Prediction) no BERT padrão. Para similaridade de sentenças, a representação [CLS] não foi otimizada e frequentemente captura características de NSP ao invés de similaridade semântica geral.

    Mean pooling calcula a média de todos os tokens da sequência (excluindo padding), aproveitando a informação contextual de cada posição. Cada token contribui igualmente para a representação final, capturando aspectos diferentes da semântica da frase. Isso produz embeddings mais ricos e estáveis para comparação de sentenças.

    No entanto, para modelos fine-tuned explicitamente para sentence similarity (como os da família sentence-transformers), o [CLS] pode ser igualmente bom ou melhor, pois foi treinado especificamente para capturar similaridade no [CLS]. A diferença é significativa apenas para modelos BERT "base" sem fine-tuning em similaridade de sentenças.

  6. Você precisa construir um sistema de análise de sentimentos em português para comentários de produtos de e-commerce. Descreva passo a passo como você usaria o Hugging Face para isso, incluindo escolha do modelo, pré-processamento, fine-tuning e deploy.

    ✓ Resposta:

    Passo 1 — Escolha do modelo base: neuralmind/bert-base-portuguese-cased para português. É BERT treinado em português, o melhor ponto de partida para textos de e-commerce brasileiro.

    Passo 2 — Coleta e preparação dos dados: coletar comentários de produtos já rotulados (positivo/negativo/neutro). Verificar balanceamento de classes. Definir guidelines de anotação (como tratar comentários mistos).

    Passo 3 — Tokenização:

    tokenizer = AutoTokenizer.from_pretrained(
        "neuralmind/bert-base-portuguese-cased"
    )
    encoded = tokenizer(textos, truncation=True,
                         max_length=128, padding=True,
                         return_tensors="pt")
    

    Passo 4 — Fine-tuning:

    modelo = AutoModelForSequenceClassification.from_pretrained(
        "neuralmind/bert-base-portuguese-cased", num_labels=3
    )
    trainer = Trainer(modelo, TrainingArguments(
        num_train_epochs=3, learning_rate=2e-5,
        per_device_train_batch_size=32, warmup_ratio=0.1
    ), train_dataset, eval_dataset, compute_metrics=...)
    trainer.train()
    

    Passo 5 — Avaliação: acurácia, F1 macro (classes desbalanceadas), análise de erros por categoria de produto.

    Passo 6 — Deploy: salvar modelo e tokenizer, criar endpoint FastAPI ou Gradio, usar model.half() ou quantização para reduzir latência, implementar batching para throughput, monitorar drift ao longo do tempo.

Referências