Imagine que você está construindo um app que analisa conversas de uma equipe de desenvolvimento. A ideia é detectar se o tom da equipe é saudável ou se há sinais de estresse. Você utiliza o NLTagger da Apple porque ele está disponível, é gratuito, funciona on-device e não precisa de servidor. Três linhas de código e tudo parece resolvido.

Primeira surpresa: a frase “kill the process and restart the daemon” recebe uma pontuação de -0.6. Negativa. Quase hostil. “Fatal error in memory allocation” ganha um -0.8. E “crash report uploaded successfully” — que literalmente é uma boa notícia — gera um -0.4.

Sua ferramenta de bem-estar da equipe acabou de decidir que seus desenvolvedores estão à beira de um colapso emocional. E tudo o que aconteceu foi alguém reiniciar um serviço.

O problema: um modelo treinado para outro planeta

O NLTagger com .sentimentScore devolve um valor entre -1.0 (muito negativo) e 1.0 (muito positivo). A Apple introduziu-o no iOS 13 e macOS 10.15, utilizando um modelo on-device integrado no sistema. Não há como personalizá-lo nem ver com quais dados ele foi treinado.

Para simplificar: é uma caixa preta que te dá um número e espera que você confie.

O modelo funciona razoavelmente bem para o propósito para o qual foi desenhado: avaliações de produtos, comentários em redes sociais, textos de consumo onde “horrível” significa ruim e “fantástico” significa bom. Esse é o seu domínio. O problema aparece quando você o tira desse contexto.

O experimento: texto técnico vs. texto cotidiano

Vamos testá-lo. Este é um código puro em Swift que você pode executar em um Playground:

import NaturalLanguage

func sentiment(of text: String) -> Double {
    let tagger = NLTagger(tagSchemes: [.sentimentScore])
    tagger.string = text
    let (tag, _) = tagger.tag(
        at: text.startIndex,
        unit: .paragraph,
        scheme: .sentimentScore
    )
    return Double(tag?.rawValue ?? "0") ?? 0
}

Agora vamos passar frases que qualquer programador diria em um dia normal:

FrasePontuaçãoSentimento real
“Kill the background process”-0.5 ~ -0.7Neutro (instrução técnica)
“Fatal error: index out of range”-0.7 ~ -0.9Neutro (mensagem do compilador)
“Abort the deployment”-0.4 ~ -0.6Neutro (decisão operacional)
“Destroy the old database”-0.5 ~ -0.7Neutro (manutenção)
“The crash was caused by a null pointer”-0.5 ~ -0.8Neutro (diagnóstico)
“Successfully deployed to production”+0.3 ~ +0.5Positivo

Preste atenção: cinco das seis frases perfeitamente normais em um contexto técnico têm pontuações negativas. A única positiva é a que contém “successfully” — uma palavra que o modelo reconhece do universo das avaliações.

Agora a comparação com frases do domínio para o qual ele foi treinado:

FrasePontuaçãoSentimento real
“This product is terrible”-0.7 ~ -0.9Negativo (correto)
“I love this app, it’s amazing”+0.7 ~ +0.9Positivo (correto)
“The food was okay, nothing special”-0.1 ~ +0.1Neutro (correto)

Aqui ele acerta as três. O modelo não é ruim. Está fora de contexto.

Por que isso acontece: o viés lexical

O modelo do NLTagger não entende semântica. Ele não sabe que “kill a process” é uma metáfora técnica e “kill a person” é violência. Para ele, “kill” é uma palavra com carga negativa. E ponto final.

Isso é chamado de lexical bias — o viés lexical. O modelo atribui polaridade a palavras individuais (ou n-grams curtos) sem compreender o contexto do domínio. É como um tradutor automático interpretar “I’m dying of laughter” como uma emergência médica.

O vocabulário técnico está repleto de palavras que, no idioma cotidiano, têm conotação negativa:

  • kill, terminate, abort, destroy — operações normais de processos
  • fatal, critical, severe — níveis de log
  • crash, panic, fault — eventos do sistema
  • dead, zombie, orphan — estados de processos
  • reject, deny, block — controle de acesso
  • break, interrupt, suspend — controle de fluxo

Qualquer parágrafo que contenha três ou quatro dessas palavras — um log de erros, um postmortem, uma discussão em um PR — será avaliado como se alguém estivesse escrevendo de um fundo de poço.

Mais contexto resolveria o problema?

Você poderia pensar: “se eu passar um parágrafo mais longo com contexto, o modelo deveria entender melhor”. Vamos testar:

