Introduction to PyALAF
PyALAF is developed to solve a large range of Active Learning problems by making use of so called acquisition functions. This enables to choose from a broad range of settings for running the Active Learning. PyALAF enables pool based learning as well as population based learning. It also allows to find several suggested data points in one iteration. By the choice of the acquisition function one can control whether the model function that approximates the true data is optimized only close to the maximum in order to find that maximum or whether the full model function is optimized. PyALAF is compatible with most of sklearns regression models and also can use linear regression models.
The usage of PyALAF is very simple. First, we need to import some libraries.
[1]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from PyALAF.models import inv_sphere
from PyALAF.multi_optimize import run_continuous_batch_learning_multi
from PyALAF.aggregation_fn import identity_aggregation_fn as identity
from sklearn.gaussian_process import GaussianProcessRegressor as GPR
from sklearn.gaussian_process.kernels import RBF, WhiteKernel
random_state=41
Disabled warnings
Disabled warnings
Disabled warnings
Now, we create some test data by first setting some parameters. For this basic example, we use only 1D data with a grid spacing of 100 data points in any dimension in the intervall [-2,2].
[2]:
#Parameters for grid
grid_size = 100 #Number of data points per dimension
dimensions = 1 #Number of dimensions
lim = [[-2,],[2,]] #Bounds for each dimension. First lower bounds, than upper bounds
#Create a grid for arbitrary number of dimensions
x = []
[x.append(np.linspace(lim[0][i],lim[1][i], grid_size )) for i in range(dimensions)]
pool = np.meshgrid(*x)
pool = np.array(pool).T
test_pool = pool.reshape(grid_size**dimensions, dimensions)
print('Shape of the test pool: ', test_pool.shape)
Shape of the test pool: (100, 1)
Next, we define a data_model. The data model represents the true underlying data in this case. We sample data from this model, where we can also decide that this data is noisy. Therefore, we set the noise parameter.
[3]:
#Data model
data_model = inv_sphere(d=dimensions, random_state=random_state)
noise = np.sqrt(1e-6)
Then, we define a machine learning model that is used to approximate the true data. Here, we decide for a Gaussian Process Regression model with the standard RBF kernel and an additional WhiteKernel.
[4]:
kernel = RBF(length_scale=0.1, length_scale_bounds=[0.05,10])+WhiteKernel()
regression_model = GPR(kernel)
Now, we are equipped with everything to start the actual active learning routine. But before we call this routine, we need to define some parameters. Most parameters have default values that you can look up in the documentation.
[5]:
initial_samples=5 #Number of initial data points
max_samples=30 #Number of data points that are maximally acquired
batch_size=5 #Collect data points in batches of five before evaluating the model with a test data set
n_repetitions=2 #Repeat the active learning two times
initialization_method = 'random' #Algorithm to collect initial data
optimization_method = 'PSO' #Algorithm to search for the maximum of the acquisition function. Choose from 'PSO' or 'lbfgs'AttributeError
n_jobs = 1 #Number of jobs to start for parallel computing
pso_options = {'c1': 0.5, 'c2': 0.3, 'w': 0.9, 'p':10*dimensions, 'i':50} #Options for particle swarm optimization
active_learning_steps = int((max_samples-initial_samples)/batch_size) #Number of AL steps is calculated automatically
In this example, we want to compare the performance of 4 different acquisition functions, which we define below. In the dictionary, we save the name of the acquisition function together with a hyperparameter alpha. This is only used for the ‘ideal’ algorithm here.
[6]:
acquisition_functions = {'random':[0], 'ideal': [10], 'std': [0], 'GSx':[0]}
To save the results, we create a dictionary that contains an empty data frame for each acquisition function.
[7]:
score_dict = {}
for acquisition_function, alpha_values in acquisition_functions.items():
for alpha in alpha_values:
key = acquisition_function+'_'+str(alpha)
score_dict[key] = pd.DataFrame()
score_dict
[7]:
{'random_0': Empty DataFrame
Columns: []
Index: [],
'ideal_10': Empty DataFrame
Columns: []
Index: [],
'std_0': Empty DataFrame
Columns: []
Index: [],
'GSx_0': Empty DataFrame
Columns: []
Index: []}
No, we run the Active Learning algorithm. We repeat the AL experiment only two times here. However, you can increase n_repetitions to run several AL experiments with different random seeds. Statistics will get better for running the experiment more often. Thus, for getting meaningful results when comparing different algorithms, it is suggested to run the AL experiment several times. Note that in the final application the AL experiment is only run once and without testing the models.
For running the Active Learning it is recommended to use either the run_continuous_batch_learning_multi or run_batch_learning_multi for the population-based or pool-based approach, respectively. Both functions allow in principle to combine multiple objectives via aggregation functions. However, since we are here interested only in a single objective, we chose the identiy aggregation function.
[8]:
%%capture
for i in range(n_repetitions):
random_state += 1
for acquisition_function, alpha_values in acquisition_functions.items():
for alpha in alpha_values:
samples, values, result_dict = run_continuous_batch_learning_multi(
models = [data_model],
aggregation_function = identity,
regression_models = [regression_model],
acquisition_function = acquisition_function,
opt_method = optimization_method,
pool = test_pool,
batch_size=batch_size,
noise=noise,
initial_samples=initial_samples,
active_learning_steps=active_learning_steps,
lim_features=lim,
alpha=alpha,
n_jobs=n_jobs,
random_state=random_state,
calculate_test_metrics=True,
initialization=initialization_method,
pso_options=pso_options
)
result = result_dict['aggregated']
key = acquisition_function+'_'+str(alpha)
score_dict[key] = pd.concat([score_dict[key], result])
2026-01-30 12:13:14,519 - basic_logger - INFO - Setting up Active Learning
2026-01-30 12:13:14,521 - basic_logger - INFO - Noise converted:
2026-01-30 12:13:14,522 - basic_logger - INFO - from 0.001 to [0.001]
2026-01-30 12:13:14,523 - basic_logger - INFO - Test metrics will be calculated.
2026-01-30 12:13:14,526 - basic_logger - INFO - Initialization method: random
2026-01-30 12:13:14,527 - basic_logger - INFO - Initialization finished.
2026-01-30 12:13:14,550 - basic_logger - INFO - Start Active Learning
2026-01-30 12:13:14,553 - basic_logger - INFO - Optimization method: PSO
2026-01-30 12:13:14,554 - basic_logger - INFO - Acquisition function: random
2026-01-30 12:13:14,561 - basic_logger - INFO - Step 1
2026-01-30 12:13:14,659 - basic_logger - INFO - Step 2
2026-01-30 12:13:14,766 - basic_logger - INFO - Step 3
2026-01-30 12:13:14,908 - basic_logger - INFO - Step 4
2026-01-30 12:13:15,008 - basic_logger - INFO - Step 5
2026-01-30 12:13:15,105 - basic_logger - INFO - Finished Active Learning
2026-01-30 12:13:15,108 - basic_logger - INFO - Setting up Active Learning
2026-01-30 12:13:15,110 - basic_logger - INFO - Noise converted:
2026-01-30 12:13:15,111 - basic_logger - INFO - from 0.001 to [0.001]
2026-01-30 12:13:15,111 - basic_logger - INFO - Test metrics will be calculated.
2026-01-30 12:13:15,113 - basic_logger - INFO - Initialization method: random
2026-01-30 12:13:15,114 - basic_logger - INFO - Initialization finished.
2026-01-30 12:13:15,136 - basic_logger - INFO - Start Active Learning
2026-01-30 12:13:15,137 - basic_logger - INFO - Optimization method: PSO
2026-01-30 12:13:15,138 - basic_logger - INFO - Acquisition function: ideal
2026-01-30 12:13:15,139 - basic_logger - INFO - Step 1
As an output we get the acquired data points in the order they were acquired and also the result data frame which we save. You can see how this data frame looks like below. The mean squared error (MSE) the maximum error (MaxE), the mean absolute error (MAE) and the maximum observed value are stored.
[9]:
ave_scores = score_dict['random_0'].groupby('m').mean()
ave_scores
[9]:
| mean_MSE_train | mean_MAE_train | mean_MaxE_train | mean_MSE_test | mean_MAE_test | mean_MaxE_test | max_observation | |
|---|---|---|---|---|---|---|---|
| m | |||||||
| 5.0 | 2.468643 | 1.190516 | 2.867850 | 1.649410 | 1.021930 | 3.051051 | -0.038660 |
| 10.0 | 0.000046 | 0.003899 | 0.010574 | 0.012076 | 0.064867 | 0.291011 | -0.020379 |
| 15.0 | 0.000002 | 0.001037 | 0.002343 | 0.003411 | 0.022224 | 0.164779 | -0.020379 |
| 20.0 | 0.000001 | 0.000910 | 0.001985 | 0.002405 | 0.017744 | 0.147543 | -0.003877 |
| 25.0 | 0.000002 | 0.001151 | 0.003629 | 0.000015 | 0.002096 | 0.021392 | -0.003877 |
| 30.0 | 0.000002 | 0.001173 | 0.003828 | 0.000012 | 0.001913 | 0.019268 | -0.003877 |
We can now for example compare the MSE of the 4 different acquisition functions. We observe that all 3 AL algorithms perform better than just randomly choosing data points.
[10]:
for key, df in score_dict.items():
ave_scores = df.groupby('m').mean()
plt.plot(ave_scores.index, ave_scores['mean_MSE_test'], 'o--', label=key)
plt.legend()
plt.yscale('log')
plt.ylabel('MSE')
plt.xlabel('Data points')
[10]:
Text(0.5, 0, 'Data points')