3 359 42

Canarim BERT POS Tagger para Nheengatu

Como desenvolvi o classificador de classes gramaticais em Nheengatu e o que aprendi com o UD Nheengatu-CompLin.

Canarim BERT POS Tagger: o que aprendi sobre as classes do Nheengatu

Eu desenvolvi o Canarim BERT POS Tagger para Nheengatu como um checkpoint de classificação de tokens que atribui uma classe gramatical universal a cada palavra de uma sentença em Nheengatu. Ele não deve ser confundido com o dominguesm/canarim-bert-nheengatu: esse segundo checkpoint é o modelo de linguagem mascarada usado como base; o primeiro acrescenta e ajusta uma cabeça de classificação para part-of-speech tagging (POS tagging) [1][2]. Essa distinção corrige também o H1 anterior, que nomeava apenas o BERT-base.

Publiquei o modelo no Hugging Face Hub em 28 de dezembro de 2023. Sua configuração declara BertForTokenClassification, 12 camadas, 12 cabeças de atenção, dimensão oculta 768, vocabulário de 32.768 itens, limite posicional de 128 e 110.333.199 parâmetros em float32. A cabeça possui 15 saídas, de B-ADJ a B-VERB [1]. O prefixo B- é uma convenção herdada da interface de classificação de tokens; aqui todas as saídas são classes UPOS, não inícios de entidades nomeadas.

Preservei a ficha, a configuração, os gráficos, a versão histórica do treebank e as referências do projeto. Nas seções de dados, treinamento e reprodutibilidade, explico diretamente as decisões que não registrei.

O problema linguístico e o padrão UD

Nheengatu, também chamado Tupi Moderno ou Língua Geral Amazônica, tem código ISO 639-3 yrl. O UD Nheengatu-CompLin é seu primeiro treebank morfossintático no ecossistema Universal Dependencies (UD) [3][4]. O UD procura tornar segmentação, morfologia e dependências comparáveis entre línguas: no formato CoNLL-U, a coluna FORM contém a forma observada, LEMMA o lema, UPOS a classe universal, FEATS os traços morfológicos e HEAD/DEPREL a árvore sintática [5]. Neste projeto, ajustei apenas FORM -> UPOS; não treinei predição de lema, traços ou relações de dependência.

O treebank reúne fala espontânea, descrições gramaticais, fábulas, mitos, livros didáticos, dicionários, textos bíblicos, ficção e não ficção. A anotação foi produzida de modo semiautomático: o analisador Yauti gerou análises no padrão UD e anotadores as revisaram manualmente [3][6]. Portanto, os rótulos não são uma tradução automática de categorias do português. Eles pertencem ao inventário UD e foram aplicados segundo análise do Nheengatu.

As 15 classes aprendidas pelo checkpoint são:

UPOSCategoriaUPOSCategoriaUPOSCategoria
ADJadjetivoADPadposiçãoADVadvérbio
AUXauxiliarCCONJconjunção coordenativaDETdeterminante
INTJinterjeiçãoNOUNsubstantivo comumNUMnumeral
PARTpartículaPRONpronomePROPNnome próprio
PUNCTpontuaçãoSCONJconjunção subordinativaVERBverbo

O inventário UD completo também prevê SYM e X [5]. O snapshot usado no fim de 2023 tinha 15 UPOS e não continha exemplos de SYM ou X; por isso essas classes não aparecem em id2label. Entradas fora desse inventário continuam forçadas a uma das 15 saídas: o modelo não dispõe de uma classe explícita “desconhecida”.

De 1.336 sentenças aos splits 1.068/134/134

Dividi o UD Nheengatu-CompLin em 1.068 sentenças de treino, 134 de validação e 134 de teste, ou 80/10/10, com os campos id, tokens, pos_tags e text [1]. A soma é exatamente:

1068+134+134=1336.1068 + 134 + 134 = 1336.

Por arredondamento, as frações efetivas são 79,94%, 10,03% e 10,03%. O número não coincide com o release UD 2.13, que registrava 1.239 sentenças, porque o corpus continuava em desenvolvimento. Uma versão de desenvolvimento datada de 15 de dezembro de 2023 contém 1.336 blocos sent_id e 13.374 palavras sintáticas [7]. Essa é a fonte histórica compatível com a ficha publicada treze dias depois.

