Wallaroo Model Observability: Anomaly Detection with CCFraud
Models:
The following tutorials are available from the Wallaroo Tutorials Repository.
Wallaroo Model Observability: Anomaly Detection with CCFraud
The following tutorial demonstrates the use case of detecting anomalies: inference input or output data that does not match typical validations.
Wallaroo provides validations to detect anomalous data from inference inputs and outputs. Validations are added to a Wallaroo pipeline with the wallaroo.pipeline.add_validations
method.
Adding validations takes the format:
pipeline.add_validations(
validation_name_01 = polars.col(in|out.{column_name}) EXPRESSION,
validation_name_02 = polars.col(in|out.{column_name}) EXPRESSION
...{additional rules}
)
validation_name
: The user provided name of the validation. The names must match Python variable naming requirements.- IMPORTANT NOTE: Using the name
count
as a validation name returns a warning. Any validation rules namedcount
are dropped upon request and an warning returned.
- IMPORTANT NOTE: Using the name
polars.col(in|out.{column_name})
: Specifies the input or output for a specific field aka “column” in an inference result. Wallaroo inference requests are in the formatin.{field_name}
for inputs, andout.{field_name}
for outputs.- More than one field can be selected, as long as they follow the rules of the polars 0.18 Expressions library.
EXPRESSION
: The expression to validate. When the expression returns True, that indicates an anomaly detected.
The polars
library version 0.18.5 is used to create the validation rule. This is installed by default with the Wallaroo SDK. This provides a powerful range of comparisons to organizations tracking anomalous data from their ML models.
When validations are added to a pipeline, inference request outputs return the following fields:
Field | Type | Description |
---|---|---|
anomaly.count | Integer | The total of all validations that returned True. |
anomaly.{validation name} | Bool | The output of the validation {validation_name} . |
When validation returns True
, an anomaly is detected.
For example, adding the validation fraud
to the following pipeline returns anomaly.count
of 1
when the validation fraud
returns True
. The validation fraud
returns True
when the output field dense_1 at index 0 is greater than 0.9.
sample_pipeline = wallaroo.client.build_pipeline("sample-pipeline")
sample_pipeline.add_model_step(ccfraud_model)
# add the validation
sample_pipeline.add_validations(
fraud=pl.col("out.dense_1").list.get(0) > 0.9,
)
# deploy the pipeline
sample_pipeline.deploy()
# sample inference
display(sample_pipeline.infer_from_file("dev_high_fraud.json", data_format='pandas-records'))
time | in.tensor | out.dense_1 | anomaly.count | anomaly.fraud | |
---|---|---|---|---|---|
0 | 2024-02-02 16:05:42.152 | [1.0678324729, 18.1555563975, -1.6589551058, 5…] | [0.981199] | 1 | True |
Detecting Anomalies from Inference Request Results
When an inference request is submitted to a Wallaroo pipeline with validations, the following fields are output:
Field | Type | Description |
---|---|---|
anomaly.count | Integer | The total of all validations that returned True. |
anomaly.{validation name} | Bool | The output of each pipeline validation {validation_name} . |
For example, adding the validation fraud
to the following pipeline returns anomaly.count
of 1
when the validation fraud
returns True
.
sample_pipeline = wallaroo.client.build_pipeline("sample-pipeline")
sample_pipeline.add_model_step(ccfraud_model)
# add the validation
sample_pipeline.add_validations(
fraud=pl.col("out.dense_1").list.get(0) > 0.9,
)
# deploy the pipeline
sample_pipeline.deploy()
# sample inference
display(sample_pipeline.infer_from_file("dev_high_fraud.json", data_format='pandas-records'))
time | in.tensor | out.dense_1 | anomaly.count | anomaly.fraud | |
---|---|---|---|---|---|
0 | 2024-02-02 16:05:42.152 | [1.0678324729, 18.1555563975, -1.6589551058, 5…] | [0.981199] | 1 | True |
Anomaly Detection Demonstration
The following demonstrates how to:
- Upload a ccfraud ML model trained to detect the likelihood of a transaction being fraudulent. This outputs the field
dense_1
as an float where the closer to 1, the higher the likelihood of the transaction being fraudulent. - Add the ccfraud model as a pipeline step.
- Add the validation
fraud
to detect when the output ofdense_1
at index 0 when the values are greater than0.9
. - Deploy the pipeline and performing sample inferences on it.
- Perform sample inferences to show when the
fraud
validation returnsTrue
andFalse
. - Perform sample inference with different datasets to show enable or disable certain fields from displaying in the inference results.
Prerequisites
- Wallaroo version 2023.4.1 and above.
polars
version 0.18.5. This is installed by default with the Wallaroo SDK.
Tutorial Steps
Load Libraries
The first step is to import the libraries used in this notebook.
import wallaroo
wallaroo.__version__
'2024.2.0b3'
Connect to the Wallaroo Instance through the User Interface
The next step is to connect to Wallaroo through the Wallaroo client. The Python library is included in the Wallaroo install and available through the Jupyter Hub interface provided with your Wallaroo environment.
This is accomplished using the wallaroo.Client()
command, which provides a URL to grant the SDK permission to your specific Wallaroo environment. When displayed, enter the URL into a browser and confirm permissions. Store the connection into a variable that can be referenced later.
If logging into the Wallaroo instance through the internal JupyterHub service, use wl = wallaroo.Client()
. For more information on Wallaroo Client settings, see the Client Connection guide.
wl = wallaroo.Client()
Create a New Workspace
We’ll use the SDK below to create our workspace then assign as our current workspace. The current workspace is used by the Wallaroo SDK for where to upload models, create pipelines, etc. We’ll also set up variables for our models and pipelines down the road, so we have one spot to change names to whatever fits your organization’s standards best.
Before starting, verify that the workspace name is unique in your Wallaroo instance.
workspace_name = 'validation-ccfraud-demonstration-jch'
pipeline_name = 'ccfraud-validation-demo'
model_name = 'ccfraud'
model_file_name = './models/ccfraud.onnx'
workspace = wl.get_workspace(name=workspace_name, create_if_not_exist=True)
wl.set_current_workspace(workspace)
{'name': 'validation-ccfraud-demonstration-jch', 'id': 8, 'archived': False, 'created_by': 'fb2916bc-551e-4a76-88e8-0f7d7720a0f9', 'created_at': '2024-07-29T20:18:41.893095+00:00', 'models': [], 'pipelines': []}
Upload the Model
Upload the model to the Wallaroo workspace with the wallaroo.client.upload_model
method. Our ccfraud ML model is a Wallaroo Default Runtime of type ONNX
, so all we need is the model name, the model file path, and the framework type of wallaroo.framework.Framework.ONNX
.
ccfraud_model = (wl.upload_model(model_name,
model_file_name,
framework=wallaroo.framework.Framework.ONNX)
)
Build the Pipeline
Pipelines are build with the wallaroo.client.build_pipeline
method, which takes the pipeline name. This will create the pipeline in our default workspace. Note that if there are any existing pipelines with the same name in this workspace, this method will retrieve that pipeline for this SDK session.
Once the pipeline is created, we add the ccfraud model as our pipeline step.
sample_pipeline = wl.build_pipeline(pipeline_name)
sample_pipeline = sample_pipeline.add_model_step(ccfraud_model)
Add Validation
Now we add our validation to our new pipeline. We will give it the following configuration.
- Validation Name:
fraud
- Validation Field:
out.dense_1
- Validation Field Index:
0
- Validation Expression: Values greater than
0.9
.
The polars
library is required for creating the validation. We will import the polars library, then add our validation to the pipeline.
- IMPORTANT NOTE: Validation names must be unique per pipeline. If a validation of the same name is added, both are included in the pipeline validations, but only most recent validation with the same name is displayed with the inference results. Anomalies detected by multiple validations of the same name are added to the
anomaly.count
inference result field.
import polars as pl
sample_pipeline = sample_pipeline.add_validations(
fraud=pl.col("out.dense_1").list.get(0) > 0.9
)
Display Pipeline And Validation Steps
The method wallaroo.pipeline.steps()
shows the current pipeline steps. The added validations are in the Check
field. This is used for demonstration purposes to show the added validation to the pipeline.
sample_pipeline.steps()
[{'ModelInference': {'models': [{'name': 'ccfraud', 'version': '2ed33db8-f7f3-4089-beea-d0fea1c7dfb0', 'sha': 'bc85ce596945f876256f41515c7501c399fd97ebcb9ab3dd41bf03f8937b4507'}]}},
{'Check': {'tree': ['{"Alias":[{"BinaryExpr":{"left":{"Function":{"input":[{"Column":"out.dense_1"},{"Literal":{"Int32":0}}],"function":{"ListExpr":"Get"},"options":{"collect_groups":"ApplyFlat","fmt_str":"","input_wildcard_expansion":false,"auto_explode":true,"cast_to_supertypes":false,"allow_rename":false,"pass_name_to_apply":false,"changes_length":false,"check_lengths":true,"allow_group_aware":true}}},"op":"Gt","right":{"Literal":{"Float64":0.9}}}},"fraud"]}']}}]
Deploy Pipeline
With the pipeline steps set and the validations created, we deploy the pipeline. Because of it’s size, we will only allocate 0.1
cpu from the cluster for the pipeline’s use.
deploy_config = wallaroo.deployment_config.DeploymentConfigBuilder() \
.cpus(0.1)\
.build()
sample_pipeline.deploy(deployment_config=deploy_config)
name | ccfraud-validation-demo |
---|---|
created | 2024-07-29 20:18:43.630980+00:00 |
last_updated | 2024-07-29 20:18:44.141893+00:00 |
deployed | True |
workspace_id | 8 |
workspace_name | validation-ccfraud-demonstration-jch |
arch | x86 |
accel | none |
tags | |
versions | e77b0604-a91d-4a60-ad94-42c92b2a6c3f, 0779abba-a1cd-4f6d-8066-91482b0a6a86 |
steps | ccfraud |
published | False |
Sample Inferences
Two sample inferences are performed with the method wallaroo.pipeline.infer_from_file
that takes either a pandas Record JSON file or an Apache Arrow table as the input.
For our demonstration, we will use the following pandas Record JSON files with the following sample data:
./data/dev_smoke_test.pandas.json
: A sample inference that generates a low (lower than 0.01) likelihood of fraud../data/dev_high_fraud.pandas.json
: A sample inference that generates a high (higher than 0.90) likelihood of fraud.
The inference request returns a pandas DataFrame.
Each of the inference outputs will include the following fields:
Field | Type | Description |
---|---|---|
time | DateTime | The DateTime of the inference request. |
in.{input_field_name} | Input Dependent | Each input field submitted is labeled as in.{input_field_name} in the inference request result. For our example, this is tensor , so the input field in the returned inference request is in.tensor . |
out.{model_output_field_name} | Output Dependent | Each field output by the ML model is labeled as out.{model_output_field_name} in the inference request result. For our example, the ccfraud model returns dense_1 as its output field, so the output field in the returned inference request is out.dense_1 . |
anomaly.count | Integer | The total number of validations that returned True . |
**anomaly.{validation_name} | Bool | Each validation added to the pipeline is returned as anomaly.{validation_name} , and returns either True if the validation returns True , indicating an anomaly is found, or False for an anomaly for the validation is not found. For our example, we will have anomaly.fraud returned. |
sample_pipeline.infer_from_file("./data/dev_smoke_test.pandas.json")
time | in.dense_input | out.dense_1 | anomaly.count | anomaly.fraud | |
---|---|---|---|---|---|
0 | 2024-07-29 20:18:57.848 | [1.0678324729, 0.2177810266, -1.7115145262, 0.... | [0.0014974177] | 0 | False |
sample_pipeline.infer_from_file("./data/dev_high_fraud.json")
time | in.dense_input | out.dense_1 | anomaly.count | anomaly.fraud | |
---|---|---|---|---|---|
0 | 2024-07-29 20:18:58.067 | [1.0678324729, 18.1555563975, -1.6589551058, 5... | [0.981199] | 1 | True |
Other Validation Examples
The following are additional examples of validations.
Multiple Validations
The following uses multiple validations to check for anomalies. We still use fraud
which detects outputs that are greater than 0.9
. The second validation too_low
triggers an anomaly when the out.dense_1
is under 0.05.
After the validations are added, the pipeline is redeployed to “set” them.
sample_pipeline = sample_pipeline.add_validations(
too_low=pl.col("out.dense_1").list.get(0) < 0.001
)
deploy_config = wallaroo.deployment_config.DeploymentConfigBuilder() \
.cpus(0.1)\
.build()
sample_pipeline.undeploy()
sample_pipeline.deploy(deployment_config=deploy_config)
name | ccfraud-validation-demo |
---|---|
created | 2024-07-29 20:18:43.630980+00:00 |
last_updated | 2024-07-29 20:19:35.509945+00:00 |
deployed | True |
workspace_id | 8 |
workspace_name | validation-ccfraud-demonstration-jch |
arch | x86 |
accel | none |
tags | |
versions | 083778ef-8473-42ec-9c7e-9e3f47bf989a, e77b0604-a91d-4a60-ad94-42c92b2a6c3f, 0779abba-a1cd-4f6d-8066-91482b0a6a86 |
steps | ccfraud |
published | False |
sample_pipeline.infer_from_file("./data/dev_smoke_test.pandas.json")
time | in.dense_input | out.dense_1 | anomaly.count | anomaly.fraud | anomaly.too_low | |
---|---|---|---|---|---|---|
0 | 2024-07-29 20:19:49.456 | [1.0678324729, 0.2177810266, -1.7115145262, 0.... | [0.0014974177] | 0 | False | False |
sample_pipeline.infer_from_file("./data/dev_high_fraud.json")
time | in.dense_input | out.dense_1 | anomaly.count | anomaly.fraud | anomaly.too_low | |
---|---|---|---|---|---|---|
0 | 2024-07-29 20:19:49.632 | [1.0678324729, 18.1555563975, -1.6589551058, 5... | [0.981199] | 1 | True | False |
Compound Validations
The following combines multiple field checks into a single validation. For this, we will check for values of out.dense_1
that are between 0.05 and 0.9.
Each expression is separated by ()
. For example:
- Expression 1:
pl.col("out.dense_1").list.get(0) < 0.9
- Expression 2:
pl.col("out.dense_1").list.get(0) > 0.001
- Compound Expression:
(pl.col("out.dense_1").list.get(0) < 0.9) & (pl.col("out.dense_1").list.get(0) > 0.001)
sample_pipeline = sample_pipeline.add_validations(
in_between_2=(pl.col("out.dense_1").list.get(0) < 0.9) & (pl.col("out.dense_1").list.get(0) > 0.001)
)
deploy_config = wallaroo.deployment_config.DeploymentConfigBuilder() \
.cpus(0.1)\
.build()
sample_pipeline.undeploy()
sample_pipeline.deploy(deployment_config=deploy_config)
name | ccfraud-validation-demo |
---|---|
created | 2024-07-29 20:18:43.630980+00:00 |
last_updated | 2024-07-29 20:20:28.062038+00:00 |
deployed | True |
workspace_id | 8 |
workspace_name | validation-ccfraud-demonstration-jch |
arch | x86 |
accel | none |
tags | |
versions | 19e97ec1-c042-4d6f-8468-8e358bd82ccf, 083778ef-8473-42ec-9c7e-9e3f47bf989a, e77b0604-a91d-4a60-ad94-42c92b2a6c3f, 0779abba-a1cd-4f6d-8066-91482b0a6a86 |
steps | ccfraud |
published | False |
results = sample_pipeline.infer_from_file("./data/cc_data_1k.df.json")
results.loc[results['anomaly.in_between_2'] == True]
time | in.dense_input | out.dense_1 | anomaly.count | anomaly.fraud | anomaly.in_between_2 | anomaly.too_low | |
---|---|---|---|---|---|---|---|
4 | 2024-07-29 20:20:42.328 | [0.5817662108, 0.097881551, 0.1546819424, 0.47... | [0.0010916889] | 1 | False | True | False |
7 | 2024-07-29 20:20:42.328 | [1.0379636346, -0.152987302, -1.0912561862, -0... | [0.0011294782] | 1 | False | True | False |
8 | 2024-07-29 20:20:42.328 | [0.1517283662, 0.6589966337, -0.3323713647, 0.... | [0.0018743575] | 1 | False | True | False |
9 | 2024-07-29 20:20:42.328 | [-0.1683100246, 0.7070470317, 0.1875234948, -0... | [0.0011520088] | 1 | False | True | False |
10 | 2024-07-29 20:20:42.328 | [0.6066235674, 0.0631839305, -0.0802961973, 0.... | [0.0016568303] | 1 | False | True | False |
... | ... | ... | ... | ... | ... | ... | ... |
982 | 2024-07-29 20:20:42.328 | [-0.0932906169, 0.2837744937, -0.061094265, 0.... | [0.0010192394] | 1 | False | True | False |
983 | 2024-07-29 20:20:42.328 | [0.0991458877, 0.5813808183, -0.3863062246, -0... | [0.0020678043] | 1 | False | True | False |
992 | 2024-07-29 20:20:42.328 | [1.0458395446, 0.2492453605, -1.5260449285, 0.... | [0.0013128221] | 1 | False | True | False |
998 | 2024-07-29 20:20:42.328 | [1.0046377125, 0.0343666504, -1.3512533246, 0.... | [0.0011070371] | 1 | False | True | False |
1000 | 2024-07-29 20:20:42.328 | [0.6118805301, 0.1726081102, 0.4310545502, 0.5... | [0.0012498498] | 1 | False | True | False |
179 rows × 7 columns
Specify Dataset
Wallaroo inference requests allow datasets to be excluded or included with the dataset_exclude
and dataset
parameters.
Parameter | Type | Description |
---|---|---|
dataset_exclude | List(String) | The list of datasets to exclude. Values include:
|
dataset | List(String) | The list of datasets and fields to include. |
For our example, we will exclude the anomaly
dataset, but include the datasets 'time'
, 'in'
, 'out'
, 'anomaly.count'
. Note that while we exclude anomaly
, we override that with by setting the anomaly field 'anomaly.count'
in our dataset
parameter.
sample_pipeline.infer_from_file("./data/dev_high_fraud.json",
dataset_exclude=['anomaly'],
dataset=['time', 'in', 'out', 'anomaly.count']
)
time | in.dense_input | out.dense_1 | anomaly.count | |
---|---|---|---|---|
0 | 2024-07-29 20:20:43.937 | [1.0678324729, 18.1555563975, -1.6589551058, 5... | [0.981199] | 1 |
Undeploy the Pipeline
With the demonstration complete, we undeploy the pipeline and return the resources back to the cluster.
sample_pipeline.undeploy()
name | ccfraud-validation-demo |
---|---|
created | 2024-07-29 20:18:43.630980+00:00 |
last_updated | 2024-07-29 20:20:28.062038+00:00 |
deployed | False |
workspace_id | 8 |
workspace_name | validation-ccfraud-demonstration-jch |
arch | x86 |
accel | none |
tags | |
versions | 19e97ec1-c042-4d6f-8468-8e358bd82ccf, 083778ef-8473-42ec-9c7e-9e3f47bf989a, e77b0604-a91d-4a60-ad94-42c92b2a6c3f, 0779abba-a1cd-4f6d-8066-91482b0a6a86 |
steps | ccfraud |
published | False |