Processamento de Linguagem Natural (NLP)

Processamento de Linguagem Natural (NLP)

NLP em Python com NLTK, spaCy, TF-IDF, Word2Vec e Hugging Face Transformers no transformers 5 — e o que os exemplos ensinam: com uma dúzia de frases o modelo clássico erra mais que uma moeda, e o pré-treinado precisa ser multilíngue e avaliado no próprio texto.
Python

• • 20 min de leitura

Processamento de Linguagem Natural é o campo da IA que lida com texto e fala humana — análise de sentimentos, classificação de documentos, extração de informações, tradução automática, chatbots e resumo automático. Python domina essa área com bibliotecas como NLTK, spaCy e a revolucionária biblioteca Transformers da Hugging Face, que democratizou o acesso a modelos de linguagem de última geração como BERT e GPT.

Instalação

pip install nltk spacy transformers torch scikit-learn sentencepiece
# Sem GPU NVIDIA, a versão só de CPU do PyTorch é bem menor:
#   pip install torch --index-url https://download.pytorch.org/whl/cpu
pip install gensim   # Word2Vec — ainda sem versão para o Python 3.14
python -m spacy download pt_core_news_sm
python -m spacy download en_core_web_sm

Pré-processamento de Texto com NLTK

import nltk
from nltk.tokenize import word_tokenize, sent_tokenize
from nltk.corpus import stopwords
from nltk.stem import PorterStemmer, RSLPStemmer
from nltk.probability import FreqDist
import string

# Download dos recursos necessários
nltk.download("punkt")
nltk.download("stopwords")
nltk.download("punkt_tab")
nltk.download("rslp")

texto = """
Python é uma linguagem de programação incrível. Ela é amplamente utilizada
em ciência de dados, desenvolvimento web e inteligência artificial.
Aprender Python abre muitas portas no mercado de trabalho brasileiro.
"""


def preprocessar_texto(texto: str, idioma: str = "portuguese") -> dict:
    """Pipeline completo de pré-processamento."""

    # 1. Tokenização
    tokens_palavras  = word_tokenize(texto.lower(), language=idioma)
    tokens_sentencas = sent_tokenize(texto, language=idioma)

    # 2. Remoção de pontuação
    tokens_limpos = [
        t for t in tokens_palavras
        if t not in string.punctuation
    ]

    # 3. Remoção de stopwords
    stop_pt = set(stopwords.words(idioma))
    tokens_sem_stop = [
        t for t in tokens_limpos
        if t not in stop_pt
    ]

    # 4. Stemming (reduz à raiz)
    stemmer = RSLPStemmer()   # stemmer para português
    tokens_stem = [stemmer.stem(t) for t in tokens_sem_stop]

    # 5. Frequência
    freq = FreqDist(tokens_sem_stop)

    return {
        "tokens":          tokens_palavras,
        "sentencas":       tokens_sentencas,
        "sem_stopwords":   tokens_sem_stop,
        "stems":           tokens_stem,
        "frequencias":     freq.most_common(10),
        "vocabulario":     len(set(tokens_sem_stop)),
        "total_tokens":    len(tokens_palavras),
    }


resultado = preprocessar_texto(texto)
print(f"Total de tokens:  {resultado['total_tokens']}")
print(f"Vocabulário único: {resultado['vocabulario']}")
print(f"\nTokens sem stopwords:\n{resultado['sem_stopwords']}")
print(f"\nTop 10 palavras:\n{resultado['frequencias']}")
print(f"\nSentencas:\n{resultado['sentencas']}")

spaCy: NLP Industrial

spaCy é mais rápido e preciso que NLTK para tarefas em produção — oferece tokenização, POS tagging, NER e análise de dependências em uma única chamada:

import spacy

nlp_pt = spacy.load("pt_core_news_sm")
nlp_en = spacy.load("en_core_web_sm")


