<jupyter_start><jupyter_text>Active LearningThe class provides tools for active learning of regression models using expected model change (EMC) and query by committee (QBC) methods and approaches for distribution shift alleviations.The central idea behind any active learning approach is to achieve lower prediction errors (higher accuracy) in machine learning models by choosing less but more informative data points.The implementation of this algorithm follows an interactive approach. In other words, we often ask you to provide labels for the selected data points.Here is a list of main methods to carry out an active learning approach:- initialize- deposit- ignore- search- visualize<jupyter_code>import warnings
warnings.filterwarnings('ignore')

from chemml.optimization import ActiveLearning
from chemml.datasets import load_organic_density
import numpy as np
import pandas as pd<jupyter_output><empty_output><jupyter_text>For this tutorial, we load a sample data from ChemML datasets.<jupyter_code>smi, density, features = load_organic_density()
print('labels  : density,    ', type(density), ', shape:', density.shape)
print('features: molecular descriptors,', type(features), ', shape:', features.shape)<jupyter_output>labels  : density,     <class 'pandas.core.frame.DataFrame'> , shape: (500, 1)
features: molecular descriptors, <class 'pandas.core.frame.DataFrame'> , shape: (500, 200)<jupyter_text>Keras model exampleThe current version of the EMC approach only work with Keras deep learning models. You should create a function that returns a Keras model. You can also set the optimizer parameters and compile the model inside the function. Here is a toy example for the Keras model:<jupyter_code>from keras.layers import Input, Dense, Concatenate
from keras.models import Sequential, Model
from keras.optimizers import Adam
from keras.callbacks import EarlyStopping
from keras.initializers import glorot_uniform
from keras import regularizers
from keras import losses
from keras import backend as K
def model_creator(nneurons=features.shape[1], activation=['relu','tanh'], lr = 0.001):
    # branch 1
    b1_in = Input(shape=(nneurons, ), name='inp1')
    l1 = Dense(12, name='l1', activation=activation[0])(b1_in)
    b1_l1 = Dense(6, name='b1_l1', activation=activation[0])(l1)
    b1_l2 = Dense(3, name='b1_l2', activation=activation[0])(b1_l1)
    # branch 2
    b2_l1 = Dense(16, name='b2_l1', activation=activation[1])(l1)
    b2_l2 = Dense(8, name='b2_l2', activation=activation[1])(b2_l1)
    # merge branches
    merged = Concatenate(name='merged')([b1_l2, b2_l2])
    # linear output
    out = Dense(1, name='outp', activation='linear')(merged)
    ###
    model = Model(inputs = b1_in, outputs = out)
    adam = Adam(lr=lr, beta_1=0.9, beta_2=0.999, epsilon=1e-8, decay=0.0)
    model.compile(optimizer = adam,
                  loss = 'mean_squared_error',
                  metrics=['mean_absolute_error'])
    return model
m = model_creator()
m.summary()<jupyter_output>Model: "model"
__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
inp1 (InputLayer)               [(None, 200)]        0                                            
__________________________________________________________________________________________________
l1 (Dense)                      (None, 12)           2412        inp1[0][0]                       
__________________________________________________________________________________________________
b1_l1 (Dense)                   (None, 6)            78          l1[0][0]                         
__________________________________________________________________________________________________
b2_l1 (Dense)                   (None, 16)           208         l1[0][0]                     [...]<jupyter_text>To view workflow, you need to install `pydot`. For example with `pip install pydot`. Then run the following code<jupyter_code>from IPython.display import SVG
from keras.utils.vis_utils import model_to_dot

SVG(model_to_dot(m).create(prog='dot', format='svg'))<jupyter_output><empty_output><jupyter_text>Initialize active learning: EMCYou first need to instantiate the active learning class using the model_creator function and the pool of candidates (i.e., U) represented by your choice of features.<jupyter_code>al = ActiveLearning(
           model_creator = model_creator,
           U = features,
           target_layer = ['b1_l2', 'b2_l2'], # we could also enter 'merged' layer because it only does the concatenation
           train_size = 50,  # 50 initial training data will be selected randomly
           test_size = 50,  # 50 independent test data will be selected randomly for the entire search
           batch_size = [20,0,0] # at each round of AL, labels for 20 candidates will be queried using EMC method
           )<jupyter_output><empty_output><jupyter_text>you first need to initialize the method to get the indices for the training and test data:<jupyter_code>tr_ind, te_ind = al.initialize()<jupyter_output><empty_output><jupyter_text>The array of queried points are always available via the queries attribute<jupyter_code>al.queries<jupyter_output><empty_output><jupyter_text>Once you acquire labels for the queried data (e.g., by experiment or simulation) give them to the al object using deposit method. This can be done partially or all at once.<jupyter_code>al.deposit(tr_ind, density.values.reshape(-1,1)[tr_ind])
