最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

scipy - Fitting with Maximum likelihood estimation in python returns initial parameters - Stack Overflow

programmeradmin1浏览0评论

I would like to use minimize from scipy.optimize to fit my data with a specific function:

I defined this log likelihood to this aim:

def log_likelihood(theta, freq, CPSD, CPSD_err):
    '''
    This function computes the log-likelihood for a given set of model parameters θ in a Bayesian framework, 
    comparing the observed data (freq, CPSD, CPSD_err) to a model. 
    - theta: A tuple containing the model parameters:
    - freq: The independent variable eg frequency
    - CPSD: The observed dependent variable: cross power spectral density
    - CPSD_err: The uncertainties (errors) on the observed values of yy.
    '''
    
    #extract the model parameters
    alpha_0, freq_O, alpha, CPSD_err = theta

    # Define the model
    model = alpha_0 * (freq_O**2 + freq**2)**(-(alpha + 3/2))

    # Calculate the total variance:
    # yerr2: The squared measurement uncertainties.
    # exp(2⋅logf)=f2, which represents the fractional noise.
    sigma2 = CPSD_err**2 #+ model**2 * np.exp(2 * log_f)

    # computes and returns the log-likelihood:
    # This is based on the formula for the log of a Gaussian likelihood:
    return -0.5 * np.sum((CPSD - model) ** 2 / sigma2 + np.log(sigma2))

# step 2: Define the Negative Log-Likelihood
def nll(*args):
    '''
    Define the Negative Log-Likelihood
    Minimizing the negative log-likelihood is equivalent to maximizing the log-likelihood.
    Purpose: This function will be minimized to estimate the model parameters.
    '''
    return -log_likelihood(*args)

following this tutorial: /

This is my function to do this:

def fit_CPSD_flattened(freq, CPSD, CPSD_err, freq_max, alpha_0, alpha, freq_O):
    '''
    This function fit the power spectrum with a flattened spectrum
- freq: frequencies, my data for the x axis
- CPSD: cross power spectral density, my data for the y axis
- freq-Max: freq until which I want to fit
- alpha_0: initial guess for parameter alpha_0
- alpha: initial guess for parameter alpha
- freq_0: initial guess for parameter freq_0
    '''
    
    # find the index of the maximum frequency for the fitting
    # np.where returns an array with elements from x where condition is True, and elements from y elsewhere.
    idx_high = np.where(freq >= freq_max)[0][0]
    print(f'{freq[idx_high]} Hz')

    # select the data range that will be fitted
    CPSD_selected = CPSD[:idx_high]
    freq_selected = freq[:idx_high]

    CPSD_err_scalar = np.std(CPSD_err)
    
    # initial guess for the parameters
    initial = np.array([alpha_0, freq_O, alpha, CPSD_err_scalar])
    
    
    # Define bounds for the parameters
    # Each tuple corresponds to (min, max) for a parameter
    bounds = [
        (1e-11, 1e-4),  # Bounds for alpha_0
        (0.0001, 2.0),     # Bounds for freq_O
        (-6, 0),        # Bounds for alpha
        (-np.inf, np.inf)       # Bounds for CPSD_err
    ]
    
    # step 4: Minimize the Negative Log-Likelihood
    soln = minimize(nll, initial, args=(freq_selected, CPSD_selected, CPSD_err_scalar), method='L-BFGS-B', bounds=bounds)

    # Step 5: Extract the Best-Fit Parameters
    alpha_0_ml, freq_O_ml, alpha_ml, CPSD_err_ml = soln.x 

    return alpha_0_ml, freq_O_ml, alpha_ml

and this is my code where I call the function with my data:

# std of the CPSD std
CPSD_err_scalar = np.std(CPSD_std_19)

#initial guess for the parameter
alpha_0 = 5e-10
freq_O = 0.006
alpha = -0.30

freq_max = 1 #Hz

# In this specific example:
alpha_0_ml, freq_O_ml, alpha_ml = fit_CPSD_flattened(bins_19[CPSD_mean_19>0], CPSD_mean_19[CPSD_mean_19>0], CPSD_err_scalar, freq_max, alpha_0, alpha, freq_O)