Naquela versão, o repositório UD continha um único arquivo chamado yrl_complin-ud-test.conllu, sem arquivos oficiais separados de treino e desenvolvimento. Portanto, 1.068/134/134 foi a repartição que fiz para este experimento. Não preservei publicamente a semente, a ordem dos exemplos, o algoritmo de embaralhamento, a estratificação, o agrupamento por fonte ou os índices de cada subconjunto; hoje consigo explicar os tamanhos, mas não reconstruir a mesma partição.

Renderizando diagrama...

No meu conjunto, “134 amostras de teste” conta sentenças e support = 1353 soma palavras avaliadas. Uma sentença contribui com vários rótulos.

Tokenização e alinhamento de subtokens

O dado UD já está segmentado em palavras sintáticas, mas o BertTokenizer usa WordPiece. Uma palavra como uma forma hifenizada pode permanecer inteira, virar vários subtokens ou conter itens desconhecidos. Além disso, [CLS], [SEP] e padding não têm UPOS. Treinar diretamente uma sequência de rótulos de palavra contra uma sequência maior de subtokens deslocaria todos os alvos.

A documentação do Transformers prescreve is_split_into_words=True, recupera word_ids() e atribui -100 a tokens especiais e a subtokens posteriores ao primeiro. O CrossEntropyLoss ignora -100 [8]. Na primeira versão deste checkpoint, não publiquei o código de pré-processamento; por isso, apresento esse procedimento como referência para uma nova execução, sem atribuí-lo retroativamente ao treino de 2023.

def tokenize_and_align(batch, tokenizer):
    encoded = tokenizer(
        batch["tokens"],
        is_split_into_words=True,
        truncation=True,
        max_length=128,
    )
    aligned = []
    previous_word = None
 
    for word_id in encoded.word_ids():
        if word_id is None or word_id == previous_word:
            aligned.append(-100)
        else:
            aligned.append(batch["pos_tags"][word_id])
        previous_word = word_id
 
    encoded["labels"] = aligned
    return encoded

Configurei o checkpoint com limite de 128. Truncar à direita significa que palavras depois desse limite não recebem predição naquela janela; em produção, textos longos devem ser segmentados em sentenças ou processados com janelas e uma política documentada para sobreposição. No sentido inverso, durante a inferência, predições de subtokens precisam voltar ao nível de palavra. No exemplo que publiquei, usei aggregation_strategy="average", que agrega escores dos subtokens e retorna entity_group, word, start e end [1].

Modelo-base e configuração da cabeça

Parti do dominguesm/canarim-bert-nheengatu, um BERT de 12 camadas e aproximadamente 110 milhões de parâmetros, pré-treinado para preenchimento de máscara em textos de Nheengatu [2]. Reutilizei seu tokenizador e encoder e troquei a cabeça de linguagem mascarada por uma projeção linear sobre 15 classes. Em cada posição ii, o encoder produz hiR768h_i \in \mathbb{R}^{768}; a cabeça calcula logits

zi=Whi+b,WR15×768,z_i = Wh_i + b, \qquad W \in \mathbb{R}^{15 \times 768},

e a distribuição é p(yi=c)=softmax(zi)cp(y_i=c)=\operatorname{softmax}(z_i)_c. Publiquei a classe de maior logit. Configurei hidden_dropout_prob = 0, attention_probs_dropout_prob = 0, embeddings posicionais absolutos e GELU. Essas decisões descrevem o checkpoint salvo, não uma recomendação geral.

Registrei estes hiperparâmetros de ajuste:

ParâmetroValor publicado
taxa de aprendizado3e-4
batch de treino16
batch de avaliação32
acumulação de gradiente1
weight decay0.01
épocas10

Na publicação inicial, não documentei otimizador, scheduler, warmup, clipping, precisão numérica do treino, hardware, semente, frequência de avaliação, critério de checkpoint, padding, versão exata do dataset nem tratamento de subtokens. A configuração salva informa Transformers 4.36.2 e pesos float32, mas isso não me permite concluir que toda a otimização ocorreu nessa precisão. Uma conta simples, 1068/16×10=670\lceil1068/16\rceil \times 10 = 670, coincide com a extensão aproximada dos gráficos em global_step; ela é uma verificação de consistência, não substitui o estado do Trainer ausente.

Curvas de perda e resultado de validação

Curva de perda de treino: queda de cerca de 2,5 no início para valores próximos de zero ao redor do passo 670, com oscilações decrescentes.

Perda de treino publicada no repositório de assets [9].

