You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Scipy Optimize Minimize成功终止但未执行任何迭代问题

问题背景

我正在编写优化器,用于寻找最优常量参数,最小化数组y与X上通用函数之间的均方误差(MSE)。该通用函数以前序遍历形式给出,例如针对X的函数x1 + c*x2可表示为[+, x1, *, c, x2],上述示例的优化目标为最小化:

sum_for_all_x (y - (x1 + c*x2))^2

以下是实现代码,提前说明两点前提:

  • X和y均为PyTorch张量
  • constants为待优化的参数值列表
def loss(self, constants, X, y):

    stack = []   # 存储部分计算结果的栈
    const = 0    # 当前使用的常量索引
    for idx in self.traversal[::-1]:   # 反转前序遍历序列
        if idx > Language.max_variables:   # 当前节点是运算符
            function = Language.idx_to_token[idx]  # 获取对应运算函数
            first_operand = stack.pop()    # 取出第一个操作数
            if function.arity == 1:   # 单目运算符(例如sin)
                stack.append(function.function(first_operand))   # 计算结果入栈
            else:   # 双目运算符
                second_operand = stack.pop()  # 取出第二个操作数
                stack.append(function.function(first_operand, second_operand))
            
        elif idx == 0:  # 当前节点是常量(索引0标记常量位置)
            stack.append(constants[const]*torch.ones(X.shape[0]))  # 常量值入栈
            const += 1   # 常量索引自增
        else:
            stack.append(X[:, idx - 1])   # 取X对应列入栈
    
    prediction = stack[0]
    return (y - prediction).pow(2).mean().cpu().numpy()


def optimize_constants(self, X, y):
    '''
    优化表达式树中的常量参数
    '''
    if 0 not in self.traversal:  # 没有需要优化的常量直接返回
        return self.traversal
    
    x0 = [0 for i in range(len(self.constants))]  # 初始值猜测
    ini = time.time()
    res = minimize(self.loss, x0, args=(X, y), method='BFGS', options={'disp': True})
    print(res)
    print('Time:', time.time() - ini)
遇到的问题

优化器输出提示已成功终止,但全程未执行任何迭代,返回的res结果示例如下:

Optimization terminated successfully.
         Current function value: 2.920725
         Iterations: 0
         Function evaluations: 2
         Gradient evaluations: 1
      fun: 2.9207253456115723
 hess_inv: array([[1]])
      jac: array([0.])
  message: 'Optimization terminated successfully.'
     nfev: 2
      nit: 0
     njev: 1
   status: 0
  success: True
        x: array([0.])

已尝试的排查方案均未解决问题:

  • 更换minimize使用的优化方法(如Nelder-Mead、SLSQP等),所有方法均出现相同现象
  • 修改损失结果的返回方式(例如改用(y - prediction).pow(2).mean().item()返回标量损失)
问题原因与修复方案

根本原因

核心问题有两个:

  1. 梯度断裂:scipy.optimize.minimize传入的constants参数是NumPy数组类型,你直接将NumPy值和PyTorch张量做运算时,PyTorch会把这个NumPy值当成固定常数,不会建立对应的反向传播计算图。scipy默认用有限差分计算梯度时,会发现参数扰动无法正确传递到损失值,计算得到的梯度始终为0,自然判定初始点就是极值点,直接终止迭代。
  2. 参数维度不匹配:你初始化x0时取的长度是len(self.constants),但实际优化时的常量总数是遍历表达式过程中遇到idx==0的次数,两个值如果不一致,会导致参数和实际优化位置不匹配。

修复方法

  1. 损失函数内先将scipy传入的NumPy格式常量转成可追踪梯度的PyTorch张量,再参与前向计算;
  2. 用PyTorch的自动微分显式计算损失对常量的梯度,返回给scipy,同时在调用minimize时设置jac=True,告诉优化器损失函数会同时返回损失值和对应梯度,不要用自带的有限差分计算梯度;
  3. 修正初始值x0的长度,直接统计遍历序列中idx==0的个数作为初始值长度,保证参数维度匹配。

修复后的核心代码参考

def loss(self, constants, X, y):
    # 将numpy输入转为可求导的torch张量,匹配X的数据类型和设备
    consts_t = torch.tensor(constants, dtype=X.dtype, device=X.device, requires_grad=True)
    stack = []
    const_idx = 0
    for idx in self.traversal[::-1]:
        if idx > Language.max_variables:
            op = Language.idx_to_token[idx]
            first_op = stack.pop()
            if op.arity == 1:
                cal_res = op.function(first_op)
            else:
                second_op = stack.pop()
                cal_res = op.function(first_op, second_op)
            stack.append(cal_res)
        elif idx == 0:
            stack.append(consts_t[const_idx] * torch.ones(X.shape[0], dtype=X.dtype, device=X.device))
            const_idx += 1
        else:
            stack.append(X[:, idx-1])
    pred = stack[0]
    loss_val = (y - pred).pow(2).mean()
    # 显式计算梯度,转成numpy格式返回给scipy
    grad_val = torch.autograd.grad(loss_val, consts_t)[0].cpu().numpy()
    return loss_val.item(), grad_val

def optimize_constants(self, X, y):
    if 0 not in self.traversal:
        return self.traversal
    # 按表达式中实际常量个数初始化初始值
    const_count = sum(1 for idx in self.traversal if idx == 0)
    x0 = [0.0 for _ in range(const_count)]
    ini = time.time()
    # 传入jac=True,使用我们自己计算的梯度
    res = minimize(self.loss, x0, args=(X, y), method='BFGS', jac=True, options={'disp': True})
    print(res)
    print('Time:', time.time() - ini)

内容的提问来源于stack exchange,提问作者Alex Ferrando

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 20:51:07