Tokenização de texto em projetos de NLP
Trabalho com processamento de linguagem natural há bastante tempo, e já vi muitos desenvolvedores travarem na mesma etapa repetidamente: separar strings corretamente antes de aplicar qualquer modelo. O que parece simples no início rapidamente vira dor de cabeça quando você precisa lidar com caracteres especiais, espaços duplos, e casos em que a separação padrão simplesmente não funciona mais.
O que é separação da palavra baleia e por que isso importa na prática
Separar palavras em português significa dividir uma string nos delimitadores apropriados — espaço, vírgula, ponto, ou em alguns casos, hífen — para transformar texto bruto em tokens que o modelo consegue consumir. No meu dia a dia, costumo me deparar com textos vindos de OCR, arquivos legado, ou dados coletados de forma automática que chegam sujos demais para qualquer pipeline padrão aguentar sem tratamento prévio. O problema é que a função split() do Python ou o StringTokenizer do Java não fazem milagre quando os dados entram sem padronização. Já perdi uma tarde inteira debugando um modelo de classificação porque uma vírgula sem espaço após ela quebrava a tokenização inteira do conjunto de teste.
Métodos práticos de separação
A abordagem mais direta é usar expressão regular. Em Python, eu gosto de regex porque resolve a maioria dos casos problemáticos de uma vez só. Um exemplo simples que uso frequentemente: import re
tokens = re.findall(r'[a-zA-ZÀ-ú]+', texto_bruto) Isso captura sequências de letras, ignorando números, pontuação solta, e caracteres especiais. Funciona bem na maior parte dos projetos. O custo é que você perde informação sobre onde estavam os delimitadores originais, o que pode ser relevante se precisar recuperar o token original ou mapear posições de volta ao texto fonte.
Se você precisa preservar a ordem e a posição, use split() com regex, que retorna uma lista mantendo os delimitadores como elementos separados: tokens = re.split(r'(\W+)', texto_bruto)
O parâmetro entre parênteses captura cada delimitador como um item separado na lista resultante. Isso é útil quando você precisa reconstruir o texto original depois de processar os tokens. Outra alternativa, especialmente quando você está lidando com texto já limpo e só precisa remover espaços múltiplos, é o split() nativo sem argumentos, que trata consecutive whitespace como um único delimitador:
tokens = texto.split() Esse método não aceita regex, mas funciona muito bem para textos organizados. A limitação é que ele considera qualquer sequência de espaços, tabs, e newlines como delimitador, o que pode ser demais ou de menos dependendo do caso.
Casos difíceis que aparecem no campo
Um problema recorrente que enfrento é a presença de hífens em palavras compostas. O split() padrão divide pelo hífen, o que pode fragmentar indevidamente termos como "pós-graduação" ou "anti-inflamatório". Em projetos com dados brasileiros, isso é especialmente comum. A solução que costumo adotar é tratar hífens dentro de palavras como parte do token, usando uma regex mais específica:
👉 Clique no botão abaixo para saber mais sobre o assunto!
tokens = re.findall(r'[a-zA-ZÀ-ú]+(?:-[a-zA-ZÀ-ú]+)?', texto) Isso mantém a palavra inteira quando contém um hífen, mas ainda separa corretamente quando o hífen aparece entre duas palavras distintas. Vale testar com seu corpus antes de confiar cegamente no resultado.
Um segundo problema frequente é a presença de aspas, parênteses e outros delimitadores gráficos que acabam grudados nas palavras. O split() sem tratamento prévio deixa esses caracteres anexados aos tokens, o que quebra a correspondência com dicionários e embeddings pré-treinados. O workaround que uso é uma etapa de limpeza separada antes da tokenização, removendo caracteres não alfabéticos mas preservando a estrutura básica:
limpo = re.sub(r'[^a-zA-ZÀ-ú\s-]', '', texto)
tokens = limpo.split() Isso remove tudo que não é letra, espaço ou hífen. O resultado costuma ser limpo o suficiente para a maioria dos pipelines. Mas fique atento: se seu modelo precisa distinguir marcadores discursivos como aspas ou exclamações, essa abordagem vai apagar informação relevante.
Pegadinhas e armadilhas comuns
Uma armadilha clássica é confiar que o split() padrão funciona igual para todos os tipos de texto. Dados de fontes diferentes podem ter codificações distintas, espaços invisíveis, ou caracteres Unicode que parecem idênticos mas não são. Já vi um projeto inteiro falhar porque os tokens vinham de arquivos UTF-8 com BOM, o que adicionava um caractere invisível no início de cada string. Outro problema comum é a diferença entre divisão por espaço e divisão por token linguístico. Separar palavras em português não é apenas cortar por espaços. Palavras como "número" e "número." precisam ser tratadas de forma diferente se você quiser manter a raiz intacta. Nesse caso, o uso de stemmers ou lematizadores pós-tokenização pode ser necessário, mas isso aumenta a complexidade do pipeline.
Há ainda o caso de textos multilíngues, onde a separação por espaço funciona bem para português mas falha completamente para chinês ou japonês, que não usam espaços como delimitadores naturais. Se seu projeto lida com múltiplos idiomas, prepare-se para adotar segmentadores específicos por língua, como o jieba para chinês ou o Mecab para japonês.
Performance e alternativas
Para textos pequenos, até 10 mil linhas, a abordagem com regex é suficiente. Mas quando você precisa processar gigabytes de dados, a sobrecarga da compilação de expressões regulares pode se tornar significativa. Nesse cenário, prefiro usar bibliotecas otimizadas como o NLTK ou o spaCy, que oferecem tokenizadores pré-treinados com boa cobertura para português. O spaCy, em especial, tem um tokenizer para português que lida bem com a maioria dos casos padrão e pode ser estendido com regras personalizadas. A desvantagem é que ele é mais pesado para instalar e importar, e o overhead inicial pode não valer a pena para projetos menores.
Uma alternativa mais leve é usar o tokenizer do HanLP ou do Stanza, que também suportam português e oferecem bom equilíbrio entre velocidade e qualidade. O trade-off aqui é que nenhum deles cobre todos os casos de borda do português brasileiro, então sempre vale a pena validar com um subset do seu corpus antes de rodar em escala.
Conclusão
A separação de palavras parece simples, mas é uma etapa que merece atenção. Dados mal tokenizados comprometem qualquer modelo downstream, não importa quão sofisticado ele seja. Na prática, o ideal é começar com uma regex básica, testar com seus dados reais, e ir refinando conforme os casos de borda aparecem. Não existe solução universal que funcione para todos os cenários, e tentar aplicar um método genérico em dados específicos costuma gerar resultados insatisfatórios.