5  Avaliação e Ajuste

Treinar um modelo é apenas parte do trabalho. Um modelo aparentemente bom pode estar apenas decorando o conjunto de treino. Avaliação adequada significa medir desempenho em dados não vistos e interpretar os resultados com critério.

5.1 Separação entre treino e teste

A primeira barreira contra o autoengano em machine learning é separar os dados em conjuntos distintos.

  • treino: usado para ajustar o modelo;
  • validação: usado para comparar configurações;
  • teste: usado para medir desempenho final de forma mais honesta.

No capítulo anterior usamos apenas treino e teste. Agora vamos ampliar essa avaliação.

5.2 Métricas para classificação

Em classificação, a acurácia é útil, mas não basta em todos os contextos.

Para entender essas métricas, usamos quatro quantidades da matriz de confusão:

  • TP (true positive): positivo previsto como positivo;
  • TN (true negative): negativo previsto como negativo;
  • FP (false positive): negativo previsto como positivo;
  • FN (false negative): positivo previsto como negativo.

5.2.1 Accuracy

Indica a proporção total de previsões corretas:

\[ Accuracy = \frac{TP + TN}{TP + TN + FP + FN} \]

É uma métrica geral e simples de comunicar, mas pode enganar em bases desbalanceadas. Se 95% dos exemplos forem da classe negativa, um modelo que quase sempre prevê “negativo” pode ter alta acurácia e ainda assim ser pouco útil.

5.2.2 Precision

Entre os exemplos previstos como positivos, quantos eram de fato positivos:

\[ Precision = \frac{TP}{TP + FP} \]

Precision alta significa poucos falsos positivos. Ela é importante quando o custo de um alarme falso é alto, como em bloqueio indevido de transações legítimas.

5.2.3 Recall

Entre os exemplos realmente positivos, quantos foram encontrados:

\[ Recall = \frac{TP}{TP + FN} \]

Recall alta significa poucos falsos negativos. Ela é essencial quando “deixar passar” um caso positivo é crítico, como triagem médica ou detecção de fraude.

5.2.4 F1-score

É a média harmônica entre precision e recall:

\[ F1 = 2 \cdot \frac{\text{Precision} \cdot \text{Recall}}{\text{Precision} + \text{Recall}} \]

O F1-score cresce quando há equilíbrio entre precision e recall e cai quando uma das duas é baixa. Por isso, é útil quando queremos uma visão única de desempenho em cenários com classes desbalanceadas ou com trade-off entre falsos positivos e falsos negativos.

5.2.5 Matriz de confusão

Mostra com mais detalhe onde o modelo está acertando e errando.

from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score, classification_report

iris = load_iris()
X, y = iris.data, iris.target

X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    test_size=0.3,
    random_state=42,
    stratify=y
)

print("Formato de X:", X.shape)
print("Classes:", iris.target_names)
model = DecisionTreeClassifier(random_state=42)
model.fit(X_train, y_train)

pred = model.predict(X_test)


from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay

cm = confusion_matrix(y_test, pred)
print(cm)

disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=iris.target_names)
disp.plot()
Formato de X: (150, 4)
Classes: ['setosa' 'versicolor' 'virginica']
[[15  0  0]
 [ 0 12  3]
 [ 0  0 15]]

5.3 Relatório de classificação

from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score, classification_report

iris = load_iris()
X, y = iris.data, iris.target

X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    test_size=0.3,
    random_state=42,
    stratify=y
)

print("Formato de X:", X.shape)
print("Classes:", iris.target_names)

from sklearn.metrics import classification_report

print(classification_report(y_test, pred, target_names=iris.target_names))
Formato de X: (150, 4)
Classes: ['setosa' 'versicolor' 'virginica']
              precision    recall  f1-score   support

      setosa       1.00      1.00      1.00        15
  versicolor       1.00      0.80      0.89        15
   virginica       0.83      1.00      0.91        15

    accuracy                           0.93        45
   macro avg       0.94      0.93      0.93        45
weighted avg       0.94      0.93      0.93        45

Esse relatório é útil porque resume várias métricas por classe.

5.4 Por que validação cruzada importa

Uma única divisão treino-teste pode ser injusta com o modelo ou benevolente demais. A validação cruzada reduz essa dependência de uma partição específica.

Na validação cruzada k-fold, dividimos os dados em k partes. O modelo treina em k-1 partes e valida na parte restante. Repetimos isso k vezes, mudando o bloco de validação.

from sklearn.model_selection import cross_val_score

scores = cross_val_score(
    DecisionTreeClassifier(random_state=42),
    X,
    y,
    cv=5,
    scoring="accuracy"
)