def analisar_com_spacy(texto: str, modelo=None):
    """Análise linguística completa com spaCy."""
    if modelo is None:
        modelo = nlp_pt

    doc = modelo(texto)

    print(f"Texto: {texto}\n")

    # Tokens com informações linguísticas
    print("=== Tokens ===")
    print(f"{'Token':15} {'Lema':15} {'POS':10} {'Dep':10} {'Stop':5}")
    print("-" * 60)
    for token in doc:
        print(f"{token.text:15} {token.lemma_:15} "
              f"{token.pos_:10} {token.dep_:10} {token.is_stop}")

    # Named Entity Recognition
    print("\n=== Entidades ===")
    for ent in doc.ents:
        print(f"  {ent.text:20} → {ent.label_} ({spacy.explain(ent.label_)})")

    # Sentenças
    print("\n=== Sentenças ===")
    for i, sent in enumerate(doc.sents, 1):
        print(f"  {i}. {sent.text.strip()}")

    # Chunks de substantivo
    print("\n=== Grupos Nominais ===")
    for chunk in doc.noun_chunks:
        print(f"  {chunk.text:20} ← {chunk.root.dep_}")

    return doc


texto_pt = (
    "O professor Ricardo Matos ensina Python em São Paulo "
    "desde 2020. A Microsoft e a Google utilizam Python "
    "extensivamente em seus produtos de IA."
)
doc = analisar_com_spacy(texto_pt)

Representação de Texto: Bag of Words e TF-IDF

from sklearn.feature_extraction.text import (
    CountVectorizer, TfidfVectorizer
)
import pandas as pd


corpus = [
    "Python é ótimo para ciência de dados",
    "Java é usado no desenvolvimento empresarial",
    "Python e R são linguagens de análise de dados",
    "Machine learning com Python é muito poderoso",
    "Desenvolvimento web com JavaScript e Python",
]

# Bag of Words
vectorizer_bow = CountVectorizer(
    max_features=20,
    stop_words=None,
    ngram_range=(1, 2)   # unigramas e bigramas
)
X_bow = vectorizer_bow.fit_transform(corpus)

print("=== Bag of Words ===")
print(f"Vocabulário: {vectorizer_bow.get_feature_names_out()}")
print(f"\nMatriz:\n{pd.DataFrame(X_bow.toarray(), columns=vectorizer_bow.get_feature_names_out())}")


# TF-IDF — Term Frequency × Inverse Document Frequency
vectorizer_tfidf = TfidfVectorizer(
    max_features=20,
    ngram_range=(1, 2),
    sublinear_tf=True    # aplica log na TF
)
X_tfidf = vectorizer_tfidf.fit_transform(corpus)

print("\n=== TF-IDF ===")
df_tfidf = pd.DataFrame(
    X_tfidf.toarray(),
    columns=vectorizer_tfidf.get_feature_names_out()
).round(3)
print(df_tfidf)

# Similaridade coseno entre documentos
from sklearn.metrics.pairwise import cosine_similarity

similaridades = cosine_similarity(X_tfidf)
print("\n=== Similaridade entre documentos ===")
df_sim = pd.DataFrame(
    similaridades,
    columns=[f"Doc{i}" for i in range(len(corpus))]
)
print(df_sim.round(3))

Análise de Sentimentos

from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.model_selection import cross_val_score, LeaveOneOut


# Dataset de reviews de alunos
reviews = [
    ("O curso é excelente, aprendi muito!", 1),
    ("Ótima didática, professor muito claro", 1),
    ("Conteúdo muito bem organizado e completo", 1),
    ("Superou minhas expectativas, recomendo", 1),
    ("Fantástico, mudou minha carreira", 1),
    ("Muito bom, exercícios práticos e relevantes", 1),
    ("Adorei cada aula, conteúdo incrível", 1),
    ("Melhor curso que já fiz, vale cada centavo", 1),
    ("Péssimo, não aprendi nada útil", 0),
    ("Muito ruim, explicações confusas", 0),
    ("Decepcionante, esperava mais conteúdo", 0),
    ("Não recomendo, material desatualizado", 0),
    ("Terrível, perda de tempo e dinheiro", 0),
    ("Frustrante, professor não responde dúvidas", 0),
    ("Horrível, conteúdo superficial demais", 0),
    ("Muito abaixo do esperado, não vale o preço", 0),
]