print(f'alpha_0_ml = {alpha_0_ml}')
print(f'freq_O_ml = {freq_O_ml}')
print(f'alpha_ml = {alpha_ml}')


slope = alpha_ml + 3/2

plt.plot(bins_19[CPSD_mean_19>0], CPSD_mean_19[CPSD_mean_19>0])
# fit
plt.plot(bins_19, flattening_CPSD(bins_19, alpha_0_ml, freq_O_ml, alpha_ml), color='lime',
         label=f'Fit: 1/k_out = {np.round(freq_O_ml, 4)}Hz, slope = -({np.round(slope*2,  3)})')
#         label=f'Fit: 1/k_out = ({np.round(freq_O_ml, 4)} ± {np.round(freq_O_ml_err, 4)})Hz, slope = -({np.round(slope*2,  3)} ± {np.round(slope_err,  3)})')
plt.loglog()
plt.legend()

but this ONLY returns the initial parameter... In the picture, the green fit is completely off the data in blue, and the fit only takes the value I gave for the initial parameter; this is obviously wrong. This drives me crazy, I don't know how to use this scipy minimize correctly, could someone help me?

This is the whole code for a reproducible example:

import numpy as np
from numpy import loadtxt
import matplotlib.pyplot as plt
from astropy.io import fits
import datetime
import scipy.stats as stats
from scipy import signal
from scipy.fftpack import fft, fftfreq
import math
from astropy.time import Time #convert julian date to normal time
from scipy.signal import find_peaks # try the find peaks scipy function
import healpy as hp # to plot the QUIJOTE's map
import matplotlib.ticker as ticke
from scipy.optimize import curve_fit # to fit functions
from matplotlib import cm # for color map
import os # to create directories
from scipy.interpolate import UnivariateSpline
import pandas as pd
import sys
from datetime import datetime
from astropy.coordinates import EarthLocation, AltAz, SkyCoord
from astropy import units as u
from tqdm import tqdm # print a progression bar
import time # use a timer
from datetime import datetime, timezone
from matplotlib.ticker import MultipleLocator # to sert the position of minor ticks
import matplotlib as mpl # to increase the thickness of plot frame
from scipy.optimize import minimize


def PS_oof_fct02(freq, f_s, signoise, alpha, f_knee):
    '''
    Generate the theoretical power spectrum of a 1/f noise signal.
    Same as above just I deleted the one in PS of the 1/f signal.
    - freq: frequency range of the power spectrum
    - f_s: sampling frequency of the theoretical signal
    - signoise: level of the white noise signal: fixed! 
    - alpha: slope of the 1/f noise signal
    _ f_knee: knee frequency of the 1/f noise signal
    '''
    ############################# 1) Power spectrum of the white noise
    # number of sample in my theoretical observation
    nsample = len(freq)

    # generate white noise level white std signoise
    White_noise = np.random.normal(0, signoise, nsample) #  loc, scale, size

    # Computing the one-dimensional discrete Fourier Transform of the white noise. Normalised
    White_noise_fft = direct_fft(White_noise, f_s) #direct_fft(f,f_s): np.fft.fft(White_noise) / f_sampling

    # Power spectrum of the white noise signal
    PS_white_noise = np.real(White_noise_fft  * np.conjugate(White_noise_fft)) 

    ############################# 2) Creates the correction term for the white noise which will be
    # the 1/f noise
    # array of zeros with the same size as F to be filled with the 1/f correction term
    PS_corr = freq * 0 
    
    ############################# 3) fill the array for the positiv frequencies
    #mask to select only the positiv frequency
    mask_pos = (freq > 0)

    # apply the mask to the array of frequencies
    freq_pos = freq[mask_pos]

    # define the corrrection term for the 1/f noise for positive frequencies
    PS_corr[mask_pos] = (f_knee / freq_pos) ** alpha

    ############################# 4) fill the array for the negativ frequencies
    #mask to select only the negative frequencies
    mask_neg = (freq < 0)

    # apply the mask to the array of frequencies
    freq_neg = freq[mask_neg]

    #define the corrrection term for the 1/f noise for negative frequencies
    PS_corr[mask_neg] =  (f_knee / np.abs(freq_neg)) ** alpha

    ############################# 5) fill the array for the frequencies equal zero
    # mask to select frequencies equal to zeros
    mask_zero = (freq == 0)

    #define the corrrection term for the 1/f noise for frequencies equal to zero
    PS_corr[mask_zero] = (f_knee / np.min(freq[freq > 0])) ** alpha

    ############################# 6) Power spectrum of the one over f noise signal
    # multiply the white noise level with the correction term
    PS_oof =  PS_white_noise * PS_corr

    ############################# 7) 1/f noise signal in time domain
    # fft of the 1/f noise signal to get the time domain signal
    oof_fft = White_noise_fft * np.sqrt(PS_corr)

    # then do in inverse FFT of it to obtain it in time domain
    oof_time  = np.fft.ifft(oof_fft).real * f_s

    return PS_oof, oof_time, PS_corr, White_noise_fft, White_noise

