pt_core_news_trf: anatomia de uma pipeline transformer para português
A falta de uma pipeline transformer integrada para análise do português motivou a pt_core_news_trf. Montei e publiquei um pacote spaCy 3.4 que combina BERTimbau Base, tokenizador, pesos, vocabulário e seis componentes para entidades, morfossintaxe, lematização, dependências e sentenças. A principal contribuição é entregar essas tarefas em uma única pipeline instalável [1, 2].
Eu projetei a pipeline para compartilhar um Transformer entre tarefas. No empacotamento final, preservei um segundo BERTimbau dentro do NER congelado, enquanto as demais tarefas compartilham outro encoder. Por isso, descrevo o resultado como pipeline multitarefa híbrida, não como um único encoder compartilhado por todas as tarefas.
Visão geral do artefato
Publiquei a versão 3.4.0, compatível com spacy>=3.4.3,<3.5.0 e spacy-transformers>=1.1.8,<1.2.0, sem vetores estáticos e sob CC BY-SA 4.0 [1]. Usei como encoder neuralmind/bert-base-portuguese-cased, um BERT Base sensível a maiúsculas, com 12 camadas, 12 cabeças de atenção, dimensão oculta 768, vocabulário WordPiece de 29.794 itens e cerca de 110 milhões de parâmetros. O BERTimbau foi pré-treinado por um milhão de passos no brWaC, com whole-word masking [8, 9].
Antes dos componentes estatísticos, o tokenizador da classe portuguesa do spaCy cria os Token do Doc. O WordPiece do BERTimbau produz uma segmentação diferente; spacy-transformers mantém um alinhamento entre as duas e aplica reduce_mean quando um token spaCy corresponde a vários wordpieces [3]. Assim, as cabeças trabalham no espaço de tokens do spaCy sem fingir que as duas tokenizações são iguais.
Renderizando diagrama...
Componente por componente
transformer. Configurei o BERTimbau como primeiro componente por meio de spacy-transformers.TransformerModel.v3. Dividi documentos longos em janelas de 128 tokens spaCy, com passo 96 e sobreposição de 32 tokens. A sobreposição reduz artefatos nas bordas, ao custo de recomputar contexto. Completei a configuração com max_batch_items=4096, mixed_precision=false e o fast tokenizer do Hugging Face. O resultado bruto fica disponível em Doc._.trf_data; o componente não prevê um rótulo e não tem uma métrica de acurácia própria [2, 3].
ner. Treinei separadamente no WikiNER um reconhecedor de LOC, MISC, ORG e PER e depois o importei para a pipeline principal. Usei TransitionBasedParser.v2, state_type="ner", largura oculta 64 e duas peças maxout. A perda privilegia a entidade completa, inclusive limites; menções aninhadas ou sobrepostas não cabem em Doc.ents [4].
tagger. Configurei spacy.Tagger.v2 para prever Token.tag_, isto é, XPOS, a partir dos vetores do listener. Publiquei 35 rótulos, incluindo combinações como ADP_DET, VERB_PRON e PROPN_PROPN, resultantes da conversão de subtokens e etiquetas do Bosque.
morphologizer. Configurei o componente para prever simultaneamente Token.pos_ (as 17 categorias UPOS) e Token.morph. Cada combinação observada, por exemplo Gender=Fem|Number=Sing|POS=NOUN, funciona como uma classe. Isso explica a maior parte dos 742 rótulos anunciados para quatro componentes: a explosão combinatória está na morfologia. Usei overwrite=true e extend=false, substituindo integralmente uma análise morfológica anterior [5].
trainable_lemmatizer. Usei EditTreeLemmatizer, não o lematizador por tabelas chamado lemmatizer. Ele aprende uma classe de transformação entre forma e lema, como remover um sufixo e inserir outro. Mantive somente árvores vistas ao menos três vezes (min_tree_freq=3) e testei a árvore mais provável (top_k=1), usando a forma ortográfica como backoff. O componente escreve Token.lemma_ [6].
parser. Usei TransitionBasedParser.v2 com state_type="parser", largura 128 e três peças maxout. Configurei 37 rótulos para Token.head e Token.dep_, transformação pseudo-projetiva quando necessária e segmentação por uma transição de quebra. Por isso, o mesmo componente define Token.is_sent_start e Doc.sents. Adotei min_action_freq=30, reduzindo ações raras em troca de estabilidade [7].
Como os componentes compartilham representações
Conectei tagger, morphologizer, trainable_lemmatizer e parser ao transformer anterior com TransformerListener.v1. Em uma passada, o encoder calcula as ativações; cada cabeça as reutiliza e devolve gradientes. Usei grad_factor=1.0 em todos os listeners, sem reescalar a contribuição diferencial de cada cabeça [2, 3]. Em notação geral, para parâmetros compartilhados e parâmetros específicos , o objetivo pode ser escrito como:
Aqui, na segunda fase, e os listeners usam fator unitário. Os training.score_weights não são os da perda: são pesos para selecionar o melhor checkpoint. A configuração atribui 0,2 a NER, tag e lema; 0,1 a POS, morfologia, UAS e LAS; precisão e revocação auxiliares recebem zero ou null.
O NER é a exceção crucial. A configuração principal usa:
[components.ner]
source = "./model-ner/"
component = "ner"
replace_listeners = ["model.tok2vec"]
[training]
frozen_components = ["ner"]Usei replace_listeners para copiar o encoder e isolar o NER, evitando que o ajuste das tarefas do Bosque alterasse a cabeça já treinada no WikiNER. No config.cfg, isso aparece como spacy-transformers.Tok2VecTransformer.v3 dentro de components.ner.model.tok2vec, enquanto as outras quatro cabeças continuam ouvindo o transformer compartilhado. Essa separação reduz a interferência entre os dois treinos, mas duplica custo, memória e tamanho; o wheel que publiquei ocupa aproximadamente 777 MiB [2].
Corpora e pré-processamento
O núcleo linguístico vem do UD Portuguese Bosque v2.8, derivado da Floresta Sintá(c)tica. Ele combina português europeu do CETEMPúblico e português brasileiro do CETENFolha, ambos no gênero jornalístico. Na versão 2.8 são 9.364 sentenças e 227.826 palavras sintáticas: 8.328 sentenças em treino, 560 em desenvolvimento e 476 em teste. Há 16.869 tokens multi-palavra, caso típico das contrações do, na e pelos [10].
Converti os três arquivos CoNLL-U para DocBin em grupos de dez sentenças e com --merge-subtokens. Isso preserva como um token spaCy unidades que o CoNLL-U representa por mais de uma palavra sintática e ajuda a explicar XPOS compostos. O corpus fornece tokenização, lema, UPOS, XPOS, FEATS, cabeças e relações de dependência.
Para NER, usei o arquivo português aij-wikiner-pt-wp3.bz2 do WikiNER, um corpus silver standard gerado automaticamente a partir da Wikipédia. A variante wp3 usa mais inferência sobre links que wp2; o formato original é delimitado por barras e mapeável para estilo CoNLL-2003. Li as linhas de wiki-ner, separei 80%/10%/10% com duas chamadas a train_test_split e converti IOB para .spacy, também em documentos de dez sentenças [1, 11]. Não defini random_state, estratificação ou divisão explícita por artigo, o que limita a reprodução e o controle de vazamento contextual.
Renderizando diagrama...
Configuração de treinamento
Treinei em uma NVIDIA A100 de 40 GB com CUDA 11.2 [1]. Primeiro, completei base_config_ner.cfg com spacy init fill-config e ajustei NER e Transformer no WikiNER. Na segunda fase, usei base_config_core.cfg para importar o NER, substituir seu listener, congelá-lo e ajustar os demais componentes no treino e desenvolvimento do Bosque.
Usei até 20.000 passos, avaliação a cada 200, paciência 600, dropout 0,1 e acumulação de gradiente por três lotes. Configurei o batcher para agrupar por tamanho preenchido, com alvo 2.000, buffer 256 e descarte de exemplos grandes demais. No Adam, usei , , weight decay L2 de 0,01, corte de gradiente em 1,0 e taxa inicial , aquecida por 250 passos e reduzida linearmente até o passo 20.000 [2]. Não usei precisão mista no BERT, favorecendo previsibilidade numérica em troca de memória e velocidade.
Comandos equivalentes aos documentados são:
python -m spacy convert data/pt_bosque-ud-train.conllu corpus \
--converter conllu --n-sents 10 --merge-subtokens
python -m spacy init fill-config configs/base_config_core.cfg config_core.cfg
python -m spacy train config_core.cfg --output training --gpu-id 0 \
--paths.train corpus/pt_bosque-ud-train.spacy \
--paths.dev corpus/pt_bosque-ud-dev.spacy \
--training.patience 600Para uma reprodução rigorosa, ainda preciso fixar versões de software e dados, além das sementes, registrar o comando de avaliação e executar spacy debug data antes do treino. Não registrei todos esses itens no roteiro original.
Métricas: leitura correta
Para tokens avaliados, excluídas as convenções do avaliador para pontuação, UAS exige apenas a cabeça correta e LAS exige cabeça e relação corretas:
Para entidades ou fronteiras de sentença, com correspondência exata, precisão, revocação e F1 são:
Publiquei os valores abaixo [1, 2]. Eles devem ser interpretados por componente e por corpus, não como uma única métrica global.
| Componente | Métrica publicada | Resultado |
|---|---|---|
ner | precisão / revocação / F1 de entidade | 92,75 / 92,94 / 92,84 |
tagger | acurácia XPOS | 97,82 |
morphologizer | acurácia UPOS / FEATS | 97,81 / 96,11 |
trainable_lemmatizer | acurácia de lema | 97,35 |
parser | UAS / LAS | 92,84 / 89,66 |
parser | P / R / F1 de sentenças | 93,49 / 94,28 / 93,88 |
transformer | métrica intrínseca | não se aplica |
Preservei dois grupos de resultados. Em results.json, não incluí NER e registrei, entre outros números, XPOS 98,12, UPOS 98,07, morfologia 96,54, lema 97,48, UAS 92,90, LAS 89,88 e F1 de sentenças 95,06, além de 6.834 palavras/s sem hardware identificado. Em meta.json, publiquei os valores da tabela e o detalhamento por classe: case alcança LAS F1 98,21, enquanto parataxis fica em 52,67, iobj em 64,75 e relações ausentes ou raríssimas chegam a zero. Em morfologia, Number tem F1 99,13, mas Abbr 80,0 e Typo zero. Isso mostra por que médias altas não eliminam erros de cauda longa.
Sem o comando, o checkpoint e o corpus exato de cada exportação, os dois grupos de números não devem ser misturados. O F1 de NER deriva do ramo WikiNER; sintaxe, morfologia e lema derivam do Bosque. Comparações externas exigem reavaliar o wheel em conjuntos fixos e publicar intervalos por semente.
Empacotamento, instalação e inferência
Empacotei config.cfg, meta.json, tokenizador, vocabulário e os subdiretórios dos componentes com spacy package ... --build wheel. A tag py3-none-any indica que o wheel não contém extensão compilada específica de plataforma, não que toda a pilha seja leve ou independente de plataforma. spaCy, PyTorch e suas dependências continuam sujeitos ao sistema operacional, à versão de Python e, em GPU, a CUDA [12].
Por causa dos limites estritos, a instalação mais segura usa ambiente isolado:
python -m venv .venv
source .venv/bin/activate
python -m pip install "spacy>=3.4.3,<3.5.0" "spacy-transformers>=1.1.8,<1.2.0"
python -m pip install \
"https://huggingface.co/dominguesm/pt_core_news_trf/resolve/main/pt_core_news_trf-any-py3-none-any.whl"
python -m spacy validateUma aplicação pode carregar pelo registro spaCy ou pelo módulo do pacote:
import spacy
# Chame spacy.require_gpu() antes de load() se uma GPU compatível for obrigatória.
nlp = spacy.load("pt_core_news_trf")
text = "A Embraer anunciou em São José dos Campos que entregará novas aeronaves."
doc = nlp(text)
for token in doc:
print({
"texto": token.text,
"lema": token.lemma_,
"upos": token.pos_,
"xpos": token.tag_,
"morfologia": str(token.morph),
"dependencia": token.dep_,
"cabeca": token.head.text,
})
print("entidades:", [(ent.text, ent.label_) for ent in doc.ents])
print("sentencas:", [sent.text for sent in doc.sents])Para volume, nlp.pipe(textos, batch_size=...) evita chamadas unitárias. CPU é funcional, mas dois encoders BERT tornam latência e memória muito superiores às de uma pipeline CNN. Desabilitar componentes reduz cabeças, porém os quatro listeners dependem do transformer; remover esse componente e manter um listener não é válido. O pacote também não oferece vetores estáticos (0 chaves, dimensão 0), portanto não deve ser escolhido para similaridade lexical baseada em Token.vector.
Comportamentos conhecidos, compatibilidade e segurança
O compartilhamento reduz quatro execuções do BERTimbau a uma e permite que sinais de morfologia, lema e sintaxe ajustem uma representação comum. Em contrapartida, acopla as tarefas: trocar só o encoder ou mover uma cabeça para outra pipeline deixa de ser simples, e gradientes podem competir. O NER isolado evita essa interferência, mas praticamente duplica os parâmetros transformer. Uma versão futura poderia treinar todos os corpora com anotações parciais e um encoder único, ou destilar/quantizar os dois ramos; qualquer mudança exigiria medir se a economia compensa perda de F1.
A faixa spaCy 3.4 é parte do contrato serializado. Atualizar diretamente para spaCy atual, alterar arquiteturas no config.cfg após o treino ou usar outra série de spacy-transformers pode falhar na resolução do registro ou desserialização dos pesos. Para modernizar, o caminho robusto é reconstruir os dados e retreinar com configuração atual, não editar o wheel. Para produção legada, fixe dependências e mantenha testes de carregamento e inferência.
Os vieses acompanham toda a linhagem. Bosque representa notícia escrita de dois veículos e mistura variedades europeia e brasileira, mas não cobre proporcionalmente fala, redes sociais, medicina, direito ou todas as regiões. WikiNER é anotação automática baseada em links: herda a desigualdade de cobertura da Wikipédia, privilegia entidades notáveis e capitalizadas, contém ruído de fronteira e limita a ontologia a quatro classes. O próprio registro chama o conjunto de silver standard e reconhece viés na distribuição de artigos [11]. BERTimbau traz padrões e estereótipos da web brasileira do brWaC; ser cased também torna caixa e ortografia fontes fortes de sinal. Não há no cartão avaliação por gênero, raça, região, variedade do português ou domínio. As métricas, portanto, não são evidência de equidade.
Quanto às licenças, o pacote declara CC BY-SA 4.0; Bosque usa CC BY-SA 4.0; o depósito WikiNER usa CC BY 4.0; e os pesos BERTimbau são MIT [1, 8, 10, 11]. Redistribuição deve manter atribuição, avisos e, quando aplicável, compartilhamento pela mesma licença. Como pesos derivados e bases podem envolver interpretações jurídicas específicas, essa enumeração técnica não substitui revisão legal do uso pretendido.
Conclusão
Com a pt_core_news_trf, reuni seis componentes linguísticos em um pacote spaCy instalável baseado no BERTimbau e facilitei a análise integrada de português. Aprendi a importância de documentar o compartilhamento efetivo de encoders e validar métricas e compatibilidade no domínio de reutilização.
Referências
- Domingues, M. Portuguese Transformer (spaCy pipeline): README e TRAINING. GitHub, 2022.
- Domingues, M. pt_core_news_trf: model card, config.cfg, meta.json e results.json. Hugging Face, 2022.
- Explosion. Embeddings, Transformers and Transfer Learning; Transformer API. spaCy.
- Explosion. EntityRecognizer API. spaCy.
- Explosion. Morphologizer API. spaCy.
- Explosion. EditTreeLemmatizer API. spaCy.
- Explosion. DependencyParser API. spaCy.
- Souza, F.; Nogueira, R.; Lotufo, R. BERTimbau: Pretrained BERT Models for Brazilian Portuguese. BRACIS, 2020.
- Wagner Filho, J. A. et al. The brWaC Corpus: A New Open Resource for Brazilian Portuguese. LREC, 2018.
- Rademaker, A. et al. Universal Dependencies for Portuguese. DepLing, 2017; UD Portuguese Bosque v2.8.
- Nothman, J. et al. Learning multilingual named entity recognition from Wikipedia. Artificial Intelligence, 194, 2013; dados no Figshare.
- Explosion. Saving, loading and distributing trained pipelines; package CLI. spaCy.
BibTeX
@software{domingues2022ptcorenewstrf,
author = {Maicon Domingues},
title = {pt_core_news_trf},
year = {2022},
version = {3.4.0},
url = {https://github.com/DominguesM/pt_core_news_trf}
}
@inproceedings{souza2020bertimbau,
author = {F{\'a}bio Souza and Rodrigo Nogueira and Roberto Lotufo},
title = {{BERT}imbau: Pretrained {BERT} Models for Brazilian Portuguese},
booktitle = {Intelligent Systems},
series = {Lecture Notes in Computer Science},
volume = {12319},
pages = {403--417},
publisher = {Springer},
year = {2020},
doi = {10.1007/978-3-030-61377-8_28}
}
@inproceedings{rademaker2017universal,
author = {Alexandre Rademaker and Fabricio Chalub and Livy Real and
Cl{\'a}udia Freitas and Eckhard Bick and Valeria de Paiva},
title = {Universal Dependencies for Portuguese},
booktitle = {Proceedings of the Fourth International Conference on Dependency Linguistics},
pages = {197--206},
year = {2017},
url = {https://aclanthology.org/W17-6523/}
}
@article{nothman2013wikiner,
author = {Joel Nothman and Nicky Ringland and Will Radford and Tara Murphy and James R. Curran},
title = {Learning Multilingual Named Entity Recognition from Wikipedia},
journal = {Artificial Intelligence},
volume = {194},
pages = {151--175},
year = {2013},
doi = {10.1016/j.artint.2012.03.006}
}
@inproceedings{wagnerfilho2018brwac,
author = {Jorge A. Wagner Filho and Rodrigo Wilkens and Marco Idiart and Aline Villavicencio},
title = {The brWaC Corpus: A New Open Resource for Brazilian Portuguese},
booktitle = {Proceedings of the Eleventh International Conference on Language Resources and Evaluation},
year = {2018},
url = {https://aclanthology.org/L18-1686/}
}