textos, labels = zip(*reviews)
textos = list(textos)
labels = list(labels)

# Pipeline completo: TF-IDF + Classificador
pipeline_sentimento = Pipeline([
    ("tfidf", TfidfVectorizer(
        ngram_range=(1, 2),
        max_features=500,
        sublinear_tf=True
    )),
    ("clf", LogisticRegression(C=1.0, max_iter=1000))
])

# Avaliação: um corte de 30% deixaria só 5 frases para teste. A validação
# cruzada leave-one-out testa cada frase num modelo treinado com as outras 15
acuracia = cross_val_score(
    pipeline_sentimento, textos, labels, cv=LeaveOneOut()
).mean()
print("=== Análise de Sentimentos ===")
print(f"Acurácia leave-one-out: {acuracia:.1%}")   # 6.2% — pior que chutar

# Para classificar frases novas, treina com as 16
pipeline_sentimento.fit(textos, labels)

# Testando com novas frases
novas_frases = [
    "Curso muito bem estruturado e didático",
    "Não gostei, muito caro para pouco conteúdo",
    "Razoável, mas poderia ter mais exercícios",
]

for frase in novas_frases:
    pred  = pipeline_sentimento.predict([frase])[0]
    prob  = pipeline_sentimento.predict_proba([frase])[0]
    label = "Positivo" if pred == 1 else "Negativo"
    conf  = max(prob)
    print(f"  {label:10} ({conf:.1%}) — {frase}")

Os 6,2% não são defeito do código: são o limite dos dados. Com 16 frases curtas, a frase deixada de fora quase nunca repete uma palavra das outras, e as poucas que se repetem — "muito", "curso", "conteúdo" — aparecem nos dois lados, de modo que o modelo aprende pistas que apontam para o lado errado. Trocar palavras por n-gramas de caracteres ou afrouxar a regularização não passou de 44% no mesmo teste. TF-IDF com regressão logística é uma linha de base excelente quando há exemplos suficientes para as palavras se repetirem; com uma dúzia de frases, o caminho é um modelo pré-treinado, como os da seção de Transformers adiante.

Word Embeddings com Word2Vec

from gensim.models import Word2Vec

# Oito frases servem para ver a API funcionando, não para aprender significado:
# aqui, os "mais próximos" de 'python' saem 'javascript' e 'sql' (~0,23), que
# nunca aparecem na mesma frase que ele, e 'java' — que divide o contexto
# "linguagem programacao" — sai com -0,09. Embeddings úteis pedem milhões de
# frases; na prática, usam-se vetores pré-treinados.
corpus_frases = [
    ["python", "linguagem", "programacao", "dados"],
    ["java", "linguagem", "programacao", "empresarial"],
    ["machine", "learning", "python", "algoritmos"],
    ["dados", "analise", "estatistica", "python"],
    ["web", "desenvolvimento", "javascript", "html"],
    ["inteligencia", "artificial", "redes", "neurais"],
    ["banco", "dados", "sql", "consultas"],
    ["api", "rest", "flask", "fastapi", "python"],
]

modelo_w2v = Word2Vec(
    sentences=corpus_frases,
    vector_size=100,    # dimensão dos embeddings
    window=5,           # janela de contexto
    min_count=1,
    workers=4,
    epochs=100
)

print("=== Word2Vec ===")
print(f"\nVetor de 'python': shape={modelo_w2v.wv['python'].shape}")

# Palavras mais similares
similares = modelo_w2v.wv.most_similar("python", topn=5)
print(f"\nPalavras mais similares a 'python':")
for palavra, score in similares:
    print(f"  {palavra:15}: {score:.4f}")

# Analogia: machine - python + java = ?
try:
    analogia = modelo_w2v.wv.most_similar(
        positive=["machine", "java"],
        negative=["python"],
        topn=3
    )
    print(f"\nAnalogias:")
    for p, s in analogia:
        print(f"  {p}: {s:.4f}")
