Python 如何将分段(线性段和常数段交替)函数拟合到抛物线函数?
例如,我有一个函数,但也可以是其他函数,比如二次函数或对数函数。我只对这个领域感兴趣。函数的参数(本例中为a和k)也是已知的 我的目标是将一个连续分段函数与此相匹配,其中包含线性函数的交替段(即倾斜直线段,每个段的截距为0)和常数(即将倾斜段连接在一起的水平段)。第一段和最后一段都是倾斜的。分段数量应在9-29之间预先选择(即5-15个线性台阶+4-14个恒定高原) 正式Python 如何将分段(线性段和常数段交替)函数拟合到抛物线函数?,python,algorithm,math,curve-fitting,approximation,Python,Algorithm,Math,Curve Fitting,Approximation,例如,我有一个函数,但也可以是其他函数,比如二次函数或对数函数。我只对这个领域感兴趣。函数的参数(本例中为a和k)也是已知的 我的目标是将一个连续分段函数与此相匹配,其中包含线性函数的交替段(即倾斜直线段,每个段的截距为0)和常数(即将倾斜段连接在一起的水平段)。第一段和最后一段都是倾斜的。分段数量应在9-29之间预先选择(即5-15个线性台阶+4-14个恒定高原) 正式 输入功能: 拟合分段函数: 如果事先指定了段号(n),我将寻找最佳结果参数(c、r、b)(以最小二乘法表示)。
- 输入功能:
- 拟合分段函数:
import numpy as np
import matplotlib.pyplot as plt
import pwlf
# The input function
def input_func(x,k,a):
return np.power(x,1/a)*k
x = np.arange(1,5e4)
y = input_func(x, 1.8, 1.3)
plt.plot(x,y);
def pw_fit(函数、x_r、无seg、*fparams):
#在指定范围内工作
x=np.arange(1,x_r)
y_输入=func(x,*fparams)
my_pwlf=pwlf.分段线性拟合(x,y_输入,度=0)
res=我的自我适合度(无分段)
yHat=my_pwlf.predict(x)
#断点处的函数值
y_isec=func(res,*fparams)
#断点处的坡度值
斜率=np.四舍五入(y_isec/res,小数=2)
坡度=坡度[1:]
#对于第一个坡度值,我使用第一个恒定平台和输入函数的交点
slopes=np.insert(slopes,0,np.round(y_输入[np.argwhere(np.diff(np.sign(y_输入-yHat))).flatten()[0]/np.argwhere(np.diff(np.sign(y_输入-yHat)).flatten()[0],小数=2))
高原=np.唯一(np.圆形(yHat))
#如果由于舍入斜率值(到两个小数),在后续步骤中没有变化,我只删除这些段
to_del=np.argwhere(np.diff(slopes)==0).flatten()
坡度=np.删除(坡度,至+1)
高原=np.删除(高原,至删除)
断点=[np.ceil(高原[0]/坡度[0])]
对于idx,枚举中的j(斜率[1:-1]):
断点追加(np.floor(高原[idx]/j))
断点.append(np.ceil(平台[idx+1]/j))
附加断点(np.楼层(高原[-1]/坡度[-1]))
返回坡度、高原、断点
slo、平台、断裂=pw_拟合(输入函数,50000,8,1.8,1.3)
#分段函数本身
def pw_计算(x、坡度、高原、断裂):
x=x.astype('float')
条件列表=[x<中断[0]]
对于idx,枚举中的j(中断符[:-1]):
cond_list.append((j)(不重要,但我认为拟合的分段函数不是连续的。间隔应该是x。我们可以将线性和常量片段的平方误差函数进行集成,并让SciPy对其进行优化。Python 3:
import matplotlib.pyplot as plt
import numpy as np
import scipy.optimize
xl = 1
xh = 50000
a = 1.3
p = 1 / a
n = 8
def split_b_and_c(bc):
return bc[::2], bc[1::2]
def solve_for_r(b, c):
r = np.empty(2 * n)
r[0] = xl
r[1:-1:2] = c / b[:-1]
r[2::2] = c / b[1:]
r[-1] = xh
return r
def linear_residual_integral(b, x):
return (
(x ** (2 * p + 1)) / (2 * p + 1)
- 2 * b * x ** (p + 2) / (p + 2)
+ b ** 2 * x ** 3 / 3
)
def constant_residual_integral(c, x):
return x ** (2 * p + 1) / (2 * p + 1) - 2 * c * x ** (p + 1) / (p + 1) + c ** 2 * x
def squared_error(bc):
b, c = split_b_and_c(bc)
r = solve_for_r(b, c)
linear = np.sum(
linear_residual_integral(b, r[1::2]) - linear_residual_integral(b, r[::2])
)
constant = np.sum(
constant_residual_integral(c, r[2::2])
- constant_residual_integral(c, r[1:-1:2])
)
return linear + constant
def evaluate(x, b, c, r):
i = 0
while x > r[i + 1]:
i += 1
return b[i // 2] * x if i % 2 == 0 else c[i // 2]
def main():
bc0 = (xl + (xh - xl) * np.arange(1, 4 * n - 2, 2) / (4 * n - 2)) ** (
p - 1 + np.arange(2 * n - 1) % 2
)
bc = scipy.optimize.minimize(
squared_error, bc0, bounds=[(1e-06, None) for i in range(2 * n - 1)]
).x
b, c = split_b_and_c(bc)
r = solve_for_r(b, c)
X = np.linspace(xl, xh, 1000)
Y = [evaluate(x, b, c, r) for x in X]
plt.plot(X, X ** p)
plt.plot(X, Y)
plt.show()
if __name__ == "__main__":
main()
我自己也试着提出了一个新的解决方案,基于这样的想法,我已经划分了域,曲线拟合了一个双常数和线性段(在np.maximum的帮助下)。我使用了1/f(x)'作为指定断点的函数,但我知道这是任意的,并且不提供全局优化。可能有一些用于这些断点的优化函数。但是这个解决方案对我来说是可行的,因为它可能适合在第一个段进行更好的拟合,而以牺牲后面段的错误为代价。(任务本身实际上是基于成本的零售利润计算{供应价格->增加利润},因为零售POS软件只能使用这种分段利润功能)。
如果允许参数为浮点数,则得出的答案是正确的最优解。不幸的是,POS软件不能使用浮点数。之后可以将c-s和r-s四舍五入。但b-s应四舍五入到两位小数,因为这些小数被输入为百分比,此约束将破坏具有长浮点数的最优解。我将尝试to通过Amo和David的宝贵意见进一步改进我的解决方案。谢谢
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
# The input function f(x)
def input_func(x,k,a):
return np.power(x,1/a) * k
# 1 / f(x)'
def one_per_der(x,k,a):
return a / (k * np.power(x, 1/a-1))
# 1 / f(x)' inverted
def one_per_der_inv(x,k,a):
return np.power(a / (x*k), a / (1-a))
def segment_fit(start,end,y,first_val):
b, _ = curve_fit(lambda x,b: np.maximum(first_val, b*x), np.arange(start,end), y[start-1:end-1])
b = float(np.round(b, decimals=2))
bp = np.round(first_val / b)
last_val = np.round(b * end)
return b, bp, last_val
def pw_fit(end_range, no_seg, **fparams):
y_bps = np.linspace(one_per_der(1, **fparams), one_per_der(end_range,**fparams) , no_seg+1)[1:]
x_bps = np.round(one_per_der_inv(y_bps, **fparams))
y = input_func(x, **fparams)
slopes = [np.round(float(curve_fit(lambda x,b: x * b, np.arange(1,x_bps[0]), y[:int(x_bps[0])-1])[0]), decimals = 2)]
plats = [np.round(x_bps[0] * slopes[0])]
bps = []
for i, xbp in enumerate(x_bps[1:]):
b, bp, last_val = segment_fit(int(x_bps[i]+1), int(xbp), y, plats[i])
slopes.append(b); bps.append(bp); plats.append(last_val)
breaks = sorted(list(x_bps) + bps)[:-1]
# If due to rounding slope values (to two decimals), there is no change in a subsequent step, I just remove those segments
to_del = np.argwhere(np.diff(slopes) == 0).flatten()
breaks_to_del = np.concatenate((to_del * 2, to_del * 2 + 1))
slopes = np.delete(slopes,to_del + 1)
plats = np.delete(plats[:-1],to_del)
breaks = np.delete(breaks,breaks_to_del)
return slopes, plats, breaks
def pw_calc(x, slopes, plateaus, breaks):
x = x.astype('float')
cond_list = [x < breaks[0]]
for idx, j in enumerate(breaks[:-1]):
cond_list.append((j <= x) & (x < breaks[idx+1]))
cond_list.append(breaks[-1] <= x)
func_list = [lambda x: x * slopes[0]]
for idx, j in enumerate(slopes[1:]):
func_list.append(plateaus[idx])
func_list.append(lambda x, j=j: x * j)
return np.piecewise(x, cond_list, func_list)
fparams = {'k':1.8, 'a':1.2}
end_range = 5e4
no_steps = 10
x = np.arange(1, end_range)
y = input_func(x, **fparams)
slopes, plats, breaks = pw_fit(end_range, no_steps, **fparams)
y_output = pw_calc(x, slopes, plats, breaks)
plt.plot(x,y_output,y);
将numpy导入为np
将matplotlib.pyplot作为plt导入
从scipy.optimize导入曲线\u拟合
#输入函数f(x)
def输入函数(x,k,a):
返回np.功率(x,1/a)*k
#一楼(x)'
定义一个(x,k,a):
返回a/(k*np.幂(x,1/a-1))
#1/f(x)“倒置”
定义每个库存的一个库存(x、k、a):
返回np.幂(a/(x*k),a/(1-a))
def段拟合(开始、结束、y、首个值):
b、 曲线拟合(λx,b:np.最大值(第一个值,b*x),np.arange(开始,结束),y[start-1:end-1])
b=浮点(np.四舍五入(b,小数=2))
bp=np.四舍五入(第一值)
最后一轮=np轮(b*结束)
返回b,bp,最后一个值
def pw_配合(端部范围、无seg、**F参数):
y_bps=np.linspace(1,**fparams),1_/der(end_range,**fparams),无seg+1)[1:]
x_bps=np.轮(每个存货一个(y_bps,**fparams))
y=输入函数(x,**F参数)
斜率=[np.圆形(浮点(曲线拟合(λx,b:x*b,np.arange(1,x_bps[0]),y[:int(x_bps[0])-1]),小数
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
# The input function f(x)
def input_func(x,k,a):
return np.power(x,1/a) * k
# 1 / f(x)'
def one_per_der(x,k,a):
return a / (k * np.power(x, 1/a-1))
# 1 / f(x)' inverted
def one_per_der_inv(x,k,a):
return np.power(a / (x*k), a / (1-a))
def segment_fit(start,end,y,first_val):
b, _ = curve_fit(lambda x,b: np.maximum(first_val, b*x), np.arange(start,end), y[start-1:end-1])
b = float(np.round(b, decimals=2))
bp = np.round(first_val / b)
last_val = np.round(b * end)
return b, bp, last_val
def pw_fit(end_range, no_seg, **fparams):
y_bps = np.linspace(one_per_der(1, **fparams), one_per_der(end_range,**fparams) , no_seg+1)[1:]
x_bps = np.round(one_per_der_inv(y_bps, **fparams))
y = input_func(x, **fparams)
slopes = [np.round(float(curve_fit(lambda x,b: x * b, np.arange(1,x_bps[0]), y[:int(x_bps[0])-1])[0]), decimals = 2)]
plats = [np.round(x_bps[0] * slopes[0])]
bps = []
for i, xbp in enumerate(x_bps[1:]):
b, bp, last_val = segment_fit(int(x_bps[i]+1), int(xbp), y, plats[i])
slopes.append(b); bps.append(bp); plats.append(last_val)
breaks = sorted(list(x_bps) + bps)[:-1]
# If due to rounding slope values (to two decimals), there is no change in a subsequent step, I just remove those segments
to_del = np.argwhere(np.diff(slopes) == 0).flatten()
breaks_to_del = np.concatenate((to_del * 2, to_del * 2 + 1))
slopes = np.delete(slopes,to_del + 1)
plats = np.delete(plats[:-1],to_del)
breaks = np.delete(breaks,breaks_to_del)
return slopes, plats, breaks
def pw_calc(x, slopes, plateaus, breaks):
x = x.astype('float')
cond_list = [x < breaks[0]]
for idx, j in enumerate(breaks[:-1]):
cond_list.append((j <= x) & (x < breaks[idx+1]))
cond_list.append(breaks[-1] <= x)
func_list = [lambda x: x * slopes[0]]
for idx, j in enumerate(slopes[1:]):
func_list.append(plateaus[idx])
func_list.append(lambda x, j=j: x * j)
return np.piecewise(x, cond_list, func_list)
fparams = {'k':1.8, 'a':1.2}
end_range = 5e4
no_steps = 10
x = np.arange(1, end_range)
y = input_func(x, **fparams)
slopes, plats, breaks = pw_fit(end_range, no_steps, **fparams)
y_output = pw_calc(x, slopes, plats, breaks)
plt.plot(x,y_output,y);