Identificação de Dados Sensíveis com NER usando GLiNER
Em aplicações modernas, é comum lidar com grandes volumes de informações que podem conter dados pessoais e sensíveis.
Um sistema pode receber:
- Nome completo
- CPF
- Telefone
- Endereço
- Número de cartão
- Informações bancárias
- Dados médicos
- Informações profissionais
Antes de armazenar, processar ou enviar essas informações para outros sistemas, muitas aplicações precisam identificar automaticamente quais partes do texto contêm dados sensíveis.
Uma das técnicas utilizadas para isso é o Named Entity Recognition (NER).
Neste artigo, veremos como utilizar o GLiNER com Python para identificar entidades sensíveis em textos, utilizando o modelo urchade/gliner_multi-v2.1.
O que é NER?
Named Entity Recognition, ou NER, é uma técnica de Processamento de Linguagem Natural (NLP) utilizada para identificar entidades dentro de um texto.
Por exemplo:
O cliente João Silva mora em Joinville e pode ser
contatado pelo e-mail joao@email.com.
Um sistema tradicional de NER poderia identificar:
João Silva
→ PERSON
Joinville
→ LOCATION
joao@email.com
→ EMAIL
O resultado seria algo semelhante a:
[João Silva] → PERSON
[Joinville] → LOCATION
[joao@email.com] → EMAIL
Essas informações podem então ser utilizadas para:
- Mascaramento de dados.
- Anonimização.
- Redação automática.
- Prevenção de vazamento de informações.
- Classificação de documentos.
- Compliance.
- DLP (Data Loss Prevention).
O problema dos modelos NER tradicionais
Modelos NER tradicionais normalmente são treinados para reconhecer categorias pré-definidas.
Por exemplo:
PERSON
LOCATION
ORGANIZATION
DATE
O problema é que aplicações de segurança e privacidade frequentemente precisam detectar categorias específicas.
Por exemplo:
CPF
CNPJ
EMAIL
PHONE
CREDIT_CARD
BANK_ACCOUNT
MEDICAL_RECORD
Treinar um modelo específico para cada nova entidade pode ser trabalhoso.
É aqui que entra o GLiNER.
O que é GLiNER?
O GLiNER é um modelo de Named Entity Recognition capaz de realizar zero-shot NER.
Isso significa que podemos informar ao modelo quais tipos de entidades estamos procurando diretamente no momento da inferência.
Por exemplo:
labels = [
"person",
"email",
"phone number",
"CPF",
"credit card"
]
O modelo então tenta identificar essas entidades no texto.
Isso é interessante para identificação de dados sensíveis porque podemos definir nossas próprias categorias.
Por exemplo:
PERSON
EMAIL
PHONE
CPF
CNPJ
ADDRESS
BANK_ACCOUNT
CREDIT_CARD
Sem necessariamente precisar treinar um modelo específico para cada categoria.
Instalando o GLiNER
Podemos instalar a biblioteca utilizando pip:
pip install gliner
Também precisaremos do PyTorch:
pip install torch
Dependendo do ambiente, pode ser necessário instalar uma versão específica do PyTorch compatível com sua GPU.
Carregando o modelo
Vamos utilizar o modelo:
urchade/gliner_multi-v2.1
O multi indica que o modelo foi desenvolvido para trabalhar com múltiplos idiomas.
Isso é especialmente interessante para aplicações que recebem textos em diferentes idiomas.
Podemos carregar o modelo:
from gliner import GLiNER
model = GLiNER.from_pretrained(
"urchade/gliner_multi-v2.1"
)
Depois disso, o modelo estará pronto para realizar a identificação das entidades.
Primeiro exemplo
Vamos criar um texto:
text = """
O cliente João da Silva possui CPF 123.456.789-00.
Seu e-mail é joao@email.com e seu telefone
é (47) 99999-9999.
"""
Agora definimos as entidades que queremos encontrar:
labels = [
"person",
"CPF",
"email",
"phone number"
]
Podemos realizar a predição:
entities = model.predict_entities(
text,
labels
)
E imprimir os resultados:
for entity in entities:
print(entity)
Dependendo da versão da biblioteca, o resultado terá informações semelhantes a:
{
"text": "João da Silva",
"label": "person",
"score": 0.95,
"start": 18,
"end": 31
}
Outro resultado:
{
"text": "joao@email.com",
"label": "email",
"score": 0.98,
"start": 65,
"end": 80
}
E:
{
"text": "(47) 99999-9999",
"label": "phone number",
"score": 0.94,
"start": 97,
"end": 112
}
Os valores de score são probabilidades ou pontuações de confiança produzidas pelo modelo.
Entendendo o resultado
Cada entidade identificada possui informações importantes.
Por exemplo:
{
"text": "João da Silva",
"label": "person",
"score": 0.95,
"start": 18,
"end": 31
}
Temos:
text
O trecho identificado:
João da Silva
label
A categoria atribuída:
person
score
A confiança do modelo:
0.95
start
Posição inicial da entidade no texto.
end
Posição final da entidade.
Essas posições são especialmente úteis para realizar operações como:
- Substituição.
- Mascaramento.
- Redação.
- Anonimização.
Criando um detector de dados sensíveis
Podemos encapsular o modelo em uma classe:
from gliner import GLiNER
class SensitiveDataDetector:
def __init__(self):
self.model = GLiNER.from_pretrained(
"urchade/gliner_multi-v2.1"
)
self.labels = [
"person",
"email address",
"phone number",
"CPF",
"CNPJ",
"address",
"credit card number",
"bank account",
"medical information"
]
def detect(self, text: str):
return self.model.predict_entities(
text,
self.labels
)
Podemos utilizar:
detector = SensitiveDataDetector()
text = """
Meu nome é João da Silva.
Meu CPF é 123.456.789-00.
Meu e-mail é joao@email.com.
"""
entities = detector.detect(text)
for entity in entities:
print(
entity["text"],
entity["label"],
entity["score"]
)
A saída pode ser semelhante a:
João da Silva person 0.95
123.456.789-00 CPF 0.98
joao@email.com email address 0.99
NER não é a mesma coisa que Regex
É importante entender que NER e Regex resolvem problemas diferentes.
Uma Regex é excelente para padrões bem definidos.
Por exemplo, um CPF:
123.456.789-00
Podemos detectar usando:
import re
pattern = r"\d{3}\.\d{3}\.\d{3}-\d{2}"
matches = re.findall(
pattern,
text
)
Isso funciona muito bem quando conhecemos o formato.
Porém, existem informações que não possuem um padrão tão rígido.
Por exemplo:
O paciente foi diagnosticado com diabetes.
Não existe necessariamente um formato fixo que permita encontrar essa informação com uma Regex simples.
Um modelo de NLP pode analisar o contexto e identificar:
diabetes
→ medical information
Por isso, uma arquitetura robusta normalmente combina as duas abordagens.
Regex + NER
Podemos utilizar:
Texto
│
▼
┌───────────────────┐
│ Regex │
│ │
│ CPF │
│ CNPJ │
│ E-mail │
│ Telefone │
└─────────┬─────────┘
│
▼
┌───────────────────┐
│ NER │
│ │
│ Pessoa │
│ Endereço │
│ Dados médicos │
│ Contexto │
└─────────┬─────────┘
│
▼
Dados identificados
Por exemplo:
regex_entities = detect_with_regex(
text
)
ner_entities = model.predict_entities(
text,
labels
)
Depois podemos combinar os resultados.
Essa abordagem pode ser mais confiável do que depender exclusivamente de um dos métodos.
Mascarando dados sensíveis
Depois de identificar as entidades, podemos mascará-las.
Por exemplo:
Original:
Meu CPF é 123.456.789-00
Resultado:
Meu CPF é [CPF_REDACTED]
Podemos implementar:
def redact(
text: str,
entities: list
):
for entity in reversed(entities):
start = entity["start"]
end = entity["end"]
label = entity["label"]
replacement = (
f"[{label.upper()}_REDACTED]"
)
text = (
text[:start]
+ replacement
+ text[end:]
)
return text
Uso:
entities = detector.detect(text)
safe_text = redact(
text,
entities
)
print(safe_text)
Resultado:
Meu nome é [PERSON_REDACTED].
Meu CPF é [CPF_REDACTED].
Meu e-mail é [EMAIL ADDRESS_REDACTED].
Usamos reversed() porque estamos alterando o texto com base nos índices originais.
Se substituirmos as entidades começando pelo início do texto, o tamanho da string pode mudar e invalidar as posições das entidades seguintes.
Aplicando um threshold
Nem toda entidade detectada deve ser considerada válida.
Por isso, podemos utilizar um limite de confiança:
THRESHOLD = 0.7
entities = model.predict_entities(
text,
labels,
threshold=THRESHOLD
)
Agora somente entidades com confiança suficiente serão retornadas.
Por exemplo:
0.95 → Aceitar
0.91 → Aceitar
0.82 → Aceitar
0.64 → Ignorar
O valor ideal depende do caso de uso.
Em um sistema de proteção de dados, pode ser preferível utilizar um threshold mais baixo e aceitar alguns falsos positivos.
Em sistemas onde a precisão é mais importante, podemos aumentar o threshold.
Falsos positivos e falsos negativos
Sistemas de identificação de dados sensíveis possuem dois problemas importantes.
Falso positivo
O sistema identifica algo como dado sensível, mas não é.
Exemplo:
O número da sala é 123.456.789-00.
O modelo pode interpretar como CPF.
Isso pode gerar:
Falso positivo
Falso negativo
O sistema não identifica um dado sensível que realmente existe.
Exemplo:
Meu documento é o número 12345678900.
Se o detector não reconhecer esse formato, teremos:
Falso negativo
Em sistemas de segurança, falsos negativos podem ser especialmente perigosos.
Por isso, uma arquitetura de produção pode combinar:
Regex
+
NER
+
Validação
+
Regras de negócio
Validando entidades
Para documentos com formatos conhecidos, podemos validar o resultado do NER.
Por exemplo, se o modelo identificar:
123.456.789-00
como CPF, podemos executar uma validação específica.
def is_valid_cpf(cpf: str) -> bool:
# Implementação da validação
# dos dígitos verificadores
return True
Assim:
NER identifica
↓
Regex verifica formato
↓
Validador confirma
↓
Entidade aceita
Isso reduz falsos positivos.
Uma arquitetura mais robusta
Em um sistema real de DLP ou anonimização, poderíamos ter:
Documento
│
▼
Pré-processamento
│
▼
┌──────────┴──────────┐
│ │
▼ ▼
Regex GLiNER
│ │
│ │
└──────────┬──────────┘
│
▼
Normalização
│
▼
Deduplicação
│
▼
Validação
│
▼
Score / Threshold
│
▼
Classificação final
│
▼
┌─────────────┴─────────────┐
│ │
▼ ▼
Armazenar Redigir
com segurança dados
Essa arquitetura permite combinar modelos de Machine Learning com regras determinísticas.
Exemplo completo
Uma implementação simplificada poderia ser:
from gliner import GLiNER
class SensitiveDataDetector:
def __init__(self):
self.model = GLiNER.from_pretrained(
"urchade/gliner_multi-v2.1"
)
self.labels = [
"person",
"email address",
"phone number",
"CPF",
"CNPJ",
"address",
"credit card number",
"bank account",
"medical information"
]
def detect(
self,
text: str,
threshold: float = 0.7
):
return self.model.predict_entities(
text,
self.labels,
threshold=threshold
)
def redact(
self,
text: str,
threshold: float = 0.7
):
entities = self.detect(
text,
threshold
)
for entity in reversed(
entities
):
start = entity["start"]
end = entity["end"]
label = entity["label"]
replacement = (
f"[{label.upper()}_REDACTED]"
)
text = (
text[:start]
+ replacement
+ text[end:]
)
return text
Uso:
detector =
SensitiveDataDetector()
text = """
João da Silva possui CPF
123.456.789-00.
Seu e-mail é
joao@email.com.
O telefone é
(47) 99999-9999.
"""
entities = detector.detect(text)
for entity in entities:
print(
f"Texto: {entity['text']}"
)
print(
f"Tipo: {entity['label']}"
)
print(
f"Confiança: {entity['score']}"
)
print("---")
Para redigir:
safe_text =
detector.redact(text)
print(safe_text)
O resultado esperado seria algo semelhante a:
[PERSON_REDACTED] possui
[CPF_REDACTED].
Seu e-mail é
[EMAIL ADDRESS_REDACTED].
O telefone é
[PHONE NUMBER_REDACTED].
Cuidados em produção
Embora o GLiNER seja bastante útil, ele não deve ser considerado uma solução perfeita de identificação de dados sensíveis.
Um modelo de Machine Learning pode:
- Errar entidades.
- Deixar dados passarem despercebidos.
- Interpretar contexto incorretamente.
- Gerar falsos positivos.
- Ter desempenho diferente entre idiomas e domínios.
Por isso, em sistemas críticos, é recomendável utilizar uma combinação de:
Regex
+
NER
+
Validadores
+
Regras de negócio
+
Thresholds
+
Testes automatizados
+
Monitoramento
Também é importante testar o modelo com dados reais e representativos do domínio da aplicação.
Por exemplo, um sistema bancário pode possuir categorias específicas que não aparecem em um dataset genérico.
Conclusão
O Named Entity Recognition é uma técnica poderosa para identificar informações dentro de textos.
Com modelos como o GLiNER, podemos definir dinamicamente as categorias que queremos encontrar, tornando a solução mais flexível para aplicações de identificação de dados sensíveis.
Uma arquitetura simples pode ser:
Texto
↓
Regex
↓
GLiNER
↓
Validação
↓
Classificação
↓
Mascaramento / Anonimização
O principal benefício de utilizar GLiNER é a possibilidade de detectar diferentes tipos de entidades sem precisar necessariamente criar um modelo específico para cada categoria.
Entretanto, para aplicações de LGPD, DLP, segurança e compliance, o ideal é não depender exclusivamente do modelo.
A abordagem mais robusta é combinar:
Machine Learning para entender contexto + Regex para detectar padrões + validação para confirmar os resultados.
Essa combinação permite construir sistemas capazes de analisar documentos, mensagens, logs e outros conteúdos automaticamente, identificando informações potencialmente sensíveis antes que elas sejam armazenadas, compartilhadas ou processadas por outros sistemas.