强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Julia 教程 / Julia 数值优化(Optim.jl 与 Convex.jl)

Julia 数值优化(Optim.jl 与 Convex.jl)

数值优化是机器学习、工程设计、金融建模等领域的核心工具。Julia 生态提供了 Optim.jl(通用优化)和 Convex.jl(凸优化建模)两套强大的优化框架。


1. Optim.jl 概述

using Optim

# 最简单的优化问题:最小化 f(x) = (x-2)²
f(x) = (x[1] - 2.0)^2

result = optimize(f, [0.0], BFGS())

println(result.minimizer)   # [2.0]
println(result.minimum)     # ≈ 0.0
方法函数需要梯度需要 Hessian适用场景
Nelder-MeadNelderMead()无梯度、噪声目标
Gradient DescentGradientDescent()简单问题
BFGSBFGS()通用无约束优化(推荐)
L-BFGSLBFGS()高维问题、内存有限
NewtonNewton()快速二次收敛
Conjugate GradientConjugateGradient()大规模问题
Particle SwarmParticleSwarm()全局优化

2. 无约束优化

2.1 BFGS 方法

using Optim

# Rosenbrock 函数(经典测试函数)
function rosenbrock(x)
    return (1.0 - x[1])^2 + 100.0 * (x[2] - x[1]^2)^2
end

x0 = [-1.0, 1.0]
result = optimize(rosenbrock, x0, BFGS(); autodiff=:forward)

println("最小值点: $(result.minimizer)")
println("最小值: $(result.minimum)")
println("迭代次数: $(result.iterations)")
println("收敛状态: $(result.converged)")

2.2 Nelder-Mead(无梯度)

# 不需要梯度的场景
result = optimize(rosenbrock, x0, NelderMead())

# 适用于目标函数不可微、含有噪声的场景
noisy_func(x) = (x[1] - 3.0)^2 + randn() * 0.01
result = optimize(noisy_func, [0.0], NelderMead())

2.3 自定义梯度

function rosenbrock(x)
    return (1.0 - x[1])^2 + 100.0 * (x[2] - x[1]^2)^2
end

function rosenbrock_grad!(g, x)
    g[1] = -2.0 * (1.0 - x[1]) - 400.0 * x[1] * (x[2] - x[1]^2)
    g[2] = 200.0 * (x[2] - x[1]^2)
end

# 手动提供梯度
result = optimize(rosenbrock, rosenbrock_grad!, x0, BFGS())

# 或使用自动微分(推荐)
result = optimize(rosenbrock, x0, BFGS(); autodiff=:forward)

⚠️ 注意: autodiff=:forward 使用 ForwardDiff.jl 进行自动微分,适用于标量返回值的函数。对于返回向量的函数需要手动提供 Jacobian。


3. 有约束优化

3.1 盒约束(Box Constraints)

using Optim

# 变量范围约束
f(x) = (x[1] - 1.0)^2 + (x[2] - 2.5)^2

# x[1] ∈ [0, 2], x[2] ∈ [0, 4]
lower = [0.0, 0.0]
upper = [2.0, 4.0]
x0 = [0.5, 0.5]

result = optimize(f, lower, upper, x0, Fminbox(GradientDescent()))
println("最优解: $(result.minimizer)")

3.2 非线性约束

using Optim

# 最小化 f(x,y) = x + y
# 约束: x² + y² ≤ 1, x ≥ 0
f(x) = x[1] + x[2]

# 使用 IPNewton(内点法)
function con_c!(c, x)
    c[1] = x[1]^2 + x[2]^2 - 1.0    # 不等式约束 ≤ 0
end

lx = [0.0, -Inf]   # 变量下界
ux = [Inf, Inf]    # 变量上界

df = TwiceDifferentiable(f, [0.5, 0.5]; autodiff=:forward)
dc = TwiceDifferentiableConstraints(con_c!, lx, ux, [0.0], [0.0])

result = optimize(df, dc, [0.5, 0.5], IPNewton())

4. 收敛判据与选项

result = optimize(f, x0, BFGS();
    autodiff = :forward,
    iterations = 1000,         # 最大迭代次数
    f_tol = 1e-12,             # 函数值容差
    g_tol = 1e-8,              # 梯度容差
    x_tol = 1e-12,             # 自变量容差
    store_trace = true,        # 保存迭代历史
    show_trace = true,         # 打印迭代过程
    show_every = 10            # 每 10 步打印一次
)

# 检查结果
println("收敛: $(result.converged)")
println("迭代次数: $(result.iterations)")
println("函数调用次数: $(result.f_calls)")
参数说明默认值
iterations最大迭代次数1000
f_tol函数值相对变化容差√eps
g_tol梯度范数容差1e-8
x_tol自变量变化容差√eps
time_limit时间限制(秒)Inf

5. 行搜索与信赖域

using Optim, LineSearches

# 自定义行搜索策略
result = optimize(f, x0, BFGS();
    linesearch = LineSearches.HagerZhang(),   # Hager-Zhang 行搜索
    autodiff = :forward
)

# 其他行搜索方法
# LineSearches.BackTracking()     # 回溯法
# LineSearches.StrongWolfe()      # 强 Wolfe 条件
# LineSearches.MoreThuente()      # More-Thuente 方法

