Python 选择最佳分段回归拟合时代码太慢
我正在做一个程序,它可以拟合数据中最多有4-5个断点的分段线性回归,然后决定有多少个断点是最好的,以防止过度拟合和欠拟合。然而,我的代码运行速度非常慢,这是因为它是多么的笨拙 我的代码草稿如下:Python 选择最佳分段回归拟合时代码太慢,python,numpy,scipy,linear-regression,Python,Numpy,Scipy,Linear Regression,我正在做一个程序,它可以拟合数据中最多有4-5个断点的分段线性回归,然后决定有多少个断点是最好的,以防止过度拟合和欠拟合。然而,我的代码运行速度非常慢,这是因为它是多么的笨拙 我的代码草稿如下: import numpy as np import pandas as pd from scipy.optimize import curve_fit, differential_evolution import matplotlib.pyplot as plt import warnings de
import numpy as np
import pandas as pd
from scipy.optimize import curve_fit, differential_evolution
import matplotlib.pyplot as plt
import warnings
def segmentedRegression_two(xData,yData):
def func(xVals,break1,break2,slope1,offset1,slope_mid,offset_mid,slope2,offset2):
returnArray=[]
for x in xVals:
if x < break1:
returnArray.append(slope1 * x + offset1)
elif (np.logical_and(x >= break1,x<break2)):
returnArray.append(slope_mid * x + offset_mid)
else:
returnArray.append(slope2 * x + offset2)
return returnArray
def sumSquaredError(parametersTuple): #Definition of an error function to minimize
model_y=func(xData,*parametersTuple)
warnings.filterwarnings("ignore") # Ignore warnings by genetic algorithm
return np.sum((yData-model_y)**2.0)
def generate_genetic_Parameters():
initial_parameters=[]
x_max=np.max(xData)
x_min=np.min(xData)
y_max=np.max(yData)
y_min=np.min(yData)
slope=10*(y_max-y_min)/(x_max-x_min)
initial_parameters.append([x_max,x_min]) #Bounds for model break point
initial_parameters.append([x_max,x_min])
initial_parameters.append([-slope,slope])
initial_parameters.append([-y_max,y_min])
initial_parameters.append([-slope,slope])
initial_parameters.append([-y_max,y_min])
initial_parameters.append([-slope,slope])
initial_parameters.append([y_max,y_min])
result=differential_evolution(sumSquaredError,initial_parameters,seed=3)
return result.x
geneticParameters = generate_genetic_Parameters() #Generates genetic parameters
fittedParameters, pcov= curve_fit(func, xData, yData, geneticParameters) #Fits the data
print('Parameters:', fittedParameters)
model=func(xData,*fittedParameters)
absError = model - yData
SE = np.square(absError)
MSE = np.mean(SE)
RMSE = np.sqrt(MSE)
Rsquared = 1.0 - (np.var(absError) / np.var(yData))
return Rsquared
def segmentedRegression_three(xData,yData):
def func(xVals,break1,break2,break3,slope1,offset1,slope2,offset2,slope3,offset3,slope4,offset4):
returnArray=[]
for x in xVals:
if x < break1:
returnArray.append(slope1 * x + offset1)
elif (np.logical_and(x >= break1,x<break2)):
returnArray.append(slope2 * x + offset2)
elif (np.logical_and(x >= break2,x<break3)):
returnArray.append(slope3 * x + offset3)
else:
returnArray.append(slope4 * x + offset4)
return returnArray
def sumSquaredError(parametersTuple): #Definition of an error function to minimize
model_y=func(xData,*parametersTuple)
warnings.filterwarnings("ignore") # Ignore warnings by genetic algorithm
return np.sum((yData-model_y)**2.0)
def generate_genetic_Parameters():
initial_parameters=[]
x_max=np.max(xData)
x_min=np.min(xData)
y_max=np.max(yData)
y_min=np.min(yData)
slope=10*(y_max-y_min)/(x_max-x_min)
initial_parameters.append([x_max,x_min]) #Bounds for model break point
initial_parameters.append([x_max,x_min])
initial_parameters.append([x_max,x_min])
initial_parameters.append([-slope,slope])
initial_parameters.append([-y_max,y_min])
initial_parameters.append([-slope,slope])
initial_parameters.append([-y_max,y_min])
initial_parameters.append([-slope,slope])
initial_parameters.append([y_max,y_min])
initial_parameters.append([-slope,slope])
initial_parameters.append([y_max,y_min])
result=differential_evolution(sumSquaredError,initial_parameters,seed=3)
return result.x
geneticParameters = generate_genetic_Parameters() #Generates genetic parameters
fittedParameters, pcov= curve_fit(func, xData, yData, geneticParameters) #Fits the data
print('Parameters:', fittedParameters)
model=func(xData,*fittedParameters)
absError = model - yData
SE = np.square(absError)
MSE = np.mean(SE)
RMSE = np.sqrt(MSE)
Rsquared = 1.0 - (np.var(absError) / np.var(yData))
return Rsquared
def segmentedRegression_four(xData,yData):
def func(xVals,break1,break2,break3,break4,slope1,offset1,slope2,offset2,slope3,offset3,slope4,offset4,slope5,offset5):
returnArray=[]
for x in xVals:
if x < break1:
returnArray.append(slope1 * x + offset1)
elif (np.logical_and(x >= break1,x<break2)):
returnArray.append(slope2 * x + offset2)
elif (np.logical_and(x >= break2,x<break3)):
returnArray.append(slope3 * x + offset3)
elif (np.logical_and(x >= break3,x<break4)):
returnArray.append(slope4 * x + offset4)
else:
returnArray.append(slope5 * x + offset5)
return returnArray
def sumSquaredError(parametersTuple): #Definition of an error function to minimize
model_y=func(xData,*parametersTuple)
warnings.filterwarnings("ignore") # Ignore warnings by genetic algorithm
return np.sum((yData-model_y)**2.0)
def generate_genetic_Parameters():
initial_parameters=[]
x_max=np.max(xData)
x_min=np.min(xData)
y_max=np.max(yData)
y_min=np.min(yData)
slope=10*(y_max-y_min)/(x_max-x_min)
initial_parameters.append([x_max,x_min]) #Bounds for model break point
initial_parameters.append([x_max,x_min])
initial_parameters.append([x_max,x_min])
initial_parameters.append([x_max,x_min])
initial_parameters.append([-slope,slope])
initial_parameters.append([-y_max,y_min])
initial_parameters.append([-slope,slope])
initial_parameters.append([-y_max,y_min])
initial_parameters.append([-slope,slope])
initial_parameters.append([y_max,y_min])
initial_parameters.append([-slope,slope])
initial_parameters.append([y_max,y_min])
initial_parameters.append([-slope,slope])
initial_parameters.append([y_max,y_min])
result=differential_evolution(sumSquaredError,initial_parameters,seed=3)
return result.x
geneticParameters = generate_genetic_Parameters() #Generates genetic parameters
fittedParameters, pcov= curve_fit(func, xData, yData, geneticParameters) #Fits the data
print('Parameters:', fittedParameters)
model=func(xData,*fittedParameters)
absError = model - yData
SE = np.square(absError)
MSE = np.mean(SE)
RMSE = np.sqrt(MSE)
Rsquared = 1.0 - (np.var(absError) / np.var(yData))
return Rsquared
虽然我可能需要使用AIC或其他东西
有什么方法可以让它在运行时更高效吗?我抓取了一个您的
func
,并将它放在测试脚本中:
import numpy as np
def func(xVals,break1,break2,break3,slope1,offset1,slope2,offset2,slope3,offset3,slope4,offset4):
returnArray=[]
for x in xVals:
if x < break1:
returnArray.append(slope1 * x + offset1)
elif (np.logical_and(x >= break1,x<break2)):
returnArray.append(slope2 * x + offset2)
elif (np.logical_and(x >= break2,x<break3)):
returnArray.append(slope3 * x + offset3)
else:
returnArray.append(slope4 * x + offset4)
return returnArray
arr = np.linspace(0,20,10000)
breaks = [4, 10, 15]
slopes = [.1, .2, .3, .4]
offsets = [1,2,3,4]
sl_off = np.array([slopes,offsets]).T.ravel().tolist()
print(sl_off)
ret = func(arr, *breaks, *sl_off)
if len(ret)<25:
print(ret)
我还做了plt.plot(xVals,ret)
以查看函数的简单绘图
我写了func1
,着眼于让它适用于你所有的3个案例。它不存在,但根据输入列表(或数组)的长度进行更改应该不难
我相信可以做更多的工作,但这应该是从正确的方向开始的
还有一个numpy
分段评估器:
np.piecewise(x, condlist, funclist, *args, **kw)
但我认为,构建两个输入列表似乎同样需要大量的工作。缩进(至少一个)被弄乱了。我同意这不优雅。在其他函数中定义函数在Python中并不常见,除非它们是小型实用程序函数。但这本身不应影响性能。您/我们需要确定的是,哪个任务很长,可能是因为它多次执行复杂的任务。这不是海报想要解决的短期调试问题。在某些方面,它可能更适合代码审查,尤其是组织部分。但我不确定代码是否足够完整。因此,代码和问题需要更多关注。对于CR,它需要完成并运行。我看到两个代码块看起来相似,具有相同的func
定义。您可能在生产代码中需要它,但在SO问题中不需要它。您可能应该专注于在xVals:
循环中为x定义func
,而不使用。我想提出一个新问题,重点是这个,我明白了。我如何使用它来最小化错误函数?在我看来,利用断点的先验知识似乎是必要的,但我可能错了。我的func1
假设有3个断点,但这很容易推广到处理不同长度的中断
。我需要优化我的程序,使其对断点的数量没有先验知识,或者知道断点的数量到+-1断点的不确定性程度。此外,我不确定如何使用差分进化
找到断点的真实位置。当我尝试这样做时,我通常会收到这样的投诉:breaks
未被识别为数组,或者除非用标量值显式指定,否则它无法执行我的代价函数(这需要func1
才能正确执行,但如果不显式指定breaks
,则无法执行)我为中断
等选择了这种表示,因为它很容易创建,并且不需要为2,3,4个案例中的每一个都使用不同的函数签名。在Python中,最好将列表、字典或数组传递给函数,而不是一长串变量。但我想证明的关键是,通过mask
索引,我们可以显著加快func
评估。速度是个大问题,对吗?
def func1(xVals, breaks, slopes, offsets):
res = np.zeros(xVals.shape)
i = 0
mask = xVals<breaks[i]
res[mask] = slopes[i]*xVals[mask]+offsets[i]
for i in [1,2]:
mask = np.logical_and(xVals>=breaks[i-1], xVals<breaks[i])
res[mask] = slopes[i]*xVals[mask]+offsets[i]
i=3
mask = xVals>=breaks[i-1]
res[mask] = slopes[i]*xVals[mask]+offsets[i]
return res
ret1 = func1(arr, breaks, slopes, offsets)
print(np.allclose(ret, ret1))
In [41]: timeit func(arr, *breaks, *sl_off)
66.2 ms ± 337 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [42]: timeit func1(arr, breaks, slopes, offsets)
165 µs ± 586 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
np.piecewise(x, condlist, funclist, *args, **kw)