Recommendation models help you deliver personalized product and content recommendations in BlueConic using custom ONNX-based machine learning models. These models score items such as products, articles, or offers in real time, allowing you to tailor recommendations based on customer behavior, profile attributes, item similarity, and business goals.
Use recommendation models to power use cases such as:
Product recommendations based on browsing or purchase behavior
Personalized content recommendations
Basket expansion and cross-sell recommendations
Related-content experiences
Popularity-based recommendations
Profile-driven recommendations using interests or affinities
Before you begin
Before creating or uploading a recommendation model, confirm the following:
You have access to the AI Workbench in BlueConic.
Your model is in ONNX format, or you have a source model ready to convert.
The product or content store you want to use is configured in BlueConic.
Any profile properties used by the model already exist in BlueConic.
You understand which recommendation inputs your model requires.
Recommendation model format
BlueConic recommendation models are stored in ONNX format. A recommendation model returns a score for each candidate item in the recommendation set.
After filtering is applied, BlueConic recommends the items with the highest scores.
Inputs
Input | Type | Description |
|
| External item IDs for all candidate items |
|
| The id of the current item |
|
| Vectorized profile features based on |
|
| Hours since the customer last ordered the item |
|
| Whether the item is currently in the shopping cart |
|
| Number of times the customer viewed the item |
|
| Number of times the customer ordered the item |
|
| The number of times the item was viewed within the configured timeframe |
|
| The number of times the item was clicked within the configured timeframe |
|
| Number of times the item was a landing page |
|
| Number of recommendation views |
|
| Number of recent add-to-cart events |
|
| Number of recent purchases |
Outputs
Recommendation models must define the following output:
Output | Type | Description |
|
| A list of scores between 0 and 1, one per item, used to rank the recommendations. If the score for an item is negative, that item will not be shown by the recommender. |
Metadata
Recommendation models support the following metadata fields.
Metadata field | Python API name | Description |
|
| The profile property IDs used as input features for the model. |
|
| Feature names used to populate the profile vector. The |
|
| The specific content/product store ID the model is trained for. If this metadata property is left empty, the recommendation model works with any content/product store. |
Note: If storeId is not configured, the model can work with any product or content store.
Upload a recommendation model
You can upload recommendation models through the BlueConic UI, AI Workbench notebooks, or the REST API.
Using the UI
Go to More > AI Workbench.
Open the Models tab.
Click Add Model.
Set the model type to Recommendation.
Upload the ONNX model file.
(Optional) Select profile properties used by the model.
(Optional) Add feature names.
(Optional) Select a store ID.
Click Save.
Using the AI Workbench (Python notebook)
If you retrain recommendation models on a schedule, use the update_model method together with a model parameter.
First, define your parameters:
import blueconic
bc = blueconic.Client()
# the model to update
MODEL_ID = bc.get_blueconic_parameter_value("Model", "model")
if not MODEL_ID:
raise ValueError("Please configure a model")
Then upload the trained ONNX recommendation model:
model = bc.get_model(MODEL_ID)
model.type = blueconic.domain.ModelType.RECOMMENDATION
model.model = onnx_model.SerializeToString()
bc.update_model(model)
Note: For more information, see the BlueConic Python API documentation.
Using the REST API
Recommendation models can also be uploaded through the BlueConic REST API, for example as part of an external recommendation pipeline.
Note: For more information, see the BlueConic REST API documentation.
Create a recommendation model
The following examples demonstrate how to build recommendation models directly in ONNX.
Your first model: random recommendations
The following model generates random scores for all candidate items.
from onnx import helper, TensorProto
id_input = helper.make_tensor_value_info("id", TensorProto.STRING, [None])
score_output = helper.make_tensor_value_info("score", TensorProto.FLOAT, [None])
random_node = helper.make_node(
op_type = "RandomUniformLike",
inputs = ["id"],
outputs = ["score"],
dtype = TensorProto.FLOAT
)
graph = helper.make_graph(
name = "Random recommendations",
nodes = [random_node],
inputs = [id_input],
outputs = [score_output]
)
model = helper.make_model(
graph = graph,
ir_version=10,
opset_imports=[
helper.make_opsetid("", 21),
]
)
Although this model is not useful in production on its own, it demonstrates several important concepts:
Recommendation models declare the inputs they require.
Recommendation models must return a
scoreoutput.Models should define both
ir_versionandopsetidto ensure platform compatibility.
Tip: When creating your own ONNX model, it's important to set the ir_version and opsetid to ensure your model is compatible with the platform.
Show popular items
One of the simplest recommendation strategies is ranking items by popularity, such as recent purchases or views.
The following example ranks products based on recent order counts.
# the number of times the product was bought recently
ordered_input = helper.make_tensor_value_info(
"recent_order",
TensorProto.INT64,
[None]
)
score_output = helper.make_tensor_value_info(
"score",
TensorProto.FLOAT,
[None]
)
cast_node = helper.make_node(
op_type="Cast",
inputs=["recent_order"],
outputs=["recent_order_f"],
to=TensorProto.FLOAT
)
max_ordered_node = helper.make_node(
op_type="ReduceMax",
inputs=["recent_order_f"],
outputs=["ordered_max"]
)
div_node = helper.make_node(
op_type="Div",
inputs=["recent_order_f", "ordered_max"],
outputs=["score"]
)
Popularity-based recommendation models provide a strong baseline when evaluating more advanced algorithms.
BlueConic also includes several built-in recommendation algorithms, including:
RECENT_VIEWRECENT_ORDERRECENT_SHOPPINGCARTRECENT_ENTRYPAGERECENT_CTR
Note: For a full list of recommendation algorithms, see:
Product Recommendation Algorithms | Content Recommendation Algorithms | BlueConic REST API
Filter items
Many recommendation use cases require excluding specific items.
Common examples include:
Excluding the current product
Excluding products already in the shopping cart
Excluding articles already viewed
When the score is negative, the item is not shown by the recommender. You can use this to let your model hide items that should not be recommended. For example, if you want to ensure that the product currently being viewed (i.e. current_item) does not show up in your recommendations, you can adapt the model above as follows:
# the ID for each candidate product
id_input = helper.make_tensor_value_info("id", TensorProto.STRING, [None])
# the ID of the item currently being viewed
current_item_input = helper.make_tensor_value_info("current_item", TensorProto.STRING, [])
# the number of times the product was bought in the configured timeframe
ordered_input = helper.make_tensor_value_info("recent_order", TensorProto.INT64, [None])
# a list of scores between 0 and 1 for all items
score_output = helper.make_tensor_value_info("score", TensorProto.FLOAT, [None])
# convert the order count to float to simplify the calculations later
cast_recent_order_node = helper.make_node(
op_type = "Cast",
inputs = ["recent_order"],
outputs = ["recent_order_f"],
to = TensorProto.FLOAT
)
# find the maximum number of times any product was bought
max_ordered_node = helper.make_node(
op_type = "ReduceMax",
inputs = ["recent_order_f"],
outputs = ["ordered_max"]
)
# scale the counts so that the score falls between 0 and 1
div_node = helper.make_node(
op_type = "Div",
inputs = ["recent_order_f", "ordered_max"],
outputs = ["scaled_score"]
)
# build a boolean mask that is True for the current item
equal_node = helper.make_node(
op_type = "Equal",
inputs = ["id", "current_item"],
outputs = ["is_current_item"]
)
# convert the boolean mask to floats (1.0 for the current item, 0.0 otherwise)
cast_is_current_item_node = helper.make_node(
op_type = "Cast",
inputs = ["is_current_item"],
outputs = ["is_current_item_f"],
to = TensorProto.FLOAT
)
# subtract 1 from the score of the current item
# to ensure it does not end up in the top recommendations
sub_node = helper.make_node(
op_type = "Sub",
inputs = ["scaled_score", "is_current_item_f"],
outputs = ["score"]
)
graph = helper.make_graph(
name = "Products the customer bought the most",
inputs = [id_input, current_item_input, ordered_input],
outputs = [score_output],
nodes = [cast_recent_order_node, max_ordered_node, div_node, equal_node,
cast_is_current_item_node, sub_node]
)
onnx_model = helper.make_model(
graph=graph,
ir_version=10,
opset_imports=[
helper.make_opsetid("", 21),
]
)
Note: For a full list of available filters, see BlueConic REST API
Show products the customer previously bought
You can recommend products a customer frequently purchases by using the ordered input instead of recent_order.
This enables use cases such as:
Replenishment recommendations
Loyalty-focused recommendations
Personalized discounts
The model can also suppress products the customer never purchased by assigning negative scores.
# the number of times the item was ordered by this customer
ordered_input = helper.make_tensor_value_info("ordered", TensorProto.INT64, [None])
# a list of scores between 0 and 1 for all items
score_output = helper.make_tensor_value_info("score", TensorProto.FLOAT, [None])
# convert the order count to float to simplify the calculations later
cast_node = helper.make_node(
op_type = "Cast",
inputs = ["ordered"],
outputs = ["ordered_f"],
to = TensorProto.FLOAT
)
# find the maximum number of times any product was bought by this customer
max_ordered_node = helper.make_node(
op_type = "ReduceMax",
inputs = ["ordered_f"],
outputs = ["ordered_max"]
)
# a *very* small number used to avoid division by zero
const_epsilon_node = helper.make_node(
op_type = "Constant",
inputs = [],
outputs = ["epsilon"],
value = helper.make_tensor("epsilon", TensorProto.FLOAT, [], [1e-6])
)
# add a small epsilon to avoid division by zero
add_node = helper.make_node(
op_type = "Add",
inputs = ["ordered_max", "epsilon"],
outputs = ["ordered_max_plus_epsilon"]
)
# scale the counts so that the score falls between 0 and 1
div_node = helper.make_node(
op_type = "Div",
inputs = ["ordered_f", "ordered_max_plus_epsilon"],
outputs = ["scaled_ordered_f"]
)
# subtract epsilon from the score so that products the customer
# never purchased end up with a negative score, excluding them
# from recommendations. Note: this may also exclude products that
# were purchased very infrequently compared to the most popular ones.
sub_node = helper.make_node(
op_type = "Sub",
inputs = ["scaled_ordered_f", "epsilon"],
outputs = ["score"]
)
graph = helper.make_graph(
name = "Products the customer bought the most",
inputs = [ordered_input],
outputs = [score_output],
nodes = [const_epsilon_node, cast_node, max_ordered_node, add_node,
div_node, sub_node]
)
onnx_model = helper.make_model(
graph=graph,
ir_version=10,
opset_imports=[
helper.make_opsetid("", 21),
]
)
Show items similar to the current page
You can use cosine similarity to find items similar to the current item.
The trick is to perform L2 normalization when training the model. Calculating cosine similarity then becomes a simple dot product inside the ONNX graph.
First, retrieve the content from the store:
import blueconic
bc = blueconic.Client()
CONNECTION_ID = bc.get_blueconic_parameter_value("Product/Content collector", "connection")
STORE = bc.get_connection(CONNECTION_ID).get_store()
ids = []
descriptions = []
for item in STORE.get_items():
ids.append(item.id)
descriptions.append(item.get_value("description"))
In this example, we are only using the description, but you should experiment with combining multiple fields — for example, by adding the item name as well.
You can then calculate embeddings for each item in the product store using the get_text_embeddings method in the BlueConic Python package:
import numpy as np
def create_embeddings_tensor(descriptions):
"""
Turn the list of descriptions into an embeddings tensor.
"""
embeddings = np.empty((len(descriptions), 256), np.float16)
for i, description in enumerate(descriptions):
embeddings[i] = bc.get_text_embeddings(
text = [description],
config = {"dimensions": 256}
)[0].embedding
return embeddings
You can also use scikit-learn:
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.decomposition import TruncatedSVD
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import Normalizer
def create_embeddings_tensor_sklearn(descriptions):
"""
Turn descriptions into embeddings.
"""
pipeline = Pipeline([
(
'tfidf',
TfidfVectorizer(
max_features=10000,
stop_words='english'
)
),
(
'svd',
TruncatedSVD(n_components=256)
),
(
'l2',
Normalizer(norm='l2', copy=False)
)
])
embeddings = pipeline.fit_transform(descriptions)
return embeddings.astype(np.float16)
If your embeddings are based on a limited vocabulary (for example, product categories only), you can omit the TruncatedSVD step.
def generate_cosine_similarity_recommender(ids, embeddings):
"""
ids: list of strings (item identifiers)
embeddings: numpy array of shape (len(ids), dims)
Note: the embeddings should already be L2-normalized.
"""
# ensure the embeddings array is converted to float16
embeddings = embeddings.astype(dtype=np.float16, copy=False)
# pre-transpose the embedding matrix so it is laid out as (dims, num_items).
# this avoids a runtime Transpose of a large constant on every inference call,
# and gives MatMul its preferred layout: (1, dims) @ (dims, num_items) = (1, num_items).
# .copy() materializes a contiguous buffer (numpy's .T is just a view).
embeddings_t = embeddings.T.copy()
dims = embeddings.shape[1]
num_items = len(ids)
# the candidate item IDs to be ranked
input_ids = helper.make_tensor_value_info("id", TensorProto.STRING, [None])
# the ID of the item currently being viewed (e.g. "item_123")
input_current = helper.make_tensor_value_info("current_item", TensorProto.STRING, [1])
output_score = helper.make_tensor_value_info("score", TensorProto.FLOAT16, [-1])
nodes = [
# the embedding matrix, stored pre-transposed as (dims, num_items)
helper.make_node(
op_type="Constant",
inputs=[],
outputs=["all_embeddings_t"],
value=numpy_helper.from_array(embeddings_t, name="all_embeddings_t")
),
# penalty value used to push the current_item score below zero
helper.make_node(
op_type="Constant",
inputs=[],
outputs=["penalty_val"],
value=numpy_helper.from_array(np.array([2.0], dtype=np.float16), name="penalty_val")
),
# helper constant used to scale cosine similarities from [-1, 1] to [0, 1]
helper.make_node(
op_type="Constant",
inputs=[],
outputs=["const_0_5"],
value=numpy_helper.from_array(np.array([0.5], dtype=np.float16), name="const_0_5")
),
# map the current_item ID to its internal embedding index
helper.make_node(
op_type="LabelEncoder",
inputs=["current_item"],
outputs=["current_idx"],
domain="ai.onnx.ml",
keys_strings=ids,
values_int64s=list(range(num_items)),
default_int64=-1
),
# pull the column for the current item from the (dims, num_items) matrix.
# axis=1 because items now live in columns. result shape: (dims, 1).
helper.make_node(
op_type="Gather",
inputs=["all_embeddings_t", "current_idx"],
outputs=["target_col"],
axis=1
),
# transpose the small (dims, 1) target into (1, dims) for the MatMul.
# this is a tiny op (only `dims` elements move) compared to transposing
# the full (num_items, dims) matrix.
helper.make_node(
op_type="Transpose",
inputs=["target_col"],
outputs=["target_embedding"],
perm=[1, 0]
),
# calculate cosine similarity (dot product).
# (1, dims) @ (dims, num_items) = (1, num_items)
helper.make_node(
op_type="MatMul",
inputs=["target_embedding", "all_embeddings_t"],
outputs=["raw_scores"],
),
# squeeze the result of the MatMul to a single 1D array of scores
helper.make_node(
op_type="Squeeze",
inputs=["raw_scores"],
outputs=["raw_scores_squeezed"]
),
# scale: (x * 0.5) + 0.5 to move the scores from [-1, 1] to [0, 1]
helper.make_node(
op_type="Mul",
inputs=["raw_scores_squeezed", "const_0_5"],
outputs=["half_scores"]
),
helper.make_node(
op_type="Add",
inputs=["half_scores", "const_0_5"],
outputs=["scaled_scores"]
),
# build a boolean mask that is True for the current item
helper.make_node(
op_type="Equal",
inputs=["id", "current_item"],
outputs=["mask_bool"]
),
# convert the boolean mask to floats (1.0 for the current item, 0.0 otherwise)
helper.make_node(
op_type="Cast",
inputs=["mask_bool"],
outputs=["mask_float"],
to=TensorProto.FLOAT16
),
# multiply the mask by the penalty value
helper.make_node(
op_type="Mul",
inputs=["mask_float", "penalty_val"],
outputs=["penalty_vector"]
),
# subtract the penalty from the scores so the current item's score becomes negative
helper.make_node(
op_type="Sub",
inputs=["scaled_scores", "penalty_vector"],
outputs=["score"]
)
]
graph = helper.make_graph(
nodes=nodes,
name="recommender_graph",
inputs=[input_ids, input_current],
outputs=[output_score]
)
return helper.make_model(
graph=graph,
ir_version=10,
opset_imports=[
helper.make_opsetid("", 21),
helper.make_opsetid("ai.onnx.ml", 5)
]
)
Show recommendations based on a profile property
The profile input contains a vectorized representation of the profile based on the profilePropertyIds and featureNames metadata.
For example, you can use an Interest Ranker Listener to recommend products that best match the visitor's favorite categories.
First, collect product categories:
import blueconic
bc = blueconic.Client()
CONNECTION_ID = bc.get_blueconic_parameter_value("Product/Content collector", "connection")
STORE = bc.get_connection(CONNECTION_ID).get_store()
ids = []
categories = []
for item in STORE.get_items():
ids.append(item.id)
categories.append(item.get_values("category"))
Then use a TfidfVectorizer to create an L2-normalized vector for each product:
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
from onnx import helper, TensorProto, numpy_helper
from scipy.sparse import coo_matrix
tfidf = TfidfVectorizer(
analyzer=lambda categories: [category.lower().strip() for category in categories],
token_pattern=None,
min_df = 2,
max_features = 1024,
dtype = np.float32
)
tfidf_matrix = tfidf.fit_transform(categories)
You can then create an ONNX model that calculates the cosine similarity between the categories in the profile and the product categories:
from onnx import helper, TensorProto, numpy_helper
# convert tfidf_matrix to COO format for easy coordinate extraction
coo = tfidf_matrix.tocoo()
# the candidate item IDs to be ranked
id_input = helper.make_tensor_value_info('id', TensorProto.STRING, [None])
# the profile input: 1.0 for categories the customer is interested in,
# 0.0 for categories the customer is not interested in
profile_input = helper.make_tensor_value_info('profile', TensorProto.FLOAT, [tfidf_matrix.shape[1]])
# a list of scores between 0 and 1 for all items
score_output = helper.make_tensor_value_info('score', TensorProto.FLOAT, [None])
nodes = [
# the TF-IDF item-category matrix.
# NOTE: tfidf_matrix rows MUST be L2-normalized — the cosine similarity
# below assumes ||item_vector|| == 1 and only normalizes the profile.
helper.make_node(
op_type = 'Constant',
inputs=[],
outputs=['tfidf_categories'],
sparse_value=helper.make_sparse_tensor(
values=helper.make_tensor(
name = 'sparse_vals',
data_type = TensorProto.FLOAT,
dims = [len(coo.data)],
vals = coo.data.astype(np.float32)
),
indices=helper.make_tensor(
name = 'sparse_indices',
data_type = TensorProto.INT64,
dims = [len(coo.data), 2],
vals = np.stack((coo.row, coo.col), axis=1).astype(np.int64).flatten()
),
# add a dummy row at the end to handle unknown items
dims=[tfidf_matrix.shape[0] + 1, tfidf_matrix.shape[1]]
)
),
# index of the dummy row, used to detect item IDs that were not in the training data
helper.make_node(
op_type = 'Constant',
inputs = [],
outputs = ['last_row_index'],
value = helper.make_tensor('last_row_index', TensorProto.INT64, [1], [tfidf_matrix.shape[0]])
),
# negative value used to filter unknown items out of recommendations
helper.make_node(
op_type = 'Constant',
inputs = [],
outputs = ['minus_one'],
value = helper.make_tensor('minus_one', TensorProto.FLOAT, [1], [-1.0])
),
# threshold for empty-profile detection.
# p_norm is either 0 (empty) or >= ~1 (at least one category set, modulo float rounding)
helper.make_node(
op_type = 'Constant',
inputs = [],
outputs = ['half'],
value = helper.make_tensor('half', TensorProto.FLOAT, [1], [0.5])
),
# safe norm value used when the profile is empty
helper.make_node(
op_type = 'Constant',
inputs = [],
outputs = ['one'],
value = helper.make_tensor('one', TensorProto.FLOAT, [1], [1.0])
),
# axis for the Squeeze op below
helper.make_node(
op_type = 'Constant',
inputs = [],
outputs = ['squeeze_axes'],
value = helper.make_tensor('squeeze_axes', TensorProto.INT64, [1], [1])
),
# target shape used to reshape the profile vector into a column for the cosine calculation
helper.make_node(
op_type = 'Constant',
inputs = [],
outputs = ['profile_reshape'],
value = helper.make_tensor('profile_reshape', TensorProto.INT64, [2], [tfidf_matrix.shape[1], 1])
),
# map item IDs to row indices in the TF-IDF matrix
helper.make_node(
op_type='LabelEncoder',
inputs=['id'],
outputs=['indices'],
domain='ai.onnx.ml',
keys_strings=ids, # the original list of item IDs
values_int64s=np.arange(tfidf_matrix.shape[0], dtype=np.int64),
default_int64=tfidf_matrix.shape[0] # index of the dummy row for unknown IDs
),
# detect unknown IDs (i.e. those that mapped to the dummy row)
helper.make_node('Equal', ['indices', 'last_row_index'], ['is_unknown']),
# gather the category vectors for the candidate item IDs
helper.make_node('Gather', ['tfidf_categories', 'indices'], ['item_vectors'], axis=0),
# normalize the profile for cosine similarity
helper.make_node('ReduceL2', ['profile'], ['p_norm'], keepdims=1),
# detect an empty profile and substitute 1.0 to avoid division by zero
helper.make_node('Less', ['p_norm', 'half'], ['is_empty_profile']),
helper.make_node('Where', ['is_empty_profile', 'one', 'p_norm'], ['p_norm_safe']),
helper.make_node('Div', ['profile', 'p_norm_safe'], ['p_normed']),
helper.make_node('Reshape', ['p_normed', 'profile_reshape'], ['p_col']),
# calculate the score (cosine similarity)
helper.make_node('MatMul', ['item_vectors', 'p_col'], ['raw_scores_2d']),
helper.make_node('Squeeze', ['raw_scores_2d', 'squeeze_axes'], ['raw_scores']),
# replace the scores of unknown IDs with -1.0
helper.make_node('Where', ['is_unknown', 'minus_one', 'raw_scores'], ['scores_with_unknown_handled']),
# if the profile is empty, replace all scores with -1.0
helper.make_node('Where', ['is_empty_profile', 'minus_one', 'scores_with_unknown_handled'], ['score'])
]
graph = helper.make_graph(
nodes=nodes,
name="Profile interests recommender",
inputs=[id_input, profile_input],
outputs=[score_output],
)
onnx_model = helper.make_model(
graph = graph,
ir_version = 10,
opset_imports=[
helper.make_opsetid("", 21),
helper.make_opsetid("ai.onnx.ml", 5)
])
Finally, upload the model and configure metadata:
MODEL_ID = bc.get_blueconic_parameter_value("Recommendation model", "model")
if not MODEL_ID:
raise ValueError("Please configure a model")
FAVORITE_CATEGORIES_PROPERTY_ID = bc.get_blueconic_parameter_value(
"Favorite categories",
"profile_property"
)
if not FAVORITE_CATEGORIES_PROPERTY_ID:
raise ValueError("Please configure a Favorite categories profile property")
model = bc.get_model(MODEL_ID)
model.type = blueconic.domain.ModelType.RECOMMENDATION
model.model = onnx_model.SerializeToString()
model.property_ids = [FAVORITE_CATEGORIES_PROPERTY_ID]
# build a one-hot encoded vector of categories for the "profile" input
model.feature_names = [
FAVORITE_CATEGORIES_PROPERTY_ID + "=" + category
for category in tfidf.get_feature_names_out()
]
Other models
The examples above are only a starting point. Many recommendation algorithms can be converted to ONNX and used in the BlueConic recommender.
Association rules
Association rules (FP-Growth / Apriori) are commonly used for:
basket expansion
cross-sell recommendations
frequently-bought-together recommendations
You can store antecedents and lift values as ONNX tensors and rank recommendations using shopping-cart inputs.
Collaborative filtering
Collaborative filtering models recommend items based on shared user behavior patterns.
Typical approaches include:
matrix factorization
LightFM
two-tower architectures
These models are especially effective for:
personalized recommendations
product discovery
"Customers also bought" experiences
Unit testing your model
It is good practice to validate ONNX model output before deployment.
First, configure your testing environment:
import onnxruntime as ort
import ipytest
import numpy as np
ipytest.autoconfig(raise_on_error=True)
Then add your unit tests. For example, to test the popular products model described earlier:
%%ipytest
def test_popular_products_model():
onnx_model = create_popular_products_model()
session = ort.InferenceSession(onnx_model.SerializeToString())
output = session.run(output_names = None, input_feed= {
"id": ["a", "b", "c", "d", "e", "f"],
"current_item": ["b"],
"recent_order": [0, 2, 1, 3, 0, 4]
})
np.testing.assert_equal(output[0], [ 0.0 , -0.5 , 0.25, 0.75, 0.0 , 1.0])
Some unit tests worth considering:
Do the scores match your expectations?
Does the model behave correctly when the
profileinput consists of only zeros?Does the model behave correctly if
current_itemis empty or an unknown ID?Does the model behave correctly if
idcontains unknown IDs?Does the model behave correctly if all IDs in
idare unknown?
Performance tips
Use sparse tensors where appropriate
Many recommendation models rely on sparse matrices where most values are zero, such as item-to-item or TF-IDF matrices.
Using sparse tensors can:
reduce model size
lower memory usage
improve runtime efficiency
Consider sparse tensors when your data contains mostly zero values (typically less than 10% non-zero values).
Use Float16 where appropriate
Converting tensors from Float32 to Float16 can significantly reduce model size and improve inference performance.
Always validate model accuracy after conversion to ensure the reduced precision does not negatively impact recommendations.
Prefer vectorized operations
Operators such as If and Loop are supported in ONNX but can reduce performance.
Whenever possible, use vectorized operators instead, such as:
Wherefor conditional logicMatMulorEinsumfor matrix operationsReduceSumfor aggregationsGatherandScatterElementsfor indexing
Vectorized tensor operations are typically faster and more scalable than iterative control flow.
Minimize data movement
Operations such as:
TransposeReshapeSqueeze
can introduce unnecessary runtime overhead because they reorganize tensor memory.
Whenever possible:
precompute tensor layouts
reduce reshaping operations
minimize runtime transposes