def PS_white_noise_fct(freq, f_s, signoise):
    '''
    Generate the theoretical power spectrum of a white noise signal
    - freq: frequency range of the power spectrum
    - f_s: sampling frequency of the theoretical signal
    - signoise: amplitude of the white noise level
    '''

    # number of sample in my theoretical observation
    nsample = len(freq)

    # generate white noise level white std signoise
    White_noise_time = np.random.normal(0, signoise, nsample) #  loc, scale, size

    # Computing the one-dimensional discrete Fourier Transform of the white noise. Normalised
    White_noise_fft = direct_fft(White_noise_time, f_s) #direct_fft(f,f_s): np.fft.fft(White_noise) / f_sampling

    # Power spectrum of the white noise signal
    PS_white_noise = np.real(White_noise_fft  * np.conjugate(White_noise_fft)) 

    return PS_white_noise, White_noise_time

def direct_fft(f,f_s):
    return  np.fft.fft(f) / f_s

def log_likelihood(theta, freq, CPSD, CPSD_err):
    '''
    This function computes the log-likelihood for a given set of model parameters θ in a Bayesian framework, 
    comparing the observed data (freq, CPSD, CPSD_err) to a model. 
    - theta: A tuple containing the model parameters:
    - freq: The independent variable eg frequency
    - CPSD: The observed dependent variable: cross power spectral density
    - CPSD_err: The uncertainties (errors) on the observed values of yy.
    '''
    
    #extract the model parameters
    alpha_0, freq_O, alpha, CPSD_err = theta

    # Define the model
    model = alpha_0 * (freq_O**2 + freq**2)**(-(alpha + 3/2))

    # Calculate the total variance:
    # yerr2: The squared measurement uncertainties.
    # exp(2⋅logf)=f2, which represents the fractional noise.
    sigma2 = CPSD_err**2 #+ model**2 * np.exp(2 * log_f)

    # computes and returns the log-likelihood:
    # This is based on the formula for the log of a Gaussian likelihood:
    return -0.5 * np.sum((CPSD - model) ** 2 / sigma2 + np.log(sigma2))

# step 2: Define the Negative Log-Likelihood
def nll(*args):
    '''
    Define the Negative Log-Likelihood
    Minimizing the negative log-likelihood is equivalent to maximizing the log-likelihood.
    Purpose: This function will be minimized to estimate the model parameters.
    '''
    return -log_likelihood(*args)


