Options Handler Tutorial

Varying options with time is a well regarded technique in particle swarm optimization for faster convergence and better solutions. This class exposes methods to do the same.

In this example, we will demonstrate some common variation techniques along with some visualisation. - oh_strategy: a dictionary containing the strategies for each option - end_opts: a dictionary containing the ending options for each option - plot_cost_history: for plotting the cost history of a swarm given a matrix - plot_contour: for plotting swarm trajectories of a 2D-swarm in two-dimensional space

[1]:
# Import modules
import matplotlib.pyplot as plt
import numpy as np
from IPython.display import Image

# Import PySwarms
import pyswarms as ps
from pyswarms.utils.functions import single_obj as fx
from pyswarms.utils.plotters import (plot_cost_history, plot_contour)

from pyswarms.backend.handlers import OptionsHandler

Let’s create some optimizers for comparison. Here, we’re going to use Global-best PSO to find the minima of a sphere function. As usual, we simply create an instance of its class pyswarms.single.GlobalBestPSO by passing the required parameters that we will use. Then, we’ll call the optimize() method for 100 iterations for both and visualise the results

[2]:
options = {'c1':0.5, 'c2':0.3, 'w':0.9}  # starting options
optimizer_without_handle=ps.single.GlobalBestPSO(n_particles=50, dimensions=2, options=options)
optimizer_with_handle = ps.single.GlobalBestPSO(n_particles=50, dimensions=2, options=options, oh_strategy={"w":'exp_decay', 'c1':'lin_variation'})

cost, pos = optimizer_without_handle.optimize(fx.sphere, iters=100)
cost_h, pos_h = optimizer_with_handle.optimize(fx.sphere, iters=100)

2020-12-11 19:23:18,434 - pyswarms.single.global_best - INFO - Optimize for 100 iters with {'c1': 0.5, 'c2': 0.3, 'w': 0.9}
pyswarms.single.global_best: 100%|██████████|100/100, best_cost=7.95e-10
2020-12-11 19:23:18,540 - pyswarms.single.global_best - INFO - Optimization finished | best cost: 7.946552028624106e-10, best pos: [ 2.74938126e-05 -6.22458616e-06]
2020-12-11 19:23:18,541 - pyswarms.single.global_best - INFO - Optimize for 100 iters with {'c1': 0.5, 'c2': 0.3, 'w': 0.9}
pyswarms.single.global_best: 100%|██████████|100/100, best_cost=7.99e-28
2020-12-11 19:23:18,648 - pyswarms.single.global_best - INFO - Optimization finished | best cost: 7.987498732542365e-28, best pos: [ 7.14102116e-15 -2.73451219e-14]
[ ]:

Comparing the cost history

To plot the cost history, we simply obtain the cost_history from the optimizer class and pass it to the cost_history function. In addition, we can obtain the following histories from the same class: - mean_neighbor_history: average local best history of all neighbors throughout optimization - mean_pbest_history: average personal best of the particles throughout optimization

[3]:
fig, (ax, ax_h) = plt.subplots(ncols=2, nrows=1)
fig.set_size_inches(15,7)
plot_cost_history(ax=ax, cost_history=optimizer_without_handle.cost_history)
plot_cost_history(ax=ax_h, cost_history=optimizer_with_handle.cost_history)
ax.set_title("Cost history without inertia decay")
ax_h.set_title("Cost history with exponential intertia decay")
plt.show()
../../_images/examples_tutorials_options_handler_6_0.svg

The rapid decay of the inertia weight contributes significantly to the overall best cost and position. It also converges a lot faster.

The next part shows the explanation for this with an animation

Comparing animations

The plotters module offers two methods to perform animation, plot_contour(). This method plot the particles in a 2-D space.

The objective function contours are added using the Mesher class.

[4]:
from pyswarms.utils.plotters.formatters import Mesher
# Initialize mesher with sphere function
m = Mesher(func=fx.sphere)

Plotting in 2-D space

We can obtain the swarm’s position history using the pos_history attribute from the optimizer instance. To plot a 2D-contour, simply pass this together with the Mesher to the plot_contour() function. In addition, we can also mark the global minima of the sphere function, (0,0), to visualize the swarm’s “target”.

[5]:
%%capture

# Make and save animation
animation = plot_contour(pos_history=optimizer_without_handle.pos_history,
                         mesher=m,
                         mark=(0,0))
