Continuando nossa exploração detalhada das Máquinas de Vetores de Suporte, focaremos agora nas pontuações de decisão e estimativas de probabilidade. Primordialmente, enquanto as SVM são naturalmente classificadores que retornam decisões binárias, mecanismos adicionais permitem obter informações probabilísticas que são valiosas para muitas aplicações práticas.
Pontuações de Decisão
Analogamente ao que discutimos anteriormente sobre a função de decisão, as SVM calculam distâncias assinadas ao hiperplano de separação. Estas distâncias, conhecidas como pontuações de decisão, fornecem informação quantitativa sobre a confiança da classificação.
Formulação Matemática
Para um classificador binário, a pontuação de decisão é dada por:
\(f(x) = w^T x + b\)O sinal de f(x) determina a classe predita, enquanto a magnitude |f(x)| indica a confiança da predição.
Interpretação das Pontuações
- f(x) > 0: Predição para classe positiva
- f(x) < 0: Predição para classe negativa
- |f(x)| grande: Alta confiança na predição
- |f(x)| pequeno: Baixa confiança (próximo ao hiperplano)
Estimativas de Probabilidade
Embora as SVM não sejam naturalmente modelos probabilísticos, o scikit-learn implementa o método de Platt scaling para converter pontuações de decisão em probabilidades calibradas.
Platt Scaling
O método de Platt ajusta uma regressão logística às pontuações de decisão:
\(P(y = 1 | x) = \frac{1}{1 + \exp(A f(x) + B)}\)Onde A e B são parâmetros estimados via máxima verossimilhança em um conjunto de validação.
Implementação no scikit-learn
Para habilitar estimativas de probabilidade:
- Definir
probability=Trueao criar o classificador - Usar
predict_proba()para obter probabilidades - Usar
predict_log_proba()para log-probabilidades
Vantagens das Probabilidades
Similarmente aos modelos probabilísticos que exploramos anteriormente, as estimativas de probabilidade oferecem benefícios significativos:
- Interpretabilidade direta das incertezas
- Compatibilidade com métricas como log-loss
- Facilidade de combinação com outros modelos
- Suporte a tomadas de decisão baseadas em risco
Considerações de Calibração
Curvas de Calibração
As probabilidades geradas pelo Platt scaling devem ser avaliadas através de curvas de calibração para verificar se as probabilidades previstas correspondem às frequências observadas.
Fatores que Afetam a Calibração
- Tamanho do dataset de validação
- Complexidade do problema de classificação
- Parâmetros de regularização do SVM
- Escolha do kernel
Métodos e Atributos Relacionados
Métodos Principais
decision_function(X): Retorna pontuações de decisãopredict_proba(X): Retorna probabilidades das classespredict_log_proba(X): Retorna log-probabilidades
Atributos de Calibração
probA_: Parâmetro A do Platt scalingprobB_: Parâmetro B do Platt scalingclasses_: Classes ordenadas
Conexões com Tópicos Anteriores
Analogamente aos conceitos que exploramos em classificadores probabilísticos, as estimativas de probabilidade em SVM:
- Oferecem alternativa aos métodos como LDA/QDA para obter probabilidades
- Compartilham princípios de calibração com outros modelos
- Permitem comparação direta através de métricas como Brier score
- Facilitam a interpretação similar a modelos lineares generalizados
Exemplo Prático em Python
Para ilustrar o uso de pontuações e probabilidades em SVM, implementemos um estudo comparativo detalhado:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 |
import numpy as np import matplotlib.pyplot as plt from sklearn.svm import SVC from sklearn.model_selection import train_test_split, cross_val_score from sklearn.metrics import accuracy_score, log_loss, brier_score_loss from sklearn.calibration import calibration_curve from sklearn.datasets import make_classification from sklearn.preprocessing import StandardScaler import warnings warnings.filterwarnings('ignore') ''' Estudo comparativo de pontuações e probabilidades em SVM ''' print("=== PONTUAÇÕES E PROBABILIDADES EM SVM ===") # Criando dataset para análise detalhada print("\n1. CONFIGURAÇÃO DO EXPERIMENTO") X, y = make_classification( n_samples=1000, n_features=2, n_informative=2, n_redundant=0, n_clusters_per_class=1, class_sep=1.5, random_state=42 ) # Padronização scaler = StandardScaler() X_scaled = scaler.fit_transform(X) # Divisão treino-teste X_train, X_test, y_train, y_test = train_test_split( X_scaled, y, test_size=0.3, random_state=42, stratify=y ) print(f"Dimensões: Treino {X_train.shape}, Teste {X_test.shape}") print(f"Distribuição das classes: {np.unique(y_train, return_counts=True)}") ''' Comparação entre SVM com e sem probabilidades ''' print("\n2. COMPARAÇÃO: SVM COM E SEM PROBABILIDADES") # Modelos para comparação models = { 'SVC Sem Probabilidade': SVC(kernel='rbf', probability=False, random_state=42), 'SVC Com Probabilidade': SVC(kernel='rbf', probability=True, random_state=42) } resultados = {} for model_name, model in models.items(): print(f"\nTreinando {model_name}...") # Treinamento model.fit(X_train, y_train) # Previsões básicas y_pred = model.predict(X_test) accuracy = accuracy_score(y_test, y_pred) # Coletar informações específicas model_info = { 'model': model, 'accuracy': accuracy, 'predictions': y_pred } # Pontuações de decisão if hasattr(model, 'decision_function'): decision_scores = model.decision_function(X_test) model_info['decision_scores'] = decision_scores print(f" Pontuações de decisão disponíveis: shape {decision_scores.shape}") # Probabilidades (se disponível) if hasattr(model, 'predict_proba'): y_proba = model.predict_proba(X_test) model_info['probabilities'] = y_proba # Calcular métricas probabilísticas logloss = log_loss(y_test, y_proba) brier = brier_score_loss(y_test, y_proba[:, 1]) model_info['log_loss'] = logloss model_info['brier_score'] = brier print(f" Probabilidades disponíveis: shape {y_proba.shape}") print(f" Log Loss: {logloss:.4f}, Brier Score: {brier:.4f}") # Validação cruzada cv_scores = cross_val_score(model, X_scaled, y, cv=5, scoring='accuracy') model_info['cv_mean'] = np.mean(cv_scores) model_info['cv_std'] = np.std(cv_scores) print(f" Acurácia: {accuracy:.4f}, CV: {np.mean(cv_scores):.4f} ± {np.std(cv_scores):.4f}") resultados[model_name] = model_info ''' Análise detalhada das pontuações de decisão ''' print("\n3. ANÁLISE DAS PONTUAÇÕES DE DECISÃO") if 'SVC Sem Probabilidade' in resultados: model_info = resultados['SVC Sem Probabilidade'] decision_scores = model_info['decision_scores'] print(f"\nEstatísticas das pontuações de decisão:") print(f" Mínimo: {decision_scores.min():.4f}") print(f" Máximo: {decision_scores.max():.4f}") print(f" Média: {decision_scores.mean():.4f}") print(f" Desvio padrão: {decision_scores.std():.4f}") # Análise por classe real for class_label in [0, 1]: mask = (y_test == class_label) scores_class = decision_scores[mask] print(f" Classe {class_label}: {len(scores_class)} amostras, " f"média pontuação: {scores_class.mean():.4f}") ''' Visualização das pontuações de decisão ''' print("\n4. VISUALIZAÇÃO DAS PONTUAÇÕES") fig, axes = plt.subplots(2, 2, figsize=(15, 12)) # Gráfico 1: Distribuição das pontuações por classe real if 'SVC Sem Probabilidade' in resultados: decision_scores = resultados['SVC Sem Probabilidade']['decision_scores'] for class_label in [0, 1]: mask = (y_test == class_label) axes[0, 0].hist(decision_scores[mask], bins=30, alpha=0.7, label=f'Classe {class_label}', density=True) axes[0, 0].axvline(x=0, color='red', linestyle='--', label='Limite de decisão') axes[0, 0].set_xlabel('Pontuação de Decisão') axes[0, 0].set_ylabel('Densidade') axes[0, 0].set_title('Distribuição das Pontuações por Classe Real') axes[0, 0].legend() axes[0, 0].grid(True, alpha=0.3) # Gráfico 2: Pontuações vs Probabilidades if 'SVC Com Probabilidade' in resultados: decision_scores = resultados['SVC Com Probabilidade']['decision_scores'] probabilities = resultados['SVC Com Probabilidade']['probabilities'][:, 1] scatter = axes[0, 1].scatter(decision_scores, probabilities, c=y_test, cmap='coolwarm', alpha=0.6) axes[0, 1].set_xlabel('Pontuação de Decisão') axes[0, 1].set_ylabel('Probabilidade Classe 1') axes[0, 1].set_title('Relação: Pontuações vs Probabilidades') axes[0, 1].grid(True, alpha=0.3) plt.colorbar(scatter, ax=axes[0, 1]) # Gráfico 3: Curva de calibração if 'SVC Com Probabilidade' in resultados: probabilities = resultados['SVC Com Probabilidade']['probabilities'][:, 1] # Calcular curva de calibração fraction_of_positives, mean_predicted_value = calibration_curve( y_test, probabilities, n_bins=10 ) axes[1, 0].plot(mean_predicted_value, fraction_of_positives, 's-', label='SVC Com Probabilidade') axes[1, 0].plot([0, 1], [0, 1], '--', color='gray', label='Calibração perfeita') axes[1, 0].set_xlabel('Probabilidade Média Prevista') axes[1, 0].set_ylabel('Fração de Positivos') axes[1, 0].set_title('Curva de Calibração') axes[1, 0].legend() axes[1, 0].grid(True, alpha=0.3) # Gráfico 4: Comparação de confiança if 'SVC Com Probabilidade' in resultados: probabilities = resultados['SVC Com Probabilidade']['probabilities'] confidence = np.max(probabilities, axis=1) correct_predictions = (resultados['SVC Com Probabilidade']['predictions'] == y_test) # Separar por previsões corretas e incorretas axes[1, 1].hist(confidence[correct_predictions], bins=20, alpha=0.7, label='Previsões Corretas', density=True) axes[1, 1].hist(confidence[~correct_predictions], bins=20, alpha=0.7, label='Previsões Incorretas', density=True) axes[1, 1].set_xlabel('Confiança da Previsão') axes[1, 1].set_ylabel('Densidade') axes[1, 1].set_title('Distribuição de Confiança por Acerto') axes[1, 1].legend() axes[1, 1].grid(True, alpha=0.3) plt.tight_layout() plt.show() ''' Análise dos parâmetros de calibração ''' print("\n5. ANÁLISE DOS PARÂMETROS DE CALIBRAÇÃO") if 'SVC Com Probabilidade' in resultados: model = resultados['SVC Com Probabilidade']['model'] print(f"\nParâmetros do Platt Scaling:") if hasattr(model, 'probA_') and hasattr(model, 'probB_'): print(f" probA_: {model.probA_[0]:.4f}") print(f" probB_: {model.probB_[0]:.4f}") # Interpretação dos parâmetros print(f"\nInterpretação:") print(f" Probabilidade = 1 / (1 + exp({model.probA_[0]:.4f} * score + {model.probB_[0]:.4f}))") print(f" Classes: {model.classes_}") ''' Exemplo de uso prático com diferentes limiares ''' print("\n6. USO PRÁTICO: AJUSTE DE LIMIARES") if 'SVC Com Probabilidade' in resultados: probabilities = resultados['SVC Com Probabilidade']['probabilities'][:, 1] print(f"\nPerformance com diferentes limiares de decisão:") print("="*60) print(f"{'Limiar':<8} {'Acurácia':<10} {'Precisão':<10} {'Recall':<10} {'F1-Score':<10}") print("="*60) for threshold in [0.3, 0.4, 0.5, 0.6, 0.7]: # Aplicar limiar personalizado y_pred_custom = (probabilities >= threshold).astype(int) # Calcular métricas from sklearn.metrics import precision_score, recall_score, f1_score accuracy = accuracy_score(y_test, y_pred_custom) precision = precision_score(y_test, y_pred_custom) recall = recall_score(y_test, y_pred_custom) f1 = f1_score(y_test, y_pred_custom) print(f"{threshold:<8} {accuracy:<10.4f} {precision:<10.4f} {recall:<10.4f} {f1:<10.4f}") ''' Análise de casos limítrofes ''' print("\n7. ANÁLISE DE CASOS LIMÍTROFES") if 'SVC Com Probabilidade' in resultados: probabilities = resultados['SVC Com Probabilidade']['probabilities'][:, 1] decision_scores = resultados['SVC Com Probabilidade']['decision_scores'] # Identificar casos com probabilidade próxima de 0.5 borderline_mask = (probabilities > 0.4) & (probabilities < 0.6) borderline_indices = np.where(borderline_mask)[0] print(f"\nCasos limítrofes (probabilidade entre 0.4 e 0.6):") print(f" Total de casos: {len(borderline_indices)}/{len(y_test)} ({len(borderline_indices)/len(y_test):.1%})") if len(borderline_indices) > 0: print(f"\nExemplo de casos limítrofes:") for i, idx in enumerate(borderline_indices[:5]): true_class = y_test[idx] pred_class = resultados['SVC Com Probabilidade']['predictions'][idx] prob = probabilities[idx] score = decision_scores[idx] print(f" Amostra {idx}: Classe Real={true_class}, " f"Predita={pred_class}, Prob={prob:.4f}, Score={score:.4f}") ''' Comparação de métricas entre modelos ''' print("\n8. COMPARAÇÃO DE MÉTRICAS") print("\nResumo Comparativo:") print("="*70) print(f"{'Modelo':<25} {'Acurácia':<10} {'Log Loss':<10} {'Brier Score':<12} {'CV Score':<12}") print("="*70) for model_name, info in resultados.items(): accuracy = info['accuracy'] cv_score = f"{info['cv_mean']:.4f} ± {info['cv_std']:.4f}" if 'log_loss' in info: logloss = info['log_loss'] brier = info['brier_score'] print(f"{model_name:<25} {accuracy:<10.4f} {logloss:<10.4f} {brier:<12.4f} {cv_score:<12}") else: print(f"{model_name:<25} {accuracy:<10.4f} {'N/A':<10} {'N/A':<12} {cv_score:<12}") ''' Recomendações práticas ''' print("\n9. RECOMENDAÇÕES PRÁTICAS") print("\nQuando usar probabilidades:") print(" - Aplicações que requerem estimativas de incerteza") print(" - Combinação com outros modelos (ensembles)") print(" - Tomada de decisão baseada em custo/benefício") print(" - Análise de risco e cenários de 'what-if'") print("\nConsiderações de performance:") print(" - Habilitar probability=True aumenta tempo de treinamento") print(" - Platt scaling requer validação cruzada interna") print(" - Verificar calibração para aplicações críticas") print("\nInterpretação das pontuações:") print(" - Magnitude indica confiança na predição") print(" - Sinal indica direção da predição") print(" - Valores próximos de zero indicam incerteza") print("\nUso de limiares personalizados:") print(" - Ajustar trade-off entre precisão e recall") print(" - Considerar custos assimétricos de erro") print(" - Validar em conjunto de teste independente") print("\nBoas práticas:") print(" - Sempre verificar calibração das probabilidades") print(" - Considerar recalibração para datasets específicos") print(" - Documentar limitações das estimativas probabilísticas") print(" - Usar múltiplas métricas para avaliação completa") |
Interpretação dos Resultados
Analisando os experimentos com pontuações e probabilidades, podemos observar padrões importantes:
- As pontuações de decisão fornecem informação contínua sobre a confiança das predições
- O Platt scaling transforma efetivamente as pontuações em probabilidades calibradas
- As probabilidades permitem ajuste flexível de limiares de decisão
- A calibração adequada é crucial para aplicações que dependem de probabilidades
Considerações para Aplicações Práticas
Seleção do Modo de Operação
Inegavelmente, a escolha entre usar apenas pontuações ou habilitar probabilidades depende do contexto:
- Apenas pontuações: Para classificação simples onde velocidade é crítica
- Com probabilidades: Para aplicações que requerem quantificação de incerteza
- Limiares personalizados: Quando os custos de erro são assimétricos
Avaliação da Calibração
Para aplicações críticas, a calibração das probabilidades deve ser rigorosamente avaliada:
- Usar curvas de calibração para verificação visual
- Calcular Brier score para avaliação quantitativa
- Considerar técnicas de recalibração se necessário
- Validar em conjuntos de dados representativos
Conclusão
As funcionalidades de pontuações e probabilidades representam uma extensão valiosa das Máquinas de Vetores de Suporte, transformando classificadores determinísticos em modelos que podem quantificar incerteza. Embora as probabilidades geradas pelo Platt scaling sejam aproximações, oferecem ferramentas poderosas para aplicações do mundo real.
Portanto, o entendimento profundo dessas funcionalidades permite aos praticantes de machine learning extrair o máximo valor dos classificadores SVM, adaptando-se a uma variedade de cenários que vão desde classificação simples até tomada de decisão baseada em risco e incerteza.
Referência
Este post explora o item 1.4.1.2. Pontuações e probabilidades da documentação do scikit-learn:
https://scikit-learn.org/0.21/modules/svm.html#scores-and-probabilities