def fit_CPSD_flattened(freq, CPSD, CPSD_err, freq_max, alpha_0, alpha, freq_O):
    '''
    This function fit the power spectrum with a flattened spectrum
- freq: frequencies, my data for the x axis
- CPSD: cross power spectral density, my data for the y axis
- freq-Max: freq until which I want to fit
- alpha_0: initial guess for parameter alpha_0
- alpha: initial guess for parameter alpha
- freq_0: initial guess for parameter freq_0
    '''
    
    # find the index of the maximum frequency for the fitting
    # np.where returns an array with elements from x where condition is True, and elements from y elsewhere.
    idx_high = np.where(freq >= freq_max)[0][0]
    print(f'{freq[idx_high]} Hz')

    # select the data range that will be fitted
    CPSD_selected = CPSD[:idx_high]
    freq_selected = freq[:idx_high]

    CPSD_err_scalar = np.std(CPSD_err)
    
    # initial guess for the parameters
    initial = np.array([alpha_0, freq_O, alpha, CPSD_err_scalar])
    
    
    # Define bounds for the parameters
    # Each tuple corresponds to (min, max) for a parameter
    bounds = [
        (1e-11, 1e-4),  # Bounds for alpha_0
        (0.0001, 2.0),     # Bounds for freq_O
        (-6, 0),        # Bounds for alpha
        (-np.inf, np.inf)       # Bounds for CPSD_err
    ]
    
    # step 4: Minimize the Negative Log-Likelihood
    soln = minimize(nll, initial, args=(freq_selected, CPSD_selected, CPSD_err_scalar), method='L-BFGS-B', bounds=bounds)

    # Step 5: Extract the Best-Fit Parameters
    alpha_0_ml, freq_O_ml, alpha_ml, CPSD_err_ml = soln.x 

    return alpha_0_ml, freq_O_ml, alpha_ml


def flattening_CPSD(freq, alpha_0, freq_O, alpha):
    '''
    Function I want to fit to my data
    - freq: x axis
    - alpha_0, freq_O, alpha: functions parameters
    '''
    return alpha_0 * (freq_O**2 + freq**2)**(-(alpha + 3/2))

############################################################## This part generate synthetic data
# sampling frequency
f_sampling_MFI = 1/0.04 # 25 Hz

nsamp = 4000

# standard deviation for the repetition of the 1/f noise
signoise = 0.01

# standard deviation for the repetition of the white noise signal 1
signoise01 = 0.3

# standard deviation for the repetition of the white noise signal 2
signoise02 = 0.3
alpha = 2.66
f_knee = 5.0 
f_s = f_sampling_MFI

# define the frequencies for this sample size ans spacing
freq = np.fft.fftfreq(nsamp, d = 1/f_sampling_MFI)

# theoretical atmospheric 1/f noise
oof_ps, time_oof, ps_corr, white_noise_fft, white_noise = PS_oof_fct02(freq, f_sampling_MFI, signoise, alpha, f_knee)

# theoretical intrumental noise horn 2
PS_white_noise01, time_white_noise01 = PS_white_noise_fct(freq, f_sampling_MFI, signoise01)

# Power spectrum of horn 2 theoretical white noise + atmopsheric 1/f noise level
PS_h2_theo = oof_ps + PS_white_noise01



# Shift the zero-frequency component to the center of the spectrum.
freq_shf = np.fft.fftshift(freq)
PS_h2_theo_shf = np.fft.fftshift(PS_h2_theo)


plt.figure()
# PS horn 2 theo
plt.plot(freq_shf, PS_h2_theo_shf)
plt.loglog()
plt.xlabel('Freq [Hz]')
plt.ylabel('Power')


############################################################## This part tries to fit the data

# std of the CPSD std
CPSD_err_scalar = np.std(PS_h2_theo)

#initial guess for the parameter
alpha_0 = 5e-10
freq_O = 0.006
alpha = -0.30

freq_max = 1 #Hz

# In this specific example:
alpha_0_ml, freq_O_ml, alpha_ml = fit_CPSD_flattened(freq_shf, PS_h2_theo_shf, CPSD_err_scalar, freq_max, alpha_0, alpha, freq_O)

print(f'alpha_0_ml = {alpha_0_ml}')
print(f'freq_O_ml = {freq_O_ml}')
print(f'alpha_ml = {alpha_ml}')

# parameter to print in the figure
slope = alpha_ml + 3/2

plt.figure()
plt.plot(freq_shf, PS_h2_theo_shf)
# fit
plt.plot(freq_shf, flattening_CPSD(freq_shf, alpha_0_ml, freq_O_ml, alpha_ml), color='lime',
         label=f'Fit: 1/k_out = {np.round(freq_O_ml, 4)}Hz, slope = -({np.round(slope*2,  3)})')
plt.loglog()
plt.legend()
发布评论

评论列表(0)

  1. 暂无评论