Hyperparameter optimisation for initial conditions

This example demonstrates the use of HyperTuning to find good initial conditions for an asymmetric auction problem with 2 players. Note this example uses the asymmetric code base but both players have the same distribution so is effectively symmetric.

Install dependencies

Enter the Julia REPL (julia) and run:

using Pkg
Pkg.add("ConstrainedStrategicEquilibrium")
Pkg.add("HyperTuning")

Load required modules

using ConstrainedStrategicEquilibrium
using Plots
using Distributions
using NonlinearSolve
using HyperTuning
using Logging

Define the objective function

The objective function solves the CSE and computes the Mean Squared Error (MSE) against the Bayesian Nash Equilibrium (BNE). It takes trial values for a, b, c and uses them as initial guesses.

We are able to compute the BNE (and therefore MSE) because both players have the same distribution. If you were to run this for a true asymmetric case then you would need to choose another metric to return from the objective function, such as sol.c_2 or the norm of the residual vector from the solver.

We create the CSE problem only for the initial value of n (2 in this case) and set the tolerances loosely as we only need to roughly converge on a good initial guess.

function objective(trial)
    # unpack the initial guess and store it in a vector suitable for passing to the problem
    @unpack a, b, c = trial
    xguess = [a, b, c]

    # create the problem setting inin==maxn
    cse_prob = AsymmetricAfrprogsCSEProblem(
        inin=2,
        maxn=2,
        np=2,
        mc=10000,
        solver_kwargs=(show_trace=Val(false), maxiters=2000, abstol=1e-6, reltol=1e-6),
        solver_initial_guess=xguess,
        distributions=[Beta(3, 3), Beta(3, 3)],
        knot_refinement_strategy=:even_spacing,
    )

    # solve the CSE
    solutions = compute_cse(cse_prob)

    # now we compute the BNE for the same problem
    bnex = Vector{Float64}(undef, 101)
    bney = Vector{Float64}(undef, 101)
    for m = 1:101
        ti = (m - 1.0) / 100.0
        if ti == 0
            true_bne = 0.0
        else
            true_bne = compute_bne(ti, cse_prob.distributions[1], cse_prob.np)
        end
        bnex[m] = ti
        bney[m] = true_bne
    end

    # compute the MSE for the CSE against the BNE and return that
    mse = Inf
    sol = solutions[1]
    if sol.success
        mse1_sum = 0.0
        mse2_sum = 0.0
        for m = 1:101
            if m == 1
                cse1 = cse2 = 0
            else
                cse1 = sol.cse[!, "CSE(x) 1"][m]
                cse2 = sol.cse[!, "CSE(x) 2"][m]
            end
            mse1_sum += (sol.cse[!, "CSE(x) 1"][m] - bney[m])^2
            mse2_sum += (sol.cse[!, "CSE(x) 2"][m] - bney[m])^2
        end
        mse1 = mse1_sum / 101
        mse2 = mse2_sum / 101
        mse = mse1 + mse2
    end

    return mse
end
objective (generic function with 1 method)

Run the optimisation

Create the HyperTuning Scenario and define some ranges for the initial guess parameters (these could be made wider if needed).

scenario = Scenario(
    a=(0.1 .. 3.0),
    b=(0.1 .. 3.0),
    c=(-3.0 .. -0.1),
    max_trials=400,
)
Scenario:
          parameters: a, b, c
   space cardinality: Huge!
           instances: 1
          batch_size: 4
             sampler: HyperTuning.BCAPSampler{Random.Xoshiro}
              pruner: HyperTuning.NeverPrune
          max_trials: 400
           max_evals: 400

Run the optimisation, noting that we set the log level to be really high so that we don't get inundated with messages from the hundreds of trials being run, and print the best initial guess that was found.

logger = ConsoleLogger(stderr, Logging.AboveMaxLevel)
best_guess = with_logger(logger) do
    sc = HyperTuning.optimize(objective, scenario)
    best_guess = [sc.best_trial.values[:a], sc.best_trial.values[:b], sc.best_trial.values[:c]]

    return best_guess
end
println("Best guess initial condition: $best_guess")
Best guess initial condition: [1.5828494642895379, 1.0281314725215498, -1.6648340402004687]

Use the best guess

Create a full problem, in this case solving for n=2..16, starting from the best initial guess that we found above.

cse_prob = AsymmetricAfrprogsCSEProblem(
    inin=2,
    maxn=16,
    np=2,
    mc=10000,
    solver_kwargs=(show_trace=Val(false), maxiters=2000, abstol=1e-6),
    solver_initial_guess=best_guess,
    distributions=[Beta(3, 3), Beta(3, 3)],
    knot_refinement_strategy=:even_spacing,
)
AsymmetricAfrprogsCSEProblem(np=2, mc=10000, n=2..16)

Solve the problem.

solutions = compute_cse(cse_prob)
15-element Vector{ConstrainedStrategicEquilibrium.AsymmetricCSESolution}:
 AsymmetricCSESolution(n=02, C_1=(NaN, NaN), C_2=3.67e-07)
 AsymmetricCSESolution(n=03, C_1=(9.30e-04, 9.30e-04), C_2=1.68e-07)
 AsymmetricCSESolution(n=04, C_1=(4.70e-04, 4.70e-04), C_2=5.80e-07)
 AsymmetricCSESolution(n=05, C_1=(2.63e-04, 2.63e-04), C_2=1.20e-07)
 AsymmetricCSESolution(n=06, C_1=(1.91e-04, 1.91e-04), C_2=2.76e-07)
 AsymmetricCSESolution(n=07, C_1=(1.32e-04, 1.31e-04), C_2=6.50e-07)
 AsymmetricCSESolution(n=08, C_1=(1.02e-04, 1.02e-04), C_2=3.27e-07)
 AsymmetricCSESolution(n=09, C_1=(6.88e-05, 6.89e-05), C_2=1.15e-06)
 AsymmetricCSESolution(n=10, C_1=(6.42e-05, 6.43e-05), C_2=3.37e-07)
 AsymmetricCSESolution(n=11, C_1=(6.06e-05, 6.06e-05), C_2=3.71e-08)
 AsymmetricCSESolution(n=12, C_1=(4.46e-05, 4.46e-05), C_2=1.55e-06)
 AsymmetricCSESolution(n=13, C_1=(3.31e-05, 3.31e-05), C_2=9.67e-07)
 AsymmetricCSESolution(n=14, C_1=(3.34e-05, 3.34e-05), C_2=7.51e-07)
 AsymmetricCSESolution(n=15, C_1=(2.68e-05, 2.68e-05), C_2=8.03e-07)
 AsymmetricCSESolution(n=16, C_1=(2.31e-05, 2.31e-05), C_2=9.56e-07)

Compute the BNE too

bnex = Vector{Float64}(undef, 101)
bney = Vector{Float64}(undef, 101)
for m = 1:101
    ti = (m - 1.0) / 100.0
    if ti == 0
        true_bne = 0.0
    else
        true_bne = compute_bne(ti, cse_prob.distributions[1], cse_prob.np)
    end
    bnex[m] = ti
    bney[m] = true_bne
end

Plot the last CSE solution and the BNE

cseplot(solutions[end])
plot!(bnex, bney, label="BNE")

This page was generated using Literate.jl.