except KeyError as e:
    print(f"Token não encontrado: {e}")

Hugging Face Transformers

from transformers import pipeline
import torch


# Sentiment Analysis em português
print("=== Análise de Sentimentos com Transformers ===")
analisador = pipeline(
    "sentiment-analysis",
    model="lxyuan/distilbert-base-multilingual-cased-sentiments-student",
    device=0 if torch.cuda.is_available() else -1
)

frases = [
    "Python é incrível para ciência de dados!",
    "Esse framework é muito lento e frustrante.",
    "O curso foi razoável, nada extraordinário.",
]

for frase in frases:
    resultado = analisador(frase)[0]
    print(f"  {resultado['label']:10} ({resultado['score']:.1%}) — {frase}")
# Classificação Zero-Shot — sem treinamento específico
print("\n=== Zero-Shot Classification ===")
classificador = pipeline(
    "zero-shot-classification",
    # multilíngue: o facebook/bart-large-mnli só foi treinado em inglês
    # e punha a frase abaixo em "política", com 80%
    model="MoritzLaurer/mDeBERTa-v3-base-mnli-xnli"
)

texto = "O governo anunciou novas medidas para controlar a inflação."
categorias = ["economia", "política", "tecnologia", "esporte", "saúde"]

resultado = classificador(texto, categorias)
print(f"Texto: {texto}")
for label, score in zip(resultado["labels"][:3], resultado["scores"][:3]):
    print(f"  {label:15}: {score:.1%}")
# Question Answering — o pipeline "question-answering" saiu no
# transformers 5; o modelo continua disponível pelas classes Auto*
from transformers import AutoTokenizer, AutoModelForQuestionAnswering

print("\n=== Question Answering ===")
nome_qa   = "deepset/roberta-base-squad2"
tok_qa    = AutoTokenizer.from_pretrained(nome_qa)
modelo_qa = AutoModelForQuestionAnswering.from_pretrained(nome_qa)


def responder(pergunta: str, contexto: str) -> str:
    entrada = tok_qa(pergunta, contexto, return_tensors="pt",
                     truncation="only_second")
    with torch.no_grad():
        saida = modelo_qa(**entrada)
    # o modelo aponta o token de início e o de fim da resposta no contexto
    inicio = int(saida.start_logits.argmax())
    fim    = int(saida.end_logits.argmax())
    return tok_qa.decode(entrada["input_ids"][0][inicio:fim + 1]).strip()


contexto = """
Python foi criado por Guido van Rossum e lançado em 1991.
É uma linguagem de programação de alto nível, interpretada e de propósito geral.
Python enfatiza a legibilidade do código e usa indentação significativa.
A versão 3.14 do Python foi lançada em outubro de 2025.
"""

perguntas = [
    "Quem criou o Python?",                          # Guido van Rossum
    "Quando o Python foi lançado?",                  # 1991
    "Qual versão foi lançada em outubro de 2025?",   # 3.14
]

for pergunta in perguntas:
    print(f"  P: {pergunta}")
    print(f"  R: {responder(pergunta, contexto)}\n")
# Geração de texto com GPT-2
print("=== Geração de Texto ===")
gerador = pipeline(
    "text-generation",
    model="gpt2",
    device=0 if torch.cuda.is_available() else -1
)

prompt = "Python is a powerful programming language that"
resultado = gerador(
    prompt,
    max_new_tokens=60,
    num_return_sequences=2,
    temperature=0.8,
    do_sample=True,
    pad_token_id=50256
)

for i, r in enumerate(resultado, 1):
    print(f"\n[Geração {i}]")
    print(r["generated_text"])

Sumarização Automática

# O pipeline "summarization" saiu no transformers 5: o modelo
# seq2seq é carregado e o resumo sai do generate()
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM

tok_sum    = AutoTokenizer.from_pretrained("facebook/bart-large-cnn")
modelo_sum = AutoModelForSeq2SeqLM.from_pretrained("facebook/bart-large-cnn")