Curva de perda de validação: queda inicial de aproximadamente 2,0 para perto de 0,4, seguida por oscilação e estabilização ao redor de 0,53 até o passo 670.

Perda de validação publicada no repositório de assets [9].

Nos gráficos que publiquei, a perda de treino cai continuamente até quase zero. A de validação cai rapidamente, atinge visualmente sua região mínima perto de 0,4 entre aproximadamente 100 e 150 passos e depois sobe gradualmente para 0.533778 na época 10. Esse afastamento é compatível com sobreajuste: o modelo continua memorizando o treino sem melhorar a perda fora dele. Não preservei valores tabulares por passo nem um checkpoint antecipado; por isso, não declaro um passo ótimo apenas pela imagem.

No fim da décima época, registei no subconjunto de validação eval_loss = 0.533778, precisão, recall e F1 iguais a 0.913736, acurácia 0.913736, 134 amostras e tempo de 0,1957 s [1]. A igualdade das quatro métricas é típica de média micro em classificação multiclasse de rótulo único. Ela não é o F1 macro de teste que publiquei no cabeçalho do Hub.

Métricas de teste: o suporte muda a interpretação

Para uma classe cc, com verdadeiros positivos TPcTP_c, falsos positivos FPcFP_c e falsos negativos FNcFN_c:

Pc=TPcTPc+FPc,Rc=TPcTPc+FNc,F1c=2PcRcPc+Rc.P_c=\frac{TP_c}{TP_c+FP_c},\qquad R_c=\frac{TP_c}{TP_c+FN_c},\qquad F1_c=\frac{2P_cR_c}{P_c+R_c}.

O support é nc=TPc+FNcn_c=TP_c+FN_c, isto é, quantas ocorrências verdadeiras daquela classe existem no teste. O relatório soma 1.353 rótulos em 134 sentenças [1]. Classes frequentes tornam a acurácia estável; classes com suporte mínimo têm estimativas muito sensíveis a um único exemplo.

ClassePrecisãoRecallF1Support
ADJ0,78950,65220,714323
ADP0,93550,91580,925595
ADV0,82610,81720,821693
AUX0,94440,91890,931537
CCONJ0,77780,87500,82358
DET0,87760,91490,895847
INTJ0,50000,50000,50004
NOUN0,92570,92220,9239270
NUM1,00000,66670,80006
PART0,97750,90620,940596
PRON0,95681,00000,9779155
PROPN0,64290,42860,514321
PUNCT0,99631,00000,9981267
SCONJ0,80000,75000,774232
VERB0,86510,93470,8986199
micro0,92020,92020,92021.353
macro0,85430,81350,82931.353
ponderada0,91910,92020,91871.353

A média macro trata as 15 classes igualmente:

F1macro=115c=115F1c=0,8293.F1_{macro}=\frac{1}{15}\sum_{c=1}^{15}F1_c=0{,}8293.

A média ponderada usa nc/1353n_c/1353 e chega a 0,9187. Já a média micro agrega todas as decisões. Pela diagonal da matriz há 1.245 acertos e 108 erros, portanto

F1micro=acuracia=12451353=0,9202.F1_{micro}=\text{acuracia}=\frac{1245}{1353}=0{,}9202.

Essa diferença de quase nove pontos entre micro F1 e macro F1 é a principal leitura do resultado: o sistema é forte no volume total, mas irregular nas classes raras. Por exemplo, PROPN tem TP=9TP=9, FN=12FN=12 e FP=5FP=5. Assim, P=9/14=0,6429P=9/14=0,6429, R=9/21=0,4286R=9/21=0,4286 e F1=18/(18+5+12)=0,5143F1=18/(18+5+12)=0,5143. Para INTJ, dois acertos, dois falsos negativos e dois falsos positivos produzem F1 0,5, mas o suporte é apenas quatro. Um único acerto adicional mudaria o recall em 25 pontos percentuais.

O que a matriz de confusão ensinou sobre as classes

Matriz de confusão 15 por 15; linhas são classes verdadeiras e colunas são classes preditas. A diagonal soma 1245 acertos. Os maiores erros fora da diagonal incluem 12 nomes próprios classificados em outras classes, 11 substantivos classificados como verbos e 8 adjetivos classificados como advérbio, substantivo ou verbo.

Matriz de confusão do teste: acurácia 0,9202, taxa de erro 0,0798 e F1 macro 0,8293 [9].