animation_h = plot_contour(pos_history=optimizer_with_handle.pos_history,
                         mesher=m,
                         mark=(0,0))
# Enables us to view it in a Jupyter notebook
animation.save('ani.gif', writer='imagemagick', fps=10)

animation_h.save('ani_h.gif', writer='imagemagick', fps=10)


2020-12-11 19:23:22,596 - matplotlib.animation - WARNING - MovieWriter imagemagick unavailable; using Pillow instead.
2020-12-11 19:23:22,597 - matplotlib.animation - INFO - Animation.save using <class 'matplotlib.animation.PillowWriter'>
2020-12-11 19:23:30,971 - matplotlib.animation - WARNING - MovieWriter imagemagick unavailable; using Pillow instead.
2020-12-11 19:23:30,973 - matplotlib.animation - INFO - Animation.save using <class 'matplotlib.animation.PillowWriter'>

Compare the two animations. Observe the convergence time and particle overshoot in both figures.

Left: Without handle

Right: With handle

[ ]:

Customizing ending options

As of the current version(1.2.0), you’ll need to create your own optimization loop to keep custom ending options. The next block shows a basic implementation of this without logging etc.

[6]:
from pyswarms.backend.operators import compute_pbest, compute_objective_function
[10]:
def optimize(objective_func, maxiters, oh_strategy,start_opts, end_opts):
    opt = ps.single.GlobalBestPSO(n_particles=50, dimensions=2, options=start_opts, oh_strategy=oh_strategy)

    swarm = opt.swarm
    opt.bh.memory = swarm.position
    opt.vh.memory = swarm.position
    swarm.pbest_cost = np.full(opt.swarm_size[0], np.
    inf)

    for i in range(maxiters):
        # Compute cost for current position and personal best
        swarm.current_cost =  compute_objective_function(swarm, objective_func)
        swarm.pbest_pos, swarm.pbest_cost = compute_pbest(swarm)

        # Set best_cost_yet_found for ftol
        best_cost_yet_found = swarm.best_cost
        swarm.best_pos, swarm.best_cost = opt.top.compute_gbest(swarm)
        # Perform options update
        swarm.options = opt.oh( opt.options, iternow=i, itermax=maxiters, end_opts=end_opts )
        print("Iteration:", i," Options: ", swarm.options)    # print to see variation
        # Perform velocity and position updates
        swarm.velocity = opt.top.compute_velocity(
            swarm, opt.velocity_clamp, opt.vh, opt.bounds
        )
        swarm.position = opt.top.compute_position(
            swarm, opt.bounds, opt.bh
        )
    # Obtain the final best_cost and the final best_position
    final_best_cost = swarm.best_cost.copy()
    final_best_pos = swarm.pbest_pos[
        swarm.pbest_cost.argmin()
    ].copy()
    return final_best_cost, final_best_pos

In the next cell, you can play around with the start and end options, maximum iterations and the function itself to compare the outputs like above.

[12]:
function = fx.rosenbrock    # optimum at [1,1]
maxiters = 100
start_opts = {'c1':2.5, 'c2':0.5, 'w':0.9}
end_opts= {'c1':0.5, 'c2':2.5, 'w':0.4}     # Ref:[1]
oh_strategy={ "w":'exp_decay', "c1":'nonlin_mod',"c2":'lin_variation'}

cost, pos=optimize(function, maxiters, oh_strategy, start_opts, end_opts)