artigo = """
Machine learning is a subset of artificial intelligence that provides systems
the ability to automatically learn and improve from experience without being
explicitly programmed. Machine learning focuses on the development of 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 look for patterns in data and make better
decisions in the future. The primary aim is to allow the computers to learn
automatically without human intervention or assistance and adjust actions accordingly.
"""

entrada = tok_sum(artigo, return_tensors="pt", truncation=True)
ids = modelo_sum.generate(
    **entrada,
    max_length=80,     # em tokens, não em palavras
    min_length=30,
    num_beams=4,
)
resumo = tok_sum.decode(ids[0], skip_special_tokens=True)

print("=== Sumarização ===")
print(f"Original: {len(artigo.split())} palavras")   # 94
print(f"Resumo:   {len(resumo.split())} palavras")   # 40
print(f"\n{resumo}")

Exemplo Completo: Sistema de Classificação de Tickets

from sklearn.pipeline import Pipeline
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.multiclass import OneVsRestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import cross_val_score, LeaveOneOut
import numpy as np


# Dataset de tickets de suporte
tickets = [
    ("Não consigo acessar minha conta, esqueci a senha", "acesso"),
    ("Sistema travando ao abrir o módulo de notas", "bug"),
    ("Como faço para exportar o boletim em PDF?", "duvida"),
    ("Meu pagamento não foi processado", "financeiro"),
    ("Erro 500 ao tentar salvar dados do aluno", "bug"),
    ("Preciso redefinir minha senha urgente", "acesso"),
    ("Qual o prazo para lançamento de notas?", "duvida"),
    ("Cobrança duplicada no cartão de crédito", "financeiro"),
    ("Botão de login não funciona no Chrome", "bug"),
    ("Como adicionar um novo aluno ao sistema?", "duvida"),
    ("Login bloqueado após várias tentativas", "acesso"),
    ("Fatura com valor incorreto", "financeiro"),
    ("Relatório gerando dados errados", "bug"),
    ("Como funciona o cálculo da média?", "duvida"),
    ("Preciso de acesso para novo professor", "acesso"),
]

textos, categorias = zip(*tickets)
textos     = list(textos)
categorias = list(categorias)

le = LabelEncoder()
y  = le.fit_transform(categorias)

pipeline_tickets = Pipeline([
    ("tfidf", TfidfVectorizer(
        ngram_range=(1, 2),
        max_features=1000,
        sublinear_tf=True
    )),
    ("clf", LogisticRegression(
        C=5.0,
        max_iter=1000,   # multiclasse já é o padrão; multi_class= foi removido
    ))
])

acuracia = cross_val_score(pipeline_tickets, textos, y, cv=LeaveOneOut()).mean()
print("=== Classificador de Tickets ===")
print(f"Acurácia leave-one-out: {acuracia:.1%}")
# Com 15 tickets, o número é baixo pelo mesmo motivo da análise de
# sentimentos. Para começar sem dados rotulados, o zero-shot multilíngue
# acima, sem nenhum exemplo de treino, acertou 9 destes 15.

pipeline_tickets.fit(textos, y)

# Classificando novos tickets
novos_tickets = [
    "Sistema não carrega após atualização",
    "Preciso de nota fiscal do pagamento",
    "Como resetar a senha do administrador?",
    "Dúvida sobre o calendário de provas",
]

print("Classificação de novos tickets:")
probs = pipeline_tickets.predict_proba(novos_tickets)
for ticket, prob in zip(novos_tickets, probs):
    idx_melhor  = np.argmax(prob)
    categoria   = le.classes_[idx_melhor]
    confianca   = prob[idx_melhor]
    print(f"\n  Ticket: {ticket}")
    print(f"  Categoria: {categoria} ({confianca:.1%})")

    # Top 3 categorias prováveis
    top3 = np.argsort(prob)[::-1][:3]
    print(f"  Distribuição: " +
          ", ".join(f"{le.classes_[i]}={prob[i]:.0%}" for i in top3))