A matriz permite ir além do ranking de F1. PROPN é o ponto mais frágil: dos 21 nomes próprios, nove são reconhecidos, cinco viram VERB, quatro NOUN, um PRON, um ADV e um ADP. O tokenizer está configurado com do_lower_case=true; perder a caixa remove um indício útil para nomes próprios. Isso é um mecanismo plausível, mas não me permite concluir causalidade para cada erro, sobretudo porque o Nheengatu tem variação ortográfica e o corpus combina gêneros e épocas.

NOUN apresenta 249 acertos em 270 casos. Seus maiores vazamentos são 11 para VERB e quatro para PROPN. No sentido inverso, cinco verbos viram NOUN. Essa assimetria é consistente com formas cuja categoria depende do contexto, mas uma conclusão linguística requer inspecionar sentenças, lemas e fontes, não somente contagens.

Os 23 adjetivos geram 15 acertos, quatro NOUN, três VERB e um ADV. NUM tem precisão perfeita porque suas quatro predições estão corretas, mas deixa dois dos seis numerais escapar, um para DET e outro para VERB. PRON acerta os 155 exemplos verdadeiros, embora receba sete falsos positivos e por isso tenha precisão 0,9568. PUNCT, com 267 ocorrências, é quase perfeito e pesa bastante na micro média. Em uma próxima avaliação lexical, pretendo publicar também métricas sem pontuação.

Renderizando diagrama...

Um bom relatório futuro deve amostrar erros por célula, preservar sent_id e fonte, verificar duplicatas entre splits e apresentar intervalos de confiança por classe. Com somente 21 PROPN, quatro INTJ e seis NUM, comparar versões por décimos de ponto transmite uma precisão que o teste não sustenta.

Inferência reproduzível

Para reproduzir o comportamento publicado, registre a versão das bibliotecas, mantenha os offsets e trate o resultado como UPOS, apesar de a API histórica usar o alias ner:

from transformers import pipeline
 
MODEL = "dominguesm/canarim-bert-postag-nheengatu"
 
tagger = pipeline(
    task="token-classification",
    model=MODEL,
    tokenizer=MODEL,
    aggregation_strategy="average",
)
 
text = "Yamunhã timbiú, yapinaitika, yamunhã kaxirí."
for item in tagger(text):
    print(item["word"], item["entity_group"], float(item["score"]))

A saída publicada identifica Yamunhã/VERB, timbiú/NOUN, vírgulas e ponto como PUNCT, yapinaitika/VERB, yamunhã/VERB e kaxirí/NOUN [1]. Escores altos são probabilidades softmax do modelo, não garantias de correção nem probabilidades calibradas. Em especial, entradas de outra ortografia, outro gênero, código misto, listas de nomes ou sentenças acima de 128 posições estão fora ou na borda da evidência de avaliação.

Para aplicações que precisam de uma linha por palavra, prefira fornecer palavras já segmentadas, recuperar word_ids() e definir explicitamente se será usada a primeira peça ou a média dos logits. Registre a versão de transformers, a data de acesso ao modelo, o texto original, a normalização Unicode e a política de segmentação. Sem isso, duas interfaces podem agrupar subtokens ou palavras adjacentes de forma diferente e produzir saídas aparentemente incompatíveis.

Governança, licença e uso responsável

O treebank histórico declara CC BY-NC-SA 4.0: exige atribuição, proíbe uso comercial e aplica compartilhamento pela mesma licença a adaptações [3][7][10]. A página do corpus ressalta ainda que os direitos das sentenças e traduções permanecem com seus autores e que os dados são oferecidos para pesquisa, ensino e aprendizagem do Nheengatu. O BERT-base, por sua vez, está marcado como CC BY 4.0 [2].

Ao revisar o POS tagger, identifiquei uma diferença que preciso manter explícita: o frontmatter e a API do Hub marcam cc-by-nc-4.0, sem ShareAlike, enquanto o texto da ficha acompanha o dataset em CC BY-NC-SA 4.0 [1]. Para redistribuição ou uso institucional, recomendo preservar atribuição e proveniência, observar a restrição não comercial e o ShareAlike do dado e solicitar a harmonização da licença do repositório do modelo. Este relato não substitui aconselhamento jurídico.