print("Scores:", scores)
print("Média:", scores.mean())
print("Desvio padrão:", scores.std())
Scores: [0.96666667 0.96666667 0.9        0.93333333 1.        ]
Média: 0.9533333333333334
Desvio padrão: 0.03399346342395189

5.4.1 Como interpretar

  • média alta sugere bom desempenho médio;
  • desvio padrão alto sugere instabilidade;
  • média baixa pode indicar que o modelo não está adequado ou que faltam ajustes.

5.5 Ajustando hiperparâmetros

Uma árvore possui vários controles de complexidade. O papel do ajuste é encontrar configurações que generalizem melhor.

5.5.1 Parâmetros mais importantes

  • max_depth: limita profundidade total;
  • min_samples_split: exige número mínimo para dividir um nó;
  • min_samples_leaf: evita folhas pequenas demais;
  • criterion: muda a função de qualidade da divisão;
  • ccp_alpha: controla poda por custo-complexidade.

5.7 Avaliando o melhor modelo no teste

best_model = grid.best_estimator_
best_pred = best_model.predict(X_test)

print("Accuracy no teste:", accuracy_score(y_test, best_pred))
print(classification_report(y_test, best_pred, target_names=iris.target_names))
Accuracy no teste: 0.9777777777777777
              precision    recall  f1-score   support

      setosa       1.00      1.00      1.00        15
  versicolor       1.00      0.93      0.97        15
   virginica       0.94      1.00      0.97        15

    accuracy                           0.98        45
   macro avg       0.98      0.98      0.98        45
weighted avg       0.98      0.98      0.98        45

Esse passo é importante porque a escolha do melhor conjunto de hiperparâmetros foi feita olhando para validação, não para o teste final.

5.8 Vieses de avaliação comuns

5.8.1 Avaliar no treino

Se medirmos o modelo no mesmo conjunto que o treinou, o resultado tende a ser otimista demais.

5.8.2 Ajustar repetidamente olhando o teste

O conjunto de teste deve ser preservado como referência final. Usar o teste para tomar decisões sucessivas vaza informação.

5.8.3 Escolher só pela acurácia

Em dados desbalanceados, acurácia pode esconder um modelo ruim para a classe de maior interesse.

5.9 Diagnóstico prático de uma árvore

Ao avaliar uma árvore, pergunte:

  • ela está muito profunda?
  • o desempenho no treino está muito acima do teste?
  • existem folhas com poucas amostras?
  • a estrutura aprendida faz sentido para o domínio?
  • a variação entre folds está alta?

5.10 Curva de complexidade

Uma estratégia didática interessante é testar profundidades crescentes e observar o desempenho.

from sklearn.metrics import f1_score

for depth in [1, 2, 3, 4, 5, None]:
    clf = DecisionTreeClassifier(max_depth=depth, random_state=42)
    clf.fit(X_train, y_train)
    pred_depth = clf.predict(X_test)
    print(
        f"depth={depth} | acc={accuracy_score(y_test, pred_depth):.3f} | "
        f"f1_macro={f1_score(y_test, pred_depth, average='macro'):.3f}"
    )
depth=1 | acc=0.667 | f1_macro=0.556
depth=2 | acc=0.889 | f1_macro=0.889
depth=3 | acc=0.978 | f1_macro=0.978
depth=4 | acc=0.889 | f1_macro=0.889
depth=5 | acc=0.933 | f1_macro=0.933
depth=None | acc=0.933 | f1_macro=0.933

Esse experimento ajuda a visualizar a relação entre simplicidade e desempenho.

NoteResumo
  • Avaliar bem é tão importante quanto treinar bem.
  • Validação cruzada reduz a dependência de uma única divisão treino-teste.
  • Grid search ajuda a explorar hiperparâmetros de forma sistemática.
  • O teste final deve ser preservado para avaliação honesta.

Avaliar bem uma árvore significa medir desempenho com honestidade, comparar configurações de maneira sistemática e interpretar os erros com atenção. No próximo capítulo, vamos aprofundar um dos riscos mais importantes desse modelo: o overfitting, e veremos como a poda ajuda a manter a árvore útil e interpretável.

WarningErros comuns
  • usar o conjunto de teste para ajustar decisões repetidas vezes;
  • escolher modelo só pela acurácia;
  • ignorar variação entre folds;
  • confundir score alto no treino com capacidade de generalização.
TipPerguntas de revisão
  1. Qual é a diferença entre treino, validação e teste?
  2. Quando a acurácia pode ser enganosa?
  3. O que a validação cruzada ajuda a reduzir?
  4. Por que o melhor modelo do grid deve ser avaliado depois no teste?