scipy.minimize(BFGS)优化失效:损失值不下降的问题求助
问题分析与优化方案
核心问题定位
从你的损失值波动情况和代码来看,主要存在以下几个关键问题导致BFGS无法有效下降:
1. 未提供精确梯度,BFGS依赖噪声大的有限差分
当前代码中do_lnp仅返回损失值,但BFGS优化器如果没有精确梯度,会自动使用有限差分近似,这不仅效率低,还会引入数值噪声,导致优化陷入局部震荡。
2. 梯度计算遗漏正则项的导数
你计算的梯度df只包含了模型主体部分的导数,完全忽略了正则化项的梯度,这会导致优化方向错误,无法收敛到最优解。
3. 正则化项定义错误
你使用了exp(param)**2作为正则化项,这是非标准的L2正则化。该形式会对正参数施加极强的惩罚(指数增长),迫使参数向负方向偏移,严重干扰优化过程。标准L2正则化应该直接对参数本身平方求和。
4. 输入数据形状不匹配
在fit_lnp中传递y.T给do_lnp,导致y和u的形状不匹配(y为行向量,u为列向量),二者的乘法会产生矩阵而非元素级运算,最终损失值计算完全错误。
具体改进步骤
步骤1:修正损失函数与梯度计算
修改do_lnp函数,修复正则化项、补充正则化梯度,并返回损失值和精确梯度:
def do_lnp(param, X, y): """ Computes the objective function and its gradient for the LNP model. Args: param (numpy.ndarray): Parameter array. X (numpy.ndarray): Input data matrix (shape: [n_samples, n_features]). y (numpy.ndarray): Output data vector (shape: [n_samples, 1]). Returns: tuple: Tuple containing the objective function value and its gradient. """ lamda = 1e-2 # compute the firing rate u = np.dot(X, param.reshape(-1, 1)) rate = np.exp(u) # 修正正则化项:标准L2正则化 reg_val = (lamda / 2) * np.sum(param ** 2) # 计算损失值 f = np.sum(rate - y * u) + reg_val print(f) # 计算梯度:包含模型主体+正则化项的导数 df_model = np.dot(X.T, (rate - y)).flatten() # 展平为与param同形状的一维数组 df_reg = lamda * param df = df_model + df_reg return f, df
步骤2:调整优化器调用,传递精确梯度
在fit_lnp中,修改minimize调用,指定jac=True(表示目标函数返回损失和梯度),同时修正y的传递:
def fit_lnp(X, y, opts): """ Fits an LNP model to the data. Args: X (numpy.ndarray): Input data matrix (shape: [n_features, n_samples]). y (numpy.ndarray): Output data vector (shape: [n_samples, 1]). opts (dict): Options for the optimizer. Returns: tuple: Tuple containing the optimized parameters and predicted values. """ init = 1e-3 * np.random.randn(X.shape[0], 1).flatten() # 传递y而非y.T,同时指定jac=True result = minimize( lambda param: do_lnp(param, X.T, y), init, method="BFGS", options=opts, jac=True ) param = result.x yHat = np.exp(np.dot(X.T, param)).reshape(-1, 1) # 保持与y一致的形状 return param, yHat
步骤3:数据标准化(可选但推荐)
如果输入特征X的数值范围差异较大,会导致exp(X@param)数值不稳定。建议对每个特征做标准化处理:
# 在fit_lnp函数开头添加 X = (X - np.mean(X, axis=1, keepdims=True)) / np.std(X, axis=1, keepdims=True)
步骤4:调整优化器参数
优化器选项opts可以调整迭代次数和收敛阈值,例如:
opts = { "maxiter": 2000, # 增加最大迭代次数 "gtol": 1e-8, # 降低梯度收敛阈值 "disp": True # 打印优化过程信息 }
是否需要更换优化方法?
在修复上述核心问题后,BFGS应该能够正常收敛。如果仍然存在问题,可以尝试以下替代方案:
- L-BFGS-B:适合大规模问题,内存占用更低,可添加参数边界约束(例如限制参数为非负,如果有物理意义)。
- Adam:Scipy 1.7+版本支持Adam优化器,对非凸问题和噪声数据鲁棒性更强,无需手动调整学习率。
例如,使用L-BFGS-B的修改:
result = minimize( lambda param: do_lnp(param, X.T, y), init, method="L-BFGS-B", options=opts, jac=True )
内容的提问来源于stack exchange,提问作者user23262295
相关产品推荐
相关产品推荐