NLP em Python tem três camadas, e este artigo passou por todas. Na primeira, NLTK e spaCy transformam texto em unidades com que se pode trabalhar — tokens, lemas, classes gramaticais, entidades. Na segunda, BoW e TF-IDF transformam essas unidades em números, e um classificador comum, como os de Machine Learning com scikit-learn, aprende com elas. Na terceira, modelos pré-treinados em bilhões de palavras chegam prontos: análise de sentimentos, classificação zero-shot, perguntas e respostas, geração e resumo, com poucas linhas de código.

A lição que os exemplos deste artigo deixam é que a camada certa depende da quantidade de dados rotulados. Com uma dúzia de frases, TF-IDF e regressão logística não aprendem nada — a validação cruzada mostrou 6% de acerto nos sentimentos — e um modelo pré-treinado resolve sem exemplo algum; com milhares de exemplos, o modelo clássico volta a ser uma linha de base difícil de bater, rápida e barata. Em qualquer caso, vale avaliar com o próprio texto, em português, antes de confiar: o modelo de zero-shot treinado só em inglês classificou uma notícia sobre inflação como política, o spaCy tomou "IA" por um lugar, e o GPT-2 afirmou com toda a fluência que o Python foi desenvolvido pela Intel.

Fontes e leituras recomendadas

Exercícios

Exercício 1

Um classificador de sentimentos treinado com 16 avaliações de alunos, TF-IDF e regressão logística, acerta 6% das frases em validação cruzada leave-one-out — bem menos que os 50% de uma moeda. Um colega conclui que há um erro invertendo os rótulos. Não há. O que explica um modelo pior que o acaso?

Ver resposta

✓ Resposta: A falta de palavras em comum. O TF-IDF só representa palavras vistas no treino, e com frases curtas e 16 exemplos a frase deixada de fora quase nunca repete uma palavra das outras 15. Medido no primeiro corte do artigo: numa frase positiva de teste, só "muito" estava no vocabulário do treino, e "ótima", "didática" e "claro" ficaram de fora. Sobra uma ou outra palavra genérica, e as que se repetem — "muito", "curso", "conteúdo" — aparecem nas duas classes. Quando uma frase positiva com "muito" sai do treino, "muito" passa a pesar mais do lado negativo, e o modelo erra justamente nela. Leave-one-out amplifica o efeito, porque tira sempre o exemplo que sustentava a pista. Trocar por n-gramas de caracteres e afrouxar a regularização levou a no máximo 44% no mesmo teste. Não há ajuste de hiperparâmetro que crie informação que os dados não têm: ou se juntam muito mais exemplos, ou se usa um modelo pré-treinado.

Exercício 2

Um serviço interno de resumo de documentos funcionava havia um ano. Depois de uma reconstrução da imagem Docker, sem mudança no código, ele morre na inicialização com KeyError: "Unknown task summarization". O requirements.txt diz apenas transformers. O que aconteceu, e como evitar?

Ver resposta

✓ Resposta: A reconstrução instalou o transformers 5, e a versão 5 removeu tarefas do pipeline. Medido no 5.17.0: a lista de tarefas suportadas não tem mais summarization nem question-answering, e as duas seções do artigo que as usavam morriam com esse erro antes de baixar o modelo. Os modelos continuam disponíveis: o artigo passou a carregar o facebook/bart-large-cnn com AutoModelForSeq2SeqLM e gerar o resumo com generate(), e o QA com AutoModelForQuestionAnswering, pegando os pontos de maior pontuação de início e fim da resposta — com os mesmos resultados de antes. A causa real foi a dependência sem versão: uma troca de versão principal pode remover API, e sem limite qualquer build pode puxar a nova. A prevenção é fixar as versões, por exemplo transformers==4.57.* ou um arquivo de lock, e atualizar de propósito, com os testes rodando.

Exercício 3

Uma equipe usa classificação zero-shot com facebook/bart-large-mnli para separar notícias em português por editoria. "O governo anunciou novas medidas para controlar a inflação" sai como política, com 80%, e economia fica com 7%. Qual é o problema, e o que o número de 80% significa?

