如何统计Scipy minimize相邻迭代间的函数与梯度评估次数
统计相邻迭代间函数/梯度评估次数的实现方法
核心思路是给目标函数、梯度函数增加调用计数逻辑,结合每次迭代触发的callback记录累计评估值,最后做相邻项差值即可得到迭代间隔的评估次数。
具体实现步骤
- 给目标函数、自定义梯度函数(如果手动传入的话)添加调用计数器,每执行一次评估对应计数器自增。如果不手动传入梯度函数,BFGS默认用有限差分做数值求导,这部分产生的函数调用会自动被目标函数的计数器捕获。
- 在已有的callback逻辑中,除了记录当前迭代的参数值,同时记录当前时刻累计的函数评估次数、梯度评估次数。
- 迭代完成后,对累计计数序列做相邻项差值计算,得到的结果就是相邻两次参数迭代之间的评估次数。
可运行代码示例
import numpy as np from scipy.optimize import minimize # 初始化存储容器 params = np.empty([0, 3]) # 存储每次迭代的参数 cum_func_evals = [] # 存储每次迭代触发时的累计函数评估次数 cum_grad_evals = [] # 存储每次迭代触发时的累计梯度评估次数 # 计数器 n_func_call = 0 n_grad_call = 0 def rosen(X): global n_func_call n_func_call += 1 return (1.0 - X[0])**2 + 100.0 * (X[1] - X[0]**2)**2 + \ (1.0 - X[1])**2 + 100.0 * (X[2] - X[1]**2)**2 # 若手动传入梯度,可添加对应计数逻辑,不传则BFGS走数值差分,不会触发该计数 def rosen_jac(X): global n_grad_call n_grad_call += 1 # 此处补充Rosenbrock函数的梯度计算代码即可 pass def callback_params(theta): global params, n_func_call, n_grad_call params = np.vstack((params, theta)) cum_func_evals.append(n_func_call) cum_grad_evals.append(n_grad_call) init = np.random.rand(3) # 若要使用自定义梯度,给minimize传入参数jac=rosen_jac即可 res = minimize(rosen, init, method='BFGS', options={'disp': True}, callback=callback_params) # 补全初始点评估的计数,对齐参数序列 cum_func_evals = [res.nfev - np.diff(cum_func_evals).sum()] + cum_func_evals cum_grad_evals = [res.njev - np.diff(cum_grad_evals).sum()] + cum_grad_evals # 计算相邻迭代间隔的评估次数 iter_interval_func_evals = np.diff(cum_func_evals) iter_interval_grad_evals = np.diff(cum_grad_evals)
说明
- 最终得到的
iter_interval_func_evals数组第i个元素,就对应params第i行参数和上一个迭代点(初始点或params第i-1行)之间的函数评估次数,梯度评估次数iter_interval_grad_evals同理。 - 优化返回结果
res自带总评估次数字段res.nfev(总函数评估次数)、res.njev(总梯度评估次数),可以用来校验计数结果是否准确。
内容的提问来源于stack exchange,提问作者Tamuzd
相关产品推荐
相关产品推荐

