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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
958c1b3abf3302a00a2a2c89af1c276afda0fb9b07f8440dac94c1bd56e3038d
|
|
| MD5 |
bcf2f2f2446f0ff73b852c3f5c8782a3
|
|
| BLAKE2b-256 |
1bfdbd13d44b13d0793e122b4f007adb8ff33c39a00e45c41888777f394322a8
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
029067ae2682298586a09166553fc09f196d123f2c7aa0112ee61a25124dbba7
|
|
| MD5 |
68f8284e3fd68c91d1667f67c66a3fef
|
|
| BLAKE2b-256 |
49b4e0e923ae483a34df2c11c64c25fbc6021283ed65a650038a85ba3a15bb15
|