Governança também vai além da licença. O corpus representa uma língua indígena viva e agrega obras de autores, comunidades, períodos e convenções ortográficas diferentes. Um sistema derivado não deve ser apresentado como autoridade normativa nem substituir falantes, professores ou linguistas. Publicações e serviços devem informar limitações por domínio, oferecer correção humana, registrar versões, evitar inferências sobre identidade dos falantes e criar um canal para remoção ou contestação de exemplos. Novos testes devem, quando possível, ser definidos com participação das comunidades e separados por fonte para medir generalização real, não memorização textual.

Resultados, limitações e próximos passos

Com o checkpoint, demonstrei que um BERT específico para Nheengatu pode alcançar 92,02% de acurácia em 1.353 palavras de teste. Considero mais informativo o F1 macro de 82,93%: PUNCT, PRON, PART, AUX, ADP e NOUN têm desempenho forte, enquanto PROPN, INTJ e ADJ expõem escassez e confusões relevantes. As curvas sugerem sobreajuste e, como não publiquei os índices dos splits nem o código de alinhamento, ainda não consigo reproduzir exatamente o experimento.

Uso o modelo como baseline, ferramenta de anotação assistida e ponto de partida para experimentos. Para uma próxima versão reprodutível, pretendo indicar uma release datada do corpus, publicar os três arquivos de split e a semente, documentar o alinhamento WordPiece, salvar o estado do treinador, selecionar checkpoint pela validação, relatar métricas com e sem pontuação e revisar erros com especialistas e falantes de Nheengatu.

Referências

  1. Hugging Face: dominguesm/canarim-bert-postag-nheengatu, ficha e configuração do modelo.
  2. Hugging Face: dominguesm/canarim-bert-nheengatu, modelo-base e configuração.
  3. UD Nheengatu-CompLin, descrição, estatísticas, autoria e licença do treebank.
  4. de Alencar, L. F. (2024). A Universal Dependencies Treebank for Nheengatu. PROPOR, p. 37-54.
  5. Nivre, J. et al. (2020). Universal Dependencies v2: An Evergrowing Multilingual Treebank Collection. LREC, p. 4034-4043.
  6. de Alencar, L. F. (2023). Yauti: A Tool for Morphosyntactic Analysis of Nheengatu within the Universal Dependencies Framework. STIL, p. 135-145.
  7. UD Nheengatu-CompLin, versão de desenvolvimento de 15/12/2023 compatível com 1.336 sentenças.
  8. Transformers: Token classification, alinhamento entre palavras, subtokens e rótulos ignorados.
  9. Assets do repositório canarim-bert-nheengatu, curvas de perda e matriz de confusão.
  10. Creative Commons BY-NC-SA 4.0, resumo e código legal da licença.
@misc{domingues2023canarimpostag,
  author = {Maicon Domingues},
  title = {Canarim-Bert-PosTag-Nheengatu},
  year = {2023},
  publisher = {Hugging Face},
  url = {https://huggingface.co/dominguesm/canarim-bert-postag-nheengatu},
}
 
@inproceedings{de-alencar-2024-universal,
  title = {A {U}niversal {D}ependencies Treebank for Nheengatu},
  author = {de Alencar, Leonel Figueiredo},
  booktitle = {Proceedings of the 16th International Conference on Computational Processing of Portuguese - Vol. 2},
  year = {2024},
  publisher = {Association for Computational Linguistics},
  pages = {37--54},
  url = {https://aclanthology.org/2024.propor-2.8/}
}
 
@inproceedings{de-alencar-2023-yauti,
  author = {de Alencar, Leonel Figueiredo},
  title = {Yauti: A Tool for Morphosyntactic Analysis of Nheengatu within the Universal Dependencies Framework},
  booktitle = {Anais do XIV Simpósio Brasileiro de Tecnologia da Informação e da Linguagem Humana},
  year = {2023},
  pages = {135--145},
  publisher = {SBC},
  doi = {10.5753/stil.2023.234131}
}
 
@inproceedings{nivre-etal-2020-universal,
  title = {{U}niversal {D}ependencies v2: An Evergrowing Multilingual Treebank Collection},
  author = {Nivre, Joakim and de Marneffe, Marie-Catherine and Ginter, Filip and Hajič, Jan and Manning, Christopher D. and Pyysalo, Sampo and Schuster, Sebastian and Tyers, Francis and Zeman, Daniel},
  booktitle = {Proceedings of the Twelfth Language Resources and Evaluation Conference},
  year = {2020},
  pages = {4034--4043},
  publisher = {European Language Resources Association},
  url = {https://aclanthology.org/2020.lrec-1.497/}
}