Skip to main content

Composable transformations for model experimentation

Project description

Lefts: composable machine-learning model transformations

Lefts is a very simple domain specific language for building complex machine learning workflows from simple ones. Starting with your favourite machine learning models, you can use Lefts operations to:

  • Build complex ensembles.
  • Build complex cross validation and hyper-parametrisation procedures.
  • Allow a model to create features or targets for another model.
  • And any creative combination of the above.

Without making subsequent model fitting or evaluation or experimentation any more complex than it was with the original model. This implementation is built on top of the excellent Polars DataFrame library.

Commands

Lefts has five commands, which give it its name:

  • Lift: trains multiple copies of a model across different subsets of data.
  • Ensemble: Takes a set of models and makes them evaluate as one.
  • Tune: Allows a model to learn its hyperparameters from another.
  • Feeds: Allows the output of one model to be used as a feature or target by another.
  • Split: Trains a model on a given train/test/validation split.

Models

Lefts can operate on any model that is defined by:

  • a fit method, which maps from training data into the model parameters
  • a predict method, which maps from model parameters and test data into predictions.

A Lefts command creates a new model by transforming these functions into a new .fit and .predict. Because this new model also has a .fit and .predict, it can be transformed with further Lefts commands.

Conventions

Lefts imposes some constraints on model interfaces.

  • All hyperparameters are passed as arguments to the fit method.
  • We expect that data is passed to fit and predict as Polars dataframes.
  • The predict method returns an iterable, with the order of predictions matching the order on the input training data frame.

See the example notebooks to understand how to adapt your models to the required format.

An example

The following code shows lefts can be used to create complex models out of more basic ones. We start with an LGBM Regression model, and train an ensemble of models to predict each quantile. The ensemble is trained in a monthly rolling retrain.

See notebooks/quantile_ensemble.py for the full code.

features = ["temp", "atemp", "hum", "windspeed", "hr", "weekday", "mnth"]
target = "cnt"
quantiles = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]
test_period_start_dates = pl.datetime_range(
    start = dt.datetime(2011, 3, 1),
    end = dt.datetime(2012, 12, 1),
    interval='1mo',
    eager=True
).to_list()

quantile_models = []
for q in quantiles:
    # Convert LGBMRegressor into the format required by lefts
    base_model = leaf(tabular_model(
                LGBMRegressor(objective="quantile", alpha=q),
                features=features,
                target=target,
            ),
            label=f"q{q}",
                     )

    # 'Lift' each per-quantile model into a family of models, each with a different train and test period
    rolling_retrain = lift(
        base_model,
        name=f"q{q}_rolling_retrain",
        values=test_period_start_dates,

        # A row is in a given train period if it is before the start of the test period
        train_filter=lambda test_period_start_date: pl.col("datetime") < test_period_start_date,
        # Each test period is one month long
        test_filter=lambda test_period_start_date: pl.col("datetime").dt.month() == test_period_start_date.month,
        aggregate_with=pl.coalesce,
    )

    quantile_models.append(rolling_retrain)

model = ensemble("quantiles", *quantile_models)

# Fits |quantiles| x |test_period_start_dates| models
model.fit(df)

# Adds |quantiles| columns, each with the unique prediction associated with that test row. 
predictions = model.predict(df)

Behind the scenes, the full workflow is constructed as a tree of lefts expression. You can see this tree by calling model.print_tree()

Ensemble 'quantiles' (198 models)  → outputs: [q0.1_rolling_retrain, ..., q0.9_rolling_retrain]
    ├── Lift 'q0.1_rolling_retrain' (22 models): [2011-03-01 00:00:00, ..., 2012-12-01 00:00:00]  ⇒ coalesce → "q0.1_rolling_retrain"
    │   └── Leaf 'q0.1' (1 model)
    ├── Lift 'q0.2_rolling_retrain' (22 models): [2011-03-01 00:00:00, ..., 2012-12-01 00:00:00]  ⇒ coalesce → "q0.2_rolling_retrain"
    │   └── Leaf 'q0.2' (1 model)
    ├── Lift 'q0.3_rolling_retrain' (22 models): [2011-03-01 00:00:00, ..., 2012-12-01 00:00:00]  ⇒ coalesce → "q0.3_rolling_retrain"
    │   └── Leaf 'q0.3' (1 model)
    ├── Lift 'q0.4_rolling_retrain' (22 models): [2011-03-01 00:00:00, ..., 2012-12-01 00:00:00]  ⇒ coalesce → "q0.4_rolling_retrain"
    │   └── Leaf 'q0.4' (1 model)
    ├── Lift 'q0.5_rolling_retrain' (22 models): [2011-03-01 00:00:00, ..., 2012-12-01 00:00:00]  ⇒ coalesce → "q0.5_rolling_retrain"
    │   └── Leaf 'q0.5' (1 model)
    ├── Lift 'q0.6_rolling_retrain' (22 models): [2011-03-01 00:00:00, ..., 2012-12-01 00:00:00]  ⇒ coalesce → "q0.6_rolling_retrain"
    │   └── Leaf 'q0.6' (1 model)
    ├── Lift 'q0.7_rolling_retrain' (22 models): [2011-03-01 00:00:00, ..., 2012-12-01 00:00:00]  ⇒ coalesce → "q0.7_rolling_retrain"
    │   └── Leaf 'q0.7' (1 model)
    ├── Lift 'q0.8_rolling_retrain' (22 models): [2011-03-01 00:00:00, ..., 2012-12-01 00:00:00]  ⇒ coalesce → "q0.8_rolling_retrain"
    │   └── Leaf 'q0.8' (1 model)
    └── Lift 'q0.9_rolling_retrain' (22 models): [2011-03-01 00:00:00, ..., 2012-12-01 00:00:00]  ⇒ coalesce → "q0.9_rolling_retrain"
        └── Leaf 'q0.9' (1 model)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

lefts-0.1.0.tar.gz (23.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

lefts-0.1.0-py3-none-any.whl (18.2 kB view details)

Uploaded Python 3

File details

Details for the file lefts-0.1.0.tar.gz.

File metadata

  • Download URL: lefts-0.1.0.tar.gz
  • Upload date:
  • Size: 23.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for lefts-0.1.0.tar.gz
Algorithm Hash digest
SHA256 958c1b3abf3302a00a2a2c89af1c276afda0fb9b07f8440dac94c1bd56e3038d
MD5 bcf2f2f2446f0ff73b852c3f5c8782a3
BLAKE2b-256 1bfdbd13d44b13d0793e122b4f007adb8ff33c39a00e45c41888777f394322a8

See more details on using hashes here.

File details

Details for the file lefts-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: lefts-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 18.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for lefts-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 029067ae2682298586a09166553fc09f196d123f2c7aa0112ee61a25124dbba7
MD5 68f8284e3fd68c91d1667f67c66a3fef
BLAKE2b-256 49b4e0e923ae483a34df2c11c64c25fbc6021283ed65a650038a85ba3a15bb15

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page