Clustering Example
This example assumes that the compresso-pytorch and compresso-recsys packages are installed:
pip install "compresso-pytorch@git+https://github.com/zombak79/compresso.git"
pip install "compresso-recsys@git+https://github.com/zombak79/compresso-recsys.git"
This notebook demonstrates a basic clustering workflow. We first import Compresso and its recommender-system extension, then download Amazon metadata and build a checkpoint for the experiment.
Build checkpoint & encode item descriptions
[1]:
import compresso
import compresso_recsys as cr
import pandas as pd
from compresso import clustering as cc
from compresso import TopKSAETrainer, TopKSAEConfig, L1Normalize
checkpoint_path = cr.build_recsys_checkpoint(
dataset="amazon2023",
amazon_category="Office_Products",
checkpoint_path="artifacts/amazon_office.zip",
metadata_text_fields=["title", "features", "description", "categories"],
split_mode="item_split",
min_entity_text_words=20,
val_items=500,
test_items=1000,
min_user_support=10,
item_min_support=10,
min_value_to_keep=1.0,
set_all_values_to=1.0,
min_source_items=1,
min_target_items=1,
annotation_source="none",
show_progress=True,
include_image_urls=True,
)
Now let’s load the item metadata from the checkpoint and inspect a sample of the data.
[2]:
with cr.read_checkpoint(checkpoint_path) as root:
split = cr.load_recsys_split(root)
meta = split["entity_metadata"]
meta[["item_id", "title", "entity_text", "image_url"]].sample(5)
[2]:
| item_id | title | entity_text | image_url | |
|---|---|---|---|---|
| 1448 | B01BYKUI9C | HP 65 Black Ink Cartridge | Works with HP AMP ... | Title: HP 65 Black Ink Cartridge | Works with ... | https://m.media-amazon.com/images/I/61GAiSy74W... |
| 2115 | B07F9SVWXW | Quartet Dry Erase Markers, Whiteboard Markers,... | Title: Quartet Dry Erase Markers, Whiteboard M... | https://m.media-amazon.com/images/I/61b1qTIOLB... |
| 1921 | B078Z65VQY | Canon PIXMA G4210 Wireless All-In-One Supertan... | Title: Canon PIXMA G4210 Wireless All-In-One S... | https://m.media-amazon.com/images/I/81sbe681tY... |
| 4175 | B0BXR5F7LS | Westcott B-70 8ths Graph Beveled Ruler, 12 in | Title: Westcott B-70 8ths Graph Beveled Ruler,... | https://m.media-amazon.com/images/I/81e+DtAFL5... |
| 134 | B0003WN0DO | Sharpie Fine Electro Pop Marker, Fine Point, A... | Title: Sharpie Fine Electro Pop Marker, Fine P... | https://m.media-amazon.com/images/I/81W3-nzwxi... |
Next, we create semantic embeddings with the sentence_transformers library.
[3]:
from sentence_transformers import SentenceTransformer
sbert = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
item_embeddings = sbert.encode(
meta.entity_text,
convert_to_numpy=True,
normalize_embeddings=True,
show_progress_bar=True
)
item_embeddings.shape
Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.
[3]:
(4445, 384)
Now let’s create a sparse autoencoder, train it, and obtain sparse item embeddings.
Train sparse autoencoder and obtain sparse codes
[4]:
sae = TopKSAETrainer(
TopKSAEConfig(
hidden_dim=4096,
k=32,
batch_size=1024,
epochs=100,
lr=1e-3,
decay=True,
post_sparsify=L1Normalize(),
sparsify_score_mode="abs",
sparsify_ste_alpha=0.01,
device="cpu",
)
)
sparse_embeddings = sae.fit_transform(item_embeddings)
sparse_embeddings.shape
[4]:
(4445, 4096)
Clustering
Next, we create clusters based on sparse-representation similarity.
[5]:
cluster_graph = cc.SRPSimilarityClustering(
threshold=0.5, # minimum semantic similarity between items inside a cluster
top_k=None, # None = all pairs above threshold
min_cluster_size=20, # smaller clusters are discarded
normalize_rows=True, # centroids of the cluster will be normalized
min_local_density=None, # optional cleanup
centroid_top_k=8, # how many top features are included in centroid definition
batch_size=32,
show_progress=True,
)(sparse_embeddings)
print(f"Number of discovered clusters: {len(cluster_graph.clusters)}, active_clusters: {len(cluster_graph.active_clusters)}, root clusters: {len(cluster_graph.root_clusters)}")
Number of discovered clusters: 28, active_clusters: 28, root clusters: 28
Labeling
Next, we label the discovered clusters.
For labeling, we need two functions:
a text extractor that receives a cluster and the item metadata dataframe, then returns item descriptions as a string
a labeling function that receives those item descriptions and returns a concise cluster name
This example uses a locally running gpt-oss:20b model through Ollama. The same pattern can be adapted to another local model or provider API.
[8]:
import ollama
MODEL = "gpt-oss:20b"
def label_cluster(items: str) -> str:
prompt = f"""You are given a list of items, each with metadata.
1. Identify the most common concept that links all the items
2. Convert that concept into a segment name suitable for a recommendation system.
The segment name must be title-case, concise (2-5 words), and sound natural — like a content
category. Prefer distinctive labels over generic ones.
Output: Only the final segment name, nothing else.
Items:
{items}
Segment name:"""
response = ollama.chat(
model=MODEL,
messages=[
{
"role": "system",
"content": (
"You are a strict classification model. "
"Return only one segment name. Never explain."
),
},
{
"role": "user",
"content": prompt,
},
],
options={
"temperature": 0.0,
"num_predict": 1000,
"num_ctx": 8192,
},
think="low",
stream=False,
)
content = response["message"]["content"] if response["message"]["content"] is not None else None
return content
def item_texts(cluster: compresso.clustering.types.SparseCluster, metadata: pd.DataFrame, limit: int = 50) -> str:
rows = metadata.iloc[cluster.entity_indices] # select rows with items that are members of the cluster
if len(rows) > limit: # optionally sample maximum allowed number of items to save tokens
rows = rows.sample(limit)
return "\n".join(rows["title"].fillna("").tolist()) # just return item titles, one per row
print("Items texts for the first cluster in the graph:\n")
texts = item_texts(cluster_graph.clusters[0], meta, limit=10)
print(texts)
print("\nLLM label for the first cluster:", label_cluster(texts))
Items texts for the first cluster in the graph:
Sharp EL-1901 Paperless Printing Calculator with Check and Correct, 12-Digit LCD Primary Display, Functions the Same as a Printing Calculator/Adding Machine with Scrolling LCD Display Instead of Paper
Texas Instruments TI-1795 SV Standard Function Calculator
Victor 1180-3A 12-Digit Standard Function Calculator, Battery and Solar Hybrid Powered Adjustable Angle LCD Display, Great for Home and Office Desks, Black
Sharp EL233SB Standard Function Calculator
Sharp El-1501 Compact Cordless Paperless Large 12-Digit Display Desktop Printing Calculator That Utilizes Printing Calculator Logic
Sharp EL-M335 10-Digit Extra Large Desktop Calculator with Currency Conversion Functions, Tax, Percent and Backspace Keys, and a Large Angled LCD Display, Perfect for Home or Office Use
Casio SL-100L Basic Solar Folding Compact Calculator, Multicolor
Sharp EL-1611V Handheld Portable Cordless 12 Digit Large LCD Display Two-Color Printing Calculator with Tax Functions, 191 x 99 x 42 mm
Casio SL-300VC Standard Function Calculator, Orange 2.75 x 4.63
Casio HS-8VA, Solar Powered Standard Function Calculator
LLM label for the first cluster: Calculator Devices
Now we can apply the labeling function to the full cluster graph.
[9]:
cluster_graph = cc.LabelClusters(
entity_metadata=meta,
text_extractor=item_texts,
label_fn=label_cluster,
cluster_scope="all",
show_progress=True,
)(cluster_graph)
Visualization
We can also visualize clusters alongside a sample of their items.
[10]:
import pandas as pd
from IPython.display import HTML
# construct a dataframe with cluster label and 5 random sampled item images belonging to the cluster
def visualize_clusters(cluster_graph):
data = [
(
cluster_graph.clusters[i].label,
meta.iloc[cluster_graph.clusters[i].entity_indices].image_url.sample(5).to_list()
) for i in range(len(cluster_graph.clusters))
]
df = pd.DataFrame(data, columns=["Cluster", "image_urls"])
def image_gallery(urls):
imgs = [
f'<img src="{url}" style="height:80px; margin:4px; object-fit:contain;">'
for url in urls
]
return "<div style='display:flex; flex-wrap:wrap; gap:6px;'>" + "".join(imgs) + "</div>"
df_render = df.copy()
df_render["Items (sample)"] = df_render["image_urls"].map(image_gallery)
html = df_render[["Cluster", "Items (sample)"]].to_html(
escape=False,
index=False
)
return HTML(html)
visualize_clusters(cluster_graph)
[10]:
| Cluster | Items (sample) |
|---|---|
| Desktop & Handheld Calculators | ![]() ![]() ![]() ![]() ![]() |
| Office Organization Essentials | ![]() ![]() ![]() ![]() ![]() |
| Office & Packaging Supplies | ![]() ![]() ![]() ![]() ![]() |
| Photo Printing Paper | ![]() ![]() ![]() ![]() ![]() |
| HP Printer Ink | ![]() ![]() ![]() ![]() ![]() |
| Paper Trimmers | ![]() ![]() ![]() ![]() ![]() |
| Dry Erase & Marker Supplies | ![]() ![]() ![]() ![]() ![]() |
| Binder Tab Dividers | ![]() ![]() ![]() ![]() ![]() |
| Notebook & Paper | ![]() ![]() ![]() ![]() ![]() |
| Pens & Pencils | ![]() ![]() ![]() ![]() ![]() |
| Pencil Sharpeners | ![]() ![]() ![]() ![]() ![]() |
| Premium Colored Pencils | ![]() ![]() ![]() ![]() ![]() |
| Label & File Organization | ![]() ![]() ![]() ![]() ![]() |
| Index Card Sets | ![]() ![]() ![]() ![]() ![]() |
| Staplers & Staples | ![]() ![]() ![]() ![]() ![]() |
| Envelopes\n\n | ![]() ![]() ![]() ![]() ![]() |
| Wireless All‑In‑One Printers | ![]() ![]() ![]() ![]() ![]() |
| Thermal Laminators & Pouches | ![]() ![]() ![]() ![]() ![]() |
| Office Printer Paper | ![]() ![]() ![]() ![]() ![]() |
| Ergonomic Gaming Desk Mat | ![]() ![]() ![]() ![]() ![]() |
| Cordless Home Phone Systems | ![]() ![]() ![]() ![]() ![]() |
| Epson All‑In‑One Printing Solutions | ![]() ![]() ![]() ![]() ![]() |
| Tombow Mono Stationery Essentials | ![]() ![]() ![]() ![]() ![]() |
| Multi‑Compartment Desk Caddy | ![]() ![]() ![]() ![]() ![]() |
| Electric Pencil Sharpeners | ![]() ![]() ![]() ![]() ![]() |
| Paper Shredders | ![]() ![]() ![]() ![]() ![]() |
| Planner & Calendar Collection | ![]() ![]() ![]() ![]() ![]() |
| Office Chair Mats | ![]() ![]() ![]() ![]() ![]() |
Using clustering pipelines
The same steps can be combined with ClusteringPipeline. Results should be similar, although labels may vary because they are generated by an LLM.
[12]:
pipeline_cluster_graph = cc.ClusteringPipeline(
[
cc.SRPSimilarityClustering(
threshold=0.5, # minimum semantic similarity between items inside a cluster
top_k=None, # None = all pairs above threshold
min_cluster_size=20, # smaller clusters are discarded
normalize_rows=True, # centroids of the cluster will be normalized
min_local_density=None, # optional cleanup
centroid_top_k=8, # how many top features are included in centroid definition
batch_size=32,
show_progress=True,
),
cc.LabelClusters(
entity_metadata=meta,
text_extractor=item_texts,
label_fn=label_cluster,
cluster_scope="all",
show_progress=True,
),
]
)(sparse_embeddings)
visualize_clusters(pipeline_cluster_graph)
[12]:
| Cluster | Items (sample) |
|---|---|
| Office & Home Standard Calculators | ![]() ![]() ![]() ![]() ![]() |
| Office Organization Essentials | ![]() ![]() ![]() ![]() ![]() |
| Office Stationery & Packing | ![]() ![]() ![]() ![]() ![]() |
| Photo Printing Paper | ![]() ![]() ![]() ![]() ![]() |
| HP Printer Ink | ![]() ![]() ![]() ![]() ![]() |
| Paper Trimmers | ![]() ![]() ![]() ![]() ![]() |
| Dry Erase & Whiteboard Supplies | ![]() ![]() ![]() ![]() ![]() |
| Binder Tab Organizers | ![]() ![]() ![]() ![]() ![]() |
| Notebook & Paper | ![]() ![]() ![]() ![]() ![]() |
| Pens & Pencils | ![]() ![]() ![]() ![]() ![]() |
| School & Office Pencil Sharpeners | ![]() ![]() ![]() ![]() ![]() |
| Premium Colored Pencils | ![]() ![]() ![]() ![]() ![]() |
| Labeling & Filing Essentials | ![]() ![]() ![]() ![]() ![]() |
| Index Card Essentials | ![]() ![]() ![]() ![]() ![]() |
| Staplers & Accessories | ![]() ![]() ![]() ![]() ![]() |
| Secure Mailing Envelopes | ![]() ![]() ![]() ![]() ![]() |
| Ink & Toner Supplies | ![]() ![]() ![]() ![]() ![]() |
| Thermal Laminators & Pouches | ![]() ![]() ![]() ![]() ![]() |
| Printing Paper | ![]() ![]() ![]() ![]() ![]() |
| Ergonomic Gaming Desk Mat | ![]() ![]() ![]() ![]() ![]() |
| Cordless Home Phone Systems | ![]() ![]() ![]() ![]() ![]() |
| Office All‑In‑One Printers | ![]() ![]() ![]() ![]() ![]() |
| Mono Stationery Essentials | ![]() ![]() ![]() ![]() ![]() |
| Desk Organizers | ![]() ![]() ![]() ![]() ![]() |
| Electric Pencil Sharpeners | ![]() ![]() ![]() ![]() ![]() |
| Office Paper Shredder | ![]() ![]() ![]() ![]() ![]() |
| Planner & Calendar Collection | ![]() ![]() ![]() ![]() ![]() |
| Office Chair Mats | ![]() ![]() ![]() ![]() ![]() |
[ ]:




































































































































































































































































