Imagine que você está ouvindo uma música complexa. Algumas batidas se repetem regularmente, outras têm variações sutis, mas todas seguem um ritmo subjacente. O kernel Exp-Seno-Quadrado é como ter um ouvido musical treinado que consegue identificar não apenas a periodicidade principal, mas também como esse padrão evolui e varia suavemente ao longo do tempo. Ele é a ferramenta perfeita para dados que dançam ao ritmo de ciclos e estações, desde batidas cardíacas até movimentos planetários.
Como isso funciona na prática?
O kernel Exp-Seno-Quadrado (Exp-Sine-Squared) é especificamente projetado para capturar padrões periódicos que não são perfeitamente rígidos, mas sim suavemente variáveis. Diferentemente de uma simples função seno que assume periodicidade perfeita, este kernel permite que a similaridade entre pontos dependa tanto do tempo quanto de quão bem seus “fases” se alinham no ciclo periódico. Ele modela a ideia de que pontos separados por exatamente um período devem ser muito similares, mas essa similaridade decai suavemente conforme nos afastamos do alinhamento perfeito de fase.
Mãos na massa: caçando periodicidades com o kernel Exp-Seno-Quadrado
|
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 319 320 321 322 323 324 325 326 327 |
""" Explorando o kernel Exp-Seno-Quadrado para dados periódicos Demonstra como capturar padrões cíclicos com variações suaves """ import numpy as np import matplotlib.pyplot as plt from sklearn.gaussian_process.kernels import ExpSineSquared, RBF from sklearn.gaussian_process import GaussianProcessRegressor # Criando dados com diferentes tipos de periodicidade def criar_dados_periodicos(): """Cria dados com vários tipos de padrões periódicos do mundo real""" np.random.seed(42) # Dados com periodicidade perfeita (para comparação) t_perfeito = np.linspace(0, 20, 200).reshape(-1, 1) y_perfeito = np.sin(2 * np.pi * t_perfeito.ravel() / 5) + 0.1 * np.random.normal(size=200) # Dados com periodicidade variável (mais realista) t_variavel = np.linspace(0, 20, 200).reshape(-1, 1) periodo_variavel = 5 + 0.5 * np.sin(t_variavel.ravel() / 3) # Período que varia suavemente y_variavel = np.sin(2 * np.pi * t_variavel.ravel() / periodo_variavel) + 0.1 * np.random.normal(size=200) # Dados com múltiplas periodicidades (caso complexo) t_multiplo = np.linspace(0, 30, 300).reshape(-1, 1) y_multiplo = (np.sin(2 * np.pi * t_multiplo.ravel() / 3) + # Período curto 0.5 * np.sin(2 * np.pi * t_multiplo.ravel() / 8) + # Período médio 0.3 * np.sin(2 * np.pi * t_multiplo.ravel() / 15) + # Período longo 0.1 * np.random.normal(size=300)) # Dados do mundo real simulados: batimentos cardíacos t_coracao = np.linspace(0, 10, 500).reshape(-1, 1) # Simula batimentos com pequenas variações no ritmo frequencia_base = 1.2 # Hz (72 batimentos por minuto) variacao_ritmo = 0.1 * np.sin(2 * np.pi * t_coracao.ravel() / 4) # Variação respiratória y_coracao = np.sin(2 * np.pi * (frequencia_base + variacao_ritmo) * t_coracao.ravel()) y_coracao += 0.05 * np.random.normal(size=500) # Ruído de medição return (t_perfeito, y_perfeito), (t_variavel, y_variavel), (t_multiplo, y_multiplo), (t_coracao, y_coracao) dados_perfeito, dados_variavel, dados_multiplo, dados_coracao = criar_dados_periodicos() print("=== EXPLORANDO O KERNEL EXP-SENO-QUADRADO ===\n") print("O kernel Exp-Seno-Quadrado é definido por:") print("[latex]k(x_i, x_j) = \\exp\\left(-\\frac{2\\sin^2(\\pi d(x_i, x_j)/p)}{l^2}\\right)[/latex]") print("Onde p é o período e l é o length_scale que controla a suavidade\n") # Explorando diferentes parâmetros do kernel parametros_testar = [ {'periodicity': 5.0, 'length_scale': 1.0}, {'periodicity': 5.0, 'length_scale': 0.5}, {'periodicity': 5.0, 'length_scale': 2.0}, {'periodicity': 3.0, 'length_scale': 1.0}, {'periodicity': 8.0, 'length_scale': 1.0} ] fig, axes = plt.subplots(2, 3, figsize=(18, 12)) axes = axes.ravel() t_dados, y_dados = dados_perfeito for idx, params in enumerate(parametros_testar): kernel_ess = ExpSineSquared(length_scale=params['length_scale'], periodicity=params['periodicity']) gpr = GaussianProcessRegressor(kernel=kernel_ess, alpha=0.0, random_state=42) gpr.fit(t_dados, y_dados) # Previsões t_pred = np.linspace(0, 20, 300).reshape(-1, 1) y_pred, sigma = gpr.predict(t_pred, return_std=True) # Plot axes[idx].plot(t_dados, y_dados, 'ro', alpha=0.4, markersize=2, label='Dados') axes[idx].plot(t_pred, y_pred, 'b-', linewidth=2, label='Previsão ESS') axes[idx].fill_between(t_pred.ravel(), y_pred - 1.96*sigma, y_pred + 1.96*sigma, alpha=0.2, label='Incerteza 95%') axes[idx].set_title(f'ESS: p={params["periodicity"]}, l={params["length_scale"]}\n' f'LL: {gpr.log_marginal_likelihood():.2f}', fontsize=11) axes[idx].grid(True, alpha=0.3) axes[idx].legend(fontsize=9) # Comparação com RBF no último subplot kernel_rbf = RBF(length_scale=1.0) gpr_rbf = GaussianProcessRegressor(kernel=kernel_rbf, alpha=0.0, random_state=42) gpr_rbf.fit(t_dados, y_dados) y_pred_rbf, sigma_rbf = gpr_rbf.predict(t_pred, return_std=True) axes[5].plot(t_dados, y_dados, 'ro', alpha=0.4, markersize=2, label='Dados') axes[5].plot(t_pred, y_pred_rbf, 'g-', linewidth=2, label='Previsão RBF') axes[5].fill_between(t_pred.ravel(), y_pred_rbf - 1.96*sigma_rbf, y_pred_rbf + 1.96*sigma_rbf, alpha=0.2, label='Incerteza 95%') axes[5].set_title(f'RBF (comparação)\nLL: {gpr_rbf.log_marginal_likelihood():.2f}', fontsize=11) axes[5].grid(True, alpha=0.3) axes[5].legend(fontsize=9) plt.tight_layout() plt.show() # Testando em diferentes cenários periódicos print("\n=== TESTANDO EM DIFERENTES CENÁRIOS PERIÓDICOS ===\n") cenarios = { 'Periodicidade Perfeita': dados_perfeito, 'Periodicidade Variável': dados_variavel, 'Múltiplas Periodicidades': dados_multiplo, 'Batimentos Cardíacos': dados_coracao } resultados_comparacao = {} fig, axes = plt.subplots(len(cenarios), 2, figsize=(15, 12)) for i, (nome_cenario, (t_data, y_data)) in enumerate(cenarios.items()): # Kernel Exp-Seno-Quadrado com otimização kernel_ess = ExpSineSquared(length_scale=1.0, periodicity=5.0) gpr_ess = GaussianProcessRegressor(kernel=kernel_ess, random_state=42) gpr_ess.fit(t_data, y_data) # RBF para comparação kernel_rbf = RBF(length_scale=1.0) gpr_rbf = GaussianProcessRegressor(kernel=kernel_rbf, random_state=42) gpr_rbf.fit(t_data, y_data) # Previsões t_pred = np.linspace(t_data.min(), t_data.max(), 300).reshape(-1, 1) y_pred_ess, sigma_ess = gpr_ess.predict(t_pred, return_std=True) y_pred_rbf, sigma_rbf = gpr_rbf.predict(t_pred, return_std=True) # Plot Exp-Seno-Quadrado axes[i, 0].plot(t_data, y_data, 'ro', alpha=0.5, markersize=2, label='Dados') axes[i, 0].plot(t_pred, y_pred_ess, 'b-', linewidth=2, label='Previsão ESS') axes[i, 0].fill_between(t_pred.ravel(), y_pred_ess - 1.96*sigma_ess, y_pred_ess + 1.96*sigma_ess, alpha=0.2, label='Incerteza') axes[i, 0].set_title(f'Exp-Seno-Quadrado - {nome_cenario}\n' f'Kernel: {gpr_ess.kernel_}\n' f'LL: {gpr_ess.log_marginal_likelihood():.2f}', fontsize=10) axes[i, 0].grid(True, alpha=0.3) axes[i, 0].legend(fontsize=8) # Plot RBF axes[i, 1].plot(t_data, y_data, 'ro', alpha=0.5, markersize=2, label='Dados') axes[i, 1].plot(t_pred, y_pred_rbf, 'g-', linewidth=2, label='Previsão RBF') axes[i, 1].fill_between(t_pred.ravel(), y_pred_rbf - 1.96*sigma_rbf, y_pred_rbf + 1.96*sigma_rbf, alpha=0.2, label='Incerteza') axes[i, 1].set_title(f'RBF - {nome_cenario}\n' f'Kernel: {gpr_rbf.kernel_}\n' f'LL: {gpr_rbf.log_marginal_likelihood():.2f}', fontsize=10) axes[i, 1].grid(True, alpha=0.3) axes[i, 1].legend(fontsize=8) # Armazenar resultados resultados_comparacao[nome_cenario] = { 'ESS_LL': gpr_ess.log_marginal_likelihood(), 'RBF_LL': gpr_rbf.log_marginal_likelihood(), 'ESS_kernel': str(gpr_ess.kernel_), 'RBF_kernel': str(gpr_rbf.kernel_) } plt.tight_layout() plt.show() # Análise dos resultados print("=== ANÁLISE DOS RESULTADOS (Log-Likelihood) ===\n") for cenario, resultados in resultados_comparacao.items(): ess_ll = resultados['ESS_LL'] rbf_ll = resultados['RBF_LL'] diferenca = ess_ll - rbf_ll melhor = "Exp-Seno-Quadrado" if diferenca > 0 else "RBF" print(f"{cenario}:") print(f" Exp-Seno-Quadrado: {ess_ll:8.2f}") print(f" RBF: {rbf_ll:8.2f}") print(f" Diferença: {diferenca:8.2f} → {melhor} é melhor") print(f" Kernel ESS otimizado: {resultados['ESS_kernel']}") print() # Visualização das funções de covariância print("=== FUNÇÕES DE COVARIÂNCIA EXP-SENO-QUADRADO ===\n") plt.figure(figsize=(15, 10)) # Plot das funções de covariância para diferentes períodos plt.subplot(2, 2, 1) distances = np.linspace(0, 10, 100) periodos = [3.0, 5.0, 8.0, 12.0] for periodo in periodos: kernel = ExpSineSquared(length_scale=1.0, periodicity=periodo) K = kernel(np.array([[0]]), distances.reshape(-1, 1)) plt.plot(distances, K, label=f'p = {periodo}', linewidth=2) plt.title('Funções de Covariância - Diferentes Períodos') plt.xlabel('Distância') plt.ylabel('Covariância') plt.legend() plt.grid(True, alpha=0.3) # Plot para diferentes length_scales plt.subplot(2, 2, 2) length_scales = [0.5, 1.0, 2.0, 5.0] for ls in length_scales: kernel = ExpSineSquared(length_scale=ls, periodicity=5.0) K = kernel(np.array([[0]]), distances.reshape(-1, 1)) plt.plot(distances, K, label=f'l = {ls}', linewidth=2) plt.title('Funções de Covariância - Diferentes Length Scales') plt.xlabel('Distância') plt.ylabel('Covariância') plt.legend() plt.grid(True, alpha=0.3) # Comparação com RBF plt.subplot(2, 2, 3) kernel_ess = ExpSineSquared(length_scale=1.0, periodicity=5.0) K_ess = kernel_ess(np.array([[0]]), distances.reshape(-1, 1)) kernel_rbf = RBF(length_scale=1.0) K_rbf = kernel_rbf(np.array([[0]]), distances.reshape(-1, 1)) plt.plot(distances, K_ess, 'b-', linewidth=2, label='Exp-Seno-Quadrado (p=5)') plt.plot(distances, K_rbf, 'g--', linewidth=2, label='RBF') plt.title('Comparação: Exp-Seno-Quadrado vs RBF') plt.xlabel('Distância') plt.ylabel('Covariância') plt.legend() plt.grid(True, alpha=0.3) # Aplicações do mundo real plt.subplot(2, 2, 4) aplicacoes = [ ('Clima/Tempo', 'Sazonalidade anual/diária', 'p = 24 (horas), 365 (dias)'), ('Finanças', 'Ciclos econômicos/sazonais', 'p = 12 (meses), 4 (trimestres)'), ('Medicina', 'Batimentos cardíacos/respiração', 'p = 0.8-1.2 (segundos)'), ('Astronomia', 'Movimentos planetários/fases', 'p = 27.3 (lunar), 365 (solar)'), ('Engenharia', 'Vibrações/rotações mecânicas', 'p = 1/frequência') ] y_pos = range(len(aplicacoes)) app_names = [app[0] for app in aplicacoes] periodos_typical = [365, 12, 1, 27.3, 0.1] # Períodos típicos plt.barh(y_pos, periodos_typical, color='lightsteelblue', alpha=0.7) plt.yticks(y_pos, app_names) plt.xlabel('Período Típico') plt.title('Aplicações do Mundo Real') for i, (nome, desc, periodo) in enumerate(aplicacoes): plt.text(10, i, f'{desc}\n{periodo}', va='center', ha='left', fontsize=8) plt.tight_layout() plt.show() # Detectando periodicidade automaticamente print("=== DETECTANDO PERIODICIDADE AUTOMATICAMENTE ===\n") def detectar_periodicidade(t, y): """Ferramenta simples para detectar periodicidade nos dados""" from scipy import signal # Usando FFT para encontrar frequências dominantes y_detrended = y - np.mean(y) fft = np.fft.fft(y_detrended) freqs = np.fft.fftfreq(len(y), t[1]-t[0]) # Encontrando pico não-DC magnitude = np.abs(fft) magnitude[0] = 0 # Ignorar componente DC idx_pico = np.argmax(magnitude[1:]) + 1 frequencia_dominante = np.abs(freqs[idx_pico]) periodo_estimado = 1.0 / frequencia_dominante if frequencia_dominante > 0 else 0 # Análise de autocorrelação autocorr = signal.correlate(y_detrended, y_detrended, mode='full') autocorr = autocorr[len(autocorr)//2:] lag_pico = signal.find_peaks(autocorr, distance=10)[0] if len(lag_pico) > 1: periodo_autocorr = (lag_pico[1] - lag_pico[0]) * (t[1]-t[0]) else: periodo_autocorr = 0 return periodo_estimado, periodo_autocorr # Testando detecção em nossos dados for nome, (t_data, y_data) in cenarios.items(): periodo_fft, periodo_autocorr = detectar_periodicidade(t_data.ravel(), y_data) print(f"{nome}:") print(f" Período estimado (FFT): {periodo_fft:.2f}") print(f" Período estimado (Autocorr): {periodo_autocorr:.2f}") print() # Resumo prático print("=== RESUMO PRÁTICO: QUANDO USAR EXP-SENO-QUADRADO ===\n") vantagens_ess = [ "• Captura especificamente padrões periódicos e quase-periódicos", "• Permite variações suaves na fase e amplitude dos ciclos", "• Parâmetro de período é interpretável diretamente", "• Excelente para dados sazonais, climáticos e biológicos", "• Pode ser combinado com outros kernels para padrões complexos" ] casos_uso_especificos = [ "Dados climáticos e meteorológicos (sazonalidade)", "Sinais fisiológicos (batimentos cardíacos, respiração)", "Dados econômicos e financeiros (ciclos sazonais)", "Sinais astronômicos (movimentos planetários)", "Vibrações mecânicas e sinais de engenharia" ] print("VANTAGENS PRINCIPAIS:") for vantagem in vantagens_ess: print(vantagem) print("\nCASOS DE USO RECOMENDADOS:") for caso in casos_uso_especificos: print(f" • {caso}") print("\nCONFIGURAÇÃO INICIAL RECOMENDADA:") print(" • Use detecção automática para estimar o período inicial") print(" • Comece com length_scale = 1.0 e ajuste conforme necessário") print(" • Combine com RBF para capturar tendências não-periódicas") print(" • Use bounds realistas baseados no conhecimento do domínio") |
Os detalhes que fazem diferença
O kernel Exp-Seno-Quadrado brilha em sua capacidade de modelar periodicidades que não são perfeitamente rígidas. O parâmetro de periodicity (p) especifica o comprimento do ciclo, enquanto o length_scale (l) controla quão rapidamente a similaridade decai quando os pontos se desalinham em fase. Valores pequenos de length_scale criam funções mais “rígidas” que exigem alinhamento quase perfeito de fase, enquanto valores maiores permitem mais flexibilidade. Uma propriedade crucial é que este kernel é estacionário – ele depende apenas da distância entre pontos, não de suas posições absolutas, o que o torna matematicamente bem comportado e eficiente computacionalmente.
- Periodicity (p): Comprimento do ciclo, altamente interpretável
- Length_scale (l): Controla a rigidez do padrão periódico
- Estacionário: Depende apenas da distância, não da posição
- Suave: Produz funções infinitamente diferenciáveis
- Combinável: Funciona bem com outros kernels via adição/multiplicação
Perguntas que os iniciantes fazem
Você deve estar se perguntando: “Como saber se meus dados são periódicos o suficiente para usar este kernel?” Observe se há picos regulares na autocorrelação ou se você consegue identificar visualmente padrões que se repetem. Uma confusão comum é tentar usar este kernel para dados que são apenas oscilatórios mas não verdadeiramente periódicos – nesse caso, RBF ou Matérn podem ser melhores. Outra dúvida frequente: “E se eu não souber o período?” Use análise de Fourier ou autocorrelação para estimar um período inicial, depois deixe a otimização refiná-lo.
Para onde ir agora?
Experimente o kernel Exp-Seno-Quadrado em seus dados que exibam sazonalidade ou ciclicidade. Comece com uma estimativa grosseira do período usando técnicas simples de análise de Fourier, depois refine com a otimização do GP. Tente combiná-lo com kernels RBF para capturar tanto a componente periódica quanto tendências de longo prazo. O momento “aha!” acontece quando você vê o modelo não apenas identificando a periodicidade, mas também capturando como ela varia suavemente ao longo do tempo, revelando os ritmos escondidos nos seus dados.
Assuntos relacionados
Para dominar este kernel, estude:
- Análise de Fourier: fundamentos da decomposição em frequências
- Processos periódicos: teoria de processos estocásticos com periodicidade
- Autocorrelação: medindo dependências temporais em séries
- Kernels espectralmente mistos: combinações para padrões complexos
- Processos gaussianos não estacionários: para periodicidades que evoluem no tempo