Aprendizado de máquina
Treinar é a parte fácil. O que separa um modelo útil de um número bonito é a avaliação.
Arcane.Cortex traz os algoritmos clássicos, implementados em DataForge puro. Eles cobrem a maioria esmagadora dos problemas de dado tabular — que é onde a maior parte do trabalho real acontece.
O caminho inteiro#
adopt Arcane.Cortex as ML
// 1. dividir ANTES de olhar
treino, teste := ML.dividir(clientes, 0.2, 42, "comprou")
// 2. treinar
modelo := ML.floresta(treino, "comprou", ["idade", "renda", "visitas"], 30)
// 3. medir no que ele NÃO viu
r := ML.avaliar(modelo, teste)
out $"acurácia {r["acuracia"]} F1 {r["f1"]}"
out ML.matriz(modelo, teste)
// 4. entender
out ML.importancia(modelo)
// 5. guardar
ML.salvar(modelo, "modelos/compra.json")Dividir antes de olhar#
dividir embaralha sempre, e isso não é detalhe. Dado quase nunca chega em ordem aleatória: vem ordenado por data, por id, por categoria. Cortar sem embaralhar põe todo um tipo de exemplo de um lado só, e a avaliação passa a medir outra coisa.
// num problema com 2% de fraude, o corte cego pode
// deixar o teste sem fraude nenhuma
treino, teste := ML.dividir(dados, 0.2, 42, "fraude")O quarto argumento estratifica: mantém a proporção das classes nos dois lados.
A acurácia mente#
Num problema com 99% de uma classe, um modelo que responde sempre a mesma coisa acerta 99%. É o caso que faz alguém publicar um modelo inútil achando que ele é bom.
{
"acuracia": 0.95,
"precisao": 0.90, "revocacao": 0.95, "f1": 0.92,
"por_classe": {
"comum": {"precisao": 0.95, "revocacao": 1.00, "f1": 0.97, "quantos": 190},
"raro": {"precisao": 0.00, "revocacao": 0.00, "f1": 0.00, "quantos": 10}
}
}95% de acurácia, e a classe que interessa nunca é encontrada. Por isso avaliar devolve precisão, revocação e F1 por classe, e a média é ponderada pelo tamanho — a simples trataria 10 exemplos como iguais a 190.
E matriz mostra onde ele erra:
real \ previsto comum raro
------------------------------------
comum 190 0
raro 10 0Uma divisão mede um sorteio#
r := ML.validacao_cruzada(dados, "comprou", colunas, "floresta", 5)
// {"media": 0.9533, "desvio": 0.0163,
// "por_dobra": [0.9833, 0.95, 0.95, 0.95, 0.9333]}Qual algoritmo#
| Situação | Use | Por quê |
|---|---|---|
| prever um número | linear | solução exata, sem taxa de aprendizado para errar |
| duas classes, fronteira reta | logistica | rápida, e dá probabilidade |
| quer entender a decisão | arvore | as regras são legíveis |
| quer o melhor resultado | floresta | corrige o que a árvore decora |
| fronteira estranha, poucos dados | vizinhos | não supõe forma nenhuma |
| classificar texto | bayes_texto | difícil de bater em texto curto |
| não há rótulo | kmedias | acha os grupos sozinho |
| muitas colunas | pca | reduz preservando a variância |
Por que a floresta bate a árvore#
Uma árvore sozinha decora o treino. A floresta corrige isso por duas fontes de variação: cada árvore vê uma amostra com reposição das linhas, e cada corte olha só um subconjunto das colunas.
Sem as duas, as árvores sairiam quase idênticas e a votação não acrescentaria nada. Num problema onde uma classe está dentro da outra — que nenhuma reta separa — a diferença medida foi de 90% para 97%.
Escala importa (para alguns)#
escala := ML.escalonar(treino, ["idade", "renda"])
treino := ML.aplicar_escala(treino, escala)
teste := ML.aplicar_escala(teste, escala) // a MESMA escalaSem escala, uma coluna em milhares domina uma em unidades no k-NN e na logística — a distância vira a da coluna grande, e as outras deixam de existir. Árvore e floresta não precisam: elas cortam por coluna, e a grandeza não muda a ordem.
Texto vira número por one-hot#
r := ML.categorico(linhas, "cidade")
// cria 'cidade_sp', 'cidade_rj', 'cidade_bh' — 0 ou 1Uma coluna por valor, e não um número por categoria: numerar azul=0, verde=1, vermelho=2 faria o modelo achar que vermelho é maior que azul, e que verde está no meio dos dois.
Guardar#
ML.salvar(modelo, "modelos/compra.json")
modelo := ML.carregar("modelos/compra.json")É JSON, e não pickle. Pickle executaria código ao carregar: um modelo baixado de qualquer lugar viraria execução arbitrária. E o JSON ainda dá para abrir e entender o que o modelo é.
Os limites, ditos#
- Não há rede neural. Sem GPU e sem retropropagação, uma rede profunda em interpretador de árvore não terminaria.
- A escala é de milhares de linhas, não milhões. Para além disso, exporte em Parquet e use uma ferramenta dedicada.
- Não há ajuste automático de hiperparâmetro.
validacao_cruzadamede; escolher é seu. - Nada disso substitui olhar os dados.
Qualidade.perfil()antes de treinar acha mais problema que qualquer modelo.