迭代拟合多项式曲线
Iteratively fitting polynomial curve
我想使用以下方法将曲线迭代地拟合到 python 中的数据:
- 拟合多项式曲线(或任何非线性方法)
- 舍弃值 > 与曲线平均值的 2 个标准差
- 重复步骤 1 和 2,直到所有值都在曲线的置信区间内
我可以如下拟合多项式曲线:
vals = array([0.00441025, 0.0049001 , 0.01041189, 0.47368389, 0.34841961,
0.3487533 , 0.35067096, 0.31142986, 0.3268407 , 0.38099566,
0.3933048 , 0.3479948 , 0.02359819, 0.36329588, 0.42535543,
0.01308297, 0.53873956, 0.6511364 , 0.61865282, 0.64750302,
0.6630047 , 0.66744816, 0.71759617, 0.05965622, 0.71335208,
0.71992683, 0.61635697, 0.12985441, 0.73410642, 0.77318621,
0.75675988, 0.03003641, 0.77527201, 0.78673995, 0.05049178,
0.55139476, 0.02665514, 0.61664748, 0.81121749, 0.05521697,
0.63404375, 0.32649395, 0.36828268, 0.68981099, 0.02874863,
0.61574739])
x_values = np.linspace(0, 1, len(vals))
poly_degree = 3
coeffs = np.polyfit(x_values, vals, poly_degree)
poly_eqn = np.poly1d(coeffs)
y_hat = poly_eqn(x_values)
如何执行第 2 步和第 3 步?
按照该过程您似乎不会得到任何有价值的东西,有更好的技术来处理意外数据。谷歌搜索 "outlier detection" 将是一个好的开始。
话虽如此,以下是回答您问题的方法:
首先引入库并获取一些数据:
import matplotlib.pyplot as plt
import numpy as np
Y = np.array([
0.00441025, 0.0049001 , 0.01041189, 0.47368389, 0.34841961,
0.3487533 , 0.35067096, 0.31142986, 0.3268407 , 0.38099566,
0.3933048 , 0.3479948 , 0.02359819, 0.36329588, 0.42535543,
0.01308297, 0.53873956, 0.6511364 , 0.61865282, 0.64750302,
0.6630047 , 0.66744816, 0.71759617, 0.05965622, 0.71335208,
0.71992683, 0.61635697, 0.12985441, 0.73410642, 0.77318621,
0.75675988, 0.03003641, 0.77527201, 0.78673995, 0.05049178,
0.55139476, 0.02665514, 0.61664748, 0.81121749, 0.05521697,
0.63404375, 0.32649395, 0.36828268, 0.68981099, 0.02874863,
0.61574739])
X = np.linspace(0, 1, len(Y))
接下来绘制数据的初始图:
plt.plot(X, Y, '.')
因为这可以让您了解我们正在处理的内容以及多项式是否适合——简短的回答是这种方法不会对这类数据走得太远
此时我们应该停下来,但为了回答我将继续的问题,主要遵循您的多项式拟合代码:
poly_degree = 5
sd_cutoff = 1 # 2 keeps everything
coeffs = np.polyfit(X, Y, poly_degree)
poly_eqn = np.poly1d(coeffs)
Y_hat = poly_eqn(X)
delta = Y - Y_hat
sd_p = np.std(delta)
ok = abs(delta) < sd_p * sd_cutoff
希望这是有道理的,我使用更高阶的多项式并且只在 1SD 处截止,否则什么都不会被丢弃。 ok
数组包含在 sd_cutoff
标准差
范围内的那些点的 True
值
为了检查这一点,我会再做一个情节。类似于:
plt.scatter(X, Y, color=np.where(ok, 'k', 'r'))
plt.fill_between(
X,
Y_hat - sd_p * sd_cutoff,
Y_hat + sd_p * sd_cutoff,
color='#00000020')
plt.plot(X, Y_hat)
这给了我:
所以黑点是要保留的点(即 X[ok]
把这些还给我,np.where(ok)
给你标记)。
你可以玩弄参数,但你可能想要一个尾部较粗的分布(例如学生 T 分布),但是,正如我上面所说,我的建议是使用 Google 进行离群值检测
由于消除点距离预期的解决方案太远,您可能正在寻找 RANSAC (RANdom SAmple Consensus), which is fitting a curve(或任何其他函数)以获取特定范围内的数据,例如 2*STD 的情况。
您可以使用 scikit-learn RANSAC estimator which is well aligned with included regressors such as LinearRegression。对于您的多项式案例,您需要定义自己的回归 class:
from sklearn.metrics import mean_squared_error
class PolynomialRegression(object):
def __init__(self, degree=3, coeffs=None):
self.degree = degree
self.coeffs = coeffs
def fit(self, X, y):
self.coeffs = np.polyfit(X.ravel(), y, self.degree)
def get_params(self, deep=False):
return {'coeffs': self.coeffs}
def set_params(self, coeffs=None, random_state=None):
self.coeffs = coeffs
def predict(self, X):
poly_eqn = np.poly1d(self.coeffs)
y_hat = poly_eqn(X.ravel())
return y_hat
def score(self, X, y):
return mean_squared_error(y, self.predict(X))
然后你就可以使用 RANSAC
from sklearn.linear_model import RANSACRegressor
ransac = RANSACRegressor(PolynomialRegression(degree=poly_degree),
residual_threshold=2 * np.std(y_vals),
random_state=0)
ransac.fit(np.expand_dims(x_vals, axis=1), y_vals)
inlier_mask = ransac.inlier_mask_
注意,X 变量被转换为二维数组,因为它是 sklearn RANSAC 实现所需要的,并且在我们的自定义中 class 由于 numpy polyfit 函数适用于一维数组,因此变平。
y_hat = ransac.predict(np.expand_dims(x_vals, axis=1))
plt.plot(x_vals, y_vals, 'bx', label='input samples')
plt.plot(x_vals[inlier_mask], y_vals[inlier_mask], 'go', label='inliers (2*STD)')
plt.plot(x_vals, y_hat, 'r-', label='estimated curve')
此外,使用多项式阶数和剩余距离,我得到以下结果,度数 = 4,范围 1*STD
另一种选择是使用高阶回归量,例如 Gaussian process
from sklearn.gaussian_process import GaussianProcessRegressor
ransac = RANSACRegressor(GaussianProcessRegressor(),
residual_threshold=np.std(y_vals))
谈到泛化到 DataFrame,你只需要设置除一列之外的所有列都是特征,剩下的一列是输出,就像这里:
import pandas as pd
df = pd.DataFrame(np.array([x_vals, y_vals]).T)
ransac.fit(df[df.columns[:-1]], df[df.columns[-1]])
y_hat = ransac.predict(df[df.columns[:-1]])
需要三个函数来解决这个问题。首先需要一个线拟合函数来将一条线拟合到一组点:
def fit_line(x_values, vals, poly_degree):
coeffs = np.polyfit(x_values, vals, poly_degree)
poly_eqn = np.poly1d(coeffs)
y_hat = poly_eqn(x_values)
return poly_eqn, y_hat
我们需要知道点到直线的标准差。此函数计算标准偏差:
def compute_sd(x_values, vals, y_hat):
distances = []
for x,y, y1 in zip(x_values, vals, y_hat): distances.append(abs(y - y1))
return np.std(distances)
最后,我们需要比较点到线的距离。如果点到直线的距离大于标准差的两倍,则该点需要被抛出。
def compare_distances(x_values, vals):
new_vals, new_x_vals = [],[]
for x,y in zip(x_values, vals):
y1 = np.polyval(poly_eqn, x)
distance = abs(y - y1)
if distance < 2*sd:
plt.plot((x,x),(y,y1), c='g')
new_vals.append(y)
new_x_vals.append(x)
else:
plt.plot((x,x),(y,y1), c='r')
plt.scatter(x,y, c='r')
return new_vals, new_x_vals
正如您在下图中所看到的,此方法不适用于将线拟合到具有大量异常值的数据。所有的点最终都因为离拟合线太远而被淘汰。
while len(vals)>0:
poly_eqn, y_hat = fit_line(x_values, vals, poly_degree)
plt.scatter(x_values, vals)
plt.plot(x_values, y_hat)
sd = compute_sd(x_values, vals, y_hat)
new_vals, new_x_vals = compare_distances(x_values, vals)
plt.show()
vals, x_values = np.array(new_vals), np.array(new_x_vals)
我想使用以下方法将曲线迭代地拟合到 python 中的数据:
- 拟合多项式曲线(或任何非线性方法)
- 舍弃值 > 与曲线平均值的 2 个标准差
- 重复步骤 1 和 2,直到所有值都在曲线的置信区间内
我可以如下拟合多项式曲线:
vals = array([0.00441025, 0.0049001 , 0.01041189, 0.47368389, 0.34841961,
0.3487533 , 0.35067096, 0.31142986, 0.3268407 , 0.38099566,
0.3933048 , 0.3479948 , 0.02359819, 0.36329588, 0.42535543,
0.01308297, 0.53873956, 0.6511364 , 0.61865282, 0.64750302,
0.6630047 , 0.66744816, 0.71759617, 0.05965622, 0.71335208,
0.71992683, 0.61635697, 0.12985441, 0.73410642, 0.77318621,
0.75675988, 0.03003641, 0.77527201, 0.78673995, 0.05049178,
0.55139476, 0.02665514, 0.61664748, 0.81121749, 0.05521697,
0.63404375, 0.32649395, 0.36828268, 0.68981099, 0.02874863,
0.61574739])
x_values = np.linspace(0, 1, len(vals))
poly_degree = 3
coeffs = np.polyfit(x_values, vals, poly_degree)
poly_eqn = np.poly1d(coeffs)
y_hat = poly_eqn(x_values)
如何执行第 2 步和第 3 步?
按照该过程您似乎不会得到任何有价值的东西,有更好的技术来处理意外数据。谷歌搜索 "outlier detection" 将是一个好的开始。
话虽如此,以下是回答您问题的方法:
首先引入库并获取一些数据:
import matplotlib.pyplot as plt
import numpy as np
Y = np.array([
0.00441025, 0.0049001 , 0.01041189, 0.47368389, 0.34841961,
0.3487533 , 0.35067096, 0.31142986, 0.3268407 , 0.38099566,
0.3933048 , 0.3479948 , 0.02359819, 0.36329588, 0.42535543,
0.01308297, 0.53873956, 0.6511364 , 0.61865282, 0.64750302,
0.6630047 , 0.66744816, 0.71759617, 0.05965622, 0.71335208,
0.71992683, 0.61635697, 0.12985441, 0.73410642, 0.77318621,
0.75675988, 0.03003641, 0.77527201, 0.78673995, 0.05049178,
0.55139476, 0.02665514, 0.61664748, 0.81121749, 0.05521697,
0.63404375, 0.32649395, 0.36828268, 0.68981099, 0.02874863,
0.61574739])
X = np.linspace(0, 1, len(Y))
接下来绘制数据的初始图:
plt.plot(X, Y, '.')
因为这可以让您了解我们正在处理的内容以及多项式是否适合——简短的回答是这种方法不会对这类数据走得太远
此时我们应该停下来,但为了回答我将继续的问题,主要遵循您的多项式拟合代码:
poly_degree = 5
sd_cutoff = 1 # 2 keeps everything
coeffs = np.polyfit(X, Y, poly_degree)
poly_eqn = np.poly1d(coeffs)
Y_hat = poly_eqn(X)
delta = Y - Y_hat
sd_p = np.std(delta)
ok = abs(delta) < sd_p * sd_cutoff
希望这是有道理的,我使用更高阶的多项式并且只在 1SD 处截止,否则什么都不会被丢弃。 ok
数组包含在 sd_cutoff
标准差
True
值
为了检查这一点,我会再做一个情节。类似于:
plt.scatter(X, Y, color=np.where(ok, 'k', 'r'))
plt.fill_between(
X,
Y_hat - sd_p * sd_cutoff,
Y_hat + sd_p * sd_cutoff,
color='#00000020')
plt.plot(X, Y_hat)
这给了我:
所以黑点是要保留的点(即 X[ok]
把这些还给我,np.where(ok)
给你标记)。
你可以玩弄参数,但你可能想要一个尾部较粗的分布(例如学生 T 分布),但是,正如我上面所说,我的建议是使用 Google 进行离群值检测
由于消除点距离预期的解决方案太远,您可能正在寻找 RANSAC (RANdom SAmple Consensus), which is fitting a curve(或任何其他函数)以获取特定范围内的数据,例如 2*STD 的情况。
您可以使用 scikit-learn RANSAC estimator which is well aligned with included regressors such as LinearRegression。对于您的多项式案例,您需要定义自己的回归 class:
from sklearn.metrics import mean_squared_error
class PolynomialRegression(object):
def __init__(self, degree=3, coeffs=None):
self.degree = degree
self.coeffs = coeffs
def fit(self, X, y):
self.coeffs = np.polyfit(X.ravel(), y, self.degree)
def get_params(self, deep=False):
return {'coeffs': self.coeffs}
def set_params(self, coeffs=None, random_state=None):
self.coeffs = coeffs
def predict(self, X):
poly_eqn = np.poly1d(self.coeffs)
y_hat = poly_eqn(X.ravel())
return y_hat
def score(self, X, y):
return mean_squared_error(y, self.predict(X))
然后你就可以使用 RANSAC
from sklearn.linear_model import RANSACRegressor
ransac = RANSACRegressor(PolynomialRegression(degree=poly_degree),
residual_threshold=2 * np.std(y_vals),
random_state=0)
ransac.fit(np.expand_dims(x_vals, axis=1), y_vals)
inlier_mask = ransac.inlier_mask_
注意,X 变量被转换为二维数组,因为它是 sklearn RANSAC 实现所需要的,并且在我们的自定义中 class 由于 numpy polyfit 函数适用于一维数组,因此变平。
y_hat = ransac.predict(np.expand_dims(x_vals, axis=1))
plt.plot(x_vals, y_vals, 'bx', label='input samples')
plt.plot(x_vals[inlier_mask], y_vals[inlier_mask], 'go', label='inliers (2*STD)')
plt.plot(x_vals, y_hat, 'r-', label='estimated curve')
此外,使用多项式阶数和剩余距离,我得到以下结果,度数 = 4,范围 1*STD
另一种选择是使用高阶回归量,例如 Gaussian process
from sklearn.gaussian_process import GaussianProcessRegressor
ransac = RANSACRegressor(GaussianProcessRegressor(),
residual_threshold=np.std(y_vals))
谈到泛化到 DataFrame,你只需要设置除一列之外的所有列都是特征,剩下的一列是输出,就像这里:
import pandas as pd
df = pd.DataFrame(np.array([x_vals, y_vals]).T)
ransac.fit(df[df.columns[:-1]], df[df.columns[-1]])
y_hat = ransac.predict(df[df.columns[:-1]])
需要三个函数来解决这个问题。首先需要一个线拟合函数来将一条线拟合到一组点:
def fit_line(x_values, vals, poly_degree):
coeffs = np.polyfit(x_values, vals, poly_degree)
poly_eqn = np.poly1d(coeffs)
y_hat = poly_eqn(x_values)
return poly_eqn, y_hat
我们需要知道点到直线的标准差。此函数计算标准偏差:
def compute_sd(x_values, vals, y_hat):
distances = []
for x,y, y1 in zip(x_values, vals, y_hat): distances.append(abs(y - y1))
return np.std(distances)
最后,我们需要比较点到线的距离。如果点到直线的距离大于标准差的两倍,则该点需要被抛出。
def compare_distances(x_values, vals):
new_vals, new_x_vals = [],[]
for x,y in zip(x_values, vals):
y1 = np.polyval(poly_eqn, x)
distance = abs(y - y1)
if distance < 2*sd:
plt.plot((x,x),(y,y1), c='g')
new_vals.append(y)
new_x_vals.append(x)
else:
plt.plot((x,x),(y,y1), c='r')
plt.scatter(x,y, c='r')
return new_vals, new_x_vals
正如您在下图中所看到的,此方法不适用于将线拟合到具有大量异常值的数据。所有的点最终都因为离拟合线太远而被淘汰。
while len(vals)>0:
poly_eqn, y_hat = fit_line(x_values, vals, poly_degree)
plt.scatter(x_values, vals)
plt.plot(x_values, y_hat)
sd = compute_sd(x_values, vals, y_hat)
new_vals, new_x_vals = compare_distances(x_values, vals)
plt.show()
vals, x_values = np.array(new_vals), np.array(new_x_vals)