我正在使用这个优化脚本:
using DifferentialEquations, Optimization, OptimizationPolyalgorithms, OptimizationOptimJL, Plots, SciMLSensitivity
parameters = \[\]
function lotka_volterra!(du, u, p, t)
x, y = u
α, β, δ, γ = p
du\[1\] = dx = α*x - β*x*y
du\[2\] = dy = -δ*y + γ*x*y
end
# Initial condition
u0 = \[1.0, 1.0\]
# Simulation interval and intermediary points
tspan = (0.0, 40.0)
tsteps = 0.0:0.1:40.0
# LV equation parameter. p = \[α, β, δ, γ\]
p = \[1.5, 1.0, 3.0, 1.0\]
# Setup the ODE problem, then solve
prob = ODEProblem(lotka_volterra!, u0, tspan, p)
sol = solve(prob, Tsit5())
# Plot the solution
using Plots
plot(sol)
savefig("LV_ode.png")
function loss(p) # This function calculates how far the solution is from the value we are aiming for
sol = solve(prob, Tsit5(), p=p, saveat = tsteps)
if any(isnan, sol) || any(isinf, sol)
println("Solution contains NaN or Inf: ", sol)
end
loss = sum(abs2, sol.-1) # Calculates the squared error; Sol for both x and y values has 1 taken away. This way, it will converge on "1" (the target) because when it reaches 1, 1-1 = 0 difference between desired and predicted points, so that is what we want.
return loss, sol
end
callback = function (p, l, pred) # p = parameters, l = loss, pred = predicted
global parameters
push!(parameters, p.u)
display(l)
plt = plot(pred, ylim = (0, 6))
display(plt)
if l \<= 0.0135
return true
else
return false
end
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)
它一直有效,直到略低于 l = 0.013。如果您运行它,您可以看到它一直在工作。我在回调函数中放入了一个“if”语句,这样当它达到 0.0135 时就会停止,但我仍然收到此错误消息:
ERROR: Output should be scalar; gradients are not defined for output
我该如何解决这个问题?
我尝试从损失函数中删除“sol”,但这给了我难以理解的错误(需要绘制 sol 并在最后几行中使用)。
我尝试了“if”语句,它应该告诉迭代在某个点之后停止,但即使它们确实停止(我更改了停止值并且它停止在任何值),之后错误仍然出现。我该如何解决这个问题?
loss(p)
应该返回一个标量。您得到的错误与损失值低于
0.0135
无关。 发生错误是因为您的 loss
和 callback
函数具有不正确的签名。使用 PolyOpt()
在掩盖真实错误方面也发挥着有趣的作用。
loss
和 callback
OptimizationFunction
的文档,您会发现构造函数带有一个参数 f
,这是您想要最小化的函数。摘自文档:
f(u,p):优化 [...] 的函数 这应该返回一个标量, 损失值,作为返回输出。
因此,您应该更改函数
loss(p)
以返回标量损失值,而不是 Tuple
。例如
function loss(p)
sol = solve(prob, Tsit5(), p=p, saveat = tsteps)
return sum(abs2, sol .- 1)
end
现在,如果您查看
Optimization
求解器选项的文档,您将阅读以下有关回调的内容:
回调函数
是一个在之后调用的函数 每个优化器步骤。它的签名是:callback
callback = (state, loss_val) -> false
因此,您应该在自己的回调函数中遵循此签名。当然,这意味着您无法传入在损失函数中计算的 ODE 解
sol
。但没关系,您可以使用 state.u
访问当前迭代参数,然后再次求解 ODE。例如
function callback(state, l)
display(l)
push!(parameters, state.u)
sol = solve(prob, Tsit5(), p=state.u, saveat = tsteps)
display(plot(sol, ylim=(0, 6)))
return false
end
如果您在优化中使用这些新函数,那就没问题了。
为什么你的代码似乎可以运行几次迭代,然后突然出错?
因为
PolyOpt()
正在做的事情。来自文档:
PolyOpt:运行 Adam,然后运行 BFGS 进行相同次数的迭代。
发生的情况是,您的
loss
和 callback
函数适用于 Adam
,但不适用于 BFGS
。 因此,当 PolyOpt()
从 Adam
切换到 BFGS
时,错误会“突然”出现出现。
您可以通过将
PolyOpt()
更改为 Optimisers.Adam
(这是 OptimizationPolyalgorithms
使用的 ADAM 版本),然后使用 BFGS
尝试相同的操作来亲自查看这一点。例如
using Optimisers
adtype = Optimization.AutoZygote()
optf = Optimization.OptimizationFunction((x, p) -> loss(x), adtype)
optprob = Optimization.OptimizationProblem(optf, p)
result_ode = Optimization.solve(optprob, Optimisers.Adam(), callback = callback, maxiters = 600)
可与您原来的
loss
和 callback
配合使用,但是
adtype = Optimization.AutoZygote()
optf = Optimization.OptimizationFunction((x, p) -> loss(x), adtype)
optprob = Optimization.OptimizationProblem(optf, p)
result_ode = Optimization.solve(optprob, BFGS(), callback = callback, maxiters = 600)
立即向您抛出错误。
现在,为什么
Optimisers.Adam
具有与 BFGS
和其他优化算法不同的界面,我不能肯定地说。我对所涉及的包及其设计了解不够。但是,我猜测 Optimisers.Adam
在某个时候将其添加为一个新接口,以很好地适应 Optimization
生态系统的其余部分,并且可能保留旧接口,以免破坏依赖它的旧代码。 无论如何,我都会坚持使用 Optimization.jl
文档中的界面。