let technical = """
The daemon was killed after a fatal memory error. \
We restarted the service and the crash did not recur. \
Deployment completed successfully with zero downtime.
"""
print(sentiment(of: technical))
// Resultado típico: -0.3 ~ -0.5

Um parágrafo que descreve um incidente resolvido com sucesso. O final é positivo. Porém, as palavras “killed”, “fatal”, “error” e “crash” pesam mais do que “successfully” e “completed”. O modelo faz uma média de carga lexical, não interpreta narrativa.

Compare com um texto de tamanho similar sobre um produto:

let review = """
The screen had some dead pixels and the battery was terrible. \
But after the replacement, the product works perfectly. \
I'm very happy with the customer service.
"""
print(sentiment(of: review))
// Resultado típico: +0.1 ~ +0.3

Aqui o modelo entende que o final positivo compensa o início negativo. Qual a diferença? “happy”, “perfectly” e “works” são palavras que o modelo viu milhões de vezes em avaliações. “Successfully” e “completed” não têm o mesmo peso no seu vocabulário.

O que usar no lugar

Se você precisa de análise de sentimento em texto técnico dentro do ecossistema Apple, há três opções reais.

1. Core ML com modelo próprio (a escolha séria)

Treine um classificador com o Create ML usando dados do seu domínio. Se você tiver logs, mensagens da equipe no Slack ou commits rotulados manualmente, pode criar um modelo .mlmodel que entenda que “kill the process” é neutro.

// Treinar com Create ML (macOS)
import CreateML

let data = try MLDataTable(contentsOf: trainingDataURL)
let classifier = try MLTextClassifier(
    trainingData: data,
    textColumn: "text",
    labelColumn: "sentiment"
)
try classifier.write(to: modelURL)

É mais trabalho, mas o modelo roda on-device assim como NLTagger. A diferença é que você controla os dados de treinamento.

2. Embeddings + classificador (a solução elegante)

Desde WWDC23, a Apple oferece embeddings contextuais baseados em transformers através do framework NaturalLanguage. Você pode usar NLEmbedding para obter representações vetoriais do texto e classificá-las. Diferente do sentiment score puro, os embeddings capturam relações semânticas — “kill process” e “stop process” estarão próximos no espaço vetorial.

import NaturalLanguage

if let embedding = NLEmbedding.sentenceEmbedding(for: .english) {
    let vector = embedding.vector(for: "Kill the background process")
    // Usar o vetor como input de um classificador
}

Você precisará de um classificador por cima (um MLClassifier simples), mas a base de representação é muito mais rica do que um score léxico simples.

3. Heurísticas de domínio (a gambiarra funcional)

Se seu caso for simples — por exemplo, filtrar mensagens por “tom” em uma ferramenta interna — às vezes o melhor é usar um dicionário de termos técnicos para neutralizar antes de enviar ao modelo:

let technicalNeutral: Set<String> = [
    "kill", "terminate", "abort", "destroy", "fatal",
    "crash", "panic", "dead", "zombie", "orphan",
    "reject", "deny", "block", "error", "fault"
]

func preprocessed(_ text: String) -> String {
    var result = text.lowercased()
    for term in technicalNeutral {
        result = result.replacingOccurrences(of: term, with: "process")
    }
    return result
}

É uma gambiarra, sim. Mas uma gambiarra explícita que você pode auditar e ajustar. Melhor do que confiar cegamente em uma caixa preta que não distingue um postmortem de uma carta de despedida.

A lição de fundo

NLTagger não está quebrado. Ele faz exatamente o que foi projetado para fazer: análise de sentimento de texto genérico, otimizado para o tipo de conteúdo que a Apple espera que as apps da App Store processem — avaliações, mensagens, notas.

O erro é nosso ao assumir que “análise de sentimento” é um problema universal. Não é. O sentimento depende do domínio. “Fatal” em um log é tão neutro quanto “olá”. “Fatal” em uma conversa é alarme máximo. Nenhum modelo genérico distinguirá os dois sem contexto de domínio.

Se seu texto vem de um ambiente técnico — logs, commits, chats de desenvolvimento, tickets — o NLTagger vai te enganar. Não por malícia, mas por ignorância. E a pior mentira é aquela que chega embrulhada em um Double com duas casas decimais de falsa precisão.

Antes de conectar um modelo de sentimento ao seu pipeline, faça uma pergunta: meu texto se parece com uma avaliação do Amazon? Se a resposta for não, NLTagger não é a ferramenta certa. E se a resposta for sim… você provavelmente nem precisa de análise de sentimento.