Machine learning validation example

The aim of this page is to provide simple use-case where kernel design is used to build a design of experiments complementary to an existing one, either to enhance a machine learning model or for validation.

import numpy as np
import openturns as ot
import otkerneldesign as otkd
import matplotlib.pyplot as plt
from matplotlib import cm

The following helper class will make plotting easier.

class DrawFunctions:
    def __init__(self):
        dim = 2
        self.grid_size = 100
        lowerbound = [0.] * dim
        upperbound = [1.] * dim
        mesher = ot.IntervalMesher([self.grid_size-1] * dim)
        interval = ot.Interval(lowerbound, upperbound)
        mesh = mesher.build(interval)
        self.nodes = mesh.getVertices()
        self.X0, self.X1 = np.array(self.nodes).T.reshape(2, self.grid_size, self.grid_size)

    def draw_2D_contour(self, title, function=None, distribution=None, colorbar=cm.viridis):
        fig = plt.figure(figsize=(7, 6))
        if distribution is not None:
            Zpdf = np.array(distribution.computePDF(self.nodes)).reshape(self.grid_size, self.grid_size)
            nb_isocurves = 9
            contours = plt.contour(self.X0, self.X1, Zpdf, nb_isocurves, colors='black', alpha=0.6)
            plt.clabel(contours, inline=True, fontsize=8)
        if function is not None:
            Z = np.array(function(self.nodes)).reshape(self.grid_size, self.grid_size)
            plt.contourf(self.X0, self.X1, Z, 18, cmap=colorbar)
            plt.colorbar()
        plt.title(title)
        plt.xlabel("$x_0$")
        plt.ylabel("$x_1$")
        return fig

Regression model of a 2D function

Define the function to be approximated.

function_expression = 'exp((2*x1-1))/5 - (2*x2-1)/5 + ((2*x2-1)^6)/3 + 4*((2*x2-1)^4) - 4*((2*x2-1)^2) + (7/10)*((2*x1-1)^2) + (2*x1-1)^4 + 3/(4*((2*x1-1)^2) + 4*((2*x2-1)^2) + 1)'
irregular_function = ot.SymbolicFunction(['x1', 'x2'], [function_expression])
irregular_function.setName("Irregular")
print(irregular_function)
[x1,x2]->[exp((2*x1-1))/5 - (2*x2-1)/5 + ((2*x2-1)^6)/3 + 4*((2*x2-1)^4) - 4*((2*x2-1)^2) + (7/10)*((2*x1-1)^2) + (2*x1-1)^4 + 3/(4*((2*x1-1)^2) + 4*((2*x2-1)^2) + 1)]

Draw a contours of the 2D function.

d = DrawFunctions()
d.draw_2D_contour("Irregular function", function=irregular_function)
plt.show()
Irregular function

Define the joint input random vector, here uniform since our goal is to build a good regression model on the entire domain.

Build a learning set, for example by Latin Hypercube Sampling.

Build a design of experiments complementary to the existing learning set (e.g., for testing). Note that the kernel herding method could also be used.

test_size = 10
sp = otkd.GreedySupportPoints(distribution, initial_design=x_learn)
x_test = sp.select_design(test_size)
y_test = irregular_function(x_test)

Plot the Learning set (in red) and testing sets (in black with the corresponding construction design order).

fig = d.draw_2D_contour("Irregular function", function=irregular_function)
plt.scatter(x_learn[:, 0], x_learn[:, 1], label='Learning set ($m={}$)'.format(len(x_learn)), marker='$L$', color='C3')
plt.scatter(x_test[:, 0], x_test[:, 1], label='Test set ($n={}$)'.format(len(x_test)), marker='$T$', color='k')
# Test set indexes
[plt.text(x_test[i][0] * 1.02, x_test[i][1] * 1.02, str(i + 1), weight="bold", fontsize=np.max((20 - i, 5))) for i in range(test_size)]
lgd = plt.legend(bbox_to_anchor=(0.5, -0.1), loc='upper center')
plt.tight_layout(pad=1)
plt.show()
Irregular function

Kriging model fit and validation

Build a simple Kriging regression model.

Build a large Monte Carlo reference test set and compute a reference performance metric on it.

xref_test = distribution.getSample(10000)
yref_test = irregular_function(xref_test)
ref_val = ot.MetaModelValidation(xref_test, yref_test, kriging_model)
ref_Q2 = ref_val.computePredictivityFactor()[0]
print("Reference Monte Carlo (n=10000) predictivity coefficient: {:.3}".format(ref_Q2))
Reference Monte Carlo (n=10000) predictivity coefficient: 0.828

In comparison, our test set underestimates the performance of the Kriging model. This situation is expected since the test points are supposed to be far from the learning set.

val = ot.MetaModelValidation(x_test, y_test, kriging_model)
estimated_Q2 = val.computePredictivityFactor()[0]
print("Support points (n={}) predictivity coefficient: {:.3}".format(test_size, estimated_Q2))
Support points (n=10) predictivity coefficient: 0.758

To take this into account, let us compute optimal weights for validation. After applying the weights, the estimated performance is more optimistic and closer to the reference value.

tsw = otkd.TestSetWeighting(x_learn, x_test, sp._candidate_set)
optimal_test_weights = tsw.compute_weights()
weighted_Q2 = 1 - np.mean(np.array(y_test).flatten() * optimal_test_weights) / y_test.computeVariance()[0]
print("Weighted support points (n={}) predictivity coefficient: {:.3}".format(test_size, weighted_Q2))
Weighted support points (n=10) predictivity coefficient: 0.871

Adding test set to learning set

The test set can now be added to the learning set to enhance the Kriging model

Enhanced Kriging - Monte Carlo (n=10000) predictivity coefficient: 0.922

Total running time of the script: ( 0 minutes 1.162 seconds)