A tool for recording and analysing data science experiments


License
MIT
Install
pip install researcher==0.0.50

Documentation

Researcher

"I walk slowly, but I never walk backward." - Abraham Lincon

Researcher makes it easier for data science practitioners to record and reproduce the results of their data science experiments. Conceptually researcher breaks the data science process into distinct experiments. Each experiment has parameters (number of training epochs, dataset used, model architecture, etc.) which differentiate it from other experiments, and results (final validation loss, lowest training accuracy, etc.) which are the observations made when an experiment was run.

The idea behind researcher is that for every experiment you run, you should record both the parameters involved and the results they achieved. This will make it easier to analyse complex interactions between different parameters and re-create experimental conditions at a later date.

Usage

To see an example of how researcher in action, check out mnist_demo.ipynb on the github repo, but essentially usage has 3 stages:

1. Define Experiment Parameters

In geneal for any data science project you will spend a great deal of your time choosing parameters for experiments. These parameters might include which activation functions to use, how many epochs to train for or which data augmentation procedures to use. Ideally every time you conduct an experiment, you should record the parameters which are involved in that experiment and those parameters should be sufficient for someone else to replicate that exact experiment at a later date. The approach favoured by researcher is to have a helper function which takes a dictionary of parameters, runs an experiment under those parameters, and returns the experiment results. Something like the following incomplete code snippet:

import researcher as rs
import tensorflow as tf

def run_experiment(params):
    model = tf.keras.models.Sequential([
      tf.keras.layers.Flatten(input_shape=(28, 28, 1)),
      tf.keras.layers.Dense(params["depth"],activation=params["activation_function"]),
      tf.keras.layers.Dense(10, activation=params["final_activation"])
    ])
    model.compile(
        loss='sparse_categorical_crossentropy',
        optimizer=tf.keras.optimizers.Adam(params["lr"]),
        metrics=['accuracy'],
    )
    model.fit(
        SOME_DATASET,
        epochs=params["epochs"],
        validation_data=SOME_OTHER_DATASET, 
    )
    
    return model.history

params = {
    "activation_function": "relu",
    "final_activation": "softmax",
    "depth": 256,
    "epochs": 8,
    "lr": 0.001
}

results = run_experiment(params)

By varying params you can perform multiple experiments under different conditions. Good practice is to give each params dictionary a "title" key which summarizes the experiment and makes it easier to quickly identify experiments in future.

params = {
    "title": "deep-adam-relu",
    "activation_function": "relu",
    "final_activation": "softmax",
    "depth": 256,
    "epochs": 8,
    "lr": 0.001
}

results = run_experiment(params)

2. Save Experiment Results

Data science experiments become much more useful when you record their results in a consistent format. Then if you discover anything important, you can easily share your discovery with others and keep track of it in a quantifiable form for your own future benefit. This is what researcher helps with. First create an empty directory to save all your experiment data:

mkdir records

Then, each time you run an experiment, simply pack the results into a dictionary and use researcher to save it in that directory, along with the experimental parameters.

final_results = {}

for key, values in results.history.items():
    final_results[key] = values

rs.record_experiment(params, "records/", observations=final_results)

The parameters and results will be saved into a .json file in the records/ directory and given a unique experiment hash that can later be used to identify it (though a "title" key is also helpful).

Note: because researcher serializes experiments using JSON, you cannot include any non json-serializable objects in params when using researcher.

3. Load and View Experiment Results

researcher also helps you load and visualize saved experiments:

experiments = rs.all_experiments("records/")

Experiments are loaded into a researcher.experiment.Experiment instance, which is a light wrapper around the raw parameters and results that were saved initially:

experiments[0].data

# {
# 'title': 'deep-adam-relu',
# 'activation_function': 'relu',
# 'final_activation': 'softmax',
# 'depth': 256,
# 'epochs': 8,
# 'lr': 0.001,
# 'hash': 'eebb49b9d1487396dd6c0e5271cc3083',
# 'timestamp': '2020-12-10_00:01:04',
# 'observations': {'loss': [0.35920432209968567,
#     0.16782893240451813,
#     0.12179006636142731,
#     0.09385494887828827,
#     0.07634902000427246],
# 'accuracy': [0.9017833471298218,
#     0.9524166584014893,
#     0.9652500152587891,
#     0.972683310508728,
#     0.9780666828155518],
# 'val_loss': [0.1982543170452118,
#     0.1372576504945755,
#     0.11536946892738342,
#     0.1035531759262085,
#     0.08903338760137558],
# 'val_accuracy': [0.9441999793052673,
#     0.9607999920845032,
#     0.9675999879837036,
#     0.9672999978065491,
#     0.9717000126838684]},
# }

You can also use some pre-built plotting functions to visualize these experimental results (see researcher/dashboard.py), but depending on what kinds of experiments you're running, you'll probably want to define your own.

import matplotlib.pyplot as plt

def plot_metric(e, metric):
    plt.plot(e.data["observations"][metric])

plot_metric(experiments[0], "loss")
plot_metric(experiments[0], "val_loss")