Converges to a true local optimum. Slightly better Silhouette in the k=6 comparison (0.0304 vs 0.0311 — within noise). Centroids are mathematically cleaner.
Labels (cluster assignments, profiles)
MiniBatchKMeans
Scales to the full 571M-review dataset at O(batch·k·d) memory. Retains ~102% of K-Means quality at 3× faster fit time (19.6s vs 57.1s). The production-ready algorithm.
What this means in practice
Inference: cluster_centroids.npy (K-Means weights) is what you load to classify new reviews. The cosine distance to these 6 centroids determines the cluster.
Cluster profiles: The sizes, avg ratings, top terms, and category distributions in cluster_profiles.json come from the MiniBatchKMeans label assignment.
Interchangeable: The silhouette gap is negligible (0.0311 vs 0.0304), meaning both algorithms agree on cluster boundaries. The centroids from either would produce nearly identical results.
Full training history with per-batch inertia tracking for both algorithms: metrics_clustering.json.
How to use cluster_centroids.npy
python
1import numpy as np
23centroids = np.load("data/models/MiniBatchKMeans/cluster_centroids.npy")4# shape: (6, 768), dtype: float325# centroids[i] is the 768-dim embedding of cluster i
To assign a new review embedding to the nearest cluster:
Silhouette interpretation: 0.031 is low — this is expected for high-dimensional text embeddings (curse of dimensionality). The score is a relative comparator between k values, not an absolute quality measure. k=6 was selected via elbow method + domain interpretability (see metrics_clustering.json for the full k-sweep).
NOTE: The inertia values differ in scale because K-Means reports total inertia across all 219,998 points, while the MiniBatchKMeans fit_steps track batch-level inertia (per 1,024 samples).
Usage Examples
Full pipeline: raw text → Nomic embedding → cosine distance → cluster label.
Example 1 — Positive book review → Cluster 2 (Books/Media)
python
1import numpy as np
2from scipy.spatial.distance import cdist
3from sentence_transformers import SentenceTransformer
45model = SentenceTransformer("nomic-ai/nomic-embed-text-v1.5", trust_remote_code=True)6centroids = np.load("data/models/MiniBatchKMeans/cluster_centroids.npy")78review ="This novel blew me away. The plot twists were masterful and I couldn't put it down."9embedding = model.encode([review])# shape: (1, 768)10distances = cdist(embedding, centroids, metric="cosine")11cluster_id =int(np.argmin(distances))
Expected output: cluster_id = 2 — Books/Media cluster.
The review talks about plot, novel, and reading experience — matches top terms book, story, read.
1review ="Bought this as a birthday gift for my nephew and he absolutely loves it! Easy to assemble and great quality."2embedding = model.encode([review])3distances = cdist(embedding, centroids, metric="cosine")4cluster_id =int(np.argmin(distances))
Expected output: cluster_id = 5 — Strong Positive cluster (avg 4.68★).
Keywords gift, love, easy, great align perfectly with Cluster 5's top terms. Sentiment: 89.5% Positive.
1review ="This charger stopped working after two weeks. Total waste of money. Don't buy this."2embedding = model.encode([review])3distances = cdist(embedding, centroids, metric="cosine")4cluster_id =int(np.argmin(distances))
Expected output: cluster_id = 4 — Strong Negative cluster (avg 1.94★).
Keywords work (broken), money (waste), don't (negative) match Cluster 4's patterns. Top categories: Cell Phones, Electronics. Sentiment: 66.2% Negative.
Batch inference (N reviews at once)
python
1reviews =["Great product, highly recommend!","Did not fit as described.","Perfect gift for kids."]2embeddings = model.encode(reviews)# shape: (3, 768)3distances = cdist(embeddings, centroids,"cosine")# shape: (3, 6)4labels = np.argmin(distances, axis=1)# e.g. [5, 3, 5]
Serving & Integration
Pick the approach that fits your stack. All examples assume the model files live at data/models/MiniBatchKMeans/.
1. Python CLI script (zero dependencies beyond the model)
Save as cluster_review.py and run: python cluster_review.py "Your review text here"
python
1"""cluster_review.py — classify a review from the command line."""2import sys, json, numpy as np
3from scipy.spatial.distance import cdist
4from sentence_transformers import SentenceTransformer
56MODEL_DIR ="data/models/MiniBatchKMeans"78# Load once at module level9embedder = SentenceTransformer("nomic-ai/nomic-embed-text-v1.5", trust_remote_code=True)10centroids = np.load(f"{MODEL_DIR}/cluster_centroids.npy")11withopen(f"{MODEL_DIR}/cluster_profiles.json")as f:12 profiles = json.load(f)1314defclassify(text:str)->dict:15 emb = embedder.encode([text])16 cluster_id =int(np.argmin(cdist(emb, centroids, metric="cosine")))17 cluster =next(c for c in profiles["clusters"]if c["cluster_id"]== cluster_id)18return{19"text": text,20"cluster_id": cluster_id,21"avg_rating": cluster["avg_rating"],22"sentiment": cluster["sentiment_distribution_pct"],23"top_terms": cluster["top_terms"],24"top_categories": cluster["top_categories"],25}2627if __name__ =="__main__":28 text =" ".join(sys.argv[1:])iflen(sys.argv)>1elseinput("Review: ")29 result = classify(text)30print(json.dumps(result, indent=2))
Sample output:
json
1{2"text":"This blender is amazing, smoothies every morning!",3"cluster_id":5,4"avg_rating":4.676,5"sentiment":{"Positive":89.5,"Neutral":8.8,"Negative":1.7},6"top_terms":["great","love","easy","gift","nice"],7"top_categories":{"Toys_and_Games":4.7,"Gift_Cards":4.6,"Office_Products":4.3}8}
1curl -X POST http://localhost:8000/classify \2 -H "Content-Type: application/json"\3 -d '{"text": "This book was a page-turner from start to finish."}'
1<!DOCTYPEhtml>2<htmllang="en">3<head>4<metacharset="UTF-8">5<title>Review Classifier</title>6<style>7body{font-family: system-ui;max-width:600px;margin:3rem auto;padding:01rem;}8textarea{width:100%;height:100px;margin-bottom:0.5rem;}9pre{background:#f5f5f5;padding:1rem;border-radius:6px;white-space: pre-wrap;}10</style>11</head>12<body>13<h2>What category is this review?</h2>14<textareaid="review"placeholder="Paste a product review..."></textarea>15<buttononclick="classify()">Classify</button>16<preid="result"></pre>1718<script>19asyncfunctionclassify(){20const text =document.getElementById("review").value;21const res =awaitfetch("http://localhost:8000/classify",{22method:"POST",23headers:{"Content-Type":"application/json"},24body:JSON.stringify({ text }),25});26const data =await res.json();27document.getElementById("result").textContent=JSON.stringify(data,null,2);28}29</script>30</body>31</html>
Open index.html in your browser, type a review, click "Classify" — result renders inline.
4. Google Colab interactive widget
python
1# Run in a Colab cell — instant text box + classify button2import ipywidgets as widgets
3from IPython.display import display, JSON
45text_input = widgets.Textarea(placeholder="Paste a review...", layout={"width":"100%","height":"80px"})6button = widgets.Button(description="Classify", button_style="primary")7output = widgets.Output()89defon_click(_):10with output:11 output.clear_output()12 emb = embedder.encode([text_input.value])13 cluster_id =int(np.argmin(cdist(emb, centroids, metric="cosine")))14 c =next(c for c in profiles["clusters"]if c["cluster_id"]== cluster_id)15 display(JSON({k: c[k]for k in["cluster_id","avg_rating","sentiment_distribution_pct","top_terms","top_categories"]}))1617button.on_click(on_click)18display(text_input, button, output)
5. Streamlit dashboard (one-liner UI)
python
1"""Save as streamlit_app.py — run: streamlit run streamlit_app.py"""2import streamlit as st
3import numpy as np, json
4from scipy.spatial.distance import cdist
5from sentence_transformers import SentenceTransformer
67@st.cache_resource8defload_model():9 m = SentenceTransformer("nomic-ai/nomic-embed-text-v1.5", trust_remote_code=True)10 c = np.load("data/models/MiniBatchKMeans/cluster_centroids.npy")11withopen("data/models/MiniBatchKMeans/cluster_profiles.json")as f:12 p = json.load(f)13return m, c, p
1415embedder, centroids, profiles = load_model()1617st.title("Review Category Classifier")18review = st.text_area("Paste a product review:", height=100)1920if st.button("Classify"):21 emb = embedder.encode([review])22 cluster_id =int(np.argmin(cdist(emb, centroids, metric="cosine")))23 c =next(c for c in profiles["clusters"]if c["cluster_id"]== cluster_id)24 col1, col2 = st.columns(2)25with col1:26 st.metric("Cluster", cluster_id)27 st.metric("Avg Rating",f"{c['avg_rating']:.1f}★")28 st.write("**Top terms:**",", ".join(c["top_terms"]))29with col2:30 st.write("**Sentiment:**")31for k, v in c["sentiment_distribution_pct"].items():32 st.progress(v /100, text=f"{k}: {v}%")33 st.write("**Categories:**", c["top_categories"])
6. Batch CSV processor (process thousands of reviews at once)
python
1"""batch_classify.py — reads a CSV with a 'review_text' column, writes results."""2import pandas as pd, numpy as np, json
3from scipy.spatial.distance import cdist
4from sentence_transformers import SentenceTransformer
56MODEL_DIR ="data/models/MiniBatchKMeans"7BATCH_SIZE =51289embedder = SentenceTransformer("nomic-ai/nomic-embed-text-v1.5", trust_remote_code=True)10centroids = np.load(f"{MODEL_DIR}/cluster_centroids.npy")11withopen(f"{MODEL_DIR}/cluster_profiles.json")as f:12 profiles = json.load(f)1314df = pd.read_csv("input_reviews.csv")15embeddings = embedder.encode(df["review_text"].tolist(), batch_size=BATCH_SIZE, show_progress_bar=True)16labels = np.argmin(cdist(embeddings, centroids, metric="cosine"), axis=1)1718df["cluster_id"]= labels
19df["cluster_rating"]=[profiles["clusters"][lid]["avg_rating"]for lid in labels]20df.to_csv("classified_reviews.csv", index=False)21print(f"Done — {len(df)} reviews classified.")
Files in this folder
File
Description
cluster_centroids.npy
(6, 768) float32 — cluster center embeddings
cluster_profiles.json
Per-cluster stats (sizes, sentiment, top terms, categories)
clusters.csv
Per-review cluster assignments
embeddings_nomic.npz
Full 219,998 × 768 embedding matrix (compressed)
metrics_clustering.json
Full training history: k-sweep, fit steps, convergence