使用scipy.optimize.least_squares拟合可变参数曲线时参数传递异常
scipy.optimize.least_squares参数传递错误排查与修复
使用scipy.optimize.least_squares()拟合一族关联函数对的参数时,出现参数无法正确传递到目标函数的问题,报错显示Ha对应的lambda函数缺少必要参数a和b。
原代码
import numpy as np import numpy.linalg as la import scipy.optimize import math Ha = {'H': lambda x, a, b : a + b*x, 'nParams': 2} Hb = {'H': lambda x, a, b, c : a + b*x + c*x*x, 'nParams': 3} Hc = {'H': lambda x, a, b, c, d : a*math.cos(b*(x-c)) + d, 'nParams': 4} points1 = [(1,0), (2,4), (3,5), (7,8)] points2 = [(1,1), (2,6), (4,1), (6,6)] def coupled_1 (x, g, *params, **kwargs): H1, H2 = kwargs.values() # H1 and H2 are dictionaries containing the two curves # to be fit, 'H', and their respective numbers of # parameters, 'nParams' matrix = np.diag([H1['H'](x, *params[:H1['nParams']]), H2['H'](x, *params[H1['nParams']:])]) \ + np.diag([g],k=1) + np.diag([g],k=-1) return la.eigh(matrix)['eigenvalues'][0] def coupled_2 (x, g, *params, **kwargs): H1, H2 = kwargs.values() matrix = np.diag([H1['H'](x, *params[:H1['nParams']]), H2['H'](x, *params[H1['nParams']:])]) \ + np.diag([g],k=1) + np.diag([g],k=-1) return la.eigh(matrix)['eigenvalues'][1] def getParameters(H1, H2, pts1, pts2): nParams = H1['nParams'] + H2['nParams'] # These points sometimes come in pairs and sometimes don't so I've found no better way to zip them pts = [(p1, p2) for p1 in pts1 for p2 in pts2 if p1[0] == p2[0]] res = lambda *params, **kwargs: \ np.sqrt(sum( [( p[0][1]-coupled_1(p[0][0], *params, **kwargs) )**2 \ + (p[1][1]-coupled_2(p[0][0], *params, **kwargs) )**2 for p in pts] )) result = scipy.optimize.least_squares(res,[1] + [0]*nParams, kwargs={'H1':H1,'H2':H2}) return result['x'] params = getParameters(Ha, Hc, points1, points2)
错误信息
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[5], line 37 34 result = scipy.optimize.least_squares(res,[1] + [0]*nParams, kwargs={'H1':H1,'H2':H2}) 35 return result['x'] ---> 37 params = getParameters(Ha, Hc, points1, points2) 38 print(params) Cell In[5], line 34, in getParameters(H1, H2, pts1, pts2) 30 pts = [(p1, p2) for p1 in pts1 for p2 in pts2 if p1[0] == p2[0]] 31 res = lambda *params, **kwargs: \ 32 np.sqrt(sum( [( p[0][1]-coupled_1(p[0][0], *params, **kwargs) )**2 \ 33 + (p[1][1]-coupled_2(p[0][0], *params, **kwargs) )**2 for p in pts] )) ---> 34 result = scipy.optimize.least_squares(res,[1] + [0]*nParams, kwargs={'H1':H1,'H2':H2}) 35 return result['x'] File ~\anaconda3\lib\site-packages\scipy\optimize\_lsq\least_squares.py:830, in least_squares(fun, x0, jac, bounds, method, ftol, xtol, gtol, x_scale, loss, f_scale, diff_step, tr_solver, tr_options, jac_sparsity, max_nfev, verbose, args, kwargs) 827 if method == 'trf': 828 x0 = make_strictly_feasible(x0, lb, ub) --> 830 f0 = fun_wrapped(x0) 832 if f0.ndim != 1: 833 raise ValueError("`fun` must return at most 1-d array_like. " 834 "f0.shape: {0}".format(f0.shape)) File ~\anaconda3\lib\site-packages\scipy\optimize\_lsq\least_squares.py:825, in least_squares.<locals>.fun_wrapped(x) 824 def fun_wrapped(x): --> 825 return np.atleast_1d(fun(x, *args, **kwargs)) Cell In[5], line 32, in getParameters.<locals>.<lambda>(*params, **kwargs) 29 # These points sometimes come in pairs and sometimes don't so I've found no better way to zip them 30 pts = [(p1, p2) for p1 in pts1 for p2 in pts2 if p1[0] == p2[0]] 31 res = lambda *params, **kwargs: \ ---> 32 np.sqrt(sum( [( p[0][1]-coupled_1(p[0][0], *params, **kwargs) )**2 \ 33 + (p[1][1]-coupled_2(p[0][0], *params, **kwargs) )**2 for p in pts] )) 34 result = scipy.optimize.least_squares(res,[1] + [0]*nParams, kwargs={'H1':H1,'H2':H2}) 35 return result['x'] Cell In[5], line 32, in <listcomp>(.0) 29 # These points sometimes come in pairs and sometimes don't so I've found no better way to zip them 30 pts = [(p1, p2) for p1 in pts1 for p2 in pts2 if p1[0] == p2[0]] 31 res = lambda *params, **kwargs: \ ---> 32 np.sqrt(sum( [( p[0][1]-coupled_1(p[0][0], *params, **kwargs) )**2 \ 33 + (p[1][1]-coupled_2(p[0][0], *params, **kwargs) )**2 for p in pts] )) 34 result = scipy.optimize.least_squares(res,[1] + [0]*nParams, kwargs={'H1':H1,'H2':H2}) 35 return result['x'] Cell In[5], line 17, in coupled_1(x, g, *params, **kwargs) 14 H1, H2 = kwargs.values() # H1 and H2 are dictionaries containing the two curves 15 # to be fit, 'H', and their respective numbers of 16 # parameters, 'nParams' ---> 17 matrix = np.diag([H1['H'](x, *params[:H1['nParams']]), H2['H'](x, *params[H1['nParams']:])]) \ 18 + np.diag([g],k=1) + np.diag([g],k=-1) 19 return la.eigh(matrix)['eigenvalues'][0] TypeError: <lambda>() missing 2 required positional arguments: 'a' and 'b'
问题原因
scipy.optimize.least_squares的目标函数要求第一个参数是优化参数的数组,但原代码中res lambda用*params接收参数,导致参数传递完全错位:
- 优化参数数组(比如
[1,0,0,0,0,0])会被当作单个位置参数传入,*params将其拆分为多个元素后,调用coupled_1时,g拿到的是数组的第二个元素,后续的*params为空,无法给Ha['H']提供所需的a和b参数。
修复后的代码
import numpy as np import numpy.linalg as la import scipy.optimize import math Ha = {'H': lambda x, a, b : a + b*x, 'nParams': 2} Hb = {'H': lambda x, a, b, c : a + b*x + c*x*x, 'nParams': 3} Hc = {'H': lambda x, a, b, c, d : a*math.cos(b*(x-c)) + d, 'nParams': 4} points1 = [(1,0), (2,4), (3,5), (7,8)] points2 = [(1,1), (2,6), (4,1), (6,6)] def coupled_1(x, g, *params, **kwargs): H1, H2 = kwargs.values() matrix = np.diag([H1['H'](x, *params[:H1['nParams']]), H2['H'](x, *params[H1['nParams']:])]) \ + np.diag([g], k=1) + np.diag([g], k=-1) return la.eigh(matrix)[0][0] # 直接取第一个特征值,更简洁 def coupled_2(x, g, *params, **kwargs): H1, H2 = kwargs.values() matrix = np.diag([H1['H'](x, *params[:H1['nParams']]), H2['H'](x, *params[H1['nParams']:])]) \ + np.diag([g], k=1) + np.diag([g], k=-1) return la.eigh(matrix)[0][1] # 直接取第二个特征值 def getParameters(H1, H2, pts1, pts2): nParams = H1['nParams'] + H2['nParams'] pts = [(p1, p2) for p1 in pts1 for p2 in pts2 if p1[0] == p2[0]] # 修正目标函数参数接收:第一个参数是优化数组,拆分出g和拟合参数 def res(x, **kwargs): g = x[0] fit_params = x[1:] total_error = 0.0 for p in pts: x_val = p[0][0] y1_true = p[0][1] y2_true = p[1][1] y1_pred = coupled_1(x_val, g, *fit_params, **kwargs) y2_pred = coupled_2(x_val, g, *fit_params, **kwargs) total_error += (y1_true - y1_pred)**2 + (y2_true - y2_pred)**2 return np.sqrt(total_error) result = scipy.optimize.least_squares(res, [1] + [0]*nParams, kwargs={'H1': H1, 'H2': H2}) return result['x'] params = getParameters(Ha, Hc, points1, points2) print(params)
关键修改点
- 将
res从lambda改为普通函数,明确接收优化参数数组x。 - 手动拆分
x为g(第一个元素)和后续的拟合参数fit_params。 - 调用
coupled_1和coupled_2时,正确传递g和*fit_params,保证参数结构匹配。
内容的提问来源于stack exchange,提问作者JoDraX
相关产品推荐
相关产品推荐

