Batch Inference
Contents
Batch Inference#
A function for inferring given input through a given model while producing a Result Set and performing Data Drift Analysis.
In this notebook we will go over the function’s docs and outputs and see an end-to-end example of running it.
Documentation
Results Prediction
Data Drift Analysis
End-to-end Demo
1. Documentation#
Perform a prediction on a given dataset with the given model. Can perform drift analysis between the sample set statistics stored in the model to the current input data. The drift rule is the value per-feature mean of the TVD and Hellinger scores according to the thresholds configures here.
1.1. Parameters:#
context:
mlrun.MLClientCtx
An MLRun context.
model:
str
The model Store path, a logged model URI.
dataset:
Union[mlrun.DataItem, list, dict, pd.DataFrame, pd.Series, np.ndarray]
The dataset to infer through the model.
Can be passed in
inputs
as either a Dataset artifact / Feature vector URI.Or, in
parameters
as a list, dictionary or numpy array.
drop_columns:
Union[str, List[str], int, List[int]]
=None
A string / integer or a list of strings / integers that represent the column names / indices to drop. When the dataset is a list or a numpy array this parameter must be represented by integers.
label_columns:
Union[str, List[str]]
=None
The target label(s) of the column(s) in the dataset. These names will be used as the column names for the predictions. The label column can be accessed from the model object, or the feature vector provided if available. The default name is
"predicted_label_i"
for thei
column.feature_columns:
Union[str, List[str]]
=None
List of feature columns that will be used to build the dataframe when dataset is from type list or numpy array.
log_result_set:
str
=True
Whether to log the result set - a DataFrame of the given inputs concatenated with the predictions. Defaulted to
True
.result_set_name:
str
="prediction"
The db key to set name of the prediction result and the filename. Defaulted to
"prediction"
.batch_id:
str
=None
The ID of the given batch (inference dataset). If
None
, it will be generated. Will be logged as a result of the run.perform_drift_analysis:
bool
=None
Whether to perform drift analysis between the sample set of the model object to the dataset given. By default,
None
, which means it will perform drift analysis if the model has a sample set statistics.sample_set:
Union[mlrun.DataItem, list, dict, pd.DataFrame, pd.Series, np.ndarray]
A sample dataset to give to compare the inputs in the drift analysis. The default chosen sample set will always be the one who is set in the model artifact itself.
Can be passed in
inputs
as either a Dataset artifact / Feature vector URI.Or, in
parameters
as a list, dictionary or numpy array.
drift_threshold:
float
=0.7
The threshold of which to mark drifts. Defaulted to 0.7.
possible_drift_threshold:
float
=0.5
The threshold of which to mark possible drifts. Defaulted to 0.5.
inf_capping:
float
=10.0
The value to set for when it reached infinity. Defaulted to 10.0.
artifacts_tag:
str
=""
Tag to use for all the artifacts resulted from the function. Defaulted to no tag.
1.2. Outputs#
The outputs are split to two actions the functions can perform:
Results Prediction - Will log:
A dataset artifact named by the
result_set_name
parameter.A
str
result named"batch_id"
of the given / generated batch ID.
Data Drift Analysis - Will log:
A
plotly
artifact named"data_drift_table"
with a visualization of the drifts results and histograms.A json artifact named
"features_drift_results"
with all the features metric values.A
bool
result named"drift_status"
of the overall drift status (True
if there was a drift andFalse
otherwise).A
float
result named"drift_score"
of the overall drift metric score.
For more details, see the next chapters.
2. Results Prediction#
The result set is a concatenated dataset of the inputs ($X$) provided and the predictions ($Y$) yielded by the model, so it will be $X | Y$.
For example, if the dataset
given as inputs was:
x1 |
x2 |
x3 |
x4 |
x5 |
---|---|---|---|---|
… |
… |
… |
… |
… |
… |
… |
… |
… |
… |
… |
… |
… |
… |
… |
And the outputs yielded by the model’s prediction was:
y1 |
y2 |
---|---|
… |
… |
… |
… |
… |
… |
Then the result set will be:
x1 |
x2 |
x3 |
x4 |
x5 |
y1 |
y2 |
---|---|---|---|---|---|---|
… |
… |
… |
… |
… |
… |
… |
… |
… |
… |
… |
… |
… |
… |
… |
… |
… |
… |
… |
… |
… |
In case the parameter log_result_set
is True
, the outputs of the results prediction will be:
The result set as described above.
The batch ID result -
batch_id
:str
- a hashing result that is given by the user or generated randomly in case it was not provided to represent the batch that was being inferred.{ "batch_id": "884a0cb00d8ae16d132dd8259aac29aa78f50a9245d0e4bd58cfbf77", }
3. Data Drift Analysis#
The data drift analysis is done per feature using two distance measure metrics for probability distributions.
Let us mark our sample set as $S$ and our inputs as $I$. We will look at one feature $x$ out of $n$ features. Assuming the histograms of feature $x$ is split into 20 bins: $b_1,b_2,…,b_{20}$, we will match the feature $x$ histogram of the inputs $I$ ($x_I$) into the same bins (meaning to $x_S$) and compare their distributions using:
Total Variance Distance: $TVD(x_S,x_I) = \frac{1}{2}\sum_{b_1}^{b_{20}} {|x_S - x_I|}$
Hellinger Distance: $H(x_S,x_I) = \sqrt{1-{\sum_{b_1}^{b_{20}}\sqrt{x_S \cdot x_I}}}$
Our rule then is calculating for each $x\in S: \frac{H(x_S,x_I)+TVD(x_S,x_I)}{2} < $ given thresholds.
In case the parameter perform_drift_analysis
is True
, the outputs of the analysis will be:
Drift table plot - The results are presented in a
plotly
table artifact named"drift_table_plot"
that shows each feature’s statistics and its TVD, Hellinger and KLD (Kullback–Leibler divergence) results as follows:
Count |
Mean |
Std |
Min |
Max |
Tvd |
Hellinger |
Kld |
Histograms |
||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Sample |
Input |
Sample |
Input |
Sample |
Input |
Sample |
Input |
Sample |
Input |
|||||
x1 |
… |
… |
… |
… |
… |
… |
… |
… |
… |
… |
… |
… |
… |
… |
x2 |
… |
… |
… |
… |
… |
… |
… |
… |
… |
… |
… |
… |
… |
… |
x3 |
… |
… |
… |
… |
… |
… |
… |
… |
… |
… |
… |
… |
… |
… |
Features drift results - A rule metric per feature dictionary is saved in a json file named
"features_drift_results"
where each key is a feature and its value is the feature’s metric value:Dict[str, float]
{ "x1": 0.12, "x2": 0.345, "x3": 0.00678, ... }
In addition, two results are being added to summarize the drift analysis:
drift_status
:bool
- A boolean value indicating whether a drift was found.drift_metric
:float
- The mean of all the features drift metric value (the rule above): for $n$ features and metric rule $M(x_S,x_I)=\frac{H(x_S,x_I)+TVD(x_S,x_I)}{2}$,drift_metric
$=\frac{1}{n}\sum_{x\in S}M(x_S,x_I)$
{ "drift_status": True, "drift_metric": 0.81234 }
4. End-to-end Demo#
We will see an end-to-end example that follows the steps below:
Generate data.
Train a model.
Infer data through the model using
batch_predict
and review the outputs.
4.1. Code review#
We are using a very simple example of training a decision tree on a binary classification problem. For that we wrote two functions:
generate_data
- Generate a binary classification data. The data will be split into a training set and data for prediction. The data for prediction will be drifted in half of its features to showcase the plot later on.train
- Train a decision tree classifier on a given data.
# mlrun: start-code
# upload environment variables from env file if exists
import os,mlrun
# Specify path
path = "/tmp/examples_ci.env"
if os.path.exists(path):
env_dict = mlrun.set_env_from_file(path, return_dict=True)
import numpy as np
import pandas as pd
from sklearn.datasets import make_classification
from sklearn.tree import DecisionTreeClassifier
import mlrun
from mlrun.frameworks.sklearn import apply_mlrun
@mlrun.handler(outputs=["training_set", "prediction_set"])
def generate_data(n_samples: int = 5000, n_features: int = 20):
# Generate a classification data:
x, y = make_classification(
n_samples=n_samples, n_features=n_features, n_classes=2
)
# Split the data into a training set and a prediction set:
x_train, x_prediction = x[: n_samples // 2], x[n_samples // 2 :]
y_train = y[: n_samples // 2]
# Randomly drift some features:
x_prediction += (
np.random.uniform(low=2, high=4, size=x_train.shape) *
np.random.randint(low=0, high=2, size=x_train.shape[1], dtype=int)
)
# Initialize dataframes:
features = [f"feature_{i}" for i in range(n_features)]
training_set = pd.DataFrame(data=x_train, columns=features)
training_set.insert(
loc=n_features, column="label", value=y_train, allow_duplicates=True
)
prediction_set = pd.DataFrame(data=x_prediction, columns=features)
return training_set, prediction_set
@mlrun.handler()
def train(training_set: pd.DataFrame):
# Get the data into x, y:
labels = pd.DataFrame(training_set["label"])
training_set.drop(columns=["label"], inplace=True)
# Initialize a model:
model = DecisionTreeClassifier()
# Apply MLRun:
apply_mlrun(model=model, model_name="model")
# Train:
model.fit(training_set, labels)
# mlrun: end-code
4.2. Run the Example with MLRun#
First, we will prepare our MLRun functions:
We will use
mlrun.code_to_function
to turn this demo notebook into an MLRun function we can run.We will use
mlrun.import_function
to import thebatch_predict
function .
# Create an MLRun function to run the notebook:
demo_function = mlrun.code_to_function(name="batch_inference_demo", kind="job")
# Import the `batch_predict` function from the marketplace:
batch_inference_function = mlrun.import_function("hub://batch_inference")
# Set the desired artifact path:
artifact_path = "./"
Now, we will follow the demo steps as discussed above:
# 1. Generate data:
generate_data_run = demo_function.run(
handler="generate_data",
artifact_path=artifact_path,
local=True,
)
# 2. Train a model:
train_run = demo_function.run(
handler="train",
artifact_path=artifact_path,
inputs={"training_set": generate_data_run.outputs["training_set"]},
local=True,
)
# 3. Perform batch prediction:
batch_inference_run = batch_inference_function.run(
handler="infer",
artifact_path=artifact_path,
inputs={"dataset": generate_data_run.outputs["prediction_set"]},
params={
"model": train_run.outputs["model"],
"label_columns": "label",
},
local=True,
)
> 2022-09-13 09:54:59,693 [warning] artifact path is not defined or is local, artifacts will not be visible in the UI
> 2022-09-13 09:54:59,694 [info] starting run batch-predict-demo-generate_data uid=a5b1ca0a37d946e892b9305b9af833c3 DB=http://mlrun-api:8080
project | uid | iter | start | state | name | labels | inputs | parameters | results | artifacts |
---|---|---|---|---|---|---|---|---|---|---|
default | 0 | Sep 13 09:54:59 | completed | batch-predict-demo-generate_data | v3io_user=guyl kind= owner=guyl host=jupyter-guyl-66857b7999-ffvsx |
training_set prediction_set |
> 2022-09-13 09:55:06,462 [info] run executed, status=completed
> 2022-09-13 09:55:06,464 [warning] artifact path is not defined or is local, artifacts will not be visible in the UI
> 2022-09-13 09:55:06,464 [info] starting run batch-predict-demo-train uid=384b36e84c4e4f91900e49e1f24ff1a6 DB=http://mlrun-api:8080
project | uid | iter | start | state | name | labels | inputs | parameters | results | artifacts |
---|---|---|---|---|---|---|---|---|---|---|
default | 0 | Sep 13 09:55:06 | completed | batch-predict-demo-train | v3io_user=guyl kind= owner=guyl host=jupyter-guyl-66857b7999-ffvsx |
training_set |
model |
> 2022-09-13 09:55:07,367 [info] run executed, status=completed
> 2022-09-13 09:55:07,370 [warning] artifact path is not defined or is local, artifacts will not be visible in the UI
> 2022-09-13 09:55:07,370 [info] starting run batch-predict-predict uid=cf88e39d59704912a5ee41ceb539cd05 DB=http://mlrun-api:8080
> 2022-09-13 09:55:07,703 [info] Loading model...'
> 2022-09-13 09:55:07,753 [info] Calculating prediction...
> 2022-09-13 09:55:07,757 [info] Logging result set (x | prediction)...
> 2022-09-13 09:55:07,952 [info] Performing drift analysis...
divide by zero encountered in log
project | uid | iter | start | state | name | labels | inputs | parameters | results | artifacts |
---|---|---|---|---|---|---|---|---|---|---|
default | 0 | Sep 13 09:55:07 | completed | batch-predict-predict | v3io_user=guyl kind= owner=guyl host=jupyter-guyl-66857b7999-ffvsx |
dataset |
model=store://artifacts/default/model:384b36e84c4e4f91900e49e1f24ff1a6 label_columns=label |
drift_status=False drift_metric=0.3880999515903545 |
prediction drift_table_plot features_drift_results |
> 2022-09-13 09:55:10,078 [info] run executed, status=completed
4.3. Review Outputs#
We will review the outputs as explained in the notebook above.
4.3.1. Results Prediction#
First we will showcase the Result Set. As we didn’t send any name, it’s default name will be "prediction"
:
batch_inference_run.artifact("prediction").as_df()
feature_0 | feature_1 | feature_2 | feature_3 | feature_4 | feature_5 | feature_6 | feature_7 | feature_8 | feature_9 | ... | feature_11 | feature_12 | feature_13 | feature_14 | feature_15 | feature_16 | feature_17 | feature_18 | feature_19 | label | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 6.319111 | 3.208210 | 0.793499 | -0.613252 | 2.634766 | 1.208352 | 4.718735 | 3.557495 | -2.116311 | 0.370145 | ... | 1.962901 | 0.581321 | 4.844532 | 1.408737 | 0.964987 | 2.111456 | 3.134610 | -1.727937 | 0.335741 | 0 |
1 | 3.138378 | 1.633661 | 0.144557 | 0.687003 | 2.404279 | 1.405990 | 3.235892 | 1.804426 | 2.019980 | 1.719908 | ... | -1.486809 | 0.648228 | 3.039797 | 1.806766 | 1.201692 | 2.475974 | 1.448559 | 0.959266 | 1.158651 | 1 |
2 | 2.353026 | 4.642879 | 0.952097 | 0.605977 | 4.051640 | -0.157584 | 1.218743 | 2.464738 | 1.706084 | -0.250366 | ... | 0.559968 | 0.979378 | 2.411703 | 3.746830 | 2.252155 | 3.406102 | 3.263166 | -0.236510 | -0.313161 | 1 |
3 | 1.617202 | 4.568332 | 2.937961 | 2.501166 | 3.952541 | 0.671749 | 3.774594 | 4.042543 | -2.173079 | -0.983443 | ... | -0.839010 | 0.953698 | 3.033551 | 1.006891 | 2.398563 | 5.047382 | 5.291260 | 1.305584 | 0.843951 | 1 |
4 | 3.344291 | 4.538357 | 1.032059 | -0.047931 | 3.118438 | 0.403812 | 4.472615 | 1.840558 | -0.714775 | 0.287726 | ... | 1.598060 | -0.805508 | 4.742032 | 4.608792 | 1.617717 | 4.514895 | 3.648923 | -1.344024 | 0.610534 | 0 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
2495 | 2.319301 | 2.996941 | 1.337934 | 0.805649 | 2.303656 | 0.203069 | 5.575559 | 3.437790 | 0.709777 | 0.392013 | ... | -0.114619 | -1.469797 | 4.538126 | 1.282498 | 5.686133 | 2.826973 | 2.445658 | -0.145780 | 0.337803 | 0 |
2496 | 2.920678 | 2.144983 | 2.153517 | -0.527295 | 2.612040 | 1.113704 | 2.438761 | 3.284425 | 1.093894 | 0.921599 | ... | -1.586852 | 0.409838 | 4.094763 | 2.636654 | 3.333414 | 3.251106 | 1.132976 | 1.072658 | -1.240186 | 1 |
2497 | 4.256698 | 2.135673 | -0.114491 | 0.329980 | 3.935633 | -0.777958 | 2.543643 | 2.195111 | -0.926822 | -0.251254 | ... | -0.952889 | 0.687820 | 2.268043 | 5.077454 | 2.248259 | 3.469704 | 2.262900 | 0.687038 | -0.614066 | 1 |
2498 | 4.738030 | 2.390842 | -0.972329 | 1.471461 | 2.904280 | -2.079088 | 2.570604 | 2.325262 | -1.602976 | -0.806244 | ... | 0.554399 | 0.027493 | 4.145728 | 3.782802 | 4.202006 | 3.272709 | 0.867462 | -1.020029 | 2.013301 | 0 |
2499 | 2.047831 | 2.153813 | 0.392484 | 0.249010 | 3.846910 | 0.300846 | 3.005997 | 2.799457 | -0.304962 | -0.990622 | ... | -0.263473 | 0.110091 | 2.995411 | 2.582843 | 4.599535 | 3.219091 | 1.592652 | -0.074851 | -0.617769 | 1 |
2500 rows × 21 columns
4.3.2. Data Drift Analysis#
Second we will review the data drift table plot and the drift results:
batch_inference_run.artifact("drift_table_plot").show()
batch_inference_run.status.results
{'drift_status': False, 'drift_metric': 0.3880999515903545}