使用SciML优化Lotka-Volterra模型时,优化函数非标量输出报错如何解决?
问题解决:Optimization.jl报错"Output should be scalar; gradients are not defined for output"
错误原因
你的loss函数返回了元组(损失值, sol),但AutoZygote自动微分工具要求损失函数的输出仅为标量损失值——它会尝试对整个返回值求导,而sol是数组类型,无法计算其梯度,因此触发报错。
解决方案
推荐两种可行的修正方式,根据你的需求选择:
方式一:Loss仅返回标量,回调中重新求解sol(逻辑最稳妥)
将loss函数简化为只返回标量损失,在回调函数中通过当前参数重新求解ODE得到sol用于绘图。虽然会多一次ODE求解,但逻辑清晰,避免AD冲突。
修改后的关键代码:
# 简化loss函数,仅返回标量损失 function loss(p) sol = solve(prob, Tsit5(), p=p, saveat = tsteps) return sum(abs2, sol .- 1) end # 调整回调函数,自行求解sol function callback(p, l) sol = solve(prob, Tsit5(), p=p, saveat = tsteps) push!(parameters, p.u) display(l) plt = plot(sol, ylim = (0, 6)) display(plt) return l <= 0.0135 end # 后续Optimization定义保持不变,注意回调参数不再有第三个值 adtype = Optimization.AutoZygote() optf = Optimization.OptimizationFunction((x, p) -> loss(x), adtype) optprob = Optimization.OptimizationProblem(optf, p) result_ode = Optimization.solve(optprob, PolyOpt(), callback = callback, maxiters = 600)
方式二:用Zygote.@ignore忽略sol的求导(效率更高)
如果不想重复求解ODE,可以用Zygote.@ignore标记sol,让自动微分工具跳过对它的梯度计算,这样loss函数可以保留返回元组,回调也无需修改。
修改后的关键代码:
# 修改loss函数,用@ignore忽略sol的求导 function loss(p) sol = solve(prob, Tsit5(), p=p, saveat = tsteps) loss_val = sum(abs2, sol .- 1) return loss_val, Zygote.@ignore(sol) end # 回调函数保持原代码不变 function callback(p, l, pred) push!(parameters, p.u) display(l) plt = plot(pred, ylim = (0, 6)) display(plt) return l <= 0.0135 end # 后续代码保持不变 adtype = Optimization.AutoZygote() optf = Optimization.OptimizationFunction((x, p) -> loss(x), adtype) optprob = Optimization.OptimizationProblem(optf, p) result_ode = Optimization.solve(optprob, PolyOpt(), callback = callback, maxiters = 600)
注意事项
- 你之前移除
sol后报错,是因为原回调函数依赖第三个参数pred(即sol),此时需要同步调整回调函数的参数列表,方式一已经处理了这个问题。 - 方式二的
Zygote.@ignore仅对Zygote有效,如果你后续更换其他AD工具(如ForwardDiff),需要改用对应工具的忽略方法。
内容的提问来源于stack exchange,提问作者AndyDufresne
相关产品推荐
相关产品推荐