💡 提示: 对于大多数问题,默认的 HagerZhang 行搜索已经足够好。如果优化不收敛,可以尝试更换行搜索策略。


6. 多起点全局优化

using Optim

# 单一局部优化可能陷入局部最优
# 使用多起点策略提高找到全局最优的概率
function multi_start(f, bounds, n_starts=100; method=BFGS())
    best_result = nothing
    best_val = Inf

    for _ in 1:n_starts
        x0 = [bounds[i][1] + rand() * (bounds[i][2] - bounds[i][1])
              for i in 1:length(bounds)]
        try
            result = optimize(f, x0, method; autodiff=:forward)
            if result.minimum < best_val
                best_val = result.minimum
                best_result = result
            end
        catch
            continue
        end
    end
    return best_result
end

# 使用示例
bounds = [(-5.0, 5.0), (-5.0, 5.0)]
result = multi_start(rosenbrock, bounds)

7. 凸优化(Convex.jl)

using Convex, SCS

# 最小化 ||Ax - b||² + λ||x||₁(LASSO 回归)
n, m = 100, 50
A = randn(n, m)
x_true = sprandn(m, 1, 0.3)    # 稀疏真实解
b = A * x_true + randn(n) * 0.1

x = Variable(m)
λ = 0.1
problem = minimize(sumsquares(A * x - b) + λ * norm(x, 1))

solve!(problem, SCS.Optimizer; silent=true)
println("最优值: $(problem.optval)")
println("解: $(x.value)")

常用凸优化原子

原子说明
minimize / maximize最小化/最大化目标
sumsquares(x)‖x‖²₂
norm(x, p)Lp 范数
quadform(x, P)xᵀPx
logsumexp(x)log(Σeˣⁱ)
logisticloss(x)logistic 损失
x >= 0逐元素非负约束
sum(x) == 1和为 1 的约束

8. 非线性最小二乘

using Optim, LsqFit

# 拟合模型 y = a * exp(b * x)
model(x, p) = p[1] .* exp.(p[2] .* x)

# 生成数据
xdata = range(0, 5, length=50)
ydata = 2.5 .* exp.(-0.3 .* xdata) .+ randn(50) * 0.1

# 使用 LsqFit.jl
p0 = [1.0, -1.0]   # 初始猜测
fit = curve_fit(model, xdata, ydata, p0)
println("拟合参数: $(fit.param)")
println("标准误: $(stderror(fit))")

# 使用 Optim.jl 的最小二乘
function least_squares(p)
    pred = model(xdata, p)
    return sum((pred - ydata) .^ 2)
end

```julia
result = optimize(least_squares, p0, LevenbergMarquardt())

💡 提示: 对于曲线拟合问题,LsqFit.jl 的 curve_fit 通常比手动构建优化问题更方便,它自动计算 Jacobian 并返回拟合标准误。


9. 实际案例

9.1 参数拟合:指数衰减模型

using Optim, Plots

# 实验数据:放射性衰减
t_data = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]
N_data = [100.0, 74.1, 54.9, 40.7, 30.1, 22.3, 16.5, 12.2, 9.1]

# 模型: N(t) = N₀ * exp(-λt)
decay_model(t, p) = p[1] .* exp.(-p[2] .* t)

function loss(p)
    pred = decay_model(t_data, p)
    return sum((pred - N_data) .^ 2)
end

result = optimize(loss, [100.0, 0.3], BFGS(); autodiff=:forward)
N0, λ = result.minimizer
println("N₀ = $N0, λ = , 半衰期 = $(log(2)/λ)")

# 绘图
t_plot = range(0, 10, length=100)
scatter(t_data, N_data, label="实验数据")
plot!(t_plot, decay_model(t_plot, [N0, λ]), label="拟合曲线")

9.2 机器学习损失优化

using Optim

# 逻辑回归的梯度下降
sigmoid(z) = 1.0 / (1.0 + exp(-z))

function logistic_loss(θ, X, y)
    z = X * θ
    probs = sigmoid.(z)
    # 防止 log(0)
    probs = clamp.(probs, 1e-10, 1 - 1e-10)
    return -sum(y .* log.(probs) .+ (1 .- y) .* log.(1 .- probs))
end

# 生成二分类数据
n, d = 200, 3
X = hcat(ones(n), randn(n, d - 1))
θ_true = [0.5, -1.0, 2.0]
y = sigmoid.(X * θ_true) .> 0.5
y = Float64.(y)

# 优化
loss(θ) = logistic_loss(θ, X, y)
result = optimize(loss, zeros(d), BFGS(); autodiff=:forward)
println("真实参数: $θ_true")
println("估计参数: $(result.minimizer)")

10. 优化算法选择指南

graph TD
    A[优化问题] --> B{目标函数可微?}
    B -->|是| C{维度?}
    B -->|否| D[Nelder-Mead / ParticleSwarm]
    C -->|低维 <100| E[BFGS / Newton]
    C -->|高维 >100| F[L-BFGS / ConjugateGradient]
    A --> G{有约束?}
    G -->|盒约束| H[Fminbox + BFGS]
    G -->|非线性约束| I[IPNewton]
    G -->|凸优化| J[Convex.jl + SCS]

扩展阅读