Track metrics, params and tags v0.11¶
Each run keeps a log of what happened in it. Model code, workflow nodes and you (from a notebook, a script or the command line) can add to this log. The log has these parts:
- Metrics. A metric is a series of points, for example the loss at each step of an optimiser. A point without a step is a scalar, for example a business KPI.
- Params. A param is a value that does not change, for example the number of draws.
- Tags. A tag is a text value that you can change at any time, for example
stage=candidate. - The name and the note. A display name and a free-text note for the run.
- Structured log lines. Log lines with a level (
debug,info,warningorerror) and fields. - Images and tables v0.14. Images (PNG or JPEG), matplotlib figures and small tables, for example a sample of predictions at each step.
The run page in the web app shows all of them (the Metrics, Params, Media, Logs and Notes tabs). You can search for runs by their metrics, params and tags. To compare runs and to group them into experiments, see Compare runs and use experiments.
| See also | |
|---|---|
| Run tracking and experiments | The concepts: what a run records, and how the platform stores and searches it |
| HTTP API: Run tracking | The endpoints, the DTOs and the search grammar |
| Run protocol: Tracking events | The metrics, params and tags events and the budgets |
| Python SDK | FitContext.log_metric and the other methods |
| Compare runs and use experiments | The runs table, saved views, the compare page and experiments |
What the platform tracks for you¶
You do not have to write code for these items.
Params of the spec. Each fit logs these params:
| Param | Value |
|---|---|
model |
The model and its version, for example mmm.bayesian@1.2.0 |
fit.engine, fit.draws, fit.tune, fit.chains, fit.target_accept, fit.seed, fit.mode |
Only the fields that the spec sets |
data.split |
The kind of split, when the spec has a split |
settings.<path> |
One param for each value of the model settings, for example settings.adstock.l_max. A list or a dict becomes compact JSON (a maximum of 500 characters). A maximum of 200 settings params. |
Live chains. PyMC models (with nutpie or the PyMC NUTS sampler) and the MMMs
(mmm.bayesian, mmm.nested and mmm.multivariate) send the progress of each chain while
they sample. The run page shows the draws, the divergences
and the step size of each chain.
Step metrics of samplers and optimisers.
| Source | Keys | Labels | Step |
|---|---|---|---|
nutpie (PyMC models, mmm.bayesian, mmm.nested, mmm.multivariate) |
step_size, num_steps (leapfrog steps of the latest draw), divergences (cumulative) |
chain |
The draws that the chain finished, with tuning (starts at 1) |
PyMC NUTS sampler (PyMC models, the MMMs with nuts_sampler: pymc) |
step_size, tree_depth, num_steps, divergences (cumulative) |
chain |
The draw index, with tuning (starts at 1). For a long chain, each k-th draw and the last draw, k = ceil(n / 5000). |
| ADVI | loss (negative ELBO) |
none | The optimisation step (starts at 1). For a long fit, each k-th step, k = ceil(n / 5000). |
The evaluation fit of evaluate_then_refit |
All of the above | also fit=evaluation |
As above. The params of the evaluation fit get the prefix evaluation., for example evaluation.n_obs. |
End-of-run metrics. The holdout metrics of a fit (for example rmse and elpd_loo)
become scalar series.
scikit-learn. A fit of a sklearn.* model logs the hyperparameters of the estimator as
estimator.<name> params (only numbers, booleans and text).
Marketing-mix models. Each MMM fit logs a summary of its result. See What an MMM fit logs.
Causal models and ts.bsts. Each fit logs the effect of the change and the fit before
the change. See What a causal fit logs.
Neural networks. The dl models log their training curves, a log line for each epoch
and images of the predictions. See What a network fit logs.
Workflows. The search.grid node logs each grid point and its scores. The
model.backtest node logs the metrics of each fold. See Workflow nodes.
What an MMM fit logs v0.12¶
At the end of a fit, each marketing-mix model (mmm.bayesian, mmm.frequentist,
mmm.nested and mmm.multivariate) logs params and scalar metrics (no step). The metrics
come from 200 thinned draws of the posterior (or of the bootstrap). Thus, you can sort and
filter runs by the return of a channel.
Params.
| Param | Models | Value |
|---|---|---|
mmm.framework_version |
all | The version of mmm-framework |
mmm.n_channels |
all | The number of channels |
mmm.n_periods |
all | The number of periods of the fit window |
mmm.n_cells |
all | The number of panel cells (geo and product combinations; 1 for national data) |
mmm.link |
all | identity, log (poisson, negative_binomial) or logit (beta) |
mmm.time_varying |
mmm.bayesian |
The time-varying channels, separated by commas (empty when there are none) |
mmm.mediators |
mmm.nested |
The mediators, separated by commas |
mmm.outcomes |
mmm.multivariate |
The outcomes, separated by commas |
mmm.cross_effects |
mmm.multivariate |
The number of cross-effects |
A param value has a maximum of 6000 characters. If a list of names is longer, the model cuts
it and adds ... at the end. The fit does not stop because of this.
Metrics.
| Metric | Labels | Value |
|---|---|---|
roi |
channel |
The posterior mean of the return on spend: the contribution of the channel over the fit window divided by its spend |
roi_p05, roi_p95 |
channel |
The 5th and the 95th percentile of the return on spend |
mroi |
channel |
The marginal return: the extra KPI for each extra unit of spend at the current spend (a central difference at 1 ± 1%) |
contribution_share |
channel |
The part of the total KPI that the channel gives |
baseline_share |
none | The part of the total KPI with no media |
A channel with no spend gets no roi. The label value is the channel name with characters
other than letters, digits, _, . and - changed to _ (TV spend becomes TV_spend).
For mmm.multivariate, the metrics use the weighted total of the outcomes. It also logs a
roi for each outcome, with the labels channel and outcome.
Find the runs where TV returns more than 2:
What a causal fit logs v0.13¶
At the end of a fit, ts.bsts and the causal models (causal.impact,
causal.synthetic_control and causal.did) log params and scalar metrics (no step). Their
samplers also send live chains and step metrics, as the other PyMC models do. The causal
metrics come from the impact group of the bundle (a maximum of 500 draws), in the units of
the target.
Params of ts.bsts and causal.impact.
| Param | Value |
|---|---|
bsts.trend |
level, local_linear or smooth |
bsts.seasonality |
The seasonal periods, separated by commas. x3 gives the harmonics, for example 12,52.18x3. |
bsts.regressors |
The drivers (the control series of causal.impact), separated by commas |
bsts.n_states |
The number of states of the model |
bsts.n_periods, bsts.missing_periods |
The number of periods of the fit, and the number of periods without a value |
Metrics of ts.bsts.
| Metric | Value |
|---|---|
sigma_obs, sigma_level |
The posterior means of the observation noise and of the level innovations, in the units of the target |
one_step_rmse |
The error of the one-step-ahead predictions in the fitted range |
one_step_coverage_90 |
The part of the observed values in the 90% interval of the one-step-ahead predictions |
Params of the causal models.
| Param | Models | Value |
|---|---|---|
causal.intervention_start, causal.intervention_end |
all | The dates (or period numbers) of the change. The end is empty when the change did not stop. |
causal.n_pre, causal.n_post |
all | The number of periods before the start and in the effect window |
causal.n_treated |
all | The number of treated units (1 for causal.impact) |
causal.n_donors, causal.weights |
causal.synthetic_control |
The number of donor markets, and simplex or horseshoe |
causal.design, causal.staggered, causal.leads, causal.lags |
causal.did |
The design, if the units got the change at different times, and the event window |
Metrics of the causal models.
| Metric | Labels | Value |
|---|---|---|
effect_total |
none (unit with two or more treated units) |
The mean of the total effect over the effect window |
effect_total_sd, effect_total_p05, effect_total_p95 |
none | The sd, the 5th and the 95th percentile of the total effect |
effect_average |
none | The mean effect in one period |
effect_relative |
none (unit with two or more treated units) |
The total effect divided by the total of the values without the change |
p_effect_positive |
none (unit with two or more treated units) |
The probability that the total effect is more than zero |
pre_rmse, pre_mape |
none | The error of the model before the change |
donor_weight |
unit, donor |
causal.synthetic_control: the posterior mean of the weight, for the 10 largest donors of each treated unit |
att, att_p05, att_p95 |
none | causal.did: the average effect after the change, with its interval |
event_coef |
event_time |
causal.did: the posterior mean of the coefficient for each period relative to the change |
For the models, see Causal models and Bayesian structural time series. For the procedures, see Measure the effect of a change.
Find the geo-lift tests with an effect that is more than zero with a probability of 95% or more:
What a network fit logs v0.15¶
The dl models of mapi-jax log the training while it runs:
lossandaccuracy(classification) orrmseandmae(regression), with the labelsplit=train(batch values) andsplit=val(at the end of each evaluation);lr,grad_normandepoch;- one log line for each epoch, with the fields
epoch,train_loss,val_lossandimages_per_second; - the params
dl.arch.*,dl.train.*,dl.backend,dl.deviceanddl.parameters; - for a classification, the media
val_predictionsandconfusion(labelsplit=val) at a maximum of 20 evaluations.
See Deep learning.
Keys, labels and values¶
Metric keys match ^[a-z][a-z0-9_]{0,63}$. Write qualifiers as labels, not in the key:
| Do not write | Write |
|---|---|
train/loss |
key loss, labels {"split": "train"} |
rmse_fold_3 |
key rmse, labels {"fold": "3"} |
chain0.step_size |
key step_size, labels {"chain": "0"} |
These rules apply:
- A series is one key and one set of labels.
lossandlosswith{"split": "val"}are two series. - A series has a maximum of 8 labels. A label key matches
^[a-z][a-z0-9_]{0,31}$. A label value matches^[A-Za-z0-9_.\-]{1,63}$. - A metric value must be a finite number. The SDK ignores NaN and infinity.
- A param key and a tag key can have letters, digits,
_,.,-and/(for examplesettings.adstock.l_max). A param value is a boolean, a number or a text of a maximum of 6000 characters. - A param does not change. If you log a param again with the same value, nothing occurs. A different value through the API is an error (409). A different value from the runner is dropped, and the first value stays.
- A tag value is a text of a maximum of 1000 characters. The last value is kept. Tag keys
that start with
mapi.are for the platform only. - A step is an integer, 0 or more. For a stepped series, the first value of a step is kept. For a scalar series (no step), the newest value is kept.
Log from a model plugin¶
FitContext and ScoreContext have the logging methods. The SDK does a check of each key
and each label when you call the method, also in a notebook without a run. Thus a bad key
fails at the line that has the error.
class MyModel(CustomModel):
def fit(self, data, ctx):
ctx.log_param("solver", "lbfgs")
ctx.set_tag("family", "gaussian")
for step in range(1, n_steps + 1):
loss = self._update()
ctx.log_metric("loss", loss, step=step)
ctx.log_metrics({"rmse": rmse, "mae": mae}, labels={"split": "train"})
ctx.log_line("refit done", level="info", fields={"iterations": n_steps})
...
| Method | What it does |
|---|---|
log_metric(key, value, step=None, labels=None) |
Log one point |
log_metrics(values, step=None, labels=None) |
Log some metrics with the same step and labels |
log_param(key, value), log_params(params) |
Log params |
set_tag(key, value), set_tags(tags) |
Set tags |
log_line(message, level="info", fields=None) |
Write a structured log line (a maximum of 16 fields) |
chains(chains) |
Report the progress of each chain (mapi_spec.run.ChainProgress) |
tracking |
True when the platform records the values. Use it to skip metrics that take a long time to calculate. |
The methods are thread-safe. A sampler callback on a different thread can call them.
Inside a run, the values go to the platform each second. The fit also writes them into the
bundle: the model group metrics_history keeps the stepped series, the scalars, the params
and the tags. The core.loss_curve plot reads this group. For the layout, see
Bundles.
Log images, figures and tables v0.14¶
A run can log images, matplotlib figures and small tables. Each item has a key, and can have a step, labels and a caption, as a metric point has. Use the same key for the same picture at different steps, for example the predictions on a sample at each epoch.
class MyModel(CustomModel):
def fit(self, data, ctx):
for epoch in range(1, n_epochs + 1):
...
if ctx.tracking_media: # skip the work when nothing records the images
ctx.log_image("samples", grid, step=epoch, labels={"split": "val"},
caption=f"Predictions after epoch {epoch}")
fig = plot_residuals(...)
ctx.log_figure("residuals", fig)
ctx.log_table("confusion_counts", counts_df, caption="Holdout counts per class")
| Method | Accepts |
|---|---|
log_image(key, image, step=None, labels=None, caption=None) |
A numpy array: (height, width) gray, (height, width, 1), (height, width, 3) RGB or (height, width, 4) RGBA. uint8 values as they are, booleans as 0 and 255, other integers from 0 to 255, floats from 0 to 1 (NaN becomes 0). Also PNG or JPEG bytes, a Pillow image or an xarray.DataArray with the dims (y, x) or (y, x, channel). |
log_figure(key, figure, step=None, labels=None, caption=None) |
A matplotlib figure. The SDK saves it as a PNG file (100 dpi). |
log_table(key, table, step=None, labels=None, caption=None) |
A pandas DataFrame, a dict of columns or a list of records. Numbers, booleans, text and empty values. NaN and infinity become empty values. Dates become ISO text. |
tracking_media |
True when the platform records images. A figure takes time to draw: use this property to skip it. |
The SDK does a check of the key, the labels, the step and the caption at each call, also without a run. A bad key fails at the line that has the error. Media keys follow the rules of metric keys. When nothing records the media (a notebook without a run, or a run that has no media upload), the call does not encode the image.
Limits:
| Limit | Value |
|---|---|
| Size of one image | 8 MiB, a maximum of 4096 pixels on each side |
| Size of one table | 1 MiB, a maximum of 10 000 rows, 64 columns and 200 000 cells. A cell has a maximum of 1000 characters. |
| Caption | 500 characters |
| Items in the run | 1000 |
| Bytes in the run | 256 MiB |
The limits count the items of all attempts of the run and the items logged through the
API. If a run logs more, the platform does not keep the extra items and each attempt gets one
MEDIA_BUDGET warning. The run does not fail. An item that replaces another item (the same
key, labels and step) frees the bytes of the old item.
What the platform does with an image. The worker writes the file in its scratch directory. The supervisor reads the file, calculates its SHA-256 and does a check of the format and the size. Then it uploads the file. The server downloads the file again, does the same checks, and keeps a copy in the bundles bucket. The platform accepts only PNG and JPEG images (no SVG, no GIF). For the details, see Run protocol: Media.
Where you see the media.
- Web app. The Media tab of the run shows each key. A slider selects the step. A select shows one value of a label. A table shows its first 200 rows. The compare page shows the images of several runs side by side.
- Python.
api.runs.media(run_id)lists the items.api.runs.download_media(run_id, item)gets the bytes and does a check of the size, the CRC32C and the SHA-256. - Command line.
mapi runs media RUNlists the items.--download DIRwrites the files.
Log media from a notebook. You can add an image or a table to a run that already exists, also after it finished. One request has a maximum of 4 MiB.
api.runs.log_image(run_id, "holdout_map", "map.png", caption="Holdout error by region")
api.runs.log_table(run_id, "lift_by_region", {"region": ["north", "south"], "lift": [0.04, 0.02]})
mapi runs log-image run_01… holdout_map map.png --step 3 --label split=val --caption "Holdout error"
Log from a workflow node¶
The NodeContext of a workflow node has the same methods. The values go to the run of the
node.
search.grid logs these items in its search run. The search run is the first item in the
child_runs of the node. The run_id of the node is the refit of the winner, which does
not have these items.
- The params
grid.metric,grid.direction,grid.pointsandgrid.nested. - One param for each value of each grid point:
trial.<i>.<pointer>, for exampletrial.3.adstock/l_max = 4. - The scores of grid point
ias metrics with stepi(mae,rmse,mape,smape,mase,coverage_90, when they are present). A nested search adds the labels{"stage": "inner", "fold": "<j>"}to the inner scores. The outer scores have the label{"stage": "outer"}and stepj. - The scalar
best_<metric>(for examplebest_rmse) and the taggrid.best_point. - A
warninglog line for each grid point that failed.
model.backtest logs the metrics of each fold as scalars with the label {"fold": "<j>"}.
The inner fits of a backtest do not log step metrics.
Log from a notebook or a script¶
You can add metrics, params and tags to a run that already exists, also after it finished.
For example, log the revenue lift that you measured after you deployed the model. You need
the runner role. An API token needs the runs:write scope.
from mapi_client import Client
api = Client() # MAPI_URL, MAPI_TOKEN and MAPI_PROJECT, or a profile
run_id = "run_01jaf3q2x8m9v7k5h4g3f2e1d0"
api.runs.log_metric(run_id, "revenue_lift", 0.042, labels={"region": "north"})
api.runs.log_params(run_id, {"analyst": "kim", "holdout_weeks": 8})
api.runs.set_tags(run_id, {"stage": "production", "old_tag": None}) # None deletes a tag
api.runs.update(run_id, name="weekly sales, adstock 4", note="Holiday weeks removed.")
with api.runs.track(run_id) as t: # sends the points in batches
for week, value in weekly_kpi:
t.log_metric("weekly_lift", value, step=week)
runs.log_metrics sends a maximum of 1000 points in each request. It divides a larger
list. runs.track keeps the points and sends them when 500 wait and when the with block
ends.
The same with the command line:
mapi runs log-metric run_01… revenue_lift 0.042 --label region=north
mapi runs log-param run_01… analyst kim
mapi runs tag run_01… stage=production --delete old_tag
mapi runs rename run_01… "weekly sales, adstock 4"
mapi runs note run_01… "Holiday weeks removed."
The same with HTTP: POST /v1/runs/{run_id}/metrics, POST /v1/runs/{run_id}/params and
PATCH /v1/runs/{run_id} (see HTTP API).
Read the tracking data¶
- Web app. Open the run. The Metrics tab shows a chart for each key. Use Colour by to give each chain (or each other label) a colour. The Params tab shows the params and the tags. The Logs tab has a level filter. The Notes tab changes the name, the tags and the note.
- Python.
api.runs.metrics(run_id)lists the series with their summaries (last,min,max,count).api.runs.metric_history(run_id, "step_size", max_points=500)gets the points.api.runs.params(run_id)lists the params. - Command line.
mapi runs metrics RUN,mapi runs metrics RUN --key lossandmapi runs params RUN. - Several runs. The compare page shows the curves of 2 to 10 runs on one chart.
The server downsamples a long series to max_points (the default is 1000). It keeps the
shape of the curve, the first point and the last point.
Search runs¶
GET /v1/runs, api.runs.list(...) and mapi runs ls can filter, sort and show runs by
their tracking data:
mapi runs ls --filter 'metric.rmse < 12' --filter 'param.fit.engine = nutpie' \
--order-by metric.rmse --column metric.rmse --column param.settings.adstock.l_max
runs = api.runs.list(
filters=["metric.loss{split=val}.min < 0.3", "tag.stage = candidate"],
order_by="-metric.elpd_loo",
columns=["metric.elpd_loo", "param.fit.draws"],
)
A metric filter compares the summary of one exact series. Add .min, .max or .count to
compare a value different from the last value. A run that does not have the series does not
match. For the full grammar, see Search runs.
In the web app, the runs table shows tracked columns and has a filter bar. You can save a search as a view. See Compare runs and use experiments.
Budgets¶
Each run has budgets for its tracking data. The defaults are:
| Budget | Default |
|---|---|
| Points in one series | 10 000 |
| Points in the run | 500 000 |
| Series in the run | 1000 |
| Params | 5000 |
| Tags | 100 |
| Structured log lines | 10 000 lines, 2 MiB |
| Images and tables v0.14 | 1000 items, 256 MiB |
If a run logs more, the platform does not keep the extra points and the run gets one
TRACKING_BUDGET warning. The run does not fail. A worker that sends more than 64 MiB of
tracking data fails with SANDBOX_VIOLATION. An API write that is more than the budget gets
429 QUOTA_EXCEEDED.
Note
Log curves with a step that grows slowly, for example each 10th step. The chart shows a maximum of 1000 points for each series.
Retention¶
An operator can set MAPI_TRACKING_RETENTION_DAYS. Then the server deletes the metric points
and the structured log lines of runs that finished more days ago than the setting. The server
keeps the summaries, the params, the tags, the name and the note. It also keeps the value of
each metric without a step. Thus search continues to work for old runs. If you log more points
to a series after this, the server adds them to the summary that it kept (count, minimum,
maximum and the last value). See Configuration.
The retention setting does not delete images and tables. The server keeps them until you delete the tenant v0.14.