ncm-classifier / README.md
AmandaGondra's picture
Release v0.1.0 (code a03a54f)
d9d3b1c verified
|
Raw History Blame Contribute Delete
10.4 kB
---
language: pt
license: mit
library_name: sklearn
pipeline_tag: text-classification
tags:
- ncm
- classificacao-fiscal
- agronegocio
- scikit-learn
- skops
- tfidf
datasets:
- AmandaGondra/ncm-classifier-dataset
---
# NCM Classifier — sugestão de posição NCM para produtos do agro
> **Aviso.** Este modelo dá **sugestões**, não decisões fiscais. A responsabilidade pela
> classificação fiscal permanece com quem emite a nota. Na dúvida, consulte um profissional
> habilitado.
## Identificação
| | |
|---|---|
| Modelo | `AmandaGondra/ncm-classifier` |
| Versão | v0.1.0 (publicado em 2026-11-03) |
| Autora | Amanda Gondra |
| Arquitetura | TF-IDF (palavras 1–2 e caracteres 3–5) + regressão logística com classes balanceadas, calibração por temperatura e regra legal da posição 31.05 |
| Modelo pré-treinado | nenhum (treinado do zero com scikit-learn 1.9.1) |
| Código | [https://github.com/amanda-gondra/ncm-classifier](https://github.com/amanda-gondra/ncm-classifier), commit `a03a54f1d0973966b0babc8fb846e681197b07e2` |
| Formato | `model.skops` (sem pickle) |
## Descrição
A partir da descrição comercial de um produto agropecuário ("Ureia agrícola saco 50 kg",
"Arame farpado 500 m"), o modelo sugere as **três posições NCM mais prováveis** (4 dígitos),
com a confiança calibrada da primeira. Quando a confiança fica abaixo de um limiar, marca a
sugestão para **revisão manual**.
O problema: quem vende para o produtor rural precisa informar a NCM na nota fiscal, e as
descrições comerciais raramente usam a linguagem da tabela.
Etapas da inferência, iguais às da avaliação:
1. **Normalização do texto:** minúsculas, sem acentos, fórmulas NPK, unidades e abreviações
padronizadas.
2. **Probabilidades** da regressão logística.
3. **Calibração por temperatura:** `softmax(log p / T)`, com T = 0.4012.
4. **Regra 31.05**, do texto legal da posição: mercadorias do Capítulo 31 em embalagens de até
10 kg vão para a 31.05.
5. **Três sugestões e revisão:** volta o top-3; a sugestão vai para revisão quando a confiança
final for menor que 0.6525.
## Uso pretendido
- **Público:** quem emite nota de produtos agropecuários (lojas, revendas, cooperativas) e quer
um ponto de partida para a classificação. Também serve como exemplo de engenharia de ML.
- **Cenários:** sugerir a NCM numa tela de cadastro de produtos; triar descrições para revisão.
- **Fora do uso pretendido:** decidir a NCM sem revisão humana; produtos fora das posições
abaixo; classificação em 8 dígitos (subitem).
Posições cobertas (nível de posição, 4 dígitos):
| Grupo | Posições |
|---|---|
| Animais e produtos de origem animal | 01.02, 01.03, 01.04, 01.05, 04.07, 05.11 |
| Sementes, grãos e forragens | 07.13, 10.05, 10.06, 10.07, 12.01, 12.09, 12.13, 12.14 |
| Rações, suplementos e sal | 23.02, 23.04, 23.09, 25.01 |
| Adubos | 31.01, 31.02, 31.03, 31.04, 31.05 |
| Defensivos agrícolas | 38.08 |
| Ferramentas, arames e cercas | 72.17, 73.13, 73.14, 73.17, 82.01, 85.43 |
| Máquinas e implementos agrícolas | 84.24, 84.32, 84.33, 84.34, 84.36, 87.01, 87.16 |
37 posições no total.
## Dados de treinamento
- **Linhas:** 900 (marketplace: 241, ncm_official: 359, public_reference: 1, synthetic: 299).
- **Fontes:**
- descrições comerciais reais de anúncios e catálogos;
- descrições da própria tabela NCM oficial;
- descrições sintéticas, geradas com um LLM e revisadas uma a uma.
- **Rotulagem:** feita à mão, com a regra legal anotada em cada linha. A fonte comprova a
descrição, não a classificação.
- **Pré-processamento:** a normalização descrita acima. Quase-duplicatas dos testes foram
removidas do treino (Jaccard de 5-gramas de caracteres ≥ 0,8), e nenhuma fonte do treino
coincide com uma fonte de teste.
- **Dados sintéticos:** foram escritos depois do primeiro teste (test_v1) existir. Por isso foi
criado um segundo teste independente (test_v2); nele, o ganho dos sintéticos foi menor, mas
se manteve.
- **Versão dos dados:** SHA-256 do arquivo de treino `2c075ec49ffcbba81b6e324c36e9bf9d06439d54c603cbe2dc790622b3f85a18`.
- **Parte publicada:** só as linhas oficiais e sintéticas estão em
[`AmandaGondra/ncm-classifier-dataset`](https://huggingface.co/datasets/AmandaGondra/ncm-classifier-dataset). As descrições de anúncios
não são redistribuídas.
## Avaliação
### Protocolo
- **Escolha do C:** validação cruzada estratificada em 5 dobras no treino (impressão digital
das dobras: `59611f6771f10d2c601009368dd868d2f83dcc91e3353a3340e1c2803f438cd0`). C = 3.0, macro F1 médio de 0.8638.
- **Calibração:** temperatura e limiar de revisão ajustados nas probabilidades fora da dobra do
treino. Fora da dobra: 0.1489 [0.1267–0.1722] enviado para revisão e 0.9504 [0.9347–0.9648] de top-1 nos aceitos.
- **Testes:** dois testes **congelados** e escritos à mão antes do treino, com checksum,
nunca usados para treino ou escolha:
- `test_v1`: 146 linhas, todas as 37 posições;
- `test_v2`: 74 linhas, de fontes novas.
- **Intervalos:** bootstrap com 1000 reamostragens, intervalo percentil de 95%, semente 0.
### Resultados (calibrado + regra 31.05)
| Métrica | test_v1 | test_v2 |
|---|---|---|
| top-1 | 0.9452 [0.9041–0.9795] | 0.9459 [0.8919–0.9865] |
| top-3 | 0.9932 [0.9795–1.0000] | 1.0000 [1.0000–1.0000] |
| macro F1 | 0.9447 [0.8942–0.9782] | 0.9198 [0.8621–0.9867] |
| macro precisão | 0.9588 [0.9032–0.9858] | 0.9219 [0.8533–0.9929] |
| macro recall | 0.9488 [0.9122–0.9804] | 0.9271 [0.8822–0.9881] |
| ECE bruto → calibrado | 0.4502 → 0.0753 | 0.4554 → 0.0624 |
| Enviado para revisão | 0.1096 [0.0616–0.1644] | 0.1351 [0.0676–0.2162] |
| top-1 dos aceitos | 0.9846 [0.9615–1.0000] | 1.0000 [1.0000–1.0000] |
| Linhas | 146 | 74 |
As métricas macro tiram a média sobre as posições presentes em cada teste. ECE é o erro de
calibração da confiança do top-1, em 10 faixas.
### Por capítulo
Linhas, acerto da posição e acerto do capítulo (os dois primeiros dígitos):
| Capítulo | test_v1: linhas / posição / capítulo | test_v2: linhas / posição / capítulo |
|---|---|---|
| 01 | 15 / 0.933 / 1.000 | 12 / 1.000 / 1.000 |
| 04 | 3 / 1.000 / 1.000 | 2 / 1.000 / 1.000 |
| 05 | 4 / 1.000 / 1.000 | — |
| 07 | 4 / 0.750 / 0.750 | 1 / 1.000 / 1.000 |
| 10 | 11 / 0.818 / 0.818 | 2 / 1.000 / 1.000 |
| 12 | 15 / 1.000 / 1.000 | 6 / 1.000 / 1.000 |
| 23 | 14 / 0.857 / 1.000 | 7 / 0.857 / 0.857 |
| 25 | 4 / 1.000 / 1.000 | 2 / 0.500 / 0.500 |
| 31 | 21 / 0.905 / 0.952 | 13 / 1.000 / 1.000 |
| 38 | 6 / 1.000 / 1.000 | 1 / 0.000 / 0.000 |
| 72 | 4 / 1.000 / 1.000 | — |
| 73 | 11 / 1.000 / 1.000 | 7 / 0.857 / 1.000 |
| 82 | 3 / 1.000 / 1.000 | 1 / 1.000 / 1.000 |
| 84 | 20 / 1.000 / 1.000 | 12 / 1.000 / 1.000 |
| 85 | 4 / 1.000 / 1.000 | 3 / 1.000 / 1.000 |
| 87 | 7 / 1.000 / 1.000 | 5 / 1.000 / 1.000 |
### Comparação com BERTimbau
Um fine-tuning do BERTimbau, com os mesmos dados, dobras, calibração e regra, empatou na
qualidade: o top-1 dos dois testes juntos foi igual, e o intervalo da diferença pareada vai de
−3,6 a +4,1 pontos. Ele custa ~50× mais por previsão em CPU. Os detalhes e o modelo estão em
[`AmandaGondra/ncm-classifier-bertimbau`](https://huggingface.co/AmandaGondra/ncm-classifier-bertimbau).
## Como usar
```bash
pip install -r requirements.txt # instala o pacote ncm-classifier na versão v0.1.0
```
```python
from huggingface_hub import snapshot_download
from ncm_classifier.inference.predictor import NcmPredictor
path = snapshot_download("AmandaGondra/ncm-classifier", revision="v0.1.0")
predictor = NcmPredictor.from_directory(path)
for prediction in predictor.predict(["Ureia agrícola, pacote 1 kg"]):
print(prediction.to_dict())
```
Cada resultado traz:
- `suggestions`: três posições, da mais provável para a menos provável, com a probabilidade
calibrada de cada uma. Leia como "provavelmente a primeira; confira as outras duas se a
descrição for ambígua".
- `confidence`: probabilidade calibrada da primeira sugestão.
- `needs_review`: `true` quando a confiança está abaixo do limiar. Nesse caso, a sugestão deve
ser conferida por uma pessoa.
- `rule_31_05`: `true` quando a regra da embalagem de até 10 kg mudou a sugestão.
O carregador só aceita, além do scikit-learn e do NumPy, a função de normalização do próprio
projeto. Qualquer outro tipo no arquivo faz o carregamento falhar.
## Limitações e riscos
- **O modelo dá sugestões, não decisões fiscais.** Ele pode errar, inclusive com confiança alta.
- **A responsabilidade pela classificação fiscal permanece com quem emite a nota.** A revisão
por um profissional habilitado pode ser necessária.
- **A qualidade depende da descrição.** Textos curtos ("Chocadeira"), siglas ("TSP") e marcas
sem contexto erram mais.
- **Composição e aplicação mudam a NCM.** Por exemplo, sal mineral é 23.09 e sal comum é 25.01;
adubos dependem da composição e da embalagem. Produtos assim podem exigir análise técnica.
- **Embalagem não declarada:** quando o texto não diz o peso, o rótulo segue a convenção de
granel (mais de 10 kg). A regra 31.05 só lê pesos escritos em grafias reconhecidas ("5 kg",
"500 g"); grafias como "5k" passam sem a regra.
- **A tabela NCM muda.** O escopo segue a tabela vigente em 30/10/2026 (Resolução Gecex
nº 926/2026).
- **Testes pequenos.** Com 220 linhas, os intervalos de confiança são largos.
- **Escopo restrito:** 37 posições, nível de 4 dígitos. Fora delas, o modelo sempre sugere uma
das 37, mesmo que nenhuma sirva.
## Licença
- **Código e modelo:** MIT.
- **Dados:** o treino inclui descrições de anúncios de terceiros, que não são redistribuídas.
O modelo guarda delas apenas estatísticas de palavras e trechos curtos. A parte publicada dos
dados tem licença própria, descrita no
[dataset card](https://huggingface.co/datasets/AmandaGondra/ncm-classifier-dataset).
- Este documento não é parecer jurídico.