У меня есть код, который предназначен для размещения данных здесь DriveFilelink
Функция read_file используется для структурированного извлечения информации. Однако я сталкиваюсь с проблемами при подгонке по Гауссу, о чем свидетельствуют несоответствия, наблюдаемые в изображении подгонки по Гауссу . Эта проблема, по-видимому, связана с ограничениями, налагаемыми на определенные параметры, например, фиксирование среднего значения всех гауссиан и трех из четырех амплитуд. Несмотря на эти ограничения, они необходимы, поскольку основаны на доступной информации.
Кто-нибудь знает, как я могу решить эту проблему? Чтобы лучше приспособиться.
Код следующий:
import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import find_peaks
from scipy.optimize import curve_fit
from google.colab import drive
import os
# Mount Google Drive
drive.mount('/content/drive')
# Function to read numeric data from a file
def read_numeric_data(file_path):
with open(file_path, 'r', encoding='latin1') as f:
data = []
live_time = None
real_time = None
for line in f:
if 'LIVE_TIME' in line:
live_time = line.strip()
elif 'REAL_TIME' in line:
real_time = line.strip()
elif all(char.isdigit() or char in ".-+e" for char in line.strip()):
row = [float(x) for x in line.split()]
data.extend(row)
return np.array(data), live_time, real_time
file_path = '/content/drive/MyDrive/ProjetoXRF_Manuel/April2024/20240402_In.mca'
data, _, _ = read_numeric_data(file_path)
a = -0.0188026396003431
b = 0.039549044037714
Data = data
# Función para convolucionar múltiples gaussianas
def multi_gaussian(x, *params):
eps = 1e-10
y = np.zeros_like(x)
amplitude_relations = [1,0.5538, 0.1673 , 0.1673*0.5185 ]
meanvalues= [24.210, 24.002 , 27.276 , 27.238]
amplitude = params[0]
for i in range(4):
sigma = params[i * 3 + 2] # Get the fitted standard deviation for this Gaussian
y += amplitude*amplitude_relations[i]* np.exp(-(x - meanvalues[i])**2 / (2 * sigma**2 + eps))
return y
sigma=[]
area=[]
# Función para graficar el espectro de energía convolucionado
def plot_convolved_spectrum(Data, a, b,i, ax=None):
maxim = np.max(Data)
workingarray = np.squeeze(Data)
# Definir puntos máximos
peaks, _ = find_peaks(workingarray / maxim, height=0.1)
peak_values = workingarray[peaks] / maxim
peak_indices = peaks
# Calcular valores de energía correspondientes a los picos
energy_spectrum = a + b * peak_indices
# Preparar datos para convolución
data = workingarray[:885] / maxim
data_y = data / data.max()
data_x = a + b * np.linspace(0, 885, num=len(data_y))
# Ajustar múltiples gaussianas al espectro de energía
peak_indices2, _ = find_peaks(data_y, height=0.1)
peak_amplitudes = [1,0.5538, 0.1673 , 0.1673*0.5185 ]
peak_means = [24.210, 24.002 , 27.276 , 27.238]
peak_sigmas = [0.1] * 4
params_init = list(zip(peak_amplitudes, peak_means, peak_sigmas))
params_init = np.concatenate(params_init)
# Ajustar curva
params, params_cov = curve_fit(multi_gaussian, data_x, data_y, p0=params_init)
# Obtener una interpolación de alta resolución del ajuste
x_fine = np.linspace(data_x.min(), data_x.max(), num=20000)
y_fit = multi_gaussian(x_fine, *params)
# Data Graphic
ax.scatter(data_x, data_y, color='black', marker='o', s=20, label = 'Data' )
y_data_error =np.sqrt(workingarray[:885])
ax.plot(data_x, data_y + y_data_error/maxim, color='black',linestyle='--')
ax.plot(data_x, data_y - y_data_error/maxim, color='black',linestyle='--')
# Fit Graphic
ax.plot(x_fine, y_fit, label = "Fit", linewidth=1.5)
# Extraer desviaciones estándar y amplitudes
sigmas_array = params[2::3]
# Calcular sigma promedio
sigma.append(np.mean(sigmas_array))
# Configuración del gráfico
ax.set_xlabel('Energy (KeV)')
ax.set_ylabel('Normalized Data')
ax.legend()
ax.set_title('Convolved Energy Spectrum')
# Imprimir información
print("Standard deviations:", sigmas_array)
fig, ax = plt.subplots()
plot_convolved_spectrum(Data, a, b,1,ax=ax)
ax.set_xlim(22, 28)
plt.show()
Примечание Я экспериментировал с установкой границ и уточнением первоначальных предположений, чтобы улучшить соответствие Гаусса. Однако важно отметить, что работа с границами не эквивалентна исправлению параметров. Моя цель — сохранить степень свободы всего двух параметров: амплитуды первой гауссианы и стандартного отклонения всех гауссиан. Изучая эти настройки, я стремлюсь найти баланс между ограничениями и гибкостью для достижения желаемой точности подгонки.
Примечание Это модель:
Параметры функции Гаусса определяются следующим образом: A представляет амплитуду, x_0 обозначает среднее значение, а сигма представляет собой стандартное отклонение. В контексте анализа энергетических спектров я обладаю предварительными знаниями о средних значениях и отношениях, определяющих амплитуды. Следовательно, я стремлюсь ограничить определенные параметры (такие как A и среднее значение) на основе этой известной информации. В частности, я намерен подогнать только первую амплитуду, а затем использовать известные соотношения, например A2 = 0,1673 * A1 для второго пика. И чтобы соответствовать соответствующему стандартному отклонению.
Зачем использовать сумму гауссиан?
Очевидная сингулярность первого пика на графике может указывать на наличие единственного гауссиана. Однако, это не так. Гауссиан, представляющий этот энергетический пик, на самом деле состоит из двух гауссиан, которые суммируются. Эта сложность возникает потому, что выброс энергии на этом уровне включает два разных значения: 24,002 и 24,210. И из-за разрешающей способности эксперимента мы не можем их отличить, просто увидев сюжет.
И по всем этим причинам я пытаюсь исправить некоторые параметры.
Если у кого-то есть проблемы с данными, вот они:
Вот MCVE, показывающий, как регрессировать переменное количество пиков из значительной части вашего сигнала.
import itertools
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy import signal, stats, optimize
Мы загружаем вам данные и подвергаем их постобработке, чтобы получить физические ряды:
data = pd.read_csv("20240402_In.mca", encoding = "latin-1", names=["y"]).loc[12:2058]
data["y"] = pd.to_numeric(data["y"])
data["y"] /= data["y"].max()
data["x"] = -0.0188026396003431 + 0.039549044037714 * data.index
data = data.loc[(20. <= data["x"]) & (data["x"] <= 30.), :]
Мы создаем модель, которая принимает переменное количество пиков (определяется решением find_peaks
):
def peak(x, A, s, x0):
law = stats.norm(loc=x0, scale=s)
return A * law.pdf(x) / law.pdf(x0)
def model(x, *parameters):
n = len(parameters) // 3
y = np.zeros_like(x)
for i in range(n):
y += peak(x, *parameters[(i * 3):((i + 1) * 3)])
return y
Выявляем вершины:
indices, bases = signal.find_peaks(data.y, prominence=0.0125)
#(array([ 67, 118, 195, 210]),
# {'prominences': array([0.01987281, 0.99920509, 0.18918919, 0.03338633]),
# 'left_bases': array([ 1, 1, 179, 205]),
# 'right_bases': array([ 76, 160, 219, 219])})
Это ключевой момент: на основе идентификации мы создаем обоснованное предположение:
x0s = data.x.values[indices]
As = bases["prominences"]
ss = (data.x.values[bases["right_bases"]] - data.x.values[bases["left_bases"]]) / 8.
p0 = list(itertools.chain(*zip(As, ss, x0s)))
#[0.01987281399046105,
# 0.37077228785356864,
# 22.682348638047493,
# 0.9992050874403816,
# 0.7860372502495658,
# 24.699349883970907,
# 0.1891891891891892,
# 0.19774522018856988,
# 27.744626274874886,
# 0.033386327503974564,
# 0.06921082706599968,
# 28.337861935440593]
Теперь мы регрессируем модель относительно вашего набора данных:
popt, pcov = optimize.curve_fit(model, data.x, data.y, p0=p0)
#array([1.03735804e-02, 9.61270732e-01, 2.29030214e+01, 9.92651381e-01,
# 1.59694755e-01, 2.46561911e+01, 1.85332645e-01, 1.21882422e-01,
# 2.77807838e+01, 3.67805911e-02, 1.21890416e-01, 2.83583849e+01])
Теперь мы можем оценить модель:
yhat = model(data.x, *popt)
Графически это приводит к:
Это вполне удовлетворительно для неограниченной подгонки, но определенно не соответствует ограничениям, которые вы хотите обеспечить.
Не могли бы вы опубликовать в своем посте свои данные, готовые для кодирования, в виде массивов?
Я признаю, что ваш код удовлетворительно подходит и решает некоторые аспекты проблемы. Однако это достигается без использования аппроксимации по Гауссу или ограничения переменных, которые были центральными в моей первоначальной концепции. Свертка гауссовских аппроксимаций является подходящей моделью для этого набора данных, поскольку это энергетический спектр. Спасибо за ваш ответ. Возможно, мы сможем придумать, как решить проблему использования ограничений для аппроксимации по Гауссу.
Добро пожаловать в СО. Подогнанные пики, безусловно, являются гауссовыми (см. документацию, чтобы убедиться в этом). Не могли бы вы обновить свой пост, указав формулу точной модели, которую вы ожидаете, в основном то, что вы подразумеваете под сверткой в вашем контексте (я не вижу никакого оператора свертки в предоставленном вами коде). Тогда нам не придется копаться в коде, чтобы понять, чего вы пытаетесь достичь. Это определенно помогло бы.
Также не могли бы вы подтвердить, что четыре пика, показанные в ответе, являются именно теми пиками, которые вас интересуют. В вашем вопросе дополнительно подчеркивается, как ограничения должны быть добавлены в вашу модель.
Чтобы уточнить, аппроксимация кривой на самом деле включает в себя сумму гауссиан, а не свернутую аппроксимацию Гаусса. Спасибо, что подняли этот вопрос. Я бы выложил модель. Насколько я понимаю, предоставленное вами решение не использует функции Гаусса, верно? Возможно, я ошибаюсь, но я так это истолковал. И еще раз спасибо за ответ
Нет, модель, которую вы видите, включает в себя сумму Гаусса с небольшой разницей в том, как вычисляется A. Я просто использую удобство scipy для их вычисления, поскольку я не справлюсь лучше, чем библиотека. То, что вы просите об ограничении, важно, поскольку оно изменит стратегию решения этой проблемы. На первый взгляд вы можете проверить регрессированные параметры и выполнить с ними некоторые арифметические действия, чтобы проверить, являются ли они допустимыми параметрами в пределах ошибок (матрица отклонений).
Я так и сделаю, добавил в пост дополнительную информацию, скажите, теперь будет легче понять.
Таким образом, большой заметный пик представляет собой сумму двух гауссовских значений с очень близкими значениями x0. И два последующих пика справа зависят от двух первых. Вот почему у вас есть 4 пика для регресса, и мы можем забыть о маленьком слева. Я правильно понимаю?
Да, это верно, и насколько близко расположены x_0, одинаково для двух других пиков. Как вы можете видеть по средним значениям. А это амплитуды и средние значения. Таким образом, фиксированные значения будут всеми средствами и этими отношениями [0,5538, 0,1673, 0,1673*0,5185]. Это будет умножено на подобранную амплитуду первого пика.
Мммм, кажется, данные, которые у меня есть, не проходят мимо x0, который вы хотите использовать в качестве ограничений. Не могли бы вы опубликовать в своем сообщении именно то подмножество, которое вы хотите разместить, правильно масштабированное как массив x и y.
Извините, я не понял эту часть: кажется, мои данные не проходят мимо x0. Вы имеете в виду, что экспериментальные значения не совпадают с теоретическими? Обратите внимание, что для значений 24,002 и 27,238 данные не предоставляют этой информации, поскольку разрешение эксперимента не позволяет различить эти пики.
Мы должны быть уверены, что работаем с одним и тем же набором данных. Моя версия масштабированного ряда из вашего файла не проходит мимо х0. Поэтому мы не работаем с одними и теми же данными. Не могли бы вы опубликовать свой набор данных, отфильтрованный и масштабированный, в виде кода в своем сообщении. Смотрите мой ответ, отредактируйте.
Я уверен, что мы работаем с одними и теми же данными.
Аааа теперь я понимаю. Первая часть моего кода предназначена для этого. Если вы знаете, в каком каталоге вы работаете, функция read_numeric_data выдает на выходе данные в виде массива. Но я могу добавить в сообщение массив из 2048 значений. Я не знаю, будет ли так же ясно, как работать с функцией, которая призвана делать это автоматически.
Я уверен, что нет, просто потому, что мы анализируем необработанный файл по-другому. Поэтому, пожалуйста, просто обновите свой пост, указав именно тот набор данных, который вы хотите разместить в виде массивов в коде.
я только что добавил текстовый файл с извлеченными данными. Но это то, что есть: я ничего не меняю в числовых значениях, когда извлекаю их с помощью функции read:numeric.
На данный момент это самый эффективный метод фиксации параметров в Python, который я обнаружил. В этом случае я использую тот же набор данных, что и раньше. Однако я подгоняю параметры только для первого гауссиана, не учитывая тот факт, что на самом деле это комбинация двух гауссиан.
Я разделил оценку на две части: приблизительную и уточненную. Уточненная аппроксимация ограничивает данные в пределах ±3 сигмы, поскольку она имеет тенденцию давать лучшую производительность.
import numpy as np
import matplotlib.pyplot as plt
from lmfit import Model
from mpl_toolkits.axes_grid1.inset_locator import zoomed_inset_axes
# Define the Gaussian function
def gaussian(x, amplitude, mean, stddev):
return amplitude * np.exp(-((x - mean) / stddev)**2 / 2)
# Data that is going to be analysed
x_data= a + b * np.linspace(0, 885, 885)
y_data =data[:885]/np.max(data[:885])
#### Bounds for the first gaussian
# Define bounds for the parameters
bounds = ([0, 24.21, 0], [1, 24.22, 2])
y_data_error =np.sqrt(data[:885])/np.max(data[:885])
# Fit the data to the Gaussian function with bounds
params, covariance = curve_fit(gaussian, x_data, y_data, bounds=bounds)
# Create the model
gaussian_model = Model(gaussian)
# Create parameters with initial values
A_value = 1.0 # Initial value for amplitude
mu_value = 24.21 # Initial value for mean
sigma_value = 1.0 # Initial value for standard deviation
params = gaussian_model.make_params(amplitude=A_value, mean=mu_value, stddev=sigma_value)
# Fix amplitude and mean
#params['amplitude'].vary = False
params['mean'].vary = False
# Fit the model to the data
result = gaussian_model.fit(y_data, params, x=x_data)
# Extract the parameters
amplitude = result.params['amplitude'].value
mean = result.params['mean'].value
stddev = result.params['stddev'].value
# Data interpolation
x_fine = np.linspace(x_data.min(), x_data.max(), num=1000)
y_fit= result.eval(x=x_fine)
ylower_limit = ( (24.21 - 3 * stddev)-a )/b
yupper_limit = ( (24.21 + 3 * stddev)-a )/b
# Selecciona los valores dentro del intervalo
y_limitado = y_data[int(ylower_limit):int(yupper_limit)]
x_limitado = a + b*np.linspace(int(ylower_limit), int(yupper_limit), 24)
ylimit_error=np.sqrt(data[int(ylower_limit):int(yupper_limit)])/np.max(data[int(ylower_limit):int(yupper_limit)])
params1, covariance1 = curve_fit(gaussian, x_limitado, y_limitado, bounds=bounds)
# Create the model
gaussian_model = Model(gaussian)
# Create parameters with initial values
A_value1 = 1.0 # Initial value for amplitude
mu_value1 = 24.21 # Initial value for mean
sigma_value1 = 1.0 # Initial value for standard deviation
params1 = gaussian_model.make_params(amplitude=A_value1, mean=mu_value1, stddev=sigma_value1,sigma=ylimit_error)
# Fix amplitude and mean
#params['amplitude'].vary = False
params1['mean'].vary = False
# Fit the model to the data
result1 = gaussian_model.fit(y_limitado, params1, x=x_limitado)
# Extract the parameters
amplitude1 = result1.params['amplitude'].value
mean1 = result1.params['mean'].value
stddev1 = result1.params['stddev'].value
# Data interpolation
x_fine1 = np.linspace(x_limitado.min(), x_limitado.max(), num=1000)
y_fit1 = result1.eval(x=x_fine1)
fig, ax = plt.subplots(figsize=(8, 6))
ax.scatter(x_limitado,y_limitado, color='black', marker='o')
ax.plot(x_fine1,y_fit1,color='blue')
ax.plot(x_fine,y_fit,color='green')
ax.errorbar(x_limitado,y_limitado,yerr=ylimit_error, fmt='o', color='red', label='errorbar')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_title('Gaussian Fit to Data')
ax.set_xlim((24.21 - 3 * stddev, 24.21 + 3 * stddev))
ax.legend()
ax.grid(True)
plt.show()
print("Amplitude:", amplitude)
print("Mean:", mean)
print("Standard Deviation:", stddev)
print("Amplitude:", amplitude1)
print("Mean:", mean1)
print("Standard Deviation:", stddev1)
Редактировать
Теперь у меня есть возможность зафиксировать больше параметров, что позволяет мне точно настроить подгонку суммы первых двух гауссиан. Однако меня озадачивает наличие дополнительного пика в посадке. Я не уверен в его происхождении.
import numpy as np
import matplotlib.pyplot as plt
from lmfit import Model
from scipy.optimize import curve_fit
# Define Gaussian function
def gaussian(x, amplitude, mean, stddev):
return amplitude * np.exp(-((x - mean) / stddev)**2 / 2)
# Generate x values for the data
x_data = a + b * np.linspace(0, 885, 885)
# Normalize y data
y_data = data[:885] / np.max(data[:885])
y_data_error = np.sqrt(data[:885]) / np.max(data[:885])
# Fit first Gaussian
bounds = ([1 - 0.0001, 24.21, 0], [1, 24.22, 2]) # Define bounds for the parameters
params, covariance = curve_fit(gaussian, x_data, y_data, bounds=bounds) # Fit Gaussian to data
# Create the model
gaussian_model = Model(gaussian)
# Initial values for parameters
A_value = 1.0
mu_value = 24.21
sigma_value = 1.0
params = gaussian_model.make_params(amplitude=A_value, mean=mu_value, stddev=sigma_value)
params['mean'].vary = False # Fix mean parameter
# Fit the model to the data
result = gaussian_model.fit(y_data, params, x=x_data)
# Extract parameters
amplitude = result.params['amplitude'].value
mean = result.params['mean'].value
stddev = result.params['stddev'].value
# Data interpolation
x_fine = np.linspace(x_data.min(), x_data.max(), num=1000)
y_fit = result.eval(x=x_fine)
# Determine y limits for data subset
ylower_limit = ((24.21 - 3 * stddev) - a) / b
yupper_limit = ((24.21 + 3 * stddev) - a) / b
# Select subset of data within the limits
y_limitado = y_data[int(ylower_limit):int(yupper_limit)]
x_limitado = a + b * np.linspace(int(ylower_limit), int(yupper_limit), 24)
ylimit_error = np.sqrt(data[int(ylower_limit):int(yupper_limit)]) / np.max(data[int(ylower_limit):int(yupper_limit)])
# Constrained fit
bounds_sum = ([0, 24.21, 0, 0, 24.0, 0], [1, 24.22, 1, 1, 24.1, 1]) # Bounds for parameters
# Define function for sum of Gaussians
def sum_of_gaussians(x, amplitude1, mean1, stddev1, amplitude2, mean2, stddev2):
gaussian1 = amplitude1 * np.exp(-((x - mean1) / stddev1)**2 / 2)
gaussian2 = amplitude2 * np.exp(-((x - mean2) / stddev2)**2 / 2)
return gaussian1 + gaussian2
# Fit the sum of Gaussians to the subset of data
params1, covariance1 = curve_fit(sum_of_gaussians, x_limitado, y_limitado, bounds=bounds_sum, sigma=ylimit_error)
# Create the model
multigaussian_model = Model(sum_of_gaussians)
# Initial values for parameters
A_value1 = amplitude
mu_value1 = 24.22
sigma_value1 = 0.1
A_value2 = amplitude * 0.5388
mu_value2 = 24.002
sigma_value2 = 0.1
# Set parameters for the model
params1 = multigaussian_model.make_params(amplitude1=A_value1, mean1=mu_value1, stddev1=sigma_value1,
amplitude2=A_value2, mean2=mu_value2, stddev2=sigma_value2, sigma=ylimit_error)
# Fix certain parameters
params1['amplitude1'].vary = False
params1['mean1'].vary = False
params1['amplitude2'].vary = False
params1['mean2'].vary = False
# Fit the model to the data
result1 = multigaussian_model.fit(y_limitado, params1, x=x_limitado)
# Extract parameters
amplitude1 = result1.params['amplitude1'].value
mean1 = result1.params['mean1'].value
stddev1 = result1.params['stddev1'].value
amplitude2 = result1.params['amplitude2'].value
mean2 = result1.params['mean2'].value
stddev2 = result1.params['stddev2'].value
# Data interpolation
x_fine1 = np.linspace(x_limitado.min(), x_limitado.max(), num=1000)
y_fit1 = result1.eval(x=x_fine1)
# Individual Gaussians
x = np.linspace(min(mean1 - 3 * stddev1, mean2 - 3 * stddev2),
max(mean1 + 3 * stddev1, mean2 + 3 * stddev2), 1000)
y1 = gaussian(x, amplitude1, mean1, stddev1)
y2 = gaussian(x, amplitude2, mean2, stddev2)
# Plotting
fig, ax = plt.subplots(figsize=(8, 6))
ax.scatter(x_limitado, y_limitado, color='black', marker='o')
ax.plot(x_fine1, y_fit1, color='blue')
ax.errorbar(x_limitado, y_limitado, yerr=ylimit_error, fmt='o', color='red', label='errorbar')
ax.plot(x, y1, label='Gaussian 1')
ax.plot(x, y2, label='Gaussian 2')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_title('Gaussian Fit to Data')
ax.set_xlim((24.21 - 3 * stddev, 24.21 + 3 * stddev))
ax.legend()
ax.grid(True)
plt.show()
# Print parameters
print("Amplitude1:", amplitude1)
print("Mean1:", mean1)
print("Standard Deviation1:", stddev1)
print("")
print("Amplitude2:", amplitude2)
print("Mean2:", mean2)
print("Standard Deviation2:", stddev2)
Сумма гауссиан известна как «модель гауссовой смеси». Здесь много глубокой теории, и вы можете быстро получить MLE с помощью алгоритмов максимизации ожидания. Наверное, лучше не реализовывать самостоятельно, используйте sklearn