print("Best cost = ", cost)
print("Best position = ", pos)
Iteration: 0  Options:  {'c1': 2.5, 'c2': 0.5, 'w': 0.8154845485377135}
Iteration: 1  Options:  {'c1': 2.476024064289623, 'c2': 0.52, 'w': 0.7638427274927154}
Iteration: 2  Options:  {'c1': 2.4520965166602724, 'c2': 0.54, 'w': 0.7212425273766464}
Iteration: 3  Options:  {'c1': 2.428217751727513, 'c2': 0.56, 'w': 0.6855550181382926}
Iteration: 4  Options:  {'c1': 2.4043881714225273, 'c2': 0.5800000000000001, 'w': 0.6552602432446853}
Iteration: 5  Options:  {'c1': 2.3806081852052783, 'c2': 0.6000000000000001, 'w': 0.6292465903435571}
Iteration: 6  Options:  {'c1': 2.3568782102861965, 'c2': 0.6200000000000001, 'w': 0.6066838570323415}
Iteration: 7  Options:  {'c1': 2.3331986718568167, 'c2': 0.6399999999999999, 'w': 0.5869404779457066}
Iteration: 8  Options:  {'c1': 2.3095700033298305, 'c2': 0.6599999999999999, 'w': 0.5695280957195421}
Iteration: 9  Options:  {'c1': 2.285992646589043, 'c2': 0.6799999999999999, 'w': 0.5540635587607113}
Iteration: 10  Options:  {'c1': 2.2624670522497583, 'c2': 0.7, 'w': 0.5402423141269194}
Iteration: 11  Options:  {'c1': 2.2389936799301475, 'c2': 0.72, 'w': 0.527819424452714}
Iteration: 12  Options:  {'c1': 2.215572998534194, 'c2': 0.74, 'w': 0.5165957922398924}
Iteration: 13  Options:  {'c1': 2.192205486546836, 'c2': 0.76, 'w': 0.5064080078932764}
Iteration: 14  Options:  {'c1': 2.1688916323420004, 'c2': 0.78, 'w': 0.4971207626134709}
Iteration: 15  Options:  {'c1': 2.1456319345042183, 'c2': 0.8, 'w': 0.4886211049862233}
Iteration: 16  Options:  {'c1': 2.1224269021646167, 'c2': 0.8200000000000001, 'w': 0.48081404179727616}
Iteration: 17  Options:  {'c1': 2.0992770553520845, 'c2': 0.8400000000000001, 'w': 0.4736191317674961}
Iteration: 18  Options:  {'c1': 2.076182925360504, 'c2': 0.8600000000000001, 'w': 0.46696782158333405}
Iteration: 19  Options:  {'c1': 2.053145055132976, 'c2': 0.8799999999999999, 'w': 0.4608013430638035}
Iteration: 20  Options:  {'c1': 2.030163999664059, 'c2': 0.8999999999999999, 'w': 0.455069038916464}
Iteration: 21  Options:  {'c1': 2.007240326421086, 'c2': 0.9199999999999999, 'w': 0.44972701900111317}
Iteration: 22  Options:  {'c1': 1.984374615785726, 'c2': 0.94, 'w': 0.4447370737567352}
Iteration: 23  Options:  {'c1': 1.961567461517037, 'c2': 0.96, 'w': 0.4400657894042114}
Iteration: 24  Options:  {'c1': 1.938819471237338, 'c2': 0.98, 'w': 0.4356838227118487}
Iteration: 25  Options:  {'c1': 1.916131266942353, 'c2': 1.0, 'w': 0.43156530287330336}
Iteration: 26  Options:  {'c1': 1.893503485537166, 'c2': 1.02, 'w': 0.4276873353495389}
Iteration: 27  Options:  {'c1': 1.87093677939967, 'c2': 1.04, 'w': 0.4240295880363697}
Iteration: 28  Options:  {'c1': 1.8484318169733072, 'c2': 1.06, 'w': 0.4205739443113132}
Iteration: 29  Options:  {'c1': 1.8259892833910558, 'c2': 1.08, 'w': 0.4173042107280646}
Iteration: 30  Options:  {'c1': 1.8036098811327728, 'c2': 1.1, 'w': 0.41420586961014694}
Iteration: 31  Options:  {'c1': 1.7812943307181757, 'c2': 1.12, 'w': 0.41126586872702486}
Iteration: 32  Options:  {'c1': 1.759043371437939, 'c2': 1.14, 'w': 0.4084724417486748}
Iteration: 33  Options:  {'c1': 1.7368577621255956, 'c2': 1.16, 'w': 0.40581495436664605}
Iteration: 34  Options:  {'c1': 1.7147382819731596, 'c2': 1.18, 'w': 0.4032837719146377}
Iteration: 35  Options:  {'c1': 1.6926857313936474, 'c2': 1.2, 'w': 0.4008701450750272}
Iteration: 36  Options:  {'c1': 1.6707009329339555, 'c2': 1.22, 'w': 0.39856611086172655}
Iteration: 37  Options:  {'c1': 1.6487847322418678, 'c2': 1.24, 'w': 0.39636440655637023}
Iteration: 38  Options:  {'c1': 1.626937999091311, 'c2': 1.26, 'w': 0.3942583946688905}
Iteration: 39  Options:  {'c1': 1.6051616284703591, 'c2': 1.28, 'w': 0.3922419973141236}
Iteration: 40  Options:  {'c1': 1.583456541736921, 'c2': 1.3, 'w': 0.3903096386581006}
Iteration: 41  Options:  {'c1': 1.5618236878475174, 'c2': 1.32, 'w': 0.3884561943027294}
Iteration: 42  Options:  {'c1': 1.5402640446650828, 'c2': 1.34, 'w': 0.3866769466548326}
Iteration: 43  Options:  {'c1': 1.518778620352329, 'c2': 1.36, 'w': 0.3849675454721789}
Iteration: 44  Options:  {'c1': 1.497368454857856, 'c2': 1.38, 'w': 0.3833239729009879}
Iteration: 45  Options:  {'c1': 1.4760346215029587, 'c2': 1.4, 'w': 0.38174251242096996}
Iteration: 46  Options:  {'c1': 1.454778228677894, 'c2': 1.42, 'w': 0.3802197211989471}
Iteration: 47  Options:  {'c1': 1.4336004216573355, 'c2': 1.44, 'w': 0.3787524054234573}
Iteration: 48  Options:  {'c1': 1.4125023845457787, 'c2': 1.46, 'w': 0.37733759825283403}
Iteration: 49  Options:  {'c1': 1.39148534236489, 'c2': 1.48, 'w': 0.3759725400600326}
Iteration: 50  Options:  {'c1': 1.3705505632961241, 'c2': 1.5, 'w': 0.3746546607005046}
Iteration: 51  Options:  {'c1': 1.349699361093501, 'c2': 1.52, 'w': 0.3733815635660021}
Iteration: 52  Options:  {'c1': 1.3289330976831786, 'c2': 1.54, 'w': 0.3721510112183664}
Iteration: 53  Options:  {'c1': 1.3082531859684736, 'c2': 1.56, 'w': 0.3709609124240106}
Iteration: 54  Options:  {'c1': 1.2876610928612768, 'c2': 1.58, 'w': 0.3698093104326385}
Iteration: 55  Options:  {'c1': 1.2671583425634432, 'c2': 1.6, 'w': 0.36869437236336694}
Iteration: 56  Options:  {'c1': 1.2467465201247816, 'c2': 1.62, 'w': 0.3676143795783189}
Iteration: 57  Options:  {'c1': 1.226427275307758, 'c2': 1.6400000000000001, 'w': 0.36656771893834633}
Iteration: 58  Options:  {'c1': 1.2062023267930964, 'c2': 1.6600000000000001, 'w': 0.36555287484817117}
Iteration: 59  Options:  {'c1': 1.1860734667651598, 'c2': 1.6800000000000002, 'w': 0.3645684220091819}
Iteration: 60  Options:  {'c1': 1.1660425659214986, 'c2': 1.7, 'w': 0.3636130188076487}
Iteration: 61  Options:  {'c1': 1.146111578957366, 'c2': 1.72, 'w': 0.36268540127440874}
Iteration: 62  Options:  {'c1': 1.126282550583548, 'c2': 1.74, 'w': 0.36178437755931375}
Iteration: 63  Options:  {'c1': 1.1065576221447462, 'c2': 1.76, 'w': 0.3609088228700637}
Iteration: 64  Options:  {'c1': 1.0869390389162645, 'c2': 1.78, 'w': 0.360057674830598}
Iteration: 65  Options:  {'c1': 1.0674291581692645, 'c2': 1.8, 'w': 0.35922992921908464}
Iteration: 66  Options:  {'c1': 1.0480304581097744, 'c2': 1.8199999999999998, 'w': 0.35842463604983676}
Iteration: 67  Options:  {'c1': 1.0287455478145502, 'c2': 1.8399999999999999, 'w': 0.3576408959672543}
Iteration: 68  Options:  {'c1': 1.0095771783084766, 'c2': 1.8599999999999999, 'w': 0.3568778569232281}
Iteration: 69  Options:  {'c1': 0.9905282549543739, 'c2': 1.88, 'w': 0.3561347111123839}
Iteration: 70  Options:  {'c1': 0.9716018513579736, 'c2': 1.9, 'w': 0.35541069214215554}
Iteration: 71  Options:  {'c1': 0.9528012250299461, 'c2': 1.92, 'w': 0.35470507241699134}
Iteration: 72  Options:  {'c1': 0.9341298350951458, 'c2': 1.94, 'w': 0.35401716071805106}
Iteration: 73  Options:  {'c1': 0.9155913623992082, 'c2': 1.96, 'w': 0.3533462999615839}
Iteration: 74  Options:  {'c1': 0.8971897324376921, 'c2': 1.98, 'w': 0.35269186512080497}
Iteration: 75  Options:  {'c1': 0.8789291416275995, 'c2': 2.0, 'w': 0.35205326129754305}
Iteration: 76  Options:  {'c1': 0.8608140875614461, 'c2': 2.02, 'w': 0.35142992193123407}
Iteration: 77  Options:  {'c1': 0.8428494040384126, 'c2': 2.04, 'w': 0.350821307133995}
Iteration: 78  Options:  {'c1': 0.8250403018670246, 'c2': 2.06, 'w': 0.3502269021415575}
Iteration: 79  Options:  {'c1': 0.8073924166953819, 'c2': 2.08, 'w': 0.3496462158707742}
Iteration: 80  Options:  {'c1': 0.7899118654710782, 'c2': 2.1, 'w': 0.3490787795752519}
Iteration: 81  Options:  {'c1': 0.7726053135965205, 'c2': 2.12, 'w': 0.3485241455914203}
Iteration: 82  Options:  {'c1': 0.7554800554745198, 'c2': 2.14, 'w': 0.3479818861680258}
Iteration: 83  Options:  {'c1': 0.7385441120054486, 'c2': 2.16, 'w': 0.34745159237265394}
Iteration: 84  Options:  {'c1': 0.7218063498096469, 'c2': 2.18, 'w': 0.3469328730694381}
Iteration: 85  Options:  {'c1': 0.7052766286755895, 'c2': 2.2, 'w': 0.3464253539626105}
Iteration: 86  Options:  {'c1': 0.6889659862428663, 'c2': 2.2199999999999998, 'w': 0.34592867670100746}
Iteration: 87  Options:  {'c1': 0.6728868726545348, 'c2': 2.24, 'w': 0.34544249803904953}
Iteration: 88  Options:  {'c1': 0.657053453585897, 'c2': 2.26, 'w': 0.34496648905008775}
Iteration: 89  Options:  {'c1': 0.6414820089421402, 'c2': 2.28, 'w': 0.3445003343883481}
Iteration: 90  Options:  {'c1': 0.6261914688960386, 'c2': 2.3, 'w': 0.3440437315960089}
Iteration: 91  Options:  {'c1': 0.6112041531021342, 'c2': 2.32, 'w': 0.34359639045222895}
Iteration: 92  Options:  {'c1': 0.5965468213847226, 'c2': 2.34, 'w': 0.34315803236119463}
Iteration: 93  Options:  {'c1': 0.5822522228837674, 'c2': 2.36, 'w': 0.34272838977648734}
Iteration: 94  Options:  {'c1': 0.5683614862434021, 'c2': 2.38, 'w': 0.3423072056592845}
Iteration: 95  Options:  {'c1': 0.5549280271653059, 'c2': 2.4, 'w': 0.3418942329680971}
Iteration: 96  Options:  {'c1': 0.5420244448704603, 'c2': 2.42, 'w': 0.3414892341779263}
Iteration: 97  Options:  {'c1': 0.529756065178477, 'c2': 2.44, 'w': 0.34109198082688047}
Iteration: 98  Options:  {'c1': 0.518292202077093, 'c2': 2.46, 'w': 0.34070225308844143}
Iteration: 99  Options:  {'c1': 0.5079621434110699, 'c2': 2.48, 'w': 0.3403198393677058}
Best cost =  3.4261311762230905e-13
Best position =  [0.99999942 0.99999884]

Changing options clearly shows better convergence. Just 100 iterations on the rosenbrock function are enough to bring the cost to very low orders of e-10 which is comparable to the ones in academia. [2]

References:

[1] Chih, Mingchang, et al. “Particle swarm optimization with time-varying acceleration coefficients for the multidimensional knapsack problem.” Applied Mathematical Modelling 38.4 (2014): 1338-1350.

[2] Satyanarayana Daggubati, “Comparison of particle swarm optimization variants”