scipy 奇怪的意外行为 curve_fit 正弦波的大数据集

scipy weird unexpected behavior curve_fit large data set for sin wave

出于某种原因,当我尝试将大量数据转换为正弦波时,它失败了,并使其适合水平线。有人可以解释一下吗?

最小工作代码:

import numpy as np
import matplotlib.pyplot as plt
from scipy import optimize
# Seed the random number generator for reproducibility
import pandas

np.random.seed(0)

# Here it work as expected
# x_data = np.linspace(-5, 5, num=50)
# y_data = 2.9 * np.sin(1.05 * x_data + 2) + 250 + np.random.normal(size=50)

# With this data it breaks
x_data = np.linspace(0, 2500, num=2500)
y_data = -100 * np.sin(0.01 * x_data + 1) + 250 + np.random.normal(size=2500)

# And plot it

plt.figure(figsize=(6, 4))
plt.scatter(x_data, y_data)


def test_func(x, a, b, c, d):
    return a * np.sin(b * x + c) + d

# Used to fit the correct function
# params, params_covariance = optimize.curve_fit(test_func, x_data, y_data)

# making some guesses
params, params_covariance = optimize.curve_fit(test_func, x_data, y_data,
                                               p0=[-80, 3, 0, 260])

print(params)
plt.figure(figsize=(6, 4))
plt.scatter(x_data, y_data, label='Data')
plt.plot(x_data, test_func(x_data, *params),
         label='Fitted function')

plt.legend(loc='best')

plt.show()

有谁知道如何解决这个问题。我应该使用不同的拟合方法而不是最小二乘法吗?或者我应该减少数据点的数量?

鉴于您的数据,您可以使用更可靠的 lmfit 而不是 scipy

特别是,您可以使用 SineModel(有关详细信息,请参阅 here)。

lmfit 中的

SineModel 不适用于“移位”正弦波,但您可以轻松处理移位

y_data_offset = y_data.mean()
y_transformed = y_data - y_data_offset
plt.scatter(x_data, y_transformed)
plt.axhline(0, color='r')

现在可以拟合正弦波了

from lmfit.models import SineModel

mod = SineModel()

pars = mod.guess(y_transformed, x=x_data)
out = mod.fit(y_transformed, pars, x=x_data)

您可以使用 print(out.fit_report()) 检查结果并使用

绘制结果
plt.plot(x_data, y_data, lw=7, color='C1')
plt.plot(x_data, out.best_fit+y_data_offset, color='k')
#           we add the offset ^^^^^^^^^^^^^

或使用内置绘图方法 out.plot_fit(),请参阅 here 了解详细信息。

请注意,在 SineModel 中,所有参数“都被限制为非负数”,因此您定义的负幅度 (-100) 在参数拟合结果中将为正 (+100)。所以相位也不会是 1,而是 π+1(PS:他们称 shift 相位)

print(out.best_values)

{'amplitude': 99.99631403054289,
 'frequency': 0.010001193681616227,
 'shift': 4.1400215410836605}