al.deposit(te_ind, density.values.reshape(-1,1)[te_ind])
print (al.X_train.shape, al.Y_train.shape)
print (al.X_test.shape, al.Y_test.shape)
al.queries<jupyter_output><empty_output><jupyter_text>You can not initialize the method or deposit any more data once you empty the list of queries by providing requested data.<jupyter_code>#al.initialize()
# the above code raises following error message:
# ValueError: The class has been already initialized and it can not be initialized again!<jupyter_output><empty_output><jupyter_text>Start the active search<jupyter_code>q = al.search(n_evaluation=1, epochs=10, verbose=0)
q
al.queries
al.results<jupyter_output><empty_output><jupyter_text>now you need to deposit the labels for the first batch of queried data using EMC approach.<jupyter_code>al.deposit(q, density.values.reshape(-1,1)[q])
q = al.search(epochs=10, verbose=0)
q<jupyter_output><empty_output><jupyter_text>Here is a data frame of the results in terms of mean absolute error, root mean square error, and r-squared<jupyter_code>al.results<jupyter_output><empty_output><jupyter_text>Get a baseline: random samplingYou can get a baseline learning curve by running a `random_search`. This method starts from the initial sets of training and test data, and adds the same number of data as batch_size every round.<jupyter_code>al.random_search(density.values.reshape(-1,1), n_evaluation=2, epochs=10, verbose=0)<jupyter_output>WARNING:tensorflow:5 out of the last 23 calls to <function Model.make_predict_function.<locals>.predict_function at 0x7f110b915940> triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has experimental_relax_shapes=True option that relaxes argument shapes that can avoid unnecessary retracing. For (3), please refer to https://www.tensorflow.org/guide/function#controlling_retracing and https://www.tensorflow.org/api_docs/python/tf/function for  more details.<jupyter_text>The results of random sampling is also accessible using `random_results` attribute:<jupyter_code>al.random_results<jupyter_output><empty_output><jupyter_text>Visualize distributionsWe keep track of many metrics to analyze your active learning search. You can store the info during your search or visualize them at each round. The `visualize` method is able to return a dictionary of matplotlib figures for the distribution of features and labels, and the learning curves.<jupyter_code>plots = al.visualize(density.values.reshape(-1,1)) # Let's provide all the labels, now that we have all of them
plots<jupyter_output><empty_output><jupyter_text>The distribution of the first principal component:<jupyter_code>%matplotlib inline
plots['dist_pc'][0]<jupyter_output><empty_output><jupyter_text>The distribution of the labels:<jupyter_code>plots['dist_y'][0]<jupyter_output><empty_output><jupyter_text>The learning curves:<jupyter_code>plots['learning_curve']<jupyter_output><empty_output><jupyter_text>Search in a loopThis is how we use this module in a loop:<jupyter_code>import os
warnings.filterwarnings('ignore')

out_dir = 'al_toy_example' # the arbitrary path to the output directory
if not os.path.exists(out_dir):
    os.makedirs(out_dir)

# all data sets must be 2-dimensional
y = density.values.reshape(-1,1)

al = ActiveLearning(
           model_creator = model_creator,
           U = features,
           target_layer = ['b1_l2', 'b2_l2'], # we could also enter 'merged' layer because it only does the concatenation
           train_size = 100,  # 100 initial training data will be selected randomly
           test_size = 50,  # 50 independent test data will be selected randomly for the entire search
           batch_size = [50,0,0] # at each round of AL, labels for 20 candidates will be queried using EMC method
           )

tr_ind, te_ind = al.initialize()


al.deposit(tr_ind, y[tr_ind])
al.deposit(te_ind, y[te_ind])


while al.query_number < 5:
    early_stopping = EarlyStopping(monitor='val_loss', min_delta=1e-6, patience=20, verbose=0, mode='auto')
    tr_ind = al.search(n_evaluation=3, ensemble='kfold', n_ensemble=3, normalize_input=True, normalize_internal=False,
    batch_size=32, epochs=5, verbose=0)#, callbacks=[early_stopping],validation_split=0.1)

    al.results.to_csv(out_dir+'/emc.csv',index=False)

    pd.DataFrame(al.train_indices).to_csv(out_dir+'/train_indices.csv',index=False)
    pd.DataFrame(al.test_indices).to_csv(out_dir+'/test_indices.csv',index=False)

    al.deposit(tr_ind, y[tr_ind])

    # you can run random search later but, I need it to be in my learning curve
    al.random_search(y, n_evaluation=3, random_state=13, batch_size=32, epochs=5, verbose=0)#, callbacks=[early_stopping],validation_split=0.05)

    al.random_results.to_csv(out_dir+'/random.csv',index=False)

    plots = al.visualize(y)
    if not os.path.exists(out_dir+"/plots"):
        os.makedirs(out_dir+"/plots")

    plots['dist_pc'][0].savefig(out_dir+"/plots/dist_pc_0_%i.png"%al.query_number , close = True, verbose = True)
    plots['dist_y'][0].savefig(out_dir+"/plots/dist_y_0_%i.png"%al.query_number , close = True, verbose = True)
    plots['learning_curve'].savefig(out_dir+"/plots/lcurve_%i.png"%al.query_number, close = True, verbose = True)

al.results
al.random_results
plots = al.visualize(y)
plots['learning_curve']<jupyter_output><empty_output><jupyter_text>Create a videoIf you store all the plots at each round you will be able to create a video similar to these ones:<jupyter_code>from IPython.display import HTML
from base64 import b64encode
with open("images/dist_y.mov", "rb") as f:
    video = f.read()
video_encoded = b64encode(video).decode('ascii')
video_tag = '<video controls alt="test" src="data:video/x-m4v;base64,{0}">'.format(video_encoded)
HTML(data=video_tag)
from IPython.display import HTML
from base64 import b64encode
with open("images/lcurve.mov", "rb") as f:
    video = f.read()
video_encoded = b64encode(video).decode('ascii')
video_tag = '<video controls alt="test" src="data:video/x-m4v;base64,{0}">'.format(video_encoded)
HTML(data=video_tag)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10387661525635682, 'fraction_numerical': 0.01992496873697374, 'mean_word_length': 4.9298072170044485, 'pattern_counts': {'":': 0, '<': 85, '<?xml version=': 0, '>': 84, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '20', 'dir': 'jupyter-structured-clean-dedup', 'id': '190768', 'license': 'BSD-3-Clause', 'n_tokens_mistral': 3767, 'n_tokens_neox': 3570, 'n_words': 1064, 'path': 'docs/ipython_notebooks/active_model_based.ipynb', 'repo_name': 'iamchetry/DataChallenge-Fall2021', 'size': '11995'}