Using a Framework to Generate Results

Preamble

In [1]:
# used to create block diagrams
%reload_ext xdiag_magic
%xdiag_output_format svg
    
import numpy as np                   # for multi-dimensional containers
import pandas as pd                  # for DataFrames
import plotly.graph_objects as go    # for data visualisation
import plotly.io as pio              # to set shahin plot layout
import platypus as plat              # multi-objective optimisation framework

pio.templates['shahin'] = pio.to_templated(go.Figure().update_layout(legend=dict(orientation="h",y=1.1, x=.5, xanchor='center'),margin=dict(t=0,r=0,b=40,l=40))).layout.template
pio.templates.default = 'shahin'

Introduction

When preparing to implement multi-objective optimisation experiments, it's often more convenient to use a ready-made framework/library instead of programming everything from scratch. There are many libraries and frameworks that have been implemented in many different programming languages. With our focus on multi-objective optimisation, our choice is an easy one. We will choose Platypus which has a focus on multi-objective problems and optimisation.

Platypus is a framework for evolutionary computing in Python with a focus on multiobjective evolutionary algorithms (MOEAs). It differs from existing optimization libraries, including PyGMO, Inspyred, DEAP, and Scipy, by providing optimization algorithms and analysis tools for multiobjective optimization.

In this section, we will use the Platypus framework to optimise solutions to an test function using the Non-dominated Sorting Genetic Algorithm II (NSGA-II)1, a fast and elitist multi-objective genetic algorithm.

Approximating DTLZ2 Solutions using NSGA-II

In this section, we will using the Platypus implementation of NSGA-II to generate solutions for the DTLZ2 test problem2. The DTLZ2 test problem comes from the DTLZ test suite, which consists of box-constrained continuous and scalable multi-objective problems.

Using Platypus, we will instantiate a DTLZ2 test problem object using its constructor with no parameters, this means our DTLZ2 problem will using the default configurations.

In [2]:
problem = plat.DTLZ2()

Next we want to create an instance of the NSGA-II optimiser, and we'll pass in our problem object as a parameter for its configurations.

In [3]:
algorithm = plat.NSGAII(problem)

Now we can execute the optimisation process. Let's give the optimiser a budget of 10,000 function evaluations as a termination criterion. This may take some time to complete depending on your processor speed and the number of function evaluations.

In [4]:
algorithm.run(10000)

Finally, we can display the results. In this case we will be printing the objective values for each solution in the final population of the above execution.

In [5]:
for solution in algorithm.result:
    print(solution.objectives)
