Imagine que você está analisando o terreno de uma montanha para planejar uma trilha. Algumas áreas são suaves como o kernel RBF, permitindo caminhadas tranquilas. Outras são irregulares e acidentadas, exigindo mais cuidado. O núcleo Matérn é como ter um controle deslizante que ajusta continuamente entre esses extremos – você pode escolher o nível exato de “acidentado” que corresponde ao seu terreno real, nem muito suave que ignore características importantes, nem muito irregular que capture apenas ruído.
Como isso funciona na prática?
O núcleo Matérn é uma família paramétrica de kernels que generaliza o RBF, oferecendo controle explícito sobre a suavidade das funções através do parâmetro ν (nu). Enquanto o RBF produz funções infinitamente diferenciáveis (extremamente suaves), o Matérn cria funções que são k vezes diferenciáveis, onde k = ⌊ν⌋. Diferentemente do RBF que assume suavidade perfeita, o Matérn admite que dados reais podem ter certa rugosidade, tornando-o mais robusto e apropriado para muitos problemas do mundo real onde suavidade infinita é uma suposição muito forte.
Mãos na massa: explorando a família Matérn com diferentes valores de ν
|
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 |
""" Explorando o núcleo Matérn com diferentes valores do parâmetro nu Demonstra como controlar a suavidade das funções geradas """ import numpy as np import matplotlib.pyplot as plt from sklearn.gaussian_process.kernels import Matern from sklearn.gaussian_process import GaussianProcessRegressor from sklearn.gaussian_process.kernels import RBF # Criando dados com diferentes níveis de rugosidade def criar_dados_rugosidade(): """Cria dados com diferentes características de suavidade/rugosidade""" np.random.seed(42) x = np.linspace(0, 10, 100).reshape(-1, 1) # Dados suaves (ideais para RBF) y_suave = np.sin(x.ravel()) + 0.05 * np.random.normal(size=100) # Dados moderadamente rugosos y_moderado = (np.sin(x.ravel()) + 0.3 * np.sin(3 * x.ravel()) + 0.1 * np.random.normal(size=100)) # Dados muito rugosos (com descontinuidades) y_rugoso = (np.sin(x.ravel()) + 0.5 * np.sin(5 * x.ravel()) + 0.3 * np.sin(8 * x.ravel()) + 0.2 * np.random.normal(size=100)) # Dados do mundo real: preços de ações (simulados) precos = 100 + np.cumsum(np.random.normal(0, 1, 100)) y_acoes = precos + 2 * np.random.normal(size=100) return x, y_suave, y_moderado, y_rugoso, y_acoes x, y_suave, y_moderado, y_rugoso, y_acoes = criar_dados_rugosidade() print("=== EXPLORANDO A FAMÍLIA MATÉRN ===\n") # Valores comuns do parâmetro nu valores_nu = [0.5, 1.5, 2.5, 10.0] # ν = 0.5, 1.5, 2.5, ∞ (aproximadamente) print("A família Matérn é definida por:") print("[latex]k_{\\nu}(d) = \\frac{2^{1-\\nu}}{\\Gamma(\\nu)} \\left(\\frac{\\sqrt{2\\nu}d}{l}\\right)^\\nu K_\\nu\\left(\\frac{\\sqrt{2\\nu}d}{l}\\right)[/latex]") print("Onde ν controla a suavidade e l é o length_scale\n") # Testando diferentes valores de nu nos dados moderadamente rugosos fig, axes = plt.subplots(2, 2, figsize=(15, 12)) axes = axes.ravel() for idx, nu in enumerate(valores_nu): kernel_matern = Matern(length_scale=1.0, nu=nu) gpr = GaussianProcessRegressor(kernel=kernel_matern, alpha=0.0, random_state=42) gpr.fit(x, y_moderado) # Previsões x_pred = np.linspace(0, 10, 200).reshape(-1, 1) y_pred, sigma = gpr.predict(x_pred, return_std=True) # Plot axes[idx].plot(x, y_moderado, 'ro', alpha=0.6, markersize=3, label='Dados') axes[idx].plot(x_pred, y_pred, 'b-', linewidth=2, label='Previsão') axes[idx].fill_between(x_pred.ravel(), y_pred - 1.96*sigma, y_pred + 1.96*sigma, alpha=0.2, label='Incerteza 95%') # Informações sobre a suavidade if nu == 0.5: suavidade = "Não diferenciável (Ornstein-Uhlenbeck)" elif nu == 1.5: suavidade = "1 vez diferenciável" elif nu == 2.5: suavidade = "2 vezes diferenciável" else: suavidade = "∞ vezes diferenciável (≈ RBF)" axes[idx].set_title(f'Matérn ν = {nu}\n{suavidade}', fontsize=12) axes[idx].grid(True, alpha=0.3) axes[idx].legend() plt.tight_layout() plt.show() # Comparação detalhada: Matérn vs RBF em diferentes tipos de dados print("\n=== COMPARAÇÃO MATÉRN VS RBF EM DIFERENTES DADOS ===\n") dados_teste = { 'Dados Suaves': y_suave, 'Dados Moderados': y_moderado, 'Dados Rugosos': y_rugoso, 'Dados Financeiros': y_acoes } kernels_comparacao = { 'RBF': RBF(length_scale=1.0), 'Matérn ν=0.5': Matern(length_scale=1.0, nu=0.5), 'Matérn ν=1.5': Matern(length_scale=1.0, nu=1.5), 'Matérn ν=2.5': Matern(length_scale=1.0, nu=2.5) } fig, axes = plt.subplots(len(dados_teste), len(kernels_comparacao), figsize=(20, 16)) resultados_loglik = {} for i, (nome_dados, y_data) in enumerate(dados_teste.items()): resultados_loglik[nome_dados] = {} for j, (nome_kernel, kernel) in enumerate(kernels_comparacao.items()): ax = axes[i, j] try: gpr = GaussianProcessRegressor(kernel=kernel, random_state=42) gpr.fit(x, y_data) # Previsões x_pred = np.linspace(0, 10, 200).reshape(-1, 1) y_pred, sigma = gpr.predict(x_pred, return_std=True) # Plot ax.plot(x, y_data, 'ro', alpha=0.6, markersize=2) ax.plot(x_pred, y_pred, 'b-', linewidth=1.5) ax.fill_between(x_pred.ravel(), y_pred - 1.96*sigma, y_pred + 1.96*sigma, alpha=0.15) log_likelihood = gpr.log_marginal_likelihood() resultados_loglik[nome_dados][nome_kernel] = log_likelihood ax.set_title(f'{nome_kernel}\nLL: {log_likelihood:.2f}', fontsize=10) ax.grid(True, alpha=0.3) # Apenas primeira coluna tem labels y if j == 0: ax.set_ylabel(nome_dados, fontsize=12) except Exception as e: ax.text(0.5, 0.5, f'Erro\n{str(e)}', ha='center', va='center', transform=ax.transAxes, fontsize=8) ax.set_title(nome_kernel, fontsize=10) plt.tight_layout() plt.show() # Análise dos resultados de log-likelihood print("=== ANÁLISE DE LOG-LIKELIHOOD (MAIOR É MELHOR) ===\n") for nome_dados, resultados in resultados_loglik.items(): print(f"{nome_dados}:") melhor_kernel = max(resultados.items(), key=lambda x: x[1]) for kernel, ll in sorted(resultados.items(), key=lambda x: x[1], reverse=True): marcador = " ★" if kernel == melhor_kernel[0] else "" print(f" {kernel:15} → {ll:8.2f}{marcador}") print() # Visualização das funções de covariância Matérn print("=== FUNÇÕES DE COVARIÂNCIA MATÉRN ===\n") plt.figure(figsize=(12, 8)) # Plot das funções de covariância distances = np.linspace(0, 3, 100) plt.subplot(2, 2, 1) for nu in valores_nu: kernel = Matern(length_scale=1.0, nu=nu) # Calculando covariância para diferentes distâncias K = kernel(np.array([[0]]), distances.reshape(-1, 1)) plt.plot(distances, K, label=f'ν = {nu}', linewidth=2) plt.title('Funções de Covariância Matérn') plt.xlabel('Distância') plt.ylabel('Covariância') plt.legend() plt.grid(True, alpha=0.3) # Plot das derivadas (mostrando suavidade) plt.subplot(2, 2, 2) distances_fine = np.linspace(0.1, 2, 200) for nu in [0.5, 1.5, 2.5]: kernel = Matern(length_scale=1.0, nu=nu) K = kernel(np.array([[0]]), distances_fine.reshape(-1, 1)) derivative = np.gradient(K, distances_fine) plt.plot(distances_fine, derivative, label=f'ν = {nu}', linewidth=2) plt.title('Derivadas das Funções Matérn') plt.xlabel('Distância') plt.ylabel('Derivada da Covariância') plt.legend() plt.grid(True, alpha=0.3) # Casos de uso específicos plt.subplot(2, 2, 3) casos_uso = { 'ν = 0.5': 'Processos financeiros, física (Ornstein-Uhlenbeck)', 'ν = 1.5': 'Aplicações gerais (balanceado)', 'ν = 2.5': 'Dados suaves (próximo do RBF)', 'ν → ∞': 'Funções analíticas (RBF)' } y_pos = range(len(casos_uso)) plt.barh(y_pos, [0.5, 1.5, 2.5, 10], color='lightblue', alpha=0.7) plt.yticks(y_pos, casos_uso.keys()) plt.xlabel('Valor de ν') plt.title('Casos de Uso Recomendados') for i, (key, desc) in enumerate(casos_uso.items()): plt.text(0.5, i, desc, va='center', ha='left', fontsize=9) plt.subplot(2, 2, 4) # Comparação de performance dados_exemplo = ['Suaves', 'Moderados', 'Rugosos', 'Financeiros'] rbf_scores = [resultados_loglik['Dados Suaves']['RBF'], resultados_loglik['Dados Moderados']['RBF'], resultados_loglik['Dados Rugosos']['RBF'], resultados_loglik['Dados Financeiros']['RBF']] matern_scores = [resultados_loglik['Dados Suaves']['Matérn ν=1.5'], resultados_loglik['Dados Moderados']['Matérn ν=1.5'], resultados_loglik['Dados Rugosos']['Matérn ν=1.5'], resultados_loglik['Dados Financeiros']['Matérn ν=1.5']] x_pos = np.arange(len(dados_exemplo)) width = 0.35 plt.bar(x_pos - width/2, rbf_scores, width, label='RBF', alpha=0.7) plt.bar(x_pos + width/2, matern_scores, width, label='Matérn ν=1.5', alpha=0.7) plt.xlabel('Tipo de Dados') plt.ylabel('Log-Likelihood') plt.title('RBF vs Matérn ν=1.5') plt.xticks(x_pos, dados_exemplo) plt.legend() plt.grid(True, alpha=0.3) plt.tight_layout() plt.show() # Guia prático de escolha print("=== GUIA PRÁTICO: QUANDO USAR CADA VALOR DE ν ===\n") guia_escolha = { 0.5: { 'nome': 'Matérn ν=0.5 (Ornstein-Uhlenbeck)', 'uso': 'Dados muito rugosos, processos financeiros, séries temporais com volatilidade', 'caracteristica': 'Não diferenciável - captura descontinuidades e mudanças abruptas', 'exemplo': 'Preços de ações, dados de sensores com ruído significativo' }, 1.5: { 'nome': 'Matérn ν=1.5', 'uso': 'Casos gerais, ponto de partida recomendado', 'caracteristica': '1 vez diferenciável - balance entre flexibilidade e suavidade', 'exemplo': 'Dados científicos, medições de engenharia, maioria dos problemas' }, 2.5: { 'nome': 'Matérn ν=2.5', 'uso': 'Dados suaves mas com alguma rugosidade', 'caracteristica': '2 vezes diferenciável - próximo do RBF mas mais flexível', 'exemplo': 'Trajetórias suaves, dados físicos bem comportados' } } for nu, info in guia_escolha.items(): print(f"{info['nome']}:") print(f" Uso: {info['uso']}") print(f" Característica: {info['caracteristica']}") print(f" Exemplo: {info['exemplo']}\n") |
Os detalhes que fazem diferença
O parâmetro ν no kernel Matérn controla precisamente quantas derivadas a função resultante terá. Valores menores de ν (como 0.5) produzem funções mais rugosas e menos suaves, apropriadas para dados com descontinuidades ou mudanças abruptas. Valores maiores (como 2.5) criam funções mais suaves, aproximando-se do comportamento do RBF quando ν → ∞. Contudo, a escolha prática mais comum é ν = 1.5, que oferece um bom balance entre flexibilidade e suavidade para a maioria dos problemas do mundo real. É importante notar que o Matérn com ν = 0.5 é equivalente ao processo de Ornstein-Uhlenbeck, amplamente usado em finanças e física.
- ν = 0.5: Ornstein-Uhlenbeck, não diferenciável, ideal para dados muito rugosos
- ν = 1.5: Ponto de partida recomendado, 1 vez diferenciável, balanceado
- ν = 2.5: 2 vezes diferenciável, para dados suaves com alguma rugosidade
- ν → ∞: Aproxima-se do RBF, infinitamente diferenciável
- Length_scale: Controla a escala de correlação, igual ao RBF
Perguntas que os iniciantes fazem
Você deve estar se perguntando: “Por que não usar sempre Matérn em vez de RBF?” Excelente questão! O RBF é mais computacionalmente eficiente e funciona bem quando você tem certeza que seus dados são suaves. Uma confusão comum é pensar que ν controla overfitting – na verdade, ele controla a suavidade intrínseca, enquanto overfitting é mais afetado pelo length_scale e pela quantidade de dados. Outra dúvida frequente: “Como escolher ν na prática?” Comece com ν = 1.5 como padrão e ajuste baseando-se no conhecimento do domínio – use valores menores para dados financeiros ou físicos com ruído, valores maiores para fenômenos naturalmente suaves.
Para onde ir agora?
Experimente o kernel Matérn em seus próprios dados, testando diferentes valores de ν e comparando com RBF usando log-verossimilhança marginal. Preste atenção especial a como cada valor de ν lida com regiões de alta variabilidade nos seus dados. Use o Matérn como seu kernel padrão quando suspeitar que suavidade infinita pode ser uma suposição muito forte. O momento “aha!” acontece quando você encontra o ν ideal que captura a “textura” real dos seus dados sem suavizar características importantes nem amplificar ruído.
Assuntos relacionados
Para entender profundamente o kernel Matérn, estude:
- Processos de Ornstein-Uhlenbeck: caso especial quando ν = 0.5
- Funções de Bessel: fundamento matemático da família Matérn
- Diferenciabilidade de funções: o que significa uma função ser k vezes diferenciável
- Processos de Lévy: generalizações de processos gaussianos
- Geoestatística: aplicações do Matérn em krigagem e análise espacial