Ver resposta

✓ Resposta: O modelo só foi treinado em inglês. O zero-shot funciona como um teste de inferência: o modelo avalia, para cada rótulo, se o texto implica a frase "This example is {rótulo}". Em português, ele se apoia em pedaços de palavra parecidos com o inglês — "governo" lembra "government" — e "inflação" pesa pouco. Trocado por um modelo multilíngue, o MoritzLaurer/mDeBERTa-v3-base-mnli-xnli, a mesma frase saiu como economia, com 71%, e política com 27%, o que é defensável: a notícia é das duas editorias. Quanto aos 80%: com multi_label=False, o padrão, as pontuações passam por um softmax entre os rótulos dados e somam 100%. São uma distribuição relativa àquela lista, não uma probabilidade de acerto; acrescentar ou tirar um rótulo muda todos os números. Para textos que pertencem a mais de uma categoria, use multi_label=True, que avalia cada rótulo por conta própria.

Exercício 4

Um sistema de indexação usa as entidades do spaCy com pt_core_news_sm para marcar lugares nos textos da escola. No mapa de lugares mais citados, aparece "IA", ao lado de São Paulo. Os textos falam de inteligência artificial. De onde veio o erro, e como tratar?

Ver resposta

✓ Resposta: Do próprio modelo de NER. Medido com a frase do artigo, o spaCy marcou "Ricardo Matos" como PER, Microsoft e Google como ORG e São Paulo como LOC, todos certos, mas também "IA" como LOC. O modelo não sabe o que é IA: decide pelo formato da palavra e pelo contexto, e uma sigla em maiúsculas depois de "de" provavelmente se parece com as siglas de lugar que ele viu no treino. O sm é o menor modelo de português, treinado em notícias, e erra mais em siglas e termos de domínio; na mesma frase, "Python" saiu como nome próprio na primeira ocorrência e como substantivo comum na segunda. Tratamentos, do mais barato ao mais caro: uma lista de exceções ou um EntityRuler com os termos do domínio, antes do NER; um modelo maior, como o pt_core_news_lg; ou retreinar com exemplos anotados do próprio texto. E, antes de tudo, medir: anotar à mão uma amostra e calcular o acerto, em vez de confiar no rótulo que o modelo devolve.

Exercício 5

Um levantamento de linguagens citadas em descrições de vagas usa CountVectorizer com as opções padrão. Python, Java e JavaScript aparecem na contagem; R, muito citada nas vagas de dados, não aparece nenhuma vez. Por quê?

Ver resposta

✓ Resposta: O padrão de token do scikit-learn é (?u)\b\w\w+\b: só palavras com dois ou mais caracteres. Medido com o corpus do artigo, que contém "Python e R são linguagens de análise de dados": o vocabulário completo tem "python" e "java", e não tem "r" — nem "e" ou "é", que caem pelo mesmo motivo. Para stopwords de uma letra isso é conveniente; para R, C ou uma nota "A", é perda silenciosa de informação. A correção é declarar o padrão: CountVectorizer(token_pattern=r"(?u)\b\w+\b") aceita tokens de um caractere, e aí é preciso lidar com as stopwords de uma letra de outra forma. Há ainda o lowercase=True padrão, que faz "R" virar "r" e se confundir com qualquer "r" solto no texto. Em contagens de termos específicos, uma lista explícita de termos procurados costuma ser mais confiável que o vocabulário automático.

Comentários

Mais em Python

Introdução ao Data Science com Python
Introdução ao Data Science com Python

NumPy, pandas, Matplotlib, Seaborn e SciPy na prática: arrays, DataFrames…

Qualidade de Código: linting, type hints e mypy
Qualidade de Código: linting, type hints e mypy

Black, isort, Ruff, mypy, Bandit e pre-commit na prática, conferidos nas…

Logging, Monitoramento e Observabilidade
Logging, Monitoramento e Observabilidade

Logging estruturado em JSON, ContextVar, métricas com Prometheus, Sentry e…