[7.012780087152611e-07, 1.0002826357967725]
[1.0028544080058333, 9.872464448633192e-06]
[0.4250247469353424, 0.9061827211108614]
[0.4587039918811783, 0.8888609789882984]
[0.7750538928590215, 0.6326249003742426]
[0.6398503189902117, 0.7693241174793044]
[0.28691799535616935, 0.9593475295570062]
[0.7981748930715649, 0.6076816327837469]
[0.8263855478267474, 0.5683877424183852]
[0.9683156597166999, 0.25502373932403316]
[0.995093353248033, 0.10096386122544034]
[0.7128076576508451, 0.7026524231802718]
[0.8390626596028963, 0.545766865043768]
[0.6244899869451896, 0.7862129914959102]
[0.9922287079349005, 0.12876227472830168]
[0.9875784935332796, 0.1623772681769986]
[0.3687376890634976, 0.930957171956034]
[0.3464132183044152, 0.9387043472171385]
[0.745620279634122, 0.670668342226853]
[0.9157247565770158, 0.4031838930163541]
[0.5743063321958009, 0.820172297509405]
[0.17586810042596923, 0.9852922409792404]
[0.4100479846209629, 0.9131041795582252]
[0.39480179161104145, 0.9192137999661805]
[0.7269600745791415, 0.6873947169656788]
[0.6564703890432988, 0.7549611936640394]
[0.8164066066108078, 0.5812377235589445]
[0.5074052176881336, 0.8621217484405559]
[0.9611184732482623, 0.2778451025146444]
[0.7022731230085696, 0.7127045397759589]
[0.9833212562246189, 0.1858164850855827]
[0.8889258861734384, 0.45893104272913665]
[0.7671029498261175, 0.6420388459857566]
[0.9340438308309713, 0.35893924103908803]
[0.8811650440637323, 0.4752972037247809]
[0.2571944557344648, 0.9668479484797228]
[0.9749284486515571, 0.22850857252589832]
[0.9798122455965437, 0.20380461957206156]
[0.6941491721912093, 0.7249950088587878]
[0.38045398213422393, 0.9263455480549595]
[0.9393552012574945, 0.34487103812950637]
[0.9438649440217703, 0.33102756260512695]
[0.49574676674755175, 0.869168440829751]
[0.9076046773145894, 0.42030553717641456]
[0.3050367034625455, 0.9544908317433823]
[0.9990570987349179, 0.04701801787340113]
[0.5856405047679037, 0.811136061574414]
[1.000123118171203, 0.020446083836859173]
[0.6718706857751435, 0.7410401354526508]
[0.4712567113509699, 0.882212897452394]
[0.14789689243412452, 0.9897058304065288]
[0.1952751355645839, 0.9813826471293661]
[0.7330096078749667, 0.6805786887602322]
[0.5599879302675519, 0.8289442075979966]
[0.08059551484702634, 0.9974036784527134]
[0.9976814131788599, 0.07405392258597272]
[0.8610045967569003, 0.510202349649188]
[0.612679085568307, 0.7915829607802869]
[0.9216769512534915, 0.38928265772787896]
[0.5534580248993545, 0.834313119567844]
[0.9723356532813964, 0.2361339484469707]
[0.8073647157555516, 0.5912977037540145]
[0.22230541920270455, 0.9756964402166974]
[0.06576981505863955, 0.9981657409270236]
[0.20563709933138088, 0.9792587876206107]
[0.15856398803767388, 0.987998602462894]
[0.5402998779007229, 0.8426804886365727]
[0.9588610919964855, 0.2866969953692897]
[0.948854263778345, 0.3169504240443748]
[0.6024023801892231, 0.7984147575175506]
[0.023161682917784043, 0.9998674454520319]
[0.33460893412593035, 0.9428271162812558]
[0.04940558327855972, 0.9989595575674314]
[0.844531459336022, 0.5371211515008015]
[0.595077603252722, 0.8042118158239959]
[0.6642641091152928, 0.7516647821408784]
[0.9541584501517283, 0.3011933589660482]
[0.8985347926240118, 0.43942838049551136]
[0.8957100460729813, 0.4493495323218722]
[0.8712957738669126, 0.4933408702616852]
[0.9281354143778197, 0.3736583390309523]
[0.9041992819745898, 0.42872774181278134]
[0.1270983238250116, 0.9925614420185604]
[0.9999281454379647, 0.039587847203672165]
[0.7546186507863776, 0.6566846642970142]
[0.7565059917164899, 0.6543282321303772]
[0.03753333284505802, 0.9997143896170751]
[0.8577245418486197, 0.5143136817634187]
[0.8753189638430146, 0.48533026646092564]
[0.5218769255128786, 0.8535964629505337]
[0.8011755229804831, 0.5991248331521347]
[0.0987274119940958, 0.9961774634746888]
[0.007812787575315061, 1.0001311804821171]
[0.997171090202464, 0.08215061495760224]
[1.0001869742042342, 0.0035411615347610242]
[0.1070118173824458, 0.994696849886557]
[0.12319397435990696, 0.9927396738836732]
[0.9262327523011945, 0.3800989374488773]
[0.23916752797650104, 0.9715752369052475]
[0.9816584001846514, 0.19713060365417864]

Visualising Solutions in Objective Space

In the last section, we concluded by printing the objective vlaues for every solution. This information will be easier to digest using a plot, so let's quickly put the data into a Pandas DataFrame and then use Plotly to create a scatterplot. Let's start by moving our Platypus data structure to a DataFrame.

In [6]:
objective_values = np.empty((0, 2))

for solution in algorithm.result:
    y = solution.objectives
    objective_values = np.vstack([objective_values, y])
    
# convert to DataFrame
objective_values = pd.DataFrame(objective_values, columns=['f1','f2'])

All done, we can have a peek at our DataFrame to make sure we've not made any obvious mistakes.

In [7]:
objective_values
Out[7]:
f1 f2
0 7.012780e-07 1.000283
1 1.002854e+00 0.000010
2 4.250247e-01 0.906183
3 4.587040e-01 0.888861
4 7.750539e-01 0.632625
... ... ...
95 1.070118e-01 0.994697
96 1.231940e-01 0.992740
97 9.262328e-01 0.380099
98 2.391675e-01 0.971575
99 9.816584e-01 0.197131

100 rows × 2 columns

Looking good. All that we have left is the scatter plot.

In [8]:
fig = go.Figure()
fig.add_scatter(x=objective_values.f1, y=objective_values.f2, mode='markers')
fig.show()