# Copyright 2021 NVIDIA Corporation. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ================================
Building Intelligent Recommender Systems with Merlin
This notebook is created using the latest stable merlin-tensorflow-inference container.
Overview
Recommender Systems (RecSys) are the engine of the modern internet and the catalyst for human decisions. Building a recommendation system is challenging because it requires multiple stages (data preprocessing, offline training, item retrieval, filtering, ranking, ordering, etc.) to work together seamlessly and efficiently. The biggest challenges for new practitioners are the lack of understanding around what RecSys look like in the real world, and the gap between examples of simple models and a production-ready end-to-end recommender systems.
The figure below represents a four-stage recommender systems. This is more complex process than only training a single model and deploying it, and it is much more realistic and closer to what’s happening in the real-world recommender production systems.
In these series of notebooks, we are going to showcase how we can deploy a four-stage recommender systems using Merlin Systems library easily on Triton Inference Server. Let’s go over the concepts in the figure briefly.
Retrieval: This is the step to narrow down millions of items into thousands of candidates. We are going to train a Two-Tower item retrieval model to retrieve the relevant top-K candidate items.
Filtering: This step is to exclude the already interacted or undesirable items from the candidate items set or to apply business logic rules. Although this is an important step, for this example we skip this step.
Scoring: This is also known as ranking. Here the retrieved and filtered candidate items are being scored. We are going to train a ranking model to be able to use at our scoring step.
Ordering: At this stage, we can order the final set of items that we want to recommend to the user. Here, we’re able to align the output of the model with business needs, constraints, or criteria.
To learn more about the four-stage recommender systems, you can listen to Even Oldridge’s Moving Beyond Recommender Models talk at KDD’21 and read more in this blog post.
Learning objectives
Understanding four stages of recommender systems
Training retrieval and ranking models with Merlin Models
Setting up feature store and approximate nearest neighbours (ANN) search libraries
Deploying trained models to Triton Inference Server with Merlin Systems
In addition to NVIDIA Merlin libraries and the Triton Inference Server client library, we use two external libraries in these series of examples:
Feast: an end-to-end open source feature store library for machine learning
Faiss: a library for efficient similarity search and clustering of dense vectors
You can find more information about Feast feature store
and Faiss
libraries in the next notebook.
Import required libraries and functions
Compatibility:
These notebooks are developed and tested using our latest merlin-tensorflow:22.XX
container on NVIDIA’s docker registry.
# for running this example on GPU, install the following libraries
# %pip install tensorflow "feast<0.20" faiss-gpu
# for running this example on CPU, uncomment the following lines
# %pip install tensorflow-cpu "feast<0.20" faiss-cpu
# %pip uninstall cudf
import os
import nvtabular as nvt
from nvtabular.ops import *
from merlin.schema.tags import Tags
import merlin.models.tf as mm
from merlin.io.dataset import Dataset
from merlin.datasets.ecommerce import transform_aliccp
import tensorflow as tf
# for running this example on CPU, comment out the line below
os.environ["TF_GPU_ALLOCATOR"] = "cuda_malloc_async"
2022-09-22 23:24:35.828030: I tensorflow/core/platform/cpu_feature_guard.cc:194] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations: SSE3 SSE4.1 SSE4.2 AVX
To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.
2022-09-22 23:24:36.997030: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1532] Created device /job:localhost/replica:0/task:0/device:GPU:0 with 16249 MB memory: -> device: 0, name: Quadro GV100, pci bus id: 0000:2d:00.0, compute capability: 7.0
# disable INFO and DEBUG logging everywhere
import logging
logging.disable(logging.WARNING)
In this example notebook, we will generate the synthetic train and test datasets mimicking the real Ali-CCP: Alibaba Click and Conversion Prediction dataset to build our recommender system models.
First, we define our input path and feature repo path.
DATA_FOLDER = os.environ.get("DATA_FOLDER", "/workspace/data/")
# set up the base dir for feature store
BASE_DIR = os.environ.get(
"BASE_DIR", "/Merlin/examples/Building-and-deploying-multi-stage-RecSys/"
)
Then, we use generate_data
utility function to generate synthetic dataset.
from merlin.datasets.synthetic import generate_data
NUM_ROWS = os.environ.get("NUM_ROWS", 100_000)
train_raw, valid_raw = generate_data("aliccp-raw", int(NUM_ROWS), set_sizes=(0.7, 0.3))
/usr/local/lib/python3.8/dist-packages/merlin/schema/tags.py:148: UserWarning: Compound tags like Tags.USER_ID have been deprecated and will be removed in a future version. Please use the atomic versions of these tags, like [<Tags.USER: 'user'>, <Tags.ID: 'id'>].
warnings.warn(
/usr/local/lib/python3.8/dist-packages/merlin/schema/tags.py:148: UserWarning: Compound tags like Tags.ITEM_ID have been deprecated and will be removed in a future version. Please use the atomic versions of these tags, like [<Tags.ITEM: 'item'>, <Tags.ID: 'id'>].
warnings.warn(
If you would like to use the real ALI-CCP dataset, you can use get_aliccp() function instead. This function takes the raw csv files, and generate parquet files that can be directly fed to NVTabular workflow above.
Feature Engineering with NVTabular
output_path = os.path.join(DATA_FOLDER, "processed_nvt")
In the NVTabular workflow below, notice that we apply Dropna()
op at the end. The reason we do that is to remove rows with missing values in the final dataframe after preceding transformations. Although, the synthetic dataset that we generate above and use in this notebook does not have null entries, you might have null entries in your user_id
and item_id
columns in your own custom dataset. Therefore while applying Dropna()
we will not be registering null user_id_raw
and item_id_raw
values in the feature store, and will be avoiding potential issues that can occur because of any null entires.
user_id_raw = ["user_id"] >> Rename(postfix='_raw') >> LambdaOp(lambda col: col.astype("int32")) >> TagAsUserFeatures()
item_id_raw = ["item_id"] >> Rename(postfix='_raw') >> LambdaOp(lambda col: col.astype("int32")) >> TagAsItemFeatures()
user_id = ["user_id"] >> Categorify(dtype="int32") >> TagAsUserID()
item_id = ["item_id"] >> Categorify(dtype="int32") >> TagAsItemID()
item_features = (
["item_category", "item_shop", "item_brand"] >> Categorify(dtype="int32") >> TagAsItemFeatures()
)
user_features = (
[
"user_shops",
"user_profile",
"user_group",
"user_gender",
"user_age",
"user_consumption_2",
"user_is_occupied",
"user_geography",
"user_intentions",
"user_brands",
"user_categories",
] >> Categorify(dtype="int32") >> TagAsUserFeatures()
)
targets = ["click"] >> AddMetadata(tags=[Tags.BINARY_CLASSIFICATION, "target"])
outputs = user_id + item_id + item_features + user_features + user_id_raw + item_id_raw + targets
# add dropna op to filter rows with nulls
outputs = outputs >> Dropna()
Let’s call transform_aliccp
utility function to be able to perform fit
and transform
steps on the raw dataset applying the operators defined in the NVTabular workflow pipeline below, and also save our workflow model. After fit and transform, the processed parquet files are saved to output_path.
transform_aliccp(
(train_raw, valid_raw), output_path, nvt_workflow=outputs, workflow_name="workflow"
)
/usr/local/lib/python3.8/dist-packages/cudf/core/frame.py:384: UserWarning: The deep parameter is ignored and is only included for pandas compatibility.
warnings.warn(
Training a Retrieval Model with Two-Tower Model
We start with the offline candidate retrieval stage. We are going to train a Two-Tower model for item retrieval. To learn more about the Two-tower model you can visit 05-Retrieval-Model.ipynb.
Feature Engineering with NVTabular
We are going to process our raw categorical features by encoding them using Categorify()
operator and tag the features with user
or item
tags in the schema file. To learn more about NVTabular and the schema object visit this example notebook in the Merlin Models repo.
Define a new output path to store the filtered datasets and schema files.
output_path2 = os.path.join(DATA_FOLDER, "processed/retrieval")
train_tt = Dataset(os.path.join(output_path, "train", "*.parquet"))
valid_tt = Dataset(os.path.join(output_path, "valid", "*.parquet"))
We select only positive interaction rows where click==1
in the dataset with Filter()
operator.
inputs = train_tt.schema.column_names
outputs = inputs >> Filter(f=lambda df: df["click"] == 1)
workflow2 = nvt.Workflow(outputs)
workflow2.fit(train_tt)
workflow2.transform(train_tt).to_parquet(
output_path=os.path.join(output_path2, "train")
)
workflow2.transform(valid_tt).to_parquet(
output_path=os.path.join(output_path2, "valid")
)
NVTabular exported the schema file, schema.pbtxt
a protobuf text file, of our processed dataset. To learn more about the schema object and schema file you can explore 02-Merlin-Models-and-NVTabular-integration.ipynb notebook.
Read filtered parquet files as Dataset objects.
train_tt = Dataset(os.path.join(output_path2, "train", "*.parquet"), part_size="500MB")
valid_tt = Dataset(os.path.join(output_path2, "valid", "*.parquet"), part_size="500MB")
schema = train_tt.schema.select_by_tag([Tags.ITEM_ID, Tags.USER_ID, Tags.ITEM, Tags.USER]).without(['user_id_raw', 'item_id_raw', 'click'])
train_tt.schema = schema
valid_tt.schema = schema
model_tt = mm.TwoTowerModel(
schema,
query_tower=mm.MLPBlock([128, 64], no_activation_last_layer=True),
samplers=[mm.InBatchSampler()],
embedding_options=mm.EmbeddingOptions(infer_embedding_sizes=True),
)
model_tt.compile(
optimizer="adam",
run_eagerly=False,
loss="categorical_crossentropy",
metrics=[mm.RecallAt(10), mm.NDCGAt(10)],
)
model_tt.fit(train_tt, validation_data=valid_tt, batch_size=1024 * 8, epochs=1)
5/5 [==============================] - 10s 412ms/step - loss: 8.9092 - recall_at_10: 0.0117 - ndcg_at_10: 0.0075 - regularization_loss: 0.0000e+00 - val_loss: 8.9037 - val_recall_at_10: 0.0191 - val_ndcg_at_10: 0.0144 - val_regularization_loss: 0.0000e+00
<keras.callbacks.History at 0x7f52c84da9d0>
Exporting query (user) model
We export the query tower to use it later during the model deployment stage with Merlin Systems.
query_tower = model_tt.retrieval_block.query_block()
query_tower.save(os.path.join(BASE_DIR, "query_tower"))
Training a Ranking Model with DLRM
Now we will move onto training an offline ranking model. This ranking model will be used for scoring our retrieved items.
Read processed parquet files. We use the schema
object to define our model.
# define train and valid dataset objects
train = Dataset(os.path.join(output_path, "train", "*.parquet"), part_size="500MB")
valid = Dataset(os.path.join(output_path, "valid", "*.parquet"), part_size="500MB")
# define schema object
schema = train.schema.without(['user_id_raw', 'item_id_raw'])
/usr/local/lib/python3.8/dist-packages/cudf/core/frame.py:384: UserWarning: The deep parameter is ignored and is only included for pandas compatibility.
warnings.warn(
/usr/local/lib/python3.8/dist-packages/merlin/schema/tags.py:148: UserWarning: Compound tags like Tags.USER_ID have been deprecated and will be removed in a future version. Please use the atomic versions of these tags, like [<Tags.USER: 'user'>, <Tags.ID: 'id'>].
warnings.warn(
/usr/local/lib/python3.8/dist-packages/merlin/schema/tags.py:148: UserWarning: Compound tags like Tags.ITEM_ID have been deprecated and will be removed in a future version. Please use the atomic versions of these tags, like [<Tags.ITEM: 'item'>, <Tags.ID: 'id'>].
warnings.warn(
target_column = schema.select_by_tag(Tags.TARGET).column_names[0]
target_column
'click'
Deep Learning Recommendation Model (DLRM) architecture is a popular neural network model originally proposed by Facebook in 2019. The model was introduced as a personalization deep learning model that uses embeddings to process sparse features that represent categorical data and a multilayer perceptron (MLP) to process dense features, then interacts these features explicitly using the statistical techniques proposed in here. To learn more about DLRM architetcture please visit Exploring-different-models
notebook in the Merlin Models GH repo.
model = mm.DLRMModel(
schema,
embedding_dim=64,
bottom_block=mm.MLPBlock([128, 64]),
top_block=mm.MLPBlock([128, 64, 32]),
prediction_tasks=mm.BinaryClassificationTask(target_column),
)
model.compile(optimizer="adam", run_eagerly=False, metrics=[tf.keras.metrics.AUC()])
model.fit(train, validation_data=valid, batch_size=16 * 1024)
5/5 [==============================] - 5s 251ms/step - loss: 0.6932 - auc: 0.4982 - regularization_loss: 0.0000e+00 - val_loss: 0.6932 - val_auc: 0.5000 - val_regularization_loss: 0.0000e+00
<keras.callbacks.History at 0x7f52906b93a0>
Let’s save our DLRM model to be able to load back at the deployment stage.
model.save(os.path.join(BASE_DIR, "dlrm"))
In the following cells we are going to export the required user and item features files, and save the query (user) tower model and item embeddings to disk. If you want to read more about exporting retrieval models, please visit 05-Retrieval-Model.ipynb notebook in Merlin Models library repo.
Set up a feature store with Feast
Before we move onto the next step, we need to create a Feast feature repository. Feast is an end-to-end open source feature store for machine learning. Feast (Feature Store) is a customizable operational data system that re-uses existing infrastructure to manage and serve machine learning features to real-time models.
We will create the feature repo in the current working directory, which is BASE_DIR
for us.
!rm -rf $BASE_DIR/feature_repo
!cd $BASE_DIR && feast init feature_repo
Creating a new Feast repository in /Merlin/examples/Building-and-deploying-multi-stage-RecSys/feature_repo.
You should be seeing a message like Creating a new Feast repository in … printed out above. Now, navigate to the feature_repo
folder and remove the demo parquet file created by default, and examples.py
file.
feature_repo_path = os.path.join(BASE_DIR, "feature_repo")
if os.path.exists(f"{feature_repo_path}/example.py"):
os.remove(f"{feature_repo_path}/example.py")
if os.path.exists(f"{feature_repo_path}/data/driver_stats.parquet"):
os.remove(f"{feature_repo_path}/data/driver_stats.parquet")
Exporting user and item features
from merlin.models.utils.dataset import unique_rows_by_features
user_features = (
unique_rows_by_features(train, Tags.USER, Tags.USER_ID)
.compute()
.reset_index(drop=True)
)
user_features.head()
user_id | user_shops | user_profile | user_group | user_gender | user_age | user_consumption_2 | user_is_occupied | user_geography | user_intentions | user_brands | user_categories | user_id_raw | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 6 |
1 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 2 | 2 | 8 |
2 | 3 | 3 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 3 | 3 | 3 | 7 |
3 | 4 | 4 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 4 | 4 | 4 | 5 |
4 | 5 | 5 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 5 | 5 | 5 | 10 |
We will artificially add datetime
and created
timestamp columns to our user_features dataframe. This required by Feast to track the user-item features and their creation time and to determine which version to use when we query Feast.
from datetime import datetime
user_features["datetime"] = datetime.now()
user_features["datetime"] = user_features["datetime"].astype("datetime64[ns]")
user_features["created"] = datetime.now()
user_features["created"] = user_features["created"].astype("datetime64[ns]")
user_features.head()
user_id | user_shops | user_profile | user_group | user_gender | user_age | user_consumption_2 | user_is_occupied | user_geography | user_intentions | user_brands | user_categories | user_id_raw | datetime | created | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 6 | 2022-09-22 23:02:17.150145 | 2022-09-22 23:02:17.152070 |
1 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 2 | 2 | 8 | 2022-09-22 23:02:17.150145 | 2022-09-22 23:02:17.152070 |
2 | 3 | 3 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 3 | 3 | 3 | 7 | 2022-09-22 23:02:17.150145 | 2022-09-22 23:02:17.152070 |
3 | 4 | 4 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 4 | 4 | 4 | 5 | 2022-09-22 23:02:17.150145 | 2022-09-22 23:02:17.152070 |
4 | 5 | 5 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 5 | 5 | 5 | 10 | 2022-09-22 23:02:17.150145 | 2022-09-22 23:02:17.152070 |
user_features.to_parquet(
os.path.join(BASE_DIR, "feature_repo/data", "user_features.parquet")
)
item_features = (
unique_rows_by_features(train, Tags.ITEM, Tags.ITEM_ID)
.compute()
.reset_index(drop=True)
)
item_features["datetime"] = datetime.now()
item_features["datetime"] = item_features["datetime"].astype("datetime64[ns]")
item_features["created"] = datetime.now()
item_features["created"] = item_features["created"].astype("datetime64[ns]")
item_features.head()
item_id | item_category | item_shop | item_brand | item_id_raw | datetime | created | |
---|---|---|---|---|---|---|---|
0 | 1 | 1 | 1 | 1 | 5 | 2022-09-22 23:02:17.245267 | 2022-09-22 23:02:17.246515 |
1 | 2 | 2 | 2 | 2 | 7 | 2022-09-22 23:02:17.245267 | 2022-09-22 23:02:17.246515 |
2 | 3 | 3 | 3 | 3 | 10 | 2022-09-22 23:02:17.245267 | 2022-09-22 23:02:17.246515 |
3 | 4 | 4 | 4 | 4 | 6 | 2022-09-22 23:02:17.245267 | 2022-09-22 23:02:17.246515 |
4 | 5 | 5 | 5 | 5 | 8 | 2022-09-22 23:02:17.245267 | 2022-09-22 23:02:17.246515 |
# save to disk
item_features.to_parquet(
os.path.join(BASE_DIR, "feature_repo/data", "item_features.parquet")
)
Extract and save Item embeddings
item_embs = model_tt.item_embeddings(
Dataset(item_features, schema=schema), batch_size=1024
)
item_embs_df = item_embs.compute(scheduler="synchronous")
/usr/local/lib/python3.8/dist-packages/merlin/schema/tags.py:148: UserWarning: Compound tags like Tags.ITEM_ID have been deprecated and will be removed in a future version. Please use the atomic versions of these tags, like [<Tags.ITEM: 'item'>, <Tags.ID: 'id'>].
warnings.warn(
# select only item_id together with embedding columns
item_embeddings = item_embs_df.drop(
columns=["item_category", "item_shop", "item_brand"]
)
item_embeddings.head()
item_id | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ... | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 0.030497 | 0.029997 | 0.012621 | -0.001204 | 0.012877 | -0.031165 | -0.009491 | -0.024208 | -0.011206 | ... | 0.010395 | -0.044563 | 0.002028 | -0.011641 | -0.017367 | -0.016538 | 0.003312 | -0.020471 | 0.016938 | 0.037699 |
1 | 2 | 0.014305 | 0.004831 | -0.006791 | -0.010725 | 0.002375 | -0.010010 | -0.006006 | -0.016317 | 0.019688 | ... | -0.023776 | -0.028429 | -0.039675 | 0.035854 | 0.007236 | -0.001316 | 0.014094 | 0.024848 | 0.023687 | 0.020931 |
2 | 3 | 0.026491 | -0.011876 | 0.023269 | -0.004026 | 0.038133 | 0.016866 | -0.037301 | -0.014816 | 0.018586 | ... | -0.016928 | -0.003044 | 0.017992 | -0.043302 | 0.000884 | -0.027940 | 0.005639 | -0.008831 | -0.009807 | -0.000746 |
3 | 4 | 0.046828 | 0.017710 | -0.033954 | -0.039186 | 0.014467 | -0.056866 | -0.011080 | 0.001606 | -0.000757 | ... | -0.014907 | -0.020841 | -0.039584 | 0.009472 | -0.009085 | -0.037578 | 0.006459 | 0.008231 | 0.010318 | -0.005625 |
4 | 5 | 0.050902 | -0.001969 | -0.003946 | -0.050269 | -0.011292 | -0.016854 | -0.031103 | -0.010389 | 0.007709 | ... | 0.009147 | -0.000667 | 0.019289 | -0.006992 | 0.018633 | 0.013128 | -0.017529 | 0.040066 | 0.040147 | 0.035671 |
5 rows × 65 columns
# save to disk
item_embeddings.to_parquet(os.path.join(BASE_DIR, "item_embeddings.parquet"))
Create feature definitions
Now we will create our user and item features definitions in the user_features.py and item_features.py files and save these files in the feature_repo.
file = open(os.path.join(BASE_DIR, "feature_repo/", "user_features.py"), "w")
file.write(
"""
from google.protobuf.duration_pb2 import Duration
import datetime
from feast import Entity, Feature, FeatureView, ValueType
from feast.infra.offline_stores.file_source import FileSource
user_features = FileSource(
path="{}",
event_timestamp_column="datetime",
created_timestamp_column="created",
)
user_raw = Entity(name="user_id_raw", value_type=ValueType.INT32, description="user id raw",)
user_features_view = FeatureView(
name="user_features",
entities=["user_id_raw"],
ttl=Duration(seconds=86400 * 7),
features=[
Feature(name="user_shops", dtype=ValueType.INT32),
Feature(name="user_profile", dtype=ValueType.INT32),
Feature(name="user_group", dtype=ValueType.INT32),
Feature(name="user_gender", dtype=ValueType.INT32),
Feature(name="user_age", dtype=ValueType.INT32),
Feature(name="user_consumption_2", dtype=ValueType.INT32),
Feature(name="user_is_occupied", dtype=ValueType.INT32),
Feature(name="user_geography", dtype=ValueType.INT32),
Feature(name="user_intentions", dtype=ValueType.INT32),
Feature(name="user_brands", dtype=ValueType.INT32),
Feature(name="user_categories", dtype=ValueType.INT32),
Feature(name="user_id", dtype=ValueType.INT32),
],
online=True,
input=user_features,
tags=dict(),
)
""".format(
os.path.join(BASE_DIR, "feature_repo/data/", "user_features.parquet")
)
)
file.close()
with open(os.path.join(BASE_DIR, "feature_repo/", "item_features.py"), "w") as f:
f.write(
"""
from google.protobuf.duration_pb2 import Duration
import datetime
from feast import Entity, Feature, FeatureView, ValueType
from feast.infra.offline_stores.file_source import FileSource
item_features = FileSource(
path="{}",
event_timestamp_column="datetime",
created_timestamp_column="created",
)
item = Entity(name="item_id", value_type=ValueType.INT32, description="item id",)
item_features_view = FeatureView(
name="item_features",
entities=["item_id"],
ttl=Duration(seconds=86400 * 7),
features=[
Feature(name="item_category", dtype=ValueType.INT32),
Feature(name="item_shop", dtype=ValueType.INT32),
Feature(name="item_brand", dtype=ValueType.INT32),
Feature(name="item_id_raw", dtype=ValueType.INT32),
],
online=True,
input=item_features,
tags=dict(),
)
""".format(
os.path.join(BASE_DIR, "feature_repo/data/", "item_features.parquet")
)
)
file.close()
Let’s checkout our Feast feature repository structure.
# install seedir
!pip install seedir
Requirement already satisfied: seedir in /usr/local/lib/python3.8/dist-packages (0.3.1)
Requirement already satisfied: natsort in /usr/local/lib/python3.8/dist-packages (from seedir) (8.1.0)
Requirement already satisfied: emoji in /usr/local/lib/python3.8/dist-packages (from seedir) (2.0.0)
import seedir as sd
feature_repo_path = os.path.join(BASE_DIR, "feature_repo")
sd.seedir(
feature_repo_path,
style="lines",
itemlimit=10,
depthlimit=3,
exclude_folders=".ipynb_checkpoints",
sort=True,
)
feature_repo/
├─__init__.py
├─data/
│ ├─item_features.parquet
│ └─user_features.parquet
├─feature_store.yaml
├─item_features.py
└─user_features.py
Next Steps
We trained and exported our ranking and retrieval models and NVTabular workflows. In the next step, we will learn how to deploy our trained models into Triton Inference Server (TIS) with Merlin Systems library.
For the next step, move on to the 02-Deploying-multi-stage-Recsys-with-Merlin-Systems.ipynb
notebook to deploy our saved models as an ensemble to TIS and obtain prediction results for a given request.