Deploy and Serve a Feature List for Model Predictions¶
Once you have a feature list ready, it's essential to serve these features effectively to make real-time or batch predictions using your machine learning model.
In this section, we'll take the feature list we previously crafted and explore two primary ways to serve its values:
REST API: Ideal for real-time predictions where you need instantaneous results. For instance, in applications where user interactions require immediate feedback based on model predictions.
Batch Processing: Best suited for scenarios where you have a bulk of data and don't require instant results.
In [1]:
Copied!
import featurebyte as fb
from datetime import datetime
# Set your profile to the tutorial environment
fb.use_profile("tutorial")
catalog_name = "Grocery Dataset Tutorial"
catalog = fb.Catalog.activate(catalog_name)
import featurebyte as fb
from datetime import datetime
# Set your profile to the tutorial environment
fb.use_profile("tutorial")
catalog_name = "Grocery Dataset Tutorial"
catalog = fb.Catalog.activate(catalog_name)
16:46:03 | INFO | Using configuration file at: /Users/viktor/.featurebyte/config.yaml 16:46:03 | INFO | Active profile: tutorial (https://tutorials.featurebyte.com/api/v1) 16:46:04 | INFO | SDK version: 0.6.0.dev121 16:46:04 | INFO | No catalog activated. 16:46:04 | INFO | 10 feature lists, 59 features deployed 16:46:04 | INFO | Using profile: tutorial 16:46:04 | INFO | Using configuration file at: /Users/viktor/.featurebyte/config.yaml 16:46:04 | INFO | Active profile: tutorial (https://tutorials.featurebyte.com/api/v1) 16:46:04 | INFO | SDK version: 0.6.0.dev121 16:46:04 | INFO | No catalog activated. 16:46:05 | INFO | 10 feature lists, 59 features deployed 16:46:05 | INFO | Catalog activated: Grocery Dataset Tutorial
List feature lists in Catalog¶
In [2]:
Copied!
catalog.list_feature_lists()
catalog.list_feature_lists()
Out[2]:
id | name | num_feature | status | deployed | readiness_frac | online_frac | tables | entities | primary_entity | created_at | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 6564b959d3f7244b7fc926a0 | Customer Simple FeatureList | 7 | DRAFT | False | 0.0 | 0.0 | [GROCERYCUSTOMER, GROCERYINVOICE, INVOICEITEMS... | [customer] | [customer] | 2023-11-27T15:44:36.966000 |
Get a feature list from Catalog¶
In [3]:
Copied!
simple_feature_list = catalog.get_feature_list("Customer Simple FeatureList")
simple_feature_list = catalog.get_feature_list("Customer Simple FeatureList")
Loading Feature(s) |████████████████████████████████████████| 7/7 [100%] in 0.7s
Deploy feature list¶
In [4]:
Copied!
# Create a deployment
deployment = simple_feature_list.deploy(
deployment_name="Customer Spending forecast",
make_production_ready=True,
)
# Create a deployment
deployment = simple_feature_list.deploy(
deployment_name="Customer Spending forecast",
make_production_ready=True,
)
Done! |████████████████████████████████████████| 100% in 6.5s (0.16%/s) Done! |████████████████████████████████████████| 100% in 3.3s (0.31%/s)
In [5]:
Copied!
# Enable deployment
deployment.enable()
# Enable deployment
deployment.enable()
Done! |████████████████████████████████████████| 100% in 42.0s (0.02%/s)
In [6]:
Copied!
# Check that the deployment is enabled
catalog.list_deployments()
# Check that the deployment is enabled
catalog.list_deployments()
Out[6]:
id | name | feature_list_name | feature_list_version | num_feature | enabled | |
---|---|---|---|---|---|---|
0 | 6564b9c77927bf2a9d6cca65 | Customer Spending forecast | Customer Simple FeatureList | V231127 | 7 | True |
In [7]:
Copied!
# Check status of the feature list
print("simple_feature_list.status:", simple_feature_list.status)
# Check status of the feature list
print("simple_feature_list.status:", simple_feature_list.status)
simple_feature_list.status: DEPLOYED
Get template for online serving¶
In [8]:
Copied!
# Get a python template for consuming the feature serving API
deployment.get_online_serving_code(language="python")
# Get a python template for consuming the feature serving API
deployment.get_online_serving_code(language="python")
Out[8]:
from typing import Any, Dict
import pandas as pd
import requests
def request_features(entity_serving_names: Dict[str, Any]) -> pd.DataFrame:
"""
Send POST request to online serving endpoint
Parameters
----------
entity_serving_names: Dict[str, Any]
Entity serving name values to used for serving request
Returns
-------
pd.DataFrame
"""
response = requests.post(
url="https://tutorials.featurebyte.com/api/v1/deployment/6564b9c77927bf2a9d6cca65/online_features",
headers={"Content-Type": "application/json", "active-catalog-id": "6564b7da5cf4e2dd964abb60", "Authorization": "Bearer 2u5EXHbdmiHNjEC4Y-EXBBJV-yeBiS35I4kK7FMjsLI"},
json={"entity_serving_names": entity_serving_names},
)
assert response.status_code == 200, response.json()
return pd.DataFrame.from_dict(response.json()["features"])
request_features([{"GROCERYCUSTOMERGUID": "016d7230-8602-42ef-9861-c672f48fb010"}])
In [9]:
Copied!
# Get shell script
deployment.get_online_serving_code(language="sh")
# Get shell script
deployment.get_online_serving_code(language="sh")
Out[9]:
#!/bin/sh
curl -X POST \
-H 'Content-Type: application/json' \
-H 'active-catalog-id: 6564b7da5cf4e2dd964abb60' \
-H 'Authorization: Bearer 2u5EXHbdmiHNjEC4Y-EXBBJV-yeBiS35I4kK7FMjsLI' \
-d '{"entity_serving_names": [{"GROCERYCUSTOMERGUID": "016d7230-8602-42ef-9861-c672f48fb010"}]}' \
https://tutorials.featurebyte.com/api/v1/deployment/6564b9c77927bf2a9d6cca65/online_features
Create batch request table¶
In [10]:
Copied!
# Get view of current customer
customer_table = catalog.get_table("GROCERYCUSTOMER")
customer_view = customer_table.get_view(view_mode="manual")
cond = customer_view.CurrentRecord == True
current_customer_view = customer_view[cond]
# Get view of current customer
customer_table = catalog.get_table("GROCERYCUSTOMER")
customer_view = customer_table.get_view(view_mode="manual")
cond = customer_view.CurrentRecord == True
current_customer_view = customer_view[cond]
In [11]:
Copied!
# Check primary entity of the deployed feature list to obtain its serving name
simple_feature_list.primary_entity
# Check primary entity of the deployed feature list to obtain its serving name
simple_feature_list.primary_entity
Out[11]:
[<featurebyte.api.entity.Entity at 0x106f19880> { 'name': 'customer', 'created_at': '2023-11-27T15:39:09.477000', 'updated_at': '2023-11-27T15:39:19.968000', 'description': None, 'serving_names': [ 'GROCERYCUSTOMERGUID' ], 'catalog_name': 'Grocery Dataset Tutorial' }]
In [12]:
Copied!
# Create batch request table from the view
batch_request_table = current_customer_view.create_batch_request_table(
name="Current Customers at " + datetime.now().strftime("%Y%m%d:%H%M"),
columns=["GroceryCustomerGuid"],
columns_rename_mapping={
"GroceryCustomerGuid": "GROCERYCUSTOMERGUID",
}
)
# Create batch request table from the view
batch_request_table = current_customer_view.create_batch_request_table(
name="Current Customers at " + datetime.now().strftime("%Y%m%d:%H%M"),
columns=["GroceryCustomerGuid"],
columns_rename_mapping={
"GroceryCustomerGuid": "GROCERYCUSTOMERGUID",
}
)
Done! |████████████████████████████████████████| 100% in 6.5s (0.16%/s)
In [13]:
Copied!
# Get name of the batch request table
batch_request_table.name
# Get name of the batch request table
batch_request_table.name
Out[13]:
'Current Customers at 20231127:1647'
In [14]:
Copied!
# List batch request tables in catalog
catalog.list_batch_request_tables()
# List batch request tables in catalog
catalog.list_batch_request_tables()
Out[14]:
id | name | type | shape | feature_store_name | created_at | |
---|---|---|---|---|---|---|
0 | 6564b9fa7927bf2a9d6cca66 | Current Customers at 20231127:1647 | view | [500, 1] | playground | 2023-11-27T15:47:08.828000 |
Compute batch feature table¶
In [15]:
Copied!
# Get deployment and batch request table
deployment = catalog.get_deployment("Customer Spending forecast")
batch_request_table = catalog.get_batch_request_table(batch_request_table.name)
# Get deployment and batch request table
deployment = catalog.get_deployment("Customer Spending forecast")
batch_request_table = catalog.get_batch_request_table(batch_request_table.name)
In [16]:
Copied!
# Compute batch features
batch_features = deployment.compute_batch_feature_table(
batch_request_table=batch_request_table,
batch_feature_table_name =
f"Customer Simple FeatureList for Spending forecast with {batch_request_table.name}"
)
# Compute batch features
batch_features = deployment.compute_batch_feature_table(
batch_request_table=batch_request_table,
batch_feature_table_name =
f"Customer Simple FeatureList for Spending forecast with {batch_request_table.name}"
)
Done! |████████████████████████████████████████| 100% in 9.8s (0.10%/s)
In [17]:
Copied!
# List observation tables
catalog.list_batch_feature_tables()
# List observation tables
catalog.list_batch_feature_tables()
Out[17]:
id | name | feature_store_name | batch_request_table_name | shape | created_at | |
---|---|---|---|---|---|---|
0 | 6564ba027927bf2a9d6cca67 | Customer Simple FeatureList for Spending forec... | playground | Current Customers at 20231127:1647 | [500, 8] | 2023-11-27T15:47:19.526000 |
In [18]:
Copied!
# Convert to pandas
batch_features.to_pandas()
# Convert to pandas
batch_features.to_pandas()
Downloading table |████████████████████████████████████████| 500/500 [100%] in 0
Out[18]:
GROCERYCUSTOMERGUID | CUSTOMER_Age_band | CUSTOMER_Latest_invoice_Amount | CUSTOMER_Count_of_invoice_14d | CUSTOMER_Avg_of_invoice_Amount_14d | CUSTOMER_Std_of_invoice_Amount_14d | CUSTOMER_Latest_invoice_Amount_Z_Score_to_invoice_Amount_28d | CUSTOMER_vs_OVERALL_item_TotalCost_across_product_ProductGroups_26w | |
---|---|---|---|---|---|---|---|---|
0 | c4f35072-c0ae-48b4-9f61-4bd4c348e48a | 60-64 | 10.09 | 0.0 | NaN | NaN | NaN | 0.740359 |
1 | c003c0a7-73f3-41c8-be4e-a7b0ede84312 | 30-34 | 17.98 | 0.0 | NaN | NaN | NaN | 0.348179 |
2 | 1d15db25-a662-41d5-9bad-1079b1e443ed | 40-44 | 6.00 | 0.0 | NaN | NaN | -1.000000 | 0.663154 |
3 | 6a780e8d-0c03-4246-b08c-d76be43470bb | 35-39 | 8.98 | 7.0 | 7.734286 | 7.824217 | -0.026837 | 0.774803 |
4 | 716c079e-a98f-456c-af24-ed7b1d87b38d | 85-89 | 5.76 | 0.0 | NaN | NaN | 1.000000 | 0.733773 |
... | ... | ... | ... | ... | ... | ... | ... | ... |
495 | 1c35daf7-e880-403f-95ab-3a2f3295f303 | 60-64 | 10.12 | 2.0 | 6.535000 | 3.585000 | 0.464564 | 0.738185 |
496 | d90fda3d-2dfb-4be0-bbcf-2b5c46c97e19 | 60-64 | 2.68 | 3.0 | 12.060000 | 11.073328 | -1.246973 | 0.641640 |
497 | f4c7dce9-ea7d-4af2-98c6-3630b7cd869d | 70-74 | 32.73 | 1.0 | 32.730000 | 0.000000 | 1.221433 | 0.780169 |
498 | 62fd4b3d-a46c-4166-971e-3c2041d7d4d8 | 20-24 | 3.88 | 2.0 | 4.440000 | 0.560000 | 0.315371 | 0.451703 |
499 | f8d62c21-4337-4a68-9063-ad3f8d16aa21 | 85-89 | 19.52 | 0.0 | NaN | NaN | -1.034224 | 0.628090 |
500 rows × 8 columns
In [19]:
Copied!
# download parquet file
batch_features.download()
# download parquet file
batch_features.download()
Downloading table |████████████████████████████████████████| 500/500 [100%] in 0
Out[19]:
PosixPath('BATCH_FEATURE_TABLE_6564ba03083bd15e6cb1e755.parquet')
In [20]:
Copied!
# delete if not needed any more
batch_features.delete()
batch_request_table.delete()
# delete if not needed any more
batch_features.delete()
batch_request_table.delete()
Done! |████████████████████████████████████████| 100% in 6.5s (0.16%/s) Done! |████████████████████████████████████████| 100% in 6.5s (0.16%/s)
Manage Deployment¶
In [21]:
Copied!
deployment = catalog.get_deployment("Customer Spending forecast")
deployment = catalog.get_deployment("Customer Spending forecast")
In [22]:
Copied!
# get feature jobs status (this will produce meaningful results once multiple jobs have been run)
deployment.get_feature_jobs_status()
# get feature jobs status (this will produce meaningful results once multiple jobs have been run)
deployment.get_feature_jobs_status()
Loading Feature(s) |████████████████████████████████████████| 7/7 [100%] in 0.7s
Out[22]:
Job statistics (last 1 hours)
request_date | job_history_window | job_duration_tolerance | |
---|---|---|---|
0 | 2023-11-27T15:47:48.386771 | 1 | 60 |
feature_name | aggregation_hash | |
---|---|---|
0 | CUSTOMER_Avg_of_invoice_Amount_14d | 45351576 |
1 | CUSTOMER_Count_of_invoice_14d | 7cee376c |
2 | CUSTOMER_Latest_invoice_Amount | 0ee300b2 |
3 | CUSTOMER_Latest_invoice_Amount_Z_Score_to_invoice_Amount_28d | 0ee300b2 |
4 | CUSTOMER_Latest_invoice_Amount_Z_Score_to_invoice_Amount_28d | 45351576 |
5 | CUSTOMER_Latest_invoice_Amount_Z_Score_to_invoice_Amount_28d | 31e54300 |
6 | CUSTOMER_Std_of_invoice_Amount_14d | 31e54300 |
7 | CUSTOMER_vs_OVERALL_item_TotalCost_across_product_ProductGroups_26w | f831676d |
8 | CUSTOMER_vs_OVERALL_item_TotalCost_across_product_ProductGroups_26w | c5e9a3c5 |
aggregation_hash | frequency(min) | completed_jobs | max_duration(s) | 95 percentile | frac_late | exceed_period | failed_jobs | incomplete_jobs | time_since_last | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 0ee300b2 | 60 | 0 | NaN | NaN | NaN | 0 | 0 | 1 | NaT |
1 | 7cee376c | 60 | 0 | NaN | NaN | NaN | 0 | 0 | 1 | NaT |
2 | 45351576 | 60 | 0 | NaN | NaN | NaN | 0 | 0 | 1 | NaT |
3 | 31e54300 | 60 | 0 | NaN | NaN | NaN | 0 | 0 | 1 | NaT |
4 | f831676d | 60 | 0 | NaN | NaN | NaN | 0 | 0 | 1 | NaT |
5 | c5e9a3c5 | 60 | 0 | NaN | NaN | NaN | 0 | 0 | 1 | NaT |
In [23]:
Copied!
# Disable deployment
deployment.disable()
# Disable deployment
deployment.disable()
Done! |████████████████████████████████████████| 100% in 9.8s (0.10%/s)
In [24]:
Copied!
# The deployment is still part of the catalog but disabled
catalog.list_deployments()
# The deployment is still part of the catalog but disabled
catalog.list_deployments()
Out[24]:
id | name | feature_list_name | feature_list_version | num_feature | enabled | |
---|---|---|---|---|---|---|
0 | 6564b9c77927bf2a9d6cca65 | Customer Spending forecast | Customer Simple FeatureList | V231127 | 7 | False |
In [